dhcp_protocol/
lib.rs

1// Copyright 2018 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use log::debug;
6use net_types::ethernet::Mac as MacAddr;
7use net_types::ip::{Ipv4, NotSubnetMaskError, PrefixLength};
8use num_derive::FromPrimitive;
9use serde::{Deserialize, Serialize};
10use std::fmt;
11use std::net::Ipv4Addr;
12use std::num::{NonZeroU16, NonZeroU8};
13use thiserror::Error;
14
15#[cfg(target_os = "fuchsia")]
16use std::convert::Infallible as Never;
17
18mod size_constrained;
19pub use crate::size_constrained::{
20    AtLeast, AtMostBytes, Error as SizeConstrainedError, U8_MAX_AS_USIZE,
21};
22
23mod size_of_contents;
24use crate::size_of_contents::SizeOfContents as _;
25
26/// The port on which DHCP servers receive messages from DHCP clients.
27///
28/// Per [RFC 2131 section 4.1], "DHCP messages from a client to a server are
29/// sent to the 'DHCP server' port (67)".
30///
31/// [RFC 2131 section 4.1]: https://datatracker.ietf.org/doc/html/rfc2131#section-4.1
32pub const SERVER_PORT: NonZeroU16 = NonZeroU16::new(67).unwrap();
33
34/// The port on which DHCP clients receive messages from DHCP servers.
35///
36/// Per [RFC 2131 section 4.1], "DHCP messages from a server to a client are
37/// sent to the 'DHCP client' port (68)".
38///
39/// [RFC 2131 section 4.1]: https://datatracker.ietf.org/doc/html/rfc2131#section-4.1
40pub const CLIENT_PORT: NonZeroU16 = NonZeroU16::new(68).unwrap();
41
42const OP_IDX: usize = 0;
43// currently unused
44//const HTYPE_IDX: usize = 1;
45//const HLEN_IDX: usize = 2;
46//const HOPS_IDX: usize = 3;
47const XID_IDX: usize = 4;
48const SECS_IDX: usize = 8;
49const FLAGS_IDX: usize = 10;
50const CIADDR_IDX: usize = 12;
51const YIADDR_IDX: usize = 16;
52const SIADDR_IDX: usize = 20;
53const GIADDR_IDX: usize = 24;
54const CHADDR_IDX: usize = 28;
55const SNAME_IDX: usize = 44;
56const FILE_IDX: usize = 108;
57const OPTIONS_START_IDX: usize = 236;
58
59const ETHERNET_HTYPE: u8 = 1;
60const ETHERNET_HLEN: u8 = 6;
61const HOPS_DEFAULT: u8 = 0;
62const MAGIC_COOKIE: [u8; 4] = [99, 130, 83, 99];
63
64const UNUSED_CHADDR_BYTES: usize = 10;
65
66const CHADDR_LEN: usize = 6;
67const SNAME_LEN: usize = 64;
68const FILE_LEN: usize = 128;
69const IPV4_ADDR_LEN: usize = 4;
70
71// Datagrams and DHCP Messages must both be at least 576 bytes.
72// - Datagram: https://datatracker.ietf.org/doc/html/rfc2132#section-4.4
73// - DHCP Message: https://datatracker.ietf.org/doc/html/rfc2132#section-9.10
74const MIN_MESSAGE_SIZE: u16 = 576;
75
76// Minimum legal value for mtu is 68.
77// https://datatracker.ietf.org/doc/html/rfc2132#section-5.1
78const MIN_MTU_VAL: u16 = 68;
79
80const ASCII_NULL: char = '\x00';
81
82#[derive(Debug, Error, PartialEq)]
83pub enum ProtocolError {
84    #[error("invalid buffer length: {}", _0)]
85    InvalidBufferLength(usize),
86
87    #[cfg(target_os = "fuchsia")]
88    #[error("option not supported in fuchsia.net.dhcp: {:?}", _0)]
89    InvalidFidlOption(DhcpOption),
90    #[error("invalid message type: {}", _0)]
91    InvalidMessageType(u8),
92    #[error("invalid bootp op code: {}", _0)]
93    InvalidOpCode(u8),
94    #[error("invalid option code: {}", _0)]
95    InvalidOptionCode(u8),
96    #[error("invalid option value. code: {}, value: {:?}", _0, _1)]
97    InvalidOptionValue(OptionCode, Vec<u8>),
98    #[error("missing opcode")]
99    MissingOpCode,
100    #[error("missing expected option: {}", _0)]
101    MissingOption(OptionCode),
102    #[error(
103        "malformed option {code} needs at least {want} bytes, but buffer has {remaining} remaining"
104    )]
105    MalformedOption { code: u8, remaining: usize, want: usize },
106
107    #[cfg(target_os = "fuchsia")]
108    #[error("received unknown fidl option variant")]
109    UnknownFidlOption,
110    #[error("invalid utf-8 after buffer index: {}", _0)]
111    Utf8(usize),
112    #[error("invalid protocol field {} = {} for message type {}", field, value, msg_type)]
113    InvalidField { field: String, value: String, msg_type: MessageType },
114}
115
116#[derive(Debug, Error, PartialEq)]
117#[error("Buffer is of invalid length: {0}")]
118struct InvalidBufferLengthError(usize);
119
120impl From<InvalidBufferLengthError> for ProtocolError {
121    fn from(err: InvalidBufferLengthError) -> ProtocolError {
122        let InvalidBufferLengthError(len) = err;
123        ProtocolError::InvalidBufferLength(len)
124    }
125}
126
127impl From<InvalidBufferLengthError> for BooleanConversionError {
128    fn from(err: InvalidBufferLengthError) -> BooleanConversionError {
129        let InvalidBufferLengthError(len) = err;
130        BooleanConversionError::InvalidBufferLength(len)
131    }
132}
133
134/// A DHCP protocol message as defined in RFC 2131.
135///
136/// All fields in `Message` follow the naming conventions outlined in the RFC.
137/// Note that `Message` does not expose `htype`, `hlen`, or `hops` fields, as
138/// these fields are effectively constants.
139#[derive(Debug, PartialEq)]
140pub struct Message {
141    pub op: OpCode,
142    pub xid: u32,
143    pub secs: u16,
144    pub bdcast_flag: bool,
145    /// `ciaddr` should be stored in Big-Endian order, e.g `[192, 168, 1, 1]`.
146    pub ciaddr: Ipv4Addr,
147    /// `yiaddr` should be stored in Big-Endian order, e.g `[192, 168, 1, 1]`.
148    pub yiaddr: Ipv4Addr,
149    /// `siaddr` should be stored in Big-Endian order, e.g `[192, 168, 1, 1]`.
150    pub siaddr: Ipv4Addr,
151    /// `giaddr` should be stored in Big-Endian order, e.g `[192, 168, 1, 1]`.
152    pub giaddr: Ipv4Addr,
153    /// `chaddr` should be stored in Big-Endian order,
154    /// e.g `[0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF]`.
155    pub chaddr: MacAddr,
156    /// `sname` should not exceed 64 characters.
157    pub sname: String,
158    /// `file` should not exceed 128 characters.
159    pub file: String,
160    pub options: Vec<DhcpOption>,
161}
162
163impl Message {
164    /// Instantiates a new `Message` from a byte buffer conforming to the DHCP
165    /// protocol as defined RFC 2131. Returns `None` if the buffer is malformed.
166    /// Any malformed configuration options will be skipped over, leaving only
167    /// well formed `DhcpOption`s in the final `Message`.
168    pub fn from_buffer(buf: &[u8]) -> Result<Self, ProtocolError> {
169        let options =
170            buf.get(OPTIONS_START_IDX..).ok_or(ProtocolError::InvalidBufferLength(buf.len()))?;
171        let options = {
172            let magic_cookie = options
173                .get(..MAGIC_COOKIE.len())
174                .ok_or(ProtocolError::InvalidBufferLength(buf.len()))?;
175            let options = options
176                .get(MAGIC_COOKIE.len()..)
177                .ok_or(ProtocolError::InvalidBufferLength(buf.len()))?;
178            if magic_cookie == MAGIC_COOKIE {
179                parse_options(options, Vec::new())?
180            } else {
181                Vec::new()
182            }
183        };
184
185        // Ordinarily, DHCP Options are stored in the variable length option field.
186        // However, a client can, at its discretion, store Options in the typically unused
187        // sname and file fields. If it wants to do this, it puts an OptionOverload option
188        // in the options field to indicate that additional options are either in the sname
189        // field, or the file field, or both. Consequently, we must:
190        //
191        // 1. Parse the options field.
192        // 2. Check if the parsed options include an OptionOverload.
193        // 3. If it does, grab the bytes from the field(s) indicated by the OptionOverload
194        //    option.
195        // 4. Parse those bytes into options.
196        // 5. Combine those parsed options with whatever was in the variable length
197        //    option field.
198        //
199        // From RFC 2131 pp23-24:
200        //
201        //     If the options in a DHCP message extend into the 'sname' and 'file'
202        //     fields, the 'option overload' option MUST appear in the 'options' field,
203        //     with value 1, 2 or 3, as specified in RFC 1533.
204        //
205        //     The options in the 'options' field MUST be interpreted first, so
206        //     that any 'option overload' options may be interpreted.
207        let overload = options.iter().find_map(|v| match v {
208            &DhcpOption::OptionOverload(overload) => Some(overload),
209            _ => None,
210        });
211        let sname =
212            buf.get(SNAME_IDX..FILE_IDX).ok_or(ProtocolError::InvalidBufferLength(buf.len()))?;
213        let file = buf
214            .get(FILE_IDX..OPTIONS_START_IDX)
215            .ok_or(ProtocolError::InvalidBufferLength(buf.len()))?;
216        let options = match overload {
217            Some(overload) => {
218                let extra_opts = match overload {
219                    Overload::SName => sname,
220                    Overload::File => file,
221                    Overload::Both => buf
222                        .get(SNAME_IDX..OPTIONS_START_IDX)
223                        .ok_or(ProtocolError::InvalidBufferLength(buf.len()))?,
224                };
225                parse_options(extra_opts, options)?
226            }
227            None => options,
228        };
229        Ok(Self {
230            op: OpCode::try_from(*buf.get(OP_IDX).ok_or(ProtocolError::MissingOpCode)?)?,
231            xid: u32::from_be_bytes(
232                <[u8; 4]>::try_from(
233                    buf.get(XID_IDX..SECS_IDX)
234                        .ok_or(ProtocolError::InvalidBufferLength(buf.len()))?,
235                )
236                .map_err(|std::array::TryFromSliceError { .. }| {
237                    ProtocolError::InvalidBufferLength(buf.len())
238                })?,
239            ),
240            secs: u16::from_be_bytes(
241                <[u8; 2]>::try_from(
242                    buf.get(SECS_IDX..FLAGS_IDX)
243                        .ok_or(ProtocolError::InvalidBufferLength(buf.len()))?,
244                )
245                .map_err(|std::array::TryFromSliceError { .. }| {
246                    ProtocolError::InvalidBufferLength(buf.len())
247                })?,
248            ),
249            bdcast_flag: *buf
250                .get(FLAGS_IDX)
251                .ok_or(ProtocolError::InvalidBufferLength(buf.len()))?
252                != 0,
253            ciaddr: ip_addr_from_buf_at(buf, CIADDR_IDX)?,
254            yiaddr: ip_addr_from_buf_at(buf, YIADDR_IDX)?,
255            siaddr: ip_addr_from_buf_at(buf, SIADDR_IDX)?,
256            giaddr: ip_addr_from_buf_at(buf, GIADDR_IDX)?,
257            chaddr: MacAddr::new(
258                buf.get(CHADDR_IDX..CHADDR_IDX + CHADDR_LEN)
259                    .ok_or(ProtocolError::InvalidBufferLength(buf.len()))?
260                    .try_into()
261                    .map_err(|std::array::TryFromSliceError { .. }| {
262                        ProtocolError::InvalidBufferLength(buf.len())
263                    })?,
264            ),
265            sname: match overload {
266                Some(Overload::SName) | Some(Overload::Both) => String::from(""),
267                Some(Overload::File) | None => buf_to_msg_string(sname)?,
268            },
269            file: match overload {
270                Some(Overload::File) | Some(Overload::Both) => String::from(""),
271                Some(Overload::SName) | None => buf_to_msg_string(file)?,
272            },
273            options,
274        })
275    }
276
277    /// Consumes the calling `Message` to serialize it into a buffer of bytes.
278    pub fn serialize(self) -> Vec<u8> {
279        let Self {
280            op,
281            xid,
282            secs,
283            bdcast_flag,
284            ciaddr,
285            yiaddr,
286            siaddr,
287            giaddr,
288            chaddr,
289            sname,
290            file,
291            options,
292        } = self;
293        let mut buffer = Vec::with_capacity(OPTIONS_START_IDX);
294        buffer.push(op.into());
295        buffer.push(ETHERNET_HTYPE);
296        buffer.push(ETHERNET_HLEN);
297        buffer.push(HOPS_DEFAULT);
298        buffer.extend_from_slice(&xid.to_be_bytes());
299        buffer.extend_from_slice(&secs.to_be_bytes());
300        if bdcast_flag {
301            // Set most significant bit.
302            buffer.push(128u8);
303        } else {
304            buffer.push(0u8);
305        }
306        buffer.push(0u8);
307        buffer.extend_from_slice(&ciaddr.octets());
308        buffer.extend_from_slice(&yiaddr.octets());
309        buffer.extend_from_slice(&siaddr.octets());
310        buffer.extend_from_slice(&giaddr.octets());
311        buffer.extend_from_slice(&chaddr.bytes().as_ref());
312        buffer.extend_from_slice(&[0u8; UNUSED_CHADDR_BYTES]);
313        trunc_string_to_n_and_push(&sname, SNAME_LEN, &mut buffer);
314        trunc_string_to_n_and_push(&file, FILE_LEN, &mut buffer);
315
316        buffer.extend_from_slice(&MAGIC_COOKIE);
317        for option in options.into_iter() {
318            option.serialize_to(&mut buffer);
319        }
320        buffer.push(OptionCode::End.into());
321
322        buffer
323    }
324
325    /// Returns the value's DHCP `MessageType` or appropriate `MessageTypeError` in case of failure.
326    pub fn get_dhcp_type(&self) -> Result<MessageType, ProtocolError> {
327        self.options
328            .iter()
329            .filter_map(|opt| match opt {
330                DhcpOption::DhcpMessageType(v) => Some(*v),
331                _ => None,
332            })
333            .next()
334            .ok_or(ProtocolError::MissingOption(OptionCode::DhcpMessageType))
335    }
336}
337
338pub mod identifier {
339    use super::{DhcpOption, Message, CHADDR_LEN};
340    use net_types::ethernet::Mac as MacAddr;
341    use std::convert::TryInto as _;
342
343    const CLIENT_IDENTIFIER_ID: &'static str = "id";
344    const CLIENT_IDENTIFIER_CHADDR: &'static str = "chaddr";
345
346    /// An opaque identifier which uniquely identifies a DHCP client to a DHCP server.
347    #[derive(Clone, Debug, Eq, Hash, PartialEq)]
348    pub struct ClientIdentifier {
349        inner: ClientIdentifierInner,
350    }
351
352    #[derive(Clone, Debug, Eq, Hash, PartialEq)]
353    enum ClientIdentifierInner {
354        /// An identifier derived from a Client-identifier DHCP Option, as defined in
355        /// https://tools.ietf.org/html/rfc2132#section-9.14.
356        Id(Vec<u8>),
357        /// An identifier derived from the chaddr field of a DHCP message, typically only used in the
358        /// absense of the Client-identifier DHCP Option.
359        Chaddr(MacAddr),
360    }
361
362    impl From<MacAddr> for ClientIdentifier {
363        fn from(v: MacAddr) -> Self {
364            Self { inner: ClientIdentifierInner::Chaddr(v) }
365        }
366    }
367
368    impl From<&Message> for ClientIdentifier {
369        /// Returns the opaque client identifier associated with the argument message.
370        ///
371        /// Typically, a message will contain a `DhcpOption::ClientIdentifier` which stores the
372        /// associated opaque client identifier. In the absence of this option, an identifier
373        /// will be constructed from the `chaddr` field of the message.
374        fn from(msg: &Message) -> ClientIdentifier {
375            msg.options
376                .iter()
377                .find_map(|opt| match opt {
378                    DhcpOption::ClientIdentifier(v) => Some(ClientIdentifier {
379                        inner: ClientIdentifierInner::Id(v.clone().into()),
380                    }),
381                    _ => None,
382                })
383                .unwrap_or_else(|| ClientIdentifier::from(msg.chaddr))
384        }
385    }
386
387    impl std::str::FromStr for ClientIdentifier {
388        type Err = anyhow::Error;
389
390        fn from_str(s: &str) -> Result<Self, Self::Err> {
391            let mut id_parts = s.splitn(2, ":");
392            let id_type = id_parts
393                .next()
394                .ok_or_else(|| anyhow::anyhow!("no client id type found in string: {}", s))?;
395            let id = id_parts
396                .next()
397                .ok_or_else(|| anyhow::anyhow!("no client id found in string: {}", s))?;
398            let () = match id_parts.next() {
399                None => (),
400                Some(v) => {
401                    return Err(anyhow::anyhow!(
402                        "client id string contained unexpected fields: {}",
403                        v
404                    ))
405                }
406            };
407            let id = hex::decode(id)?;
408            match id_type {
409                CLIENT_IDENTIFIER_ID => Ok(Self { inner: ClientIdentifierInner::Id(id) }),
410                CLIENT_IDENTIFIER_CHADDR => Ok(Self {
411                    inner: ClientIdentifierInner::Chaddr(MacAddr::new(
412                        id.get(..CHADDR_LEN)
413                            .ok_or_else(|| {
414                                anyhow::anyhow!("client id had insufficient length: {:?}", id)
415                            })?
416                            .try_into()?,
417                    )),
418                }),
419                id_type => Err(anyhow::anyhow!("unrecognized client id type: {}", id_type)),
420            }
421        }
422    }
423
424    impl std::fmt::Display for ClientIdentifier {
425        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
426            use zerocopy::IntoBytes as _;
427            let (id_type, id) = match self {
428                Self { inner: ClientIdentifierInner::Id(v) } => (CLIENT_IDENTIFIER_ID, &v[..]),
429                Self { inner: ClientIdentifierInner::Chaddr(v) } => {
430                    (CLIENT_IDENTIFIER_CHADDR, v.as_bytes())
431                }
432            };
433            write!(f, "{}:{}", id_type, hex::encode(id))
434        }
435    }
436}
437
438/// A DHCP protocol op-code as defined in RFC 2131.
439///
440/// Note that this type corresponds to the first field of a DHCP message,
441/// opcode, and is distinct from the OptionCode type. In this case, "Op"
442/// is an abbreviation for Operator, not Option.
443///
444/// `OpCode::BOOTREQUEST` should only appear in protocol messages from the
445/// client, and conversely `OpCode::BOOTREPLY` should only appear in messages
446/// from the server.
447#[derive(FromPrimitive, Copy, Clone, Debug, PartialEq)]
448#[repr(u8)]
449pub enum OpCode {
450    BOOTREQUEST = 1,
451    BOOTREPLY = 2,
452}
453
454impl fmt::Display for OpCode {
455    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
456        match self {
457            OpCode::BOOTREQUEST => write!(f, "BOOTREQUEST"),
458            OpCode::BOOTREPLY => write!(f, "BOOTREPLY"),
459        }
460    }
461}
462
463impl From<OpCode> for u8 {
464    fn from(code: OpCode) -> u8 {
465        code as u8
466    }
467}
468
469impl TryFrom<u8> for OpCode {
470    type Error = ProtocolError;
471
472    fn try_from(n: u8) -> Result<Self, Self::Error> {
473        <Self as num_traits::FromPrimitive>::from_u8(n).ok_or(ProtocolError::InvalidOpCode(n))
474    }
475}
476
477/// A DHCP option code.
478///
479/// This enum corresponds to the codes for DHCP options as defined in
480/// RFC 1533. Note that not all options defined in the RFC are represented
481/// here; options which are not in this type are not currently supported. Supported
482/// options appear in this type in the order in which they are defined in the RFC.
483#[derive(
484    Copy, Clone, Debug, Deserialize, Eq, FromPrimitive, Hash, PartialEq, Serialize, PartialOrd, Ord,
485)]
486#[repr(u8)]
487pub enum OptionCode {
488    Pad = 0,
489    SubnetMask = 1,
490    TimeOffset = 2,
491    Router = 3,
492    TimeServer = 4,
493    NameServer = 5,
494    DomainNameServer = 6,
495    LogServer = 7,
496    CookieServer = 8,
497    LprServer = 9,
498    ImpressServer = 10,
499    ResourceLocationServer = 11,
500    HostName = 12,
501    BootFileSize = 13,
502    MeritDumpFile = 14,
503    DomainName = 15,
504    SwapServer = 16,
505    RootPath = 17,
506    ExtensionsPath = 18,
507    IpForwarding = 19,
508    NonLocalSourceRouting = 20,
509    PolicyFilter = 21,
510    MaxDatagramReassemblySize = 22,
511    DefaultIpTtl = 23,
512    PathMtuAgingTimeout = 24,
513    PathMtuPlateauTable = 25,
514    InterfaceMtu = 26,
515    AllSubnetsLocal = 27,
516    BroadcastAddress = 28,
517    PerformMaskDiscovery = 29,
518    MaskSupplier = 30,
519    PerformRouterDiscovery = 31,
520    RouterSolicitationAddress = 32,
521    StaticRoute = 33,
522    TrailerEncapsulation = 34,
523    ArpCacheTimeout = 35,
524    EthernetEncapsulation = 36,
525    TcpDefaultTtl = 37,
526    TcpKeepaliveInterval = 38,
527    TcpKeepaliveGarbage = 39,
528    NetworkInformationServiceDomain = 40,
529    NetworkInformationServers = 41,
530    NetworkTimeProtocolServers = 42,
531    VendorSpecificInformation = 43,
532    NetBiosOverTcpipNameServer = 44,
533    NetBiosOverTcpipDatagramDistributionServer = 45,
534    NetBiosOverTcpipNodeType = 46,
535    NetBiosOverTcpipScope = 47,
536    XWindowSystemFontServer = 48,
537    XWindowSystemDisplayManager = 49,
538    RequestedIpAddress = 50,
539    IpAddressLeaseTime = 51,
540    OptionOverload = 52,
541    DhcpMessageType = 53,
542    ServerIdentifier = 54,
543    ParameterRequestList = 55,
544    Message = 56,
545    MaxDhcpMessageSize = 57,
546    RenewalTimeValue = 58,
547    RebindingTimeValue = 59,
548    VendorClassIdentifier = 60,
549    ClientIdentifier = 61,
550    NetworkInformationServicePlusDomain = 64,
551    NetworkInformationServicePlusServers = 65,
552    TftpServerName = 66,
553    BootfileName = 67,
554    MobileIpHomeAgent = 68,
555    SmtpServer = 69,
556    Pop3Server = 70,
557    NntpServer = 71,
558    DefaultWwwServer = 72,
559    DefaultFingerServer = 73,
560    DefaultIrcServer = 74,
561    StreetTalkServer = 75,
562    StreetTalkDirectoryAssistanceServer = 76,
563    End = 255,
564}
565
566impl From<OptionCode> for u8 {
567    fn from(code: OptionCode) -> u8 {
568        code as u8
569    }
570}
571
572impl TryFrom<u8> for OptionCode {
573    type Error = ProtocolError;
574
575    fn try_from(n: u8) -> Result<Self, Self::Error> {
576        <Self as num_traits::FromPrimitive>::from_u8(n).ok_or(ProtocolError::InvalidOptionCode(n))
577    }
578}
579
580impl fmt::Display for OptionCode {
581    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
582        fmt::Debug::fmt(&self, f)
583    }
584}
585
586mod prefix_length {
587    use std::net::Ipv4Addr;
588
589    use net_types::ip::{Ipv4, NotSubnetMaskError, PrefixLength};
590    use serde::de::{Deserialize as _, Error};
591    use serde::Serialize as _;
592
593    pub(super) fn serialize<S: serde::Serializer>(
594        prefix_length: &PrefixLength<Ipv4>,
595        serializer: S,
596    ) -> Result<S::Ok, S::Error> {
597        Ipv4Addr::serialize(&Ipv4Addr::from(prefix_length.get_mask()), serializer)
598    }
599
600    pub(super) fn deserialize<'de, D: serde::Deserializer<'de>>(
601        deserializer: D,
602    ) -> Result<PrefixLength<Ipv4>, D::Error> {
603        let addr = Ipv4Addr::deserialize(deserializer)?;
604        PrefixLength::try_from_subnet_mask(addr.into())
605            .map_err(|NotSubnetMaskError| D::Error::custom("not a valid subnet mask"))
606    }
607}
608
609/// A DHCP Option as defined in RFC 2132.
610/// DHCP Options provide a mechanism for transmitting configuration parameters
611/// between the Server and Client and vice-versa. DHCP Options also include
612/// some control and meta information needed for the operation of the DHCP
613/// protocol but which could not be included in the DHCP header because of
614/// the backwards compatibility requirement with the older BOOTP protocol.
615#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
616pub enum DhcpOption {
617    Pad(),
618    End(),
619    #[serde(with = "prefix_length")]
620    SubnetMask(PrefixLength<Ipv4>),
621    TimeOffset(i32),
622    // Router must have at least 1 element and has an 8-bit length field:
623    // https://datatracker.ietf.org/doc/html/rfc2132#section-3.5
624    Router(AtLeast<1, AtMostBytes<{ size_constrained::U8_MAX_AS_USIZE }, Vec<Ipv4Addr>>>),
625    // Time Server must have at least 1 element and has an 8-bit length field:
626    // https://datatracker.ietf.org/doc/html/rfc2132#section-3.6
627    TimeServer(AtLeast<1, AtMostBytes<{ size_constrained::U8_MAX_AS_USIZE }, Vec<Ipv4Addr>>>),
628    // Name Server must have at least 1 element and has an 8-bit length field:
629    // https://datatracker.ietf.org/doc/html/rfc2132#section-3.7
630    NameServer(AtLeast<1, AtMostBytes<{ size_constrained::U8_MAX_AS_USIZE }, Vec<Ipv4Addr>>>),
631    // Domain Name Server must have at least 1 element and has an 8-bit length field:
632    // https://datatracker.ietf.org/doc/html/rfc2132#section-3.8
633    DomainNameServer(AtLeast<1, AtMostBytes<{ size_constrained::U8_MAX_AS_USIZE }, Vec<Ipv4Addr>>>),
634    // Log Server must have at least 1 element and has an 8-bit length field:
635    // https://datatracker.ietf.org/doc/html/rfc2132#section-3.9
636    LogServer(AtLeast<1, AtMostBytes<{ size_constrained::U8_MAX_AS_USIZE }, Vec<Ipv4Addr>>>),
637    // Cookie Server must have at least 1 element and has an 8-bit length field:
638    // https://datatracker.ietf.org/doc/html/rfc2132#section-3.10
639    CookieServer(AtLeast<1, AtMostBytes<{ size_constrained::U8_MAX_AS_USIZE }, Vec<Ipv4Addr>>>),
640    // LPR Server must have at least 1 element and has an 8-bit length field:
641    // https://datatracker.ietf.org/doc/html/rfc2132#section-3.11
642    LprServer(AtLeast<1, AtMostBytes<{ size_constrained::U8_MAX_AS_USIZE }, Vec<Ipv4Addr>>>),
643    // Impress Server must have at least 1 element and has an 8-bit length field:
644    // https://datatracker.ietf.org/doc/html/rfc2132#section-3.12
645    ImpressServer(AtLeast<1, AtMostBytes<{ size_constrained::U8_MAX_AS_USIZE }, Vec<Ipv4Addr>>>),
646    // Resource Location Server must have at least 1 element and has an 8-bit length field:
647    // https://datatracker.ietf.org/doc/html/rfc2132#section-3.13
648    ResourceLocationServer(
649        AtLeast<1, AtMostBytes<{ size_constrained::U8_MAX_AS_USIZE }, Vec<Ipv4Addr>>>,
650    ),
651    HostName(String),
652    BootFileSize(u16),
653    MeritDumpFile(String),
654    DomainName(String),
655    SwapServer(Ipv4Addr),
656    RootPath(String),
657    ExtensionsPath(String),
658    IpForwarding(bool),
659    NonLocalSourceRouting(bool),
660    // Policy Filter must have at least **2** elements and has an 8-bit length field:
661    // https://datatracker.ietf.org/doc/html/rfc2132#section-4.3
662    PolicyFilter(AtLeast<2, AtMostBytes<{ size_constrained::U8_MAX_AS_USIZE }, Vec<Ipv4Addr>>>),
663    MaxDatagramReassemblySize(u16),
664    // DefaultIpTtl cannot be zero: https://datatracker.ietf.org/doc/html/rfc2132#section-4.5
665    DefaultIpTtl(NonZeroU8),
666    PathMtuAgingTimeout(u32),
667    // Path MTU Plateau Table must have at least 1 element and has an 8-bit length field:
668    // https://datatracker.ietf.org/doc/html/rfc2132#section-4.7
669    PathMtuPlateauTable(AtLeast<1, AtMostBytes<{ size_constrained::U8_MAX_AS_USIZE }, Vec<u16>>>),
670    InterfaceMtu(u16),
671    AllSubnetsLocal(bool),
672    BroadcastAddress(Ipv4Addr),
673    PerformMaskDiscovery(bool),
674    MaskSupplier(bool),
675    PerformRouterDiscovery(bool),
676    RouterSolicitationAddress(Ipv4Addr),
677    // Static Route must have at least **2** elements and has an 8-bit length field:
678    // https://datatracker.ietf.org/doc/html/rfc2132#section-5.8
679    StaticRoute(AtLeast<2, AtMostBytes<{ size_constrained::U8_MAX_AS_USIZE }, Vec<Ipv4Addr>>>),
680    TrailerEncapsulation(bool),
681    ArpCacheTimeout(u32),
682    EthernetEncapsulation(bool),
683    // TcpDefaultTtl cannot be zero: https://datatracker.ietf.org/doc/html/rfc2132#section-7.1
684    TcpDefaultTtl(NonZeroU8),
685    TcpKeepaliveInterval(u32),
686    TcpKeepaliveGarbage(bool),
687    NetworkInformationServiceDomain(String),
688    // Network Information Servers must have at least 1 element and has an 8-bit length field:
689    // https://datatracker.ietf.org/doc/html/rfc2132#section-8.2
690    NetworkInformationServers(
691        AtLeast<1, AtMostBytes<{ size_constrained::U8_MAX_AS_USIZE }, Vec<Ipv4Addr>>>,
692    ),
693    // Network Time Protocol Servers must have at least 1 element and has an 8-bit length field:
694    // https://datatracker.ietf.org/doc/html/rfc2132#section-8.3
695    NetworkTimeProtocolServers(
696        AtLeast<1, AtMostBytes<{ size_constrained::U8_MAX_AS_USIZE }, Vec<Ipv4Addr>>>,
697    ),
698    // Vendor Specific Information must have at least 1 element and has an 8-bit length field:
699    // https://datatracker.ietf.org/doc/html/rfc2132#section-8.4
700    VendorSpecificInformation(
701        AtLeast<1, AtMostBytes<{ size_constrained::U8_MAX_AS_USIZE }, Vec<u8>>>,
702    ),
703    // NetBIOS over TCP/IP Name Server must have at least 1 element and has an 8-bit length field:
704    // https://datatracker.ietf.org/doc/html/rfc2132#section-8.5
705    NetBiosOverTcpipNameServer(
706        AtLeast<1, AtMostBytes<{ size_constrained::U8_MAX_AS_USIZE }, Vec<Ipv4Addr>>>,
707    ),
708    // NetBIOS over TCP/IP Datagram Distribution Server must have at least 1 element and has an 8-bit length field:
709    // https://datatracker.ietf.org/doc/html/rfc2132#section-8.6
710    NetBiosOverTcpipDatagramDistributionServer(
711        AtLeast<1, AtMostBytes<{ size_constrained::U8_MAX_AS_USIZE }, Vec<Ipv4Addr>>>,
712    ),
713    NetBiosOverTcpipNodeType(NodeType),
714    NetBiosOverTcpipScope(String),
715    // X Window System Font Server must have at least 1 element and has an 8-bit length field:
716    // https://datatracker.ietf.org/doc/html/rfc2132#section-8.9
717    XWindowSystemFontServer(
718        AtLeast<1, AtMostBytes<{ size_constrained::U8_MAX_AS_USIZE }, Vec<Ipv4Addr>>>,
719    ),
720    // X Window System Display Manager must have at least 1 element and has an 8-bit length field:
721    // https://datatracker.ietf.org/doc/html/rfc2132#section-8.10
722    XWindowSystemDisplayManager(
723        AtLeast<1, AtMostBytes<{ size_constrained::U8_MAX_AS_USIZE }, Vec<Ipv4Addr>>>,
724    ),
725    NetworkInformationServicePlusDomain(String),
726    // Network Information Service+ Servers must have at least 1 element and has an 8-bit length field:
727    // https://datatracker.ietf.org/doc/html/rfc2132#section-8.12
728    NetworkInformationServicePlusServers(
729        AtLeast<1, AtMostBytes<{ size_constrained::U8_MAX_AS_USIZE }, Vec<Ipv4Addr>>>,
730    ),
731    // Mobile IP Home Agent has an 8-bit length field, but is allowed to have 0 elements:
732    // https://datatracker.ietf.org/doc/html/rfc2132#section-8.13
733    MobileIpHomeAgent(
734        AtLeast<0, AtMostBytes<{ size_constrained::U8_MAX_AS_USIZE }, Vec<Ipv4Addr>>>,
735    ),
736    // SMTP Server must have at least 1 element and has an 8-bit length field:
737    // https://datatracker.ietf.org/doc/html/rfc2132#section-8.14
738    SmtpServer(AtLeast<1, AtMostBytes<{ size_constrained::U8_MAX_AS_USIZE }, Vec<Ipv4Addr>>>),
739    // POP3 Server must have at least 1 element and has an 8-bit length field:
740    // https://datatracker.ietf.org/doc/html/rfc2132#section-8.15
741    Pop3Server(AtLeast<1, AtMostBytes<{ size_constrained::U8_MAX_AS_USIZE }, Vec<Ipv4Addr>>>),
742    // NNTP Server must have at least 1 element and has an 8-bit length field:
743    // https://datatracker.ietf.org/doc/html/rfc2132#section-8.16
744    NntpServer(AtLeast<1, AtMostBytes<{ size_constrained::U8_MAX_AS_USIZE }, Vec<Ipv4Addr>>>),
745    // Default WWW Server must have at least 1 element and has an 8-bit length field:
746    // https://datatracker.ietf.org/doc/html/rfc2132#section-8.17
747    DefaultWwwServer(AtLeast<1, AtMostBytes<{ size_constrained::U8_MAX_AS_USIZE }, Vec<Ipv4Addr>>>),
748    // Default Finger Server must have at least 1 element and has an 8-bit length field:
749    // https://datatracker.ietf.org/doc/html/rfc2132#section-8.18
750    DefaultFingerServer(
751        AtLeast<1, AtMostBytes<{ size_constrained::U8_MAX_AS_USIZE }, Vec<Ipv4Addr>>>,
752    ),
753    // Default IRC Server must have at least 1 element and has an 8-bit length field:
754    // https://datatracker.ietf.org/doc/html/rfc2132#section-8.19
755    DefaultIrcServer(AtLeast<1, AtMostBytes<{ size_constrained::U8_MAX_AS_USIZE }, Vec<Ipv4Addr>>>),
756    // StreetTalk Server must have at least 1 element and has an 8-bit length field:
757    // https://datatracker.ietf.org/doc/html/rfc2132#section-8.20
758    StreetTalkServer(AtLeast<1, AtMostBytes<{ size_constrained::U8_MAX_AS_USIZE }, Vec<Ipv4Addr>>>),
759    // StreetTalk Directory Assistance Server must have at least 1 element and has an 8-bit length field:
760    // https://datatracker.ietf.org/doc/html/rfc2132#section-8.21
761    StreetTalkDirectoryAssistanceServer(
762        AtLeast<1, AtMostBytes<{ size_constrained::U8_MAX_AS_USIZE }, Vec<Ipv4Addr>>>,
763    ),
764    RequestedIpAddress(Ipv4Addr),
765    IpAddressLeaseTime(u32),
766    OptionOverload(Overload),
767    TftpServerName(String),
768    BootfileName(String),
769    DhcpMessageType(MessageType),
770    ServerIdentifier(Ipv4Addr),
771    // Parameter Request List must have at least 1 element and has an 8-bit length field:
772    // https://datatracker.ietf.org/doc/html/rfc2132#section-9.8
773    ParameterRequestList(
774        AtLeast<1, AtMostBytes<{ size_constrained::U8_MAX_AS_USIZE }, Vec<OptionCode>>>,
775    ),
776    /// According to [RFC 2132 section 9.9], "The message consists of n octets
777    /// of NVT ASCII text, which the client may display on an available output
778    /// device". According to [RFC 3629 section 1], UTF-8 preserves US-ASCII
779    /// octets.
780    ///
781    /// While it's somewhat ambiguous from [RFC 854] whether "NVT ASCII"
782    /// includes the non-standard-7-bit-ASCII control codes or not, [RFC 698]
783    /// states that "it is expected normal NVT ASCII would be used for 7-bit
784    /// ASCII", suggesting that NVT ASCII does designate only the
785    /// US-ASCII-compatible subset of characters that can be output by NVT
786    /// terminals.
787    ///
788    /// Thus, it is safe to represent incoming Message options as a UTF-8 String
789    /// and discard Message options that cannot be represented in UTF-8.
790    ///
791    /// However, _outgoing_ Message options (e.g. ones written by a DHCP server
792    /// implemented using this library) must be careful to use only ASCII text
793    /// rather than full UTF-8.
794    ///
795    /// [RFC 2132 section 9.9]: https://datatracker.ietf.org/doc/html/rfc2132#section-9.9
796    /// [RFC 3629 section 1]: https://datatracker.ietf.org/doc/html/rfc3629#section-1
797    /// [RFC 854]: https://datatracker.ietf.org/doc/html/rfc854
798    /// [RFC 698]: https://datatracker.ietf.org/doc/html/rfc698
799    Message(String),
800    MaxDhcpMessageSize(u16),
801    RenewalTimeValue(u32),
802    RebindingTimeValue(u32),
803    // Vendor Class Identifier must be at least 1 byte long and has an 8-bit length field:
804    // https://datatracker.ietf.org/doc/html/rfc2132#section-9.13
805    VendorClassIdentifier(AtLeast<1, AtMostBytes<{ size_constrained::U8_MAX_AS_USIZE }, Vec<u8>>>),
806    // Client Identifier must be at least **2** bytes long and has an 8-bit length field:
807    // https://datatracker.ietf.org/doc/html/rfc2132#section-9.14
808    ClientIdentifier(
809        AtLeast<
810            { CLIENT_IDENTIFIER_MINIMUM_LENGTH },
811            AtMostBytes<{ size_constrained::U8_MAX_AS_USIZE }, Vec<u8>>,
812        >,
813    ),
814}
815
816/// The minimum length, in bytes, of the Client Identifier option.
817pub const CLIENT_IDENTIFIER_MINIMUM_LENGTH: usize = 2;
818
819/// Generates a match expression on `$option` which maps each of the supplied `DhcpOption` variants
820/// to their `OptionCode` equivalent.
821macro_rules! option_to_code {
822    ($option:ident, $(DhcpOption::$variant:tt($($v:tt)*)),*) => {
823        match $option {
824            $(DhcpOption::$variant($($v)*) => OptionCode::$variant,)*
825        }
826    };
827}
828
829impl DhcpOption {
830    fn from_raw_parts(code: OptionCode, val: &[u8]) -> Result<Self, ProtocolError> {
831        match code {
832            OptionCode::Pad => Ok(DhcpOption::Pad()),
833            OptionCode::End => Ok(DhcpOption::End()),
834            OptionCode::SubnetMask => {
835                let addr = bytes_to_addr(val)?;
836                Ok(DhcpOption::SubnetMask(
837                    PrefixLength::try_from_subnet_mask(addr.into()).map_err(
838                        |NotSubnetMaskError| ProtocolError::InvalidOptionValue(code, val.to_vec()),
839                    )?,
840                ))
841            }
842            OptionCode::TimeOffset => {
843                let offset = get_byte_array::<4>(val).map(i32::from_be_bytes)?;
844                Ok(DhcpOption::TimeOffset(offset))
845            }
846            OptionCode::Router => Ok(DhcpOption::Router(bytes_to_addrs(val)?)),
847            OptionCode::TimeServer => Ok(DhcpOption::TimeServer(bytes_to_addrs(val)?)),
848            OptionCode::NameServer => Ok(DhcpOption::NameServer(bytes_to_addrs(val)?)),
849            OptionCode::DomainNameServer => Ok(DhcpOption::DomainNameServer(bytes_to_addrs(val)?)),
850            OptionCode::LogServer => Ok(DhcpOption::LogServer(bytes_to_addrs(val)?)),
851            OptionCode::CookieServer => Ok(DhcpOption::CookieServer(bytes_to_addrs(val)?)),
852            OptionCode::LprServer => Ok(DhcpOption::LprServer(bytes_to_addrs(val)?)),
853            OptionCode::ImpressServer => Ok(DhcpOption::ImpressServer(bytes_to_addrs(val)?)),
854            OptionCode::ResourceLocationServer => {
855                Ok(DhcpOption::ResourceLocationServer(bytes_to_addrs(val)?))
856            }
857            OptionCode::HostName => Ok(DhcpOption::HostName(bytes_to_nonempty_str(val)?)),
858            OptionCode::BootFileSize => {
859                let size = get_byte_array::<2>(val).map(u16::from_be_bytes)?;
860                Ok(DhcpOption::BootFileSize(size))
861            }
862            OptionCode::MeritDumpFile => Ok(DhcpOption::MeritDumpFile(bytes_to_nonempty_str(val)?)),
863            OptionCode::DomainName => Ok(DhcpOption::DomainName(bytes_to_nonempty_str(val)?)),
864            OptionCode::SwapServer => Ok(DhcpOption::SwapServer(bytes_to_addr(val)?)),
865            OptionCode::RootPath => Ok(DhcpOption::RootPath(bytes_to_nonempty_str(val)?)),
866            OptionCode::ExtensionsPath => {
867                Ok(DhcpOption::ExtensionsPath(bytes_to_nonempty_str(val)?))
868            }
869            OptionCode::IpForwarding => {
870                let flag = bytes_to_bool(val).map_err(|e| e.to_protocol(code))?;
871                Ok(DhcpOption::IpForwarding(flag))
872            }
873            OptionCode::NonLocalSourceRouting => {
874                let flag = bytes_to_bool(val).map_err(|e| e.to_protocol(code))?;
875                Ok(DhcpOption::NonLocalSourceRouting(flag))
876            }
877            OptionCode::PolicyFilter => {
878                let addrs = bytes_to_addrs(val)?;
879                if addrs.len() < 2 || addrs.len() % 2 != 0 {
880                    return Err(ProtocolError::InvalidBufferLength(val.len()));
881                }
882                Ok(DhcpOption::PolicyFilter(addrs))
883            }
884            OptionCode::MaxDatagramReassemblySize => {
885                let max_datagram = get_byte_array::<2>(val).map(u16::from_be_bytes)?;
886                if max_datagram < MIN_MESSAGE_SIZE {
887                    return Err(ProtocolError::InvalidOptionValue(code, val.to_vec()));
888                }
889                Ok(DhcpOption::MaxDatagramReassemblySize(max_datagram))
890            }
891            OptionCode::DefaultIpTtl => {
892                let ttl = get_byte(val)?;
893                let ttl = NonZeroU8::new(ttl)
894                    .ok_or_else(|| ProtocolError::InvalidOptionValue(code, val.to_vec()))?;
895                Ok(DhcpOption::DefaultIpTtl(ttl))
896            }
897            OptionCode::PathMtuAgingTimeout => {
898                let timeout = get_byte_array::<4>(val).map(u32::from_be_bytes)?;
899                Ok(DhcpOption::PathMtuAgingTimeout(timeout))
900            }
901            OptionCode::PathMtuPlateauTable => {
902                let mtus = val
903                    .chunks(2)
904                    .map(|chunk| get_byte_array::<2>(chunk).map(u16::from_be_bytes))
905                    .collect::<Result<Vec<u16>, InvalidBufferLengthError>>()
906                    .map_err(|InvalidBufferLengthError(_)| {
907                        ProtocolError::InvalidBufferLength(val.len())
908                    })?;
909                Ok(DhcpOption::PathMtuPlateauTable(mtus.try_into().map_err(
910                    |(size_constrained::Error::SizeConstraintViolated, _)| {
911                        ProtocolError::InvalidBufferLength(val.len())
912                    },
913                )?))
914            }
915            OptionCode::InterfaceMtu => {
916                let mtu = get_byte_array::<2>(val).map(u16::from_be_bytes)?;
917                if mtu < MIN_MTU_VAL {
918                    return Err(ProtocolError::InvalidOptionValue(code, val.to_vec()));
919                }
920                Ok(DhcpOption::InterfaceMtu(mtu))
921            }
922            OptionCode::AllSubnetsLocal => {
923                let flag = bytes_to_bool(val).map_err(|e| e.to_protocol(code))?;
924                Ok(DhcpOption::AllSubnetsLocal(flag))
925            }
926            OptionCode::BroadcastAddress => Ok(DhcpOption::BroadcastAddress(bytes_to_addr(val)?)),
927            OptionCode::PerformMaskDiscovery => {
928                let flag = bytes_to_bool(val).map_err(|e| e.to_protocol(code))?;
929                Ok(DhcpOption::PerformMaskDiscovery(flag))
930            }
931            OptionCode::MaskSupplier => {
932                let flag = bytes_to_bool(val).map_err(|e| e.to_protocol(code))?;
933                Ok(DhcpOption::MaskSupplier(flag))
934            }
935            OptionCode::PerformRouterDiscovery => {
936                let flag = bytes_to_bool(val).map_err(|e| e.to_protocol(code))?;
937                Ok(DhcpOption::PerformRouterDiscovery(flag))
938            }
939            OptionCode::RouterSolicitationAddress => {
940                Ok(DhcpOption::RouterSolicitationAddress(bytes_to_addr(val)?))
941            }
942            OptionCode::StaticRoute => {
943                let addrs = bytes_to_addrs(val)?;
944                if addrs.len() < 2 || addrs.len() % 2 != 0 {
945                    return Err(ProtocolError::InvalidBufferLength(val.len()));
946                }
947                Ok(DhcpOption::StaticRoute(addrs))
948            }
949            OptionCode::TrailerEncapsulation => {
950                let flag = bytes_to_bool(val).map_err(|e| e.to_protocol(code))?;
951                Ok(DhcpOption::TrailerEncapsulation(flag))
952            }
953            OptionCode::ArpCacheTimeout => {
954                let timeout = get_byte_array::<4>(val).map(u32::from_be_bytes)?;
955                Ok(DhcpOption::ArpCacheTimeout(timeout))
956            }
957            OptionCode::EthernetEncapsulation => {
958                let flag = bytes_to_bool(val).map_err(|e| e.to_protocol(code))?;
959                Ok(DhcpOption::EthernetEncapsulation(flag))
960            }
961            OptionCode::TcpDefaultTtl => {
962                let ttl = get_byte(val)?;
963                let ttl = NonZeroU8::new(ttl)
964                    .ok_or_else(|| ProtocolError::InvalidOptionValue(code, val.to_vec()))?;
965                Ok(DhcpOption::TcpDefaultTtl(ttl))
966            }
967            OptionCode::TcpKeepaliveInterval => {
968                let interval = get_byte_array::<4>(val).map(u32::from_be_bytes)?;
969                Ok(DhcpOption::TcpKeepaliveInterval(interval))
970            }
971            OptionCode::TcpKeepaliveGarbage => {
972                let flag = bytes_to_bool(val).map_err(|e| e.to_protocol(code))?;
973                Ok(DhcpOption::TcpKeepaliveGarbage(flag))
974            }
975            OptionCode::NetworkInformationServiceDomain => {
976                let name = bytes_to_nonempty_str(val)?;
977                Ok(DhcpOption::NetworkInformationServiceDomain(name))
978            }
979            OptionCode::NetworkInformationServers => {
980                Ok(DhcpOption::NetworkInformationServers(bytes_to_addrs(val)?))
981            }
982            OptionCode::NetworkTimeProtocolServers => {
983                Ok(DhcpOption::NetworkTimeProtocolServers(bytes_to_addrs(val)?))
984            }
985            OptionCode::VendorSpecificInformation => {
986                Ok(DhcpOption::VendorSpecificInformation(val.to_owned().try_into().map_err(
987                    |(size_constrained::Error::SizeConstraintViolated, _)| {
988                        ProtocolError::InvalidBufferLength(val.len())
989                    },
990                )?))
991            }
992            OptionCode::NetBiosOverTcpipNameServer => {
993                Ok(DhcpOption::NetBiosOverTcpipNameServer(bytes_to_addrs(val)?))
994            }
995            OptionCode::NetBiosOverTcpipDatagramDistributionServer => {
996                Ok(DhcpOption::NetBiosOverTcpipDatagramDistributionServer(bytes_to_addrs(val)?))
997            }
998            OptionCode::NetBiosOverTcpipNodeType => {
999                let byte = get_byte(val)?;
1000                Ok(DhcpOption::NetBiosOverTcpipNodeType(NodeType::try_from(byte)?))
1001            }
1002            OptionCode::NetBiosOverTcpipScope => {
1003                Ok(DhcpOption::NetBiosOverTcpipScope(bytes_to_nonempty_str(val)?))
1004            }
1005            OptionCode::XWindowSystemFontServer => {
1006                Ok(DhcpOption::XWindowSystemFontServer(bytes_to_addrs(val)?))
1007            }
1008            OptionCode::XWindowSystemDisplayManager => {
1009                Ok(DhcpOption::XWindowSystemDisplayManager(bytes_to_addrs(val)?))
1010            }
1011            OptionCode::NetworkInformationServicePlusDomain => {
1012                Ok(DhcpOption::NetworkInformationServicePlusDomain(bytes_to_nonempty_str(val)?))
1013            }
1014            OptionCode::NetworkInformationServicePlusServers => {
1015                Ok(DhcpOption::NetworkInformationServicePlusServers(bytes_to_addrs(val)?))
1016            }
1017            OptionCode::MobileIpHomeAgent => {
1018                Ok(DhcpOption::MobileIpHomeAgent(bytes_to_addrs(val)?))
1019            }
1020            OptionCode::SmtpServer => Ok(DhcpOption::SmtpServer(bytes_to_addrs(val)?)),
1021            OptionCode::Pop3Server => Ok(DhcpOption::Pop3Server(bytes_to_addrs(val)?)),
1022            OptionCode::NntpServer => Ok(DhcpOption::NntpServer(bytes_to_addrs(val)?)),
1023            OptionCode::DefaultWwwServer => Ok(DhcpOption::DefaultWwwServer(bytes_to_addrs(val)?)),
1024            OptionCode::DefaultFingerServer => {
1025                Ok(DhcpOption::DefaultFingerServer(bytes_to_addrs(val)?))
1026            }
1027            OptionCode::DefaultIrcServer => Ok(DhcpOption::DefaultIrcServer(bytes_to_addrs(val)?)),
1028            OptionCode::StreetTalkServer => Ok(DhcpOption::StreetTalkServer(bytes_to_addrs(val)?)),
1029            OptionCode::StreetTalkDirectoryAssistanceServer => {
1030                Ok(DhcpOption::StreetTalkDirectoryAssistanceServer(bytes_to_addrs(val)?))
1031            }
1032            OptionCode::RequestedIpAddress => {
1033                Ok(DhcpOption::RequestedIpAddress(bytes_to_addr(val)?))
1034            }
1035            OptionCode::IpAddressLeaseTime => {
1036                let lease_time = get_byte_array::<4>(val).map(u32::from_be_bytes)?;
1037                Ok(DhcpOption::IpAddressLeaseTime(lease_time))
1038            }
1039            OptionCode::OptionOverload => {
1040                let overload = Overload::try_from(
1041                    *val.first().ok_or(ProtocolError::InvalidBufferLength(val.len()))?,
1042                )?;
1043                Ok(DhcpOption::OptionOverload(overload))
1044            }
1045            OptionCode::TftpServerName => {
1046                let name = bytes_to_nonempty_str(val)?;
1047                Ok(DhcpOption::TftpServerName(name))
1048            }
1049            OptionCode::BootfileName => {
1050                let name = bytes_to_nonempty_str(val)?;
1051                Ok(DhcpOption::BootfileName(name))
1052            }
1053            OptionCode::DhcpMessageType => {
1054                let message_type = MessageType::try_from(
1055                    *val.first().ok_or(ProtocolError::InvalidBufferLength(val.len()))?,
1056                )?;
1057                Ok(DhcpOption::DhcpMessageType(message_type))
1058            }
1059            OptionCode::ServerIdentifier => Ok(DhcpOption::ServerIdentifier(bytes_to_addr(val)?)),
1060            OptionCode::ParameterRequestList => {
1061                let opcodes = val
1062                    .iter()
1063                    .filter_map(|code| OptionCode::try_from(*code).ok())
1064                    .collect::<Vec<_>>();
1065                // Note that if we don't recognize any of the OptionCodes, we'll return Err rather
1066                // than Ok(empty parameter request list) here. This isn't strictly correct, as the
1067                // raw Parameter Request List is indeed nonempty as required by
1068                // https://www.rfc-editor.org/rfc/rfc2132#section-9.8, even though we don't
1069                // recognize any of the option codes in it. However, our usages of this fn
1070                // interpret an invalid option the same way they interpret the absence of that
1071                // option, so this is not an issue.
1072                Ok(DhcpOption::ParameterRequestList(opcodes.try_into().map_err(
1073                    |(size_constrained::Error::SizeConstraintViolated, _)| {
1074                        ProtocolError::InvalidBufferLength(val.len())
1075                    },
1076                )?))
1077            }
1078            OptionCode::Message => Ok(DhcpOption::Message(bytes_to_nonempty_str(val)?)),
1079            OptionCode::MaxDhcpMessageSize => {
1080                let max_size = get_byte_array::<2>(val).map(u16::from_be_bytes)?;
1081                if max_size < MIN_MESSAGE_SIZE {
1082                    return Err(ProtocolError::InvalidOptionValue(code, val.to_vec()));
1083                }
1084                Ok(DhcpOption::MaxDhcpMessageSize(max_size))
1085            }
1086            OptionCode::RenewalTimeValue => {
1087                let renewal_time = get_byte_array::<4>(val).map(u32::from_be_bytes)?;
1088                Ok(DhcpOption::RenewalTimeValue(renewal_time))
1089            }
1090            OptionCode::RebindingTimeValue => {
1091                let rebinding_time = get_byte_array::<4>(val).map(u32::from_be_bytes)?;
1092                Ok(DhcpOption::RebindingTimeValue(rebinding_time))
1093            }
1094            OptionCode::VendorClassIdentifier => {
1095                Ok(DhcpOption::VendorClassIdentifier(val.to_owned().try_into().map_err(
1096                    |(size_constrained::Error::SizeConstraintViolated, _)| {
1097                        ProtocolError::InvalidBufferLength(val.len())
1098                    },
1099                )?))
1100            }
1101            OptionCode::ClientIdentifier => {
1102                // Client Identifier must be at least two bytes.
1103                // https://datatracker.ietf.org/doc/html/rfc2132#section-9.14
1104                if val.len() < 2 {
1105                    return Err(ProtocolError::InvalidBufferLength(val.len()));
1106                }
1107                Ok(DhcpOption::ClientIdentifier(val.to_owned().try_into().map_err(
1108                    |(size_constrained::Error::SizeConstraintViolated, _)| {
1109                        ProtocolError::InvalidBufferLength(val.len())
1110                    },
1111                )?))
1112            }
1113        }
1114    }
1115
1116    fn serialize_to(self, buf: &mut Vec<u8>) {
1117        let code = self.code();
1118        match self {
1119            DhcpOption::Pad() => buf.push(code.into()),
1120            DhcpOption::End() => buf.push(code.into()),
1121            DhcpOption::SubnetMask(v) => serialize_address(code, v.get_mask().into(), buf),
1122            DhcpOption::TimeOffset(v) => {
1123                let size = std::mem::size_of::<i32>();
1124                buf.push(code.into());
1125                buf.push(u8::try_from(size).expect("size did not fit in u8"));
1126                buf.extend_from_slice(&v.to_be_bytes());
1127            }
1128            DhcpOption::Router(v) => serialize_addresses(code, &v, buf),
1129            DhcpOption::TimeServer(v) => serialize_addresses(code, &v, buf),
1130            DhcpOption::NameServer(v) => serialize_addresses(code, &v, buf),
1131            DhcpOption::DomainNameServer(v) => serialize_addresses(code, &v, buf),
1132            DhcpOption::LogServer(v) => serialize_addresses(code, &v, buf),
1133            DhcpOption::CookieServer(v) => serialize_addresses(code, &v, buf),
1134            DhcpOption::LprServer(v) => serialize_addresses(code, &v, buf),
1135            DhcpOption::ImpressServer(v) => serialize_addresses(code, &v, buf),
1136            DhcpOption::ResourceLocationServer(v) => serialize_addresses(code, &v, buf),
1137            DhcpOption::HostName(v) => serialize_string(code, &v, buf),
1138            DhcpOption::BootFileSize(v) => serialize_u16(code, v, buf),
1139            DhcpOption::MeritDumpFile(v) => serialize_string(code, &v, buf),
1140            DhcpOption::DomainName(v) => serialize_string(code, &v, buf),
1141            DhcpOption::SwapServer(v) => serialize_address(code, v, buf),
1142            DhcpOption::RootPath(v) => serialize_string(code, &v, buf),
1143            DhcpOption::ExtensionsPath(v) => serialize_string(code, &v, buf),
1144            DhcpOption::IpForwarding(v) => serialize_flag(code, v, buf),
1145            DhcpOption::NonLocalSourceRouting(v) => serialize_flag(code, v, buf),
1146            DhcpOption::PolicyFilter(v) => serialize_addresses(code, &v, buf),
1147            DhcpOption::MaxDatagramReassemblySize(v) => serialize_u16(code, v, buf),
1148            DhcpOption::DefaultIpTtl(v) => serialize_u8(code, v.into(), buf),
1149            DhcpOption::PathMtuAgingTimeout(v) => serialize_u32(code, v, buf),
1150            DhcpOption::PathMtuPlateauTable(v) => {
1151                let size = v.size_of_contents_in_bytes();
1152                buf.push(code.into());
1153                buf.push(u8::try_from(size).expect("size did not fit in u8"));
1154                for mtu in v {
1155                    buf.extend_from_slice(&mtu.to_be_bytes())
1156                }
1157            }
1158            DhcpOption::InterfaceMtu(v) => serialize_u16(code, v, buf),
1159            DhcpOption::AllSubnetsLocal(v) => serialize_flag(code, v, buf),
1160            DhcpOption::BroadcastAddress(v) => serialize_address(code, v, buf),
1161            DhcpOption::PerformMaskDiscovery(v) => serialize_flag(code, v, buf),
1162            DhcpOption::MaskSupplier(v) => serialize_flag(code, v, buf),
1163            DhcpOption::PerformRouterDiscovery(v) => serialize_flag(code, v, buf),
1164            DhcpOption::RouterSolicitationAddress(v) => serialize_address(code, v, buf),
1165            DhcpOption::StaticRoute(v) => serialize_addresses(code, &v, buf),
1166            DhcpOption::TrailerEncapsulation(v) => serialize_flag(code, v, buf),
1167            DhcpOption::ArpCacheTimeout(v) => serialize_u32(code, v, buf),
1168            DhcpOption::EthernetEncapsulation(v) => serialize_flag(code, v, buf),
1169            DhcpOption::TcpDefaultTtl(v) => serialize_u8(code, v.into(), buf),
1170            DhcpOption::TcpKeepaliveInterval(v) => serialize_u32(code, v, buf),
1171            DhcpOption::TcpKeepaliveGarbage(v) => serialize_flag(code, v, buf),
1172            DhcpOption::NetworkInformationServiceDomain(v) => serialize_string(code, &v, buf),
1173            DhcpOption::NetworkInformationServers(v) => serialize_addresses(code, &v, buf),
1174            DhcpOption::NetworkTimeProtocolServers(v) => serialize_addresses(code, &v, buf),
1175            DhcpOption::VendorSpecificInformation(v) => serialize_bytes(code, &v, buf),
1176            DhcpOption::NetBiosOverTcpipNameServer(v) => serialize_addresses(code, &v, buf),
1177            DhcpOption::NetBiosOverTcpipDatagramDistributionServer(v) => {
1178                serialize_addresses(code, &v, buf)
1179            }
1180            DhcpOption::NetBiosOverTcpipNodeType(v) => serialize_enum(code, v, buf),
1181            DhcpOption::NetBiosOverTcpipScope(v) => serialize_string(code, &v, buf),
1182            DhcpOption::XWindowSystemFontServer(v) => serialize_addresses(code, &v, buf),
1183            DhcpOption::XWindowSystemDisplayManager(v) => serialize_addresses(code, &v, buf),
1184            DhcpOption::NetworkInformationServicePlusDomain(v) => serialize_string(code, &v, buf),
1185            DhcpOption::NetworkInformationServicePlusServers(v) => {
1186                serialize_addresses(code, &v, buf)
1187            }
1188            DhcpOption::MobileIpHomeAgent(v) => serialize_addresses(code, &v, buf),
1189            DhcpOption::SmtpServer(v) => serialize_addresses(code, &v, buf),
1190            DhcpOption::Pop3Server(v) => serialize_addresses(code, &v, buf),
1191            DhcpOption::NntpServer(v) => serialize_addresses(code, &v, buf),
1192            DhcpOption::DefaultWwwServer(v) => serialize_addresses(code, &v, buf),
1193            DhcpOption::DefaultFingerServer(v) => serialize_addresses(code, &v, buf),
1194            DhcpOption::DefaultIrcServer(v) => serialize_addresses(code, &v, buf),
1195            DhcpOption::StreetTalkServer(v) => serialize_addresses(code, &v, buf),
1196            DhcpOption::StreetTalkDirectoryAssistanceServer(v) => {
1197                serialize_addresses(code, &v, buf)
1198            }
1199            DhcpOption::RequestedIpAddress(v) => serialize_address(code, v, buf),
1200            DhcpOption::IpAddressLeaseTime(v) => serialize_u32(code, v, buf),
1201            DhcpOption::OptionOverload(v) => serialize_enum(code, v, buf),
1202            DhcpOption::TftpServerName(v) => serialize_string(code, &v, buf),
1203            DhcpOption::BootfileName(v) => serialize_string(code, &v, buf),
1204            DhcpOption::DhcpMessageType(v) => serialize_enum(code, v, buf),
1205            DhcpOption::ServerIdentifier(v) => serialize_address(code, v, buf),
1206            DhcpOption::ParameterRequestList(v) => {
1207                let v = Vec::from(v);
1208                let size = v.size_of_contents_in_bytes();
1209                buf.push(code.into());
1210                buf.push(u8::try_from(size).expect("size did not fit in u8"));
1211                buf.extend(v.into_iter().map(u8::from));
1212            }
1213            DhcpOption::Message(v) => serialize_string(code, &v, buf),
1214            DhcpOption::MaxDhcpMessageSize(v) => serialize_u16(code, v, buf),
1215            DhcpOption::RenewalTimeValue(v) => serialize_u32(code, v, buf),
1216            DhcpOption::RebindingTimeValue(v) => serialize_u32(code, v, buf),
1217            DhcpOption::VendorClassIdentifier(v) => serialize_bytes(code, &v, buf),
1218            DhcpOption::ClientIdentifier(v) => serialize_bytes(code, &v, buf),
1219        }
1220    }
1221
1222    /// Returns the `OptionCode` variant corresponding to `self`.
1223    pub fn code(&self) -> OptionCode {
1224        option_to_code!(
1225            self,
1226            DhcpOption::Pad(),
1227            DhcpOption::End(),
1228            DhcpOption::SubnetMask(_),
1229            DhcpOption::TimeOffset(_),
1230            DhcpOption::Router(_),
1231            DhcpOption::TimeServer(_),
1232            DhcpOption::NameServer(_),
1233            DhcpOption::DomainNameServer(_),
1234            DhcpOption::LogServer(_),
1235            DhcpOption::CookieServer(_),
1236            DhcpOption::LprServer(_),
1237            DhcpOption::ImpressServer(_),
1238            DhcpOption::ResourceLocationServer(_),
1239            DhcpOption::HostName(_),
1240            DhcpOption::BootFileSize(_),
1241            DhcpOption::MeritDumpFile(_),
1242            DhcpOption::DomainName(_),
1243            DhcpOption::SwapServer(_),
1244            DhcpOption::RootPath(_),
1245            DhcpOption::ExtensionsPath(_),
1246            DhcpOption::IpForwarding(_),
1247            DhcpOption::NonLocalSourceRouting(_),
1248            DhcpOption::PolicyFilter(_),
1249            DhcpOption::MaxDatagramReassemblySize(_),
1250            DhcpOption::DefaultIpTtl(_),
1251            DhcpOption::PathMtuAgingTimeout(_),
1252            DhcpOption::PathMtuPlateauTable(_),
1253            DhcpOption::InterfaceMtu(_),
1254            DhcpOption::AllSubnetsLocal(_),
1255            DhcpOption::BroadcastAddress(_),
1256            DhcpOption::PerformMaskDiscovery(_),
1257            DhcpOption::MaskSupplier(_),
1258            DhcpOption::PerformRouterDiscovery(_),
1259            DhcpOption::RouterSolicitationAddress(_),
1260            DhcpOption::StaticRoute(_),
1261            DhcpOption::TrailerEncapsulation(_),
1262            DhcpOption::ArpCacheTimeout(_),
1263            DhcpOption::EthernetEncapsulation(_),
1264            DhcpOption::TcpDefaultTtl(_),
1265            DhcpOption::TcpKeepaliveInterval(_),
1266            DhcpOption::TcpKeepaliveGarbage(_),
1267            DhcpOption::NetworkInformationServiceDomain(_),
1268            DhcpOption::NetworkInformationServers(_),
1269            DhcpOption::NetworkTimeProtocolServers(_),
1270            DhcpOption::VendorSpecificInformation(_),
1271            DhcpOption::NetBiosOverTcpipNameServer(_),
1272            DhcpOption::NetBiosOverTcpipDatagramDistributionServer(_),
1273            DhcpOption::NetBiosOverTcpipNodeType(_),
1274            DhcpOption::NetBiosOverTcpipScope(_),
1275            DhcpOption::XWindowSystemFontServer(_),
1276            DhcpOption::XWindowSystemDisplayManager(_),
1277            DhcpOption::NetworkInformationServicePlusDomain(_),
1278            DhcpOption::NetworkInformationServicePlusServers(_),
1279            DhcpOption::MobileIpHomeAgent(_),
1280            DhcpOption::SmtpServer(_),
1281            DhcpOption::Pop3Server(_),
1282            DhcpOption::NntpServer(_),
1283            DhcpOption::DefaultWwwServer(_),
1284            DhcpOption::DefaultFingerServer(_),
1285            DhcpOption::DefaultIrcServer(_),
1286            DhcpOption::StreetTalkServer(_),
1287            DhcpOption::StreetTalkDirectoryAssistanceServer(_),
1288            DhcpOption::RequestedIpAddress(_),
1289            DhcpOption::IpAddressLeaseTime(_),
1290            DhcpOption::OptionOverload(_),
1291            DhcpOption::TftpServerName(_),
1292            DhcpOption::BootfileName(_),
1293            DhcpOption::DhcpMessageType(_),
1294            DhcpOption::ServerIdentifier(_),
1295            DhcpOption::ParameterRequestList(_),
1296            DhcpOption::Message(_),
1297            DhcpOption::MaxDhcpMessageSize(_),
1298            DhcpOption::RenewalTimeValue(_),
1299            DhcpOption::RebindingTimeValue(_),
1300            DhcpOption::VendorClassIdentifier(_),
1301            DhcpOption::ClientIdentifier(_)
1302        )
1303    }
1304}
1305
1306fn serialize_address(code: OptionCode, addr: Ipv4Addr, buf: &mut Vec<u8>) {
1307    serialize_addresses(code, &[addr], buf);
1308}
1309
1310fn serialize_addresses(code: OptionCode, addrs: &[Ipv4Addr], buf: &mut Vec<u8>) {
1311    let size = addrs.size_of_contents_in_bytes();
1312    buf.push(code.into());
1313    buf.push(u8::try_from(size).expect("size did not fit in u8"));
1314    for addr in addrs {
1315        buf.extend_from_slice(&addr.octets());
1316    }
1317}
1318
1319fn serialize_string(code: OptionCode, string: &str, buf: &mut Vec<u8>) {
1320    let size = string.len();
1321    buf.push(code.into());
1322    buf.push(u8::try_from(size).expect("size did not fit in u8"));
1323    buf.extend_from_slice(string.as_bytes());
1324}
1325
1326fn serialize_flag(code: OptionCode, flag: bool, buf: &mut Vec<u8>) {
1327    let size = std::mem::size_of::<bool>();
1328    buf.push(code.into());
1329    buf.push(u8::try_from(size).expect("size did not fit in u8"));
1330    buf.push(flag.into());
1331}
1332
1333fn serialize_u16(code: OptionCode, v: u16, buf: &mut Vec<u8>) {
1334    let size = std::mem::size_of::<u16>();
1335    buf.push(code.into());
1336    buf.push(u8::try_from(size).expect("size did not fit in u8"));
1337    buf.extend_from_slice(&v.to_be_bytes());
1338}
1339
1340fn serialize_u8(code: OptionCode, v: u8, buf: &mut Vec<u8>) {
1341    let size = std::mem::size_of::<u8>();
1342    buf.push(code.into());
1343    buf.push(u8::try_from(size).expect("size did not fit in u8"));
1344    buf.push(v);
1345}
1346
1347fn serialize_u32(code: OptionCode, v: u32, buf: &mut Vec<u8>) {
1348    let size = std::mem::size_of::<u32>();
1349    buf.push(code.into());
1350    buf.push(u8::try_from(size).expect("size did not fit in u8"));
1351    buf.extend_from_slice(&v.to_be_bytes());
1352}
1353
1354fn serialize_bytes(code: OptionCode, v: &[u8], buf: &mut Vec<u8>) {
1355    let size = v.size_of_contents_in_bytes();
1356    buf.push(code.into());
1357    buf.push(u8::try_from(size).expect("size did not fit in u8"));
1358    buf.extend_from_slice(v);
1359}
1360
1361fn serialize_enum<T: Into<u8>>(code: OptionCode, v: T, buf: &mut Vec<u8>) {
1362    let size = std::mem::size_of::<T>();
1363    buf.push(code.into());
1364    buf.push(u8::try_from(size).expect("size did not fit in u8"));
1365    buf.push(v.into());
1366}
1367
1368/// A type which can be converted to and from a FIDL type `F`.
1369#[cfg(target_os = "fuchsia")]
1370pub trait FidlCompatible<F>: Sized {
1371    type FromError;
1372    type IntoError;
1373
1374    fn try_from_fidl(fidl: F) -> Result<Self, Self::FromError>;
1375    fn try_into_fidl(self) -> Result<F, Self::IntoError>;
1376}
1377
1378/// Utility trait for infallible FIDL conversion.
1379#[cfg(target_os = "fuchsia")]
1380pub trait FromFidlExt<F>: FidlCompatible<F, FromError = Never> {
1381    fn from_fidl(fidl: F) -> Self {
1382        match Self::try_from_fidl(fidl) {
1383            Ok(slf) => slf,
1384        }
1385    }
1386}
1387
1388/// Utility trait for infallible FIDL conversion.
1389#[cfg(target_os = "fuchsia")]
1390pub trait IntoFidlExt<F>: FidlCompatible<F, IntoError = Never> {
1391    fn into_fidl(self) -> F {
1392        match self.try_into_fidl() {
1393            Ok(fidl) => fidl,
1394        }
1395    }
1396}
1397
1398#[cfg(target_os = "fuchsia")]
1399impl<F, C: FidlCompatible<F, IntoError = Never>> IntoFidlExt<F> for C {}
1400#[cfg(target_os = "fuchsia")]
1401impl<F, C: FidlCompatible<F, FromError = Never>> FromFidlExt<F> for C {}
1402
1403#[cfg(target_os = "fuchsia")]
1404impl FidlCompatible<fidl_fuchsia_net::Ipv4Address> for Ipv4Addr {
1405    type FromError = Never;
1406    type IntoError = Never;
1407
1408    fn try_from_fidl(fidl: fidl_fuchsia_net::Ipv4Address) -> Result<Self, Self::FromError> {
1409        Ok(Ipv4Addr::from(fidl.addr))
1410    }
1411
1412    fn try_into_fidl(self) -> Result<fidl_fuchsia_net::Ipv4Address, Self::IntoError> {
1413        Ok(fidl_fuchsia_net::Ipv4Address { addr: self.octets() })
1414    }
1415}
1416
1417#[cfg(target_os = "fuchsia")]
1418impl FidlCompatible<Vec<fidl_fuchsia_net::Ipv4Address>> for Vec<Ipv4Addr> {
1419    type FromError = Never;
1420    type IntoError = Never;
1421
1422    fn try_from_fidl(fidl: Vec<fidl_fuchsia_net::Ipv4Address>) -> Result<Self, Self::FromError> {
1423        Ok(fidl
1424            .into_iter()
1425            .filter_map(|addr| Ipv4Addr::try_from_fidl(addr).ok())
1426            .collect::<Vec<Ipv4Addr>>())
1427    }
1428
1429    fn try_into_fidl(self) -> Result<Vec<fidl_fuchsia_net::Ipv4Address>, Self::IntoError> {
1430        Ok(self
1431            .into_iter()
1432            .filter_map(|addr| addr.try_into_fidl().ok())
1433            .collect::<Vec<fidl_fuchsia_net::Ipv4Address>>())
1434    }
1435}
1436
1437// TODO(atait): Consider using a macro to reduce/eliminate the boilerplate in these implementations.
1438#[cfg(target_os = "fuchsia")]
1439impl FidlCompatible<fidl_fuchsia_net_dhcp::Option_> for DhcpOption {
1440    type FromError = ProtocolError;
1441    type IntoError = ProtocolError;
1442
1443    fn try_into_fidl(self) -> Result<fidl_fuchsia_net_dhcp::Option_, Self::IntoError> {
1444        match self {
1445            DhcpOption::Pad() => Err(Self::IntoError::InvalidFidlOption(self)),
1446            DhcpOption::End() => Err(Self::IntoError::InvalidFidlOption(self)),
1447            DhcpOption::SubnetMask(v) => Ok(fidl_fuchsia_net_dhcp::Option_::SubnetMask(
1448                Ipv4Addr::from(v.get_mask()).into_fidl(),
1449            )),
1450            DhcpOption::TimeOffset(v) => Ok(fidl_fuchsia_net_dhcp::Option_::TimeOffset(v)),
1451            DhcpOption::Router(v) => {
1452                Ok(fidl_fuchsia_net_dhcp::Option_::Router(Vec::from(v).into_fidl()))
1453            }
1454            DhcpOption::TimeServer(v) => {
1455                Ok(fidl_fuchsia_net_dhcp::Option_::TimeServer(Vec::from(v).into_fidl()))
1456            }
1457            DhcpOption::NameServer(v) => {
1458                Ok(fidl_fuchsia_net_dhcp::Option_::NameServer(Vec::from(v).into_fidl()))
1459            }
1460            DhcpOption::DomainNameServer(v) => {
1461                Ok(fidl_fuchsia_net_dhcp::Option_::DomainNameServer(Vec::from(v).into_fidl()))
1462            }
1463            DhcpOption::LogServer(v) => {
1464                Ok(fidl_fuchsia_net_dhcp::Option_::LogServer(Vec::from(v).into_fidl()))
1465            }
1466            DhcpOption::CookieServer(v) => {
1467                Ok(fidl_fuchsia_net_dhcp::Option_::CookieServer(Vec::from(v).into_fidl()))
1468            }
1469            DhcpOption::LprServer(v) => {
1470                Ok(fidl_fuchsia_net_dhcp::Option_::LprServer(Vec::from(v).into_fidl()))
1471            }
1472            DhcpOption::ImpressServer(v) => {
1473                Ok(fidl_fuchsia_net_dhcp::Option_::ImpressServer(Vec::from(v).into_fidl()))
1474            }
1475            DhcpOption::ResourceLocationServer(v) => {
1476                Ok(fidl_fuchsia_net_dhcp::Option_::ResourceLocationServer(Vec::from(v).into_fidl()))
1477            }
1478            DhcpOption::HostName(v) => Ok(fidl_fuchsia_net_dhcp::Option_::HostName(v)),
1479            DhcpOption::BootFileSize(v) => Ok(fidl_fuchsia_net_dhcp::Option_::BootFileSize(v)),
1480            DhcpOption::MeritDumpFile(v) => Ok(fidl_fuchsia_net_dhcp::Option_::MeritDumpFile(v)),
1481            DhcpOption::DomainName(v) => Ok(fidl_fuchsia_net_dhcp::Option_::DomainName(v)),
1482            DhcpOption::SwapServer(v) => {
1483                Ok(fidl_fuchsia_net_dhcp::Option_::SwapServer(v.into_fidl()))
1484            }
1485            DhcpOption::RootPath(v) => Ok(fidl_fuchsia_net_dhcp::Option_::RootPath(v)),
1486            DhcpOption::ExtensionsPath(v) => Ok(fidl_fuchsia_net_dhcp::Option_::ExtensionsPath(v)),
1487            DhcpOption::IpForwarding(v) => Ok(fidl_fuchsia_net_dhcp::Option_::IpForwarding(v)),
1488            DhcpOption::NonLocalSourceRouting(v) => {
1489                Ok(fidl_fuchsia_net_dhcp::Option_::NonLocalSourceRouting(v))
1490            }
1491            DhcpOption::PolicyFilter(v) => {
1492                Ok(fidl_fuchsia_net_dhcp::Option_::PolicyFilter(Vec::from(v).into_fidl()))
1493            }
1494            DhcpOption::MaxDatagramReassemblySize(v) => {
1495                Ok(fidl_fuchsia_net_dhcp::Option_::MaxDatagramReassemblySize(v))
1496            }
1497            DhcpOption::DefaultIpTtl(v) => {
1498                Ok(fidl_fuchsia_net_dhcp::Option_::DefaultIpTtl(v.into()))
1499            }
1500            DhcpOption::PathMtuAgingTimeout(v) => {
1501                Ok(fidl_fuchsia_net_dhcp::Option_::PathMtuAgingTimeout(v))
1502            }
1503            DhcpOption::PathMtuPlateauTable(v) => {
1504                Ok(fidl_fuchsia_net_dhcp::Option_::PathMtuPlateauTable(v.into()))
1505            }
1506            DhcpOption::InterfaceMtu(v) => Ok(fidl_fuchsia_net_dhcp::Option_::InterfaceMtu(v)),
1507            DhcpOption::AllSubnetsLocal(v) => {
1508                Ok(fidl_fuchsia_net_dhcp::Option_::AllSubnetsLocal(v))
1509            }
1510            DhcpOption::BroadcastAddress(v) => {
1511                Ok(fidl_fuchsia_net_dhcp::Option_::BroadcastAddress(v.into_fidl()))
1512            }
1513            DhcpOption::PerformMaskDiscovery(v) => {
1514                Ok(fidl_fuchsia_net_dhcp::Option_::PerformMaskDiscovery(v))
1515            }
1516            DhcpOption::MaskSupplier(v) => Ok(fidl_fuchsia_net_dhcp::Option_::MaskSupplier(v)),
1517            DhcpOption::PerformRouterDiscovery(v) => {
1518                Ok(fidl_fuchsia_net_dhcp::Option_::PerformRouterDiscovery(v))
1519            }
1520            DhcpOption::RouterSolicitationAddress(v) => {
1521                Ok(fidl_fuchsia_net_dhcp::Option_::RouterSolicitationAddress(v.into_fidl()))
1522            }
1523            DhcpOption::StaticRoute(v) => {
1524                Ok(fidl_fuchsia_net_dhcp::Option_::StaticRoute(Vec::from(v).into_fidl()))
1525            }
1526            DhcpOption::TrailerEncapsulation(v) => {
1527                Ok(fidl_fuchsia_net_dhcp::Option_::TrailerEncapsulation(v))
1528            }
1529            DhcpOption::ArpCacheTimeout(v) => {
1530                Ok(fidl_fuchsia_net_dhcp::Option_::ArpCacheTimeout(v))
1531            }
1532            DhcpOption::EthernetEncapsulation(v) => {
1533                Ok(fidl_fuchsia_net_dhcp::Option_::EthernetEncapsulation(v))
1534            }
1535            DhcpOption::TcpDefaultTtl(v) => {
1536                Ok(fidl_fuchsia_net_dhcp::Option_::TcpDefaultTtl(v.into()))
1537            }
1538            DhcpOption::TcpKeepaliveInterval(v) => {
1539                Ok(fidl_fuchsia_net_dhcp::Option_::TcpKeepaliveInterval(v))
1540            }
1541            DhcpOption::TcpKeepaliveGarbage(v) => {
1542                Ok(fidl_fuchsia_net_dhcp::Option_::TcpKeepaliveGarbage(v))
1543            }
1544            DhcpOption::NetworkInformationServiceDomain(v) => {
1545                Ok(fidl_fuchsia_net_dhcp::Option_::NetworkInformationServiceDomain(v))
1546            }
1547            DhcpOption::NetworkInformationServers(v) => Ok(
1548                fidl_fuchsia_net_dhcp::Option_::NetworkInformationServers(Vec::from(v).into_fidl()),
1549            ),
1550            DhcpOption::NetworkTimeProtocolServers(v) => {
1551                Ok(fidl_fuchsia_net_dhcp::Option_::NetworkTimeProtocolServers(
1552                    Vec::from(v).into_fidl(),
1553                ))
1554            }
1555            DhcpOption::VendorSpecificInformation(v) => {
1556                Ok(fidl_fuchsia_net_dhcp::Option_::VendorSpecificInformation(v.into()))
1557            }
1558            DhcpOption::NetBiosOverTcpipNameServer(v) => {
1559                Ok(fidl_fuchsia_net_dhcp::Option_::NetbiosOverTcpipNameServer(
1560                    Vec::from(v).into_fidl(),
1561                ))
1562            }
1563            DhcpOption::NetBiosOverTcpipDatagramDistributionServer(v) => {
1564                Ok(fidl_fuchsia_net_dhcp::Option_::NetbiosOverTcpipDatagramDistributionServer(
1565                    Vec::from(v).into_fidl(),
1566                ))
1567            }
1568            DhcpOption::NetBiosOverTcpipNodeType(v) => {
1569                Ok(fidl_fuchsia_net_dhcp::Option_::NetbiosOverTcpipNodeType(v.into_fidl()))
1570            }
1571            DhcpOption::NetBiosOverTcpipScope(v) => {
1572                Ok(fidl_fuchsia_net_dhcp::Option_::NetbiosOverTcpipScope(v))
1573            }
1574            DhcpOption::XWindowSystemFontServer(v) => Ok(
1575                fidl_fuchsia_net_dhcp::Option_::XWindowSystemFontServer(Vec::from(v).into_fidl()),
1576            ),
1577            DhcpOption::XWindowSystemDisplayManager(v) => {
1578                Ok(fidl_fuchsia_net_dhcp::Option_::XWindowSystemDisplayManager(
1579                    Vec::from(v).into_fidl(),
1580                ))
1581            }
1582            DhcpOption::NetworkInformationServicePlusDomain(v) => {
1583                Ok(fidl_fuchsia_net_dhcp::Option_::NetworkInformationServicePlusDomain(v))
1584            }
1585            DhcpOption::NetworkInformationServicePlusServers(v) => {
1586                Ok(fidl_fuchsia_net_dhcp::Option_::NetworkInformationServicePlusServers(
1587                    Vec::from(v).into_fidl(),
1588                ))
1589            }
1590            DhcpOption::MobileIpHomeAgent(v) => {
1591                Ok(fidl_fuchsia_net_dhcp::Option_::MobileIpHomeAgent(Vec::from(v).into_fidl()))
1592            }
1593            DhcpOption::SmtpServer(v) => {
1594                Ok(fidl_fuchsia_net_dhcp::Option_::SmtpServer(Vec::from(v).into_fidl()))
1595            }
1596            DhcpOption::Pop3Server(v) => {
1597                Ok(fidl_fuchsia_net_dhcp::Option_::Pop3Server(Vec::from(v).into_fidl()))
1598            }
1599            DhcpOption::NntpServer(v) => {
1600                Ok(fidl_fuchsia_net_dhcp::Option_::NntpServer(Vec::from(v).into_fidl()))
1601            }
1602            DhcpOption::DefaultWwwServer(v) => {
1603                Ok(fidl_fuchsia_net_dhcp::Option_::DefaultWwwServer(Vec::from(v).into_fidl()))
1604            }
1605            DhcpOption::DefaultFingerServer(v) => {
1606                Ok(fidl_fuchsia_net_dhcp::Option_::DefaultFingerServer(Vec::from(v).into_fidl()))
1607            }
1608            DhcpOption::DefaultIrcServer(v) => {
1609                Ok(fidl_fuchsia_net_dhcp::Option_::DefaultIrcServer(Vec::from(v).into_fidl()))
1610            }
1611            DhcpOption::StreetTalkServer(v) => {
1612                Ok(fidl_fuchsia_net_dhcp::Option_::StreettalkServer(Vec::from(v).into_fidl()))
1613            }
1614            DhcpOption::StreetTalkDirectoryAssistanceServer(v) => {
1615                Ok(fidl_fuchsia_net_dhcp::Option_::StreettalkDirectoryAssistanceServer(
1616                    Vec::from(v).into_fidl(),
1617                ))
1618            }
1619            DhcpOption::RequestedIpAddress(_) => Err(ProtocolError::InvalidFidlOption(self)),
1620            DhcpOption::IpAddressLeaseTime(_) => Err(ProtocolError::InvalidFidlOption(self)),
1621            DhcpOption::OptionOverload(v) => {
1622                Ok(fidl_fuchsia_net_dhcp::Option_::OptionOverload(v.into_fidl()))
1623            }
1624            DhcpOption::TftpServerName(v) => Ok(fidl_fuchsia_net_dhcp::Option_::TftpServerName(v)),
1625            DhcpOption::BootfileName(v) => Ok(fidl_fuchsia_net_dhcp::Option_::BootfileName(v)),
1626            DhcpOption::DhcpMessageType(_) => Err(ProtocolError::InvalidFidlOption(self)),
1627            DhcpOption::ServerIdentifier(_) => Err(ProtocolError::InvalidFidlOption(self)),
1628            DhcpOption::ParameterRequestList(_) => Err(ProtocolError::InvalidFidlOption(self)),
1629            DhcpOption::Message(_) => Err(ProtocolError::InvalidFidlOption(self)),
1630            DhcpOption::MaxDhcpMessageSize(v) => {
1631                Ok(fidl_fuchsia_net_dhcp::Option_::MaxDhcpMessageSize(v))
1632            }
1633            DhcpOption::RenewalTimeValue(v) => {
1634                Ok(fidl_fuchsia_net_dhcp::Option_::RenewalTimeValue(v))
1635            }
1636            DhcpOption::RebindingTimeValue(v) => {
1637                Ok(fidl_fuchsia_net_dhcp::Option_::RebindingTimeValue(v))
1638            }
1639            DhcpOption::VendorClassIdentifier(_) => Err(ProtocolError::InvalidFidlOption(self)),
1640            DhcpOption::ClientIdentifier(_) => Err(ProtocolError::InvalidFidlOption(self)),
1641        }
1642    }
1643
1644    fn try_from_fidl(v: fidl_fuchsia_net_dhcp::Option_) -> Result<Self, Self::FromError> {
1645        match v {
1646            fidl_fuchsia_net_dhcp::Option_::SubnetMask(v) => {
1647                let addr = Ipv4Addr::from_fidl(v);
1648                Ok(DhcpOption::SubnetMask(
1649                    PrefixLength::try_from_subnet_mask(addr.into()).map_err(
1650                        |NotSubnetMaskError| {
1651                            ProtocolError::InvalidOptionValue(
1652                                OptionCode::SubnetMask,
1653                                addr.octets().to_vec(),
1654                            )
1655                        },
1656                    )?,
1657                ))
1658            }
1659            fidl_fuchsia_net_dhcp::Option_::TimeOffset(v) => Ok(DhcpOption::TimeOffset(v)),
1660            fidl_fuchsia_net_dhcp::Option_::Router(v) => Ok(DhcpOption::Router({
1661                let vec = Vec::<Ipv4Addr>::from_fidl(v);
1662                let size = vec.size_of_contents_in_bytes();
1663                vec.try_into().map_err(|(size_constrained::Error::SizeConstraintViolated, _)| {
1664                    ProtocolError::InvalidBufferLength(size)
1665                })?
1666            })),
1667            fidl_fuchsia_net_dhcp::Option_::TimeServer(v) => Ok(DhcpOption::TimeServer({
1668                let vec = Vec::<Ipv4Addr>::from_fidl(v);
1669                let size = vec.size_of_contents_in_bytes();
1670                vec.try_into().map_err(|(size_constrained::Error::SizeConstraintViolated, _)| {
1671                    ProtocolError::InvalidBufferLength(size)
1672                })?
1673            })),
1674            fidl_fuchsia_net_dhcp::Option_::NameServer(v) => Ok(DhcpOption::NameServer({
1675                let vec = Vec::<Ipv4Addr>::from_fidl(v);
1676                let size = vec.size_of_contents_in_bytes();
1677                vec.try_into().map_err(|(size_constrained::Error::SizeConstraintViolated, _)| {
1678                    ProtocolError::InvalidBufferLength(size)
1679                })?
1680            })),
1681            fidl_fuchsia_net_dhcp::Option_::DomainNameServer(v) => {
1682                Ok(DhcpOption::DomainNameServer({
1683                    let vec = Vec::<Ipv4Addr>::from_fidl(v);
1684                    let vec_size = vec.size_of_contents_in_bytes();
1685                    vec.try_into().map_err(
1686                        |(size_constrained::Error::SizeConstraintViolated, _)| {
1687                            ProtocolError::InvalidBufferLength(vec_size)
1688                        },
1689                    )?
1690                }))
1691            }
1692            fidl_fuchsia_net_dhcp::Option_::LogServer(v) => Ok(DhcpOption::LogServer({
1693                let vec = Vec::<Ipv4Addr>::from_fidl(v);
1694                let size = vec.size_of_contents_in_bytes();
1695                vec.try_into().map_err(|(size_constrained::Error::SizeConstraintViolated, _)| {
1696                    ProtocolError::InvalidBufferLength(size)
1697                })?
1698            })),
1699            fidl_fuchsia_net_dhcp::Option_::CookieServer(v) => Ok(DhcpOption::CookieServer({
1700                let vec = Vec::<Ipv4Addr>::from_fidl(v);
1701                let size = vec.size_of_contents_in_bytes();
1702                vec.try_into().map_err(|(size_constrained::Error::SizeConstraintViolated, _)| {
1703                    ProtocolError::InvalidBufferLength(size)
1704                })?
1705            })),
1706            fidl_fuchsia_net_dhcp::Option_::LprServer(v) => Ok(DhcpOption::LprServer({
1707                let vec = Vec::<Ipv4Addr>::from_fidl(v);
1708                let size = vec.size_of_contents_in_bytes();
1709                vec.try_into().map_err(|(size_constrained::Error::SizeConstraintViolated, _)| {
1710                    ProtocolError::InvalidBufferLength(size)
1711                })?
1712            })),
1713            fidl_fuchsia_net_dhcp::Option_::ImpressServer(v) => Ok(DhcpOption::ImpressServer({
1714                let vec = Vec::<Ipv4Addr>::from_fidl(v);
1715                let size = vec.size_of_contents_in_bytes();
1716                vec.try_into().map_err(|(size_constrained::Error::SizeConstraintViolated, _)| {
1717                    ProtocolError::InvalidBufferLength(size)
1718                })?
1719            })),
1720            fidl_fuchsia_net_dhcp::Option_::ResourceLocationServer(v) => {
1721                Ok(DhcpOption::ResourceLocationServer({
1722                    let vec = Vec::<Ipv4Addr>::from_fidl(v);
1723                    let vec_size = vec.size_of_contents_in_bytes();
1724                    vec.try_into().map_err(
1725                        |(size_constrained::Error::SizeConstraintViolated, _)| {
1726                            ProtocolError::InvalidBufferLength(vec_size)
1727                        },
1728                    )?
1729                }))
1730            }
1731            fidl_fuchsia_net_dhcp::Option_::HostName(v) => Ok(DhcpOption::HostName(v)),
1732            fidl_fuchsia_net_dhcp::Option_::BootFileSize(v) => Ok(DhcpOption::BootFileSize(v)),
1733            fidl_fuchsia_net_dhcp::Option_::MeritDumpFile(v) => Ok(DhcpOption::MeritDumpFile(v)),
1734            fidl_fuchsia_net_dhcp::Option_::DomainName(v) => Ok(DhcpOption::DomainName(v)),
1735            fidl_fuchsia_net_dhcp::Option_::SwapServer(v) => {
1736                Ok(DhcpOption::SwapServer(Ipv4Addr::from_fidl(v)))
1737            }
1738            fidl_fuchsia_net_dhcp::Option_::RootPath(v) => Ok(DhcpOption::RootPath(v)),
1739            fidl_fuchsia_net_dhcp::Option_::ExtensionsPath(v) => Ok(DhcpOption::ExtensionsPath(v)),
1740            fidl_fuchsia_net_dhcp::Option_::IpForwarding(v) => Ok(DhcpOption::IpForwarding(v)),
1741            fidl_fuchsia_net_dhcp::Option_::NonLocalSourceRouting(v) => {
1742                Ok(DhcpOption::NonLocalSourceRouting(v))
1743            }
1744            fidl_fuchsia_net_dhcp::Option_::PolicyFilter(v) => Ok(DhcpOption::PolicyFilter({
1745                let vec = Vec::<Ipv4Addr>::from_fidl(v);
1746                let size = vec.size_of_contents_in_bytes();
1747                vec.try_into().map_err(|(size_constrained::Error::SizeConstraintViolated, _)| {
1748                    ProtocolError::InvalidBufferLength(size)
1749                })?
1750            })),
1751            fidl_fuchsia_net_dhcp::Option_::MaxDatagramReassemblySize(v) => {
1752                Ok(DhcpOption::MaxDatagramReassemblySize(v))
1753            }
1754            fidl_fuchsia_net_dhcp::Option_::DefaultIpTtl(v) => {
1755                let ttl = NonZeroU8::new(v).ok_or_else(|| {
1756                    ProtocolError::InvalidOptionValue(OptionCode::DefaultIpTtl, vec![v])
1757                })?;
1758                Ok(DhcpOption::DefaultIpTtl(ttl))
1759            }
1760            fidl_fuchsia_net_dhcp::Option_::PathMtuAgingTimeout(v) => {
1761                Ok(DhcpOption::PathMtuAgingTimeout(v))
1762            }
1763            fidl_fuchsia_net_dhcp::Option_::PathMtuPlateauTable(v) => {
1764                Ok(DhcpOption::PathMtuPlateauTable({
1765                    let size = v.size_of_contents_in_bytes();
1766                    v.try_into().map_err(
1767                        |(size_constrained::Error::SizeConstraintViolated, _)| {
1768                            ProtocolError::InvalidBufferLength(size)
1769                        },
1770                    )?
1771                }))
1772            }
1773            fidl_fuchsia_net_dhcp::Option_::InterfaceMtu(v) => Ok(DhcpOption::InterfaceMtu(v)),
1774            fidl_fuchsia_net_dhcp::Option_::AllSubnetsLocal(v) => {
1775                Ok(DhcpOption::AllSubnetsLocal(v))
1776            }
1777            fidl_fuchsia_net_dhcp::Option_::BroadcastAddress(v) => {
1778                Ok(DhcpOption::BroadcastAddress(Ipv4Addr::from_fidl(v)))
1779            }
1780            fidl_fuchsia_net_dhcp::Option_::PerformMaskDiscovery(v) => {
1781                Ok(DhcpOption::PerformMaskDiscovery(v))
1782            }
1783            fidl_fuchsia_net_dhcp::Option_::MaskSupplier(v) => Ok(DhcpOption::MaskSupplier(v)),
1784            fidl_fuchsia_net_dhcp::Option_::PerformRouterDiscovery(v) => {
1785                Ok(DhcpOption::PerformRouterDiscovery(v))
1786            }
1787            fidl_fuchsia_net_dhcp::Option_::RouterSolicitationAddress(v) => {
1788                Ok(DhcpOption::RouterSolicitationAddress(Ipv4Addr::from_fidl(v)))
1789            }
1790            fidl_fuchsia_net_dhcp::Option_::StaticRoute(v) => Ok(DhcpOption::StaticRoute({
1791                let vec = Vec::<Ipv4Addr>::from_fidl(v);
1792                let size = vec.size_of_contents_in_bytes();
1793                vec.try_into().map_err(|(size_constrained::Error::SizeConstraintViolated, _)| {
1794                    ProtocolError::InvalidBufferLength(size)
1795                })?
1796            })),
1797            fidl_fuchsia_net_dhcp::Option_::TrailerEncapsulation(v) => {
1798                Ok(DhcpOption::TrailerEncapsulation(v))
1799            }
1800            fidl_fuchsia_net_dhcp::Option_::ArpCacheTimeout(v) => {
1801                Ok(DhcpOption::ArpCacheTimeout(v))
1802            }
1803            fidl_fuchsia_net_dhcp::Option_::EthernetEncapsulation(v) => {
1804                Ok(DhcpOption::EthernetEncapsulation(v))
1805            }
1806            fidl_fuchsia_net_dhcp::Option_::TcpDefaultTtl(v) => {
1807                let ttl = NonZeroU8::new(v).ok_or_else(|| {
1808                    ProtocolError::InvalidOptionValue(OptionCode::TcpDefaultTtl, vec![v])
1809                })?;
1810                Ok(DhcpOption::TcpDefaultTtl(ttl))
1811            }
1812            fidl_fuchsia_net_dhcp::Option_::TcpKeepaliveInterval(v) => {
1813                Ok(DhcpOption::TcpKeepaliveInterval(v))
1814            }
1815            fidl_fuchsia_net_dhcp::Option_::TcpKeepaliveGarbage(v) => {
1816                Ok(DhcpOption::TcpKeepaliveGarbage(v))
1817            }
1818            fidl_fuchsia_net_dhcp::Option_::NetworkInformationServiceDomain(v) => {
1819                Ok(DhcpOption::NetworkInformationServiceDomain(v))
1820            }
1821            fidl_fuchsia_net_dhcp::Option_::NetworkInformationServers(v) => {
1822                Ok(DhcpOption::NetworkInformationServers({
1823                    let vec = Vec::<Ipv4Addr>::from_fidl(v);
1824                    let vec_size = vec.size_of_contents_in_bytes();
1825                    vec.try_into().map_err(
1826                        |(size_constrained::Error::SizeConstraintViolated, _)| {
1827                            ProtocolError::InvalidBufferLength(vec_size)
1828                        },
1829                    )?
1830                }))
1831            }
1832            fidl_fuchsia_net_dhcp::Option_::NetworkTimeProtocolServers(v) => {
1833                Ok(DhcpOption::NetworkTimeProtocolServers({
1834                    let vec = Vec::<Ipv4Addr>::from_fidl(v);
1835                    let vec_size = vec.size_of_contents_in_bytes();
1836                    vec.try_into().map_err(
1837                        |(size_constrained::Error::SizeConstraintViolated, _)| {
1838                            ProtocolError::InvalidBufferLength(vec_size)
1839                        },
1840                    )?
1841                }))
1842            }
1843            fidl_fuchsia_net_dhcp::Option_::VendorSpecificInformation(v) => {
1844                Ok(DhcpOption::VendorSpecificInformation({
1845                    let size = v.size_of_contents_in_bytes();
1846                    v.try_into().map_err(
1847                        |(size_constrained::Error::SizeConstraintViolated, _)| {
1848                            ProtocolError::InvalidBufferLength(size)
1849                        },
1850                    )?
1851                }))
1852            }
1853            fidl_fuchsia_net_dhcp::Option_::NetbiosOverTcpipNameServer(v) => {
1854                Ok(DhcpOption::NetBiosOverTcpipNameServer({
1855                    let vec = Vec::<Ipv4Addr>::from_fidl(v);
1856                    let vec_size = vec.size_of_contents_in_bytes();
1857                    vec.try_into().map_err(
1858                        |(size_constrained::Error::SizeConstraintViolated, _)| {
1859                            ProtocolError::InvalidBufferLength(vec_size)
1860                        },
1861                    )?
1862                }))
1863            }
1864            fidl_fuchsia_net_dhcp::Option_::NetbiosOverTcpipDatagramDistributionServer(v) => {
1865                Ok(DhcpOption::NetBiosOverTcpipDatagramDistributionServer({
1866                    let vec = Vec::<Ipv4Addr>::from_fidl(v);
1867                    let vec_size = vec.size_of_contents_in_bytes();
1868                    vec.try_into().map_err(
1869                        |(size_constrained::Error::SizeConstraintViolated, _)| {
1870                            ProtocolError::InvalidBufferLength(vec_size)
1871                        },
1872                    )?
1873                }))
1874            }
1875            fidl_fuchsia_net_dhcp::Option_::NetbiosOverTcpipNodeType(v) => {
1876                Ok(DhcpOption::NetBiosOverTcpipNodeType(NodeType::try_from_fidl(v)?))
1877            }
1878            fidl_fuchsia_net_dhcp::Option_::NetbiosOverTcpipScope(v) => {
1879                Ok(DhcpOption::NetBiosOverTcpipScope(v))
1880            }
1881            fidl_fuchsia_net_dhcp::Option_::XWindowSystemFontServer(v) => {
1882                Ok(DhcpOption::XWindowSystemFontServer({
1883                    let vec = Vec::<Ipv4Addr>::from_fidl(v);
1884                    let vec_size = vec.size_of_contents_in_bytes();
1885                    vec.try_into().map_err(
1886                        |(size_constrained::Error::SizeConstraintViolated, _)| {
1887                            ProtocolError::InvalidBufferLength(vec_size)
1888                        },
1889                    )?
1890                }))
1891            }
1892            fidl_fuchsia_net_dhcp::Option_::XWindowSystemDisplayManager(v) => {
1893                Ok(DhcpOption::XWindowSystemDisplayManager({
1894                    let vec = Vec::<Ipv4Addr>::from_fidl(v);
1895                    let vec_size = vec.size_of_contents_in_bytes();
1896                    vec.try_into().map_err(
1897                        |(size_constrained::Error::SizeConstraintViolated, _)| {
1898                            ProtocolError::InvalidBufferLength(vec_size)
1899                        },
1900                    )?
1901                }))
1902            }
1903            fidl_fuchsia_net_dhcp::Option_::NetworkInformationServicePlusDomain(v) => {
1904                Ok(DhcpOption::NetworkInformationServicePlusDomain(v))
1905            }
1906            fidl_fuchsia_net_dhcp::Option_::NetworkInformationServicePlusServers(v) => {
1907                Ok(DhcpOption::NetworkInformationServicePlusServers({
1908                    let vec = Vec::<Ipv4Addr>::from_fidl(v);
1909                    let vec_size = vec.size_of_contents_in_bytes();
1910                    vec.try_into().map_err(
1911                        |(size_constrained::Error::SizeConstraintViolated, _)| {
1912                            ProtocolError::InvalidBufferLength(vec_size)
1913                        },
1914                    )?
1915                }))
1916            }
1917            fidl_fuchsia_net_dhcp::Option_::MobileIpHomeAgent(v) => {
1918                Ok(DhcpOption::MobileIpHomeAgent({
1919                    let vec = Vec::<Ipv4Addr>::from_fidl(v);
1920                    let size = vec.size_of_contents_in_bytes();
1921                    vec.try_into().map_err(
1922                        |(size_constrained::Error::SizeConstraintViolated, _)| {
1923                            ProtocolError::InvalidBufferLength(size)
1924                        },
1925                    )?
1926                }))
1927            }
1928            fidl_fuchsia_net_dhcp::Option_::SmtpServer(v) => Ok(DhcpOption::SmtpServer({
1929                let vec = Vec::<Ipv4Addr>::from_fidl(v);
1930                let size = vec.size_of_contents_in_bytes();
1931                vec.try_into().map_err(|(size_constrained::Error::SizeConstraintViolated, _)| {
1932                    ProtocolError::InvalidBufferLength(size)
1933                })?
1934            })),
1935            fidl_fuchsia_net_dhcp::Option_::Pop3Server(v) => Ok(DhcpOption::Pop3Server({
1936                let vec = Vec::<Ipv4Addr>::from_fidl(v);
1937                let size = vec.size_of_contents_in_bytes();
1938                vec.try_into().map_err(|(size_constrained::Error::SizeConstraintViolated, _)| {
1939                    ProtocolError::InvalidBufferLength(size)
1940                })?
1941            })),
1942            fidl_fuchsia_net_dhcp::Option_::NntpServer(v) => Ok(DhcpOption::NntpServer({
1943                let vec = Vec::<Ipv4Addr>::from_fidl(v);
1944                let size = vec.size_of_contents_in_bytes();
1945                vec.try_into().map_err(|(size_constrained::Error::SizeConstraintViolated, _)| {
1946                    ProtocolError::InvalidBufferLength(size)
1947                })?
1948            })),
1949            fidl_fuchsia_net_dhcp::Option_::DefaultWwwServer(v) => {
1950                Ok(DhcpOption::DefaultWwwServer({
1951                    let vec = Vec::<Ipv4Addr>::from_fidl(v);
1952                    let vec_size = vec.size_of_contents_in_bytes();
1953                    vec.try_into().map_err(
1954                        |(size_constrained::Error::SizeConstraintViolated, _)| {
1955                            ProtocolError::InvalidBufferLength(vec_size)
1956                        },
1957                    )?
1958                }))
1959            }
1960            fidl_fuchsia_net_dhcp::Option_::DefaultFingerServer(v) => {
1961                Ok(DhcpOption::DefaultFingerServer({
1962                    let vec = Vec::<Ipv4Addr>::from_fidl(v);
1963                    let vec_size = vec.size_of_contents_in_bytes();
1964                    vec.try_into().map_err(
1965                        |(size_constrained::Error::SizeConstraintViolated, _)| {
1966                            ProtocolError::InvalidBufferLength(vec_size)
1967                        },
1968                    )?
1969                }))
1970            }
1971            fidl_fuchsia_net_dhcp::Option_::DefaultIrcServer(v) => {
1972                Ok(DhcpOption::DefaultIrcServer({
1973                    let vec = Vec::<Ipv4Addr>::from_fidl(v);
1974                    let vec_size = vec.size_of_contents_in_bytes();
1975                    vec.try_into().map_err(
1976                        |(size_constrained::Error::SizeConstraintViolated, _)| {
1977                            ProtocolError::InvalidBufferLength(vec_size)
1978                        },
1979                    )?
1980                }))
1981            }
1982            fidl_fuchsia_net_dhcp::Option_::StreettalkServer(v) => {
1983                Ok(DhcpOption::StreetTalkServer({
1984                    let vec = Vec::<Ipv4Addr>::from_fidl(v);
1985                    let vec_size = vec.size_of_contents_in_bytes();
1986                    vec.try_into().map_err(
1987                        |(size_constrained::Error::SizeConstraintViolated, _)| {
1988                            ProtocolError::InvalidBufferLength(vec_size)
1989                        },
1990                    )?
1991                }))
1992            }
1993            fidl_fuchsia_net_dhcp::Option_::StreettalkDirectoryAssistanceServer(v) => {
1994                Ok(DhcpOption::StreetTalkDirectoryAssistanceServer({
1995                    let vec = Vec::<Ipv4Addr>::from_fidl(v);
1996                    let vec_size = vec.size_of_contents_in_bytes();
1997                    vec.try_into().map_err(
1998                        |(size_constrained::Error::SizeConstraintViolated, _)| {
1999                            ProtocolError::InvalidBufferLength(vec_size)
2000                        },
2001                    )?
2002                }))
2003            }
2004            fidl_fuchsia_net_dhcp::Option_::OptionOverload(v) => {
2005                Ok(DhcpOption::OptionOverload(Overload::from_fidl(v)))
2006            }
2007            fidl_fuchsia_net_dhcp::Option_::TftpServerName(v) => Ok(DhcpOption::TftpServerName(v)),
2008            fidl_fuchsia_net_dhcp::Option_::BootfileName(v) => Ok(DhcpOption::BootfileName(v)),
2009            fidl_fuchsia_net_dhcp::Option_::MaxDhcpMessageSize(v) => {
2010                Ok(DhcpOption::MaxDhcpMessageSize(v))
2011            }
2012            fidl_fuchsia_net_dhcp::Option_::RenewalTimeValue(v) => {
2013                Ok(DhcpOption::RenewalTimeValue(v))
2014            }
2015            fidl_fuchsia_net_dhcp::Option_::RebindingTimeValue(v) => {
2016                Ok(DhcpOption::RebindingTimeValue(v))
2017            }
2018            fidl_fuchsia_net_dhcp::Option_Unknown!() => Err(ProtocolError::UnknownFidlOption),
2019        }
2020    }
2021}
2022
2023/// A NetBIOS over TCP/IP Node Type.
2024///
2025/// This enum and the values of its variants corresponds to the DHCP option defined
2026/// in: https://tools.ietf.org/html/rfc2132#section-8.7
2027#[derive(Clone, Copy, Debug, Deserialize, Eq, FromPrimitive, Hash, PartialEq, Serialize)]
2028#[repr(u8)]
2029pub enum NodeType {
2030    BNode = 0x1,
2031    PNode = 0x2,
2032    MNode = 0x4,
2033    HNode = 0x8,
2034}
2035
2036impl TryFrom<u8> for NodeType {
2037    type Error = ProtocolError;
2038
2039    fn try_from(n: u8) -> Result<Self, Self::Error> {
2040        <Self as num_traits::FromPrimitive>::from_u8(n).ok_or_else(|| {
2041            ProtocolError::InvalidOptionValue(OptionCode::NetBiosOverTcpipNodeType, vec![n])
2042        })
2043    }
2044}
2045
2046impl From<NodeType> for u8 {
2047    fn from(node_type: NodeType) -> u8 {
2048        node_type as u8
2049    }
2050}
2051
2052#[cfg(target_os = "fuchsia")]
2053impl FidlCompatible<fidl_fuchsia_net_dhcp::NodeTypes> for NodeType {
2054    type FromError = ProtocolError;
2055    type IntoError = Never;
2056
2057    fn try_from_fidl(fidl: fidl_fuchsia_net_dhcp::NodeTypes) -> Result<NodeType, Self::FromError> {
2058        match fidl {
2059            fidl_fuchsia_net_dhcp::NodeTypes::B_NODE => Ok(NodeType::BNode),
2060            fidl_fuchsia_net_dhcp::NodeTypes::P_NODE => Ok(NodeType::PNode),
2061            fidl_fuchsia_net_dhcp::NodeTypes::M_NODE => Ok(NodeType::MNode),
2062            fidl_fuchsia_net_dhcp::NodeTypes::H_NODE => Ok(NodeType::HNode),
2063            other => Err(ProtocolError::InvalidOptionValue(
2064                OptionCode::NetBiosOverTcpipNodeType,
2065                vec![other.bits()],
2066            )),
2067        }
2068    }
2069
2070    fn try_into_fidl(self) -> Result<fidl_fuchsia_net_dhcp::NodeTypes, Self::IntoError> {
2071        match self {
2072            NodeType::BNode => Ok(fidl_fuchsia_net_dhcp::NodeTypes::B_NODE),
2073            NodeType::PNode => Ok(fidl_fuchsia_net_dhcp::NodeTypes::P_NODE),
2074            NodeType::MNode => Ok(fidl_fuchsia_net_dhcp::NodeTypes::M_NODE),
2075            NodeType::HNode => Ok(fidl_fuchsia_net_dhcp::NodeTypes::H_NODE),
2076        }
2077    }
2078}
2079
2080/// The DHCP message fields to use for storing additional options.
2081///
2082/// A DHCP client can indicate that it wants to use the File or SName fields of
2083/// the DHCP header to store DHCP options. This enum and its variant values correspond
2084/// to the DHCP option defined in: https://tools.ietf.org/html/rfc2132#section-9.3
2085#[derive(Clone, Copy, Debug, Deserialize, Eq, FromPrimitive, Hash, PartialEq, Serialize)]
2086#[repr(u8)]
2087pub enum Overload {
2088    File = 1,
2089    SName = 2,
2090    Both = 3,
2091}
2092
2093impl From<Overload> for u8 {
2094    fn from(val: Overload) -> Self {
2095        val as u8
2096    }
2097}
2098
2099impl TryFrom<u8> for Overload {
2100    type Error = ProtocolError;
2101
2102    fn try_from(n: u8) -> Result<Self, Self::Error> {
2103        <Self as num_traits::FromPrimitive>::from_u8(n)
2104            .ok_or_else(|| ProtocolError::InvalidOptionValue(OptionCode::OptionOverload, vec![n]))
2105    }
2106}
2107
2108#[cfg(target_os = "fuchsia")]
2109impl FidlCompatible<fidl_fuchsia_net_dhcp::OptionOverloadValue> for Overload {
2110    type FromError = Never;
2111    type IntoError = Never;
2112
2113    fn try_from_fidl(
2114        fidl: fidl_fuchsia_net_dhcp::OptionOverloadValue,
2115    ) -> Result<Self, Self::FromError> {
2116        match fidl {
2117            fidl_fuchsia_net_dhcp::OptionOverloadValue::File => Ok(Overload::File),
2118            fidl_fuchsia_net_dhcp::OptionOverloadValue::Sname => Ok(Overload::SName),
2119            fidl_fuchsia_net_dhcp::OptionOverloadValue::Both => Ok(Overload::Both),
2120        }
2121    }
2122
2123    fn try_into_fidl(self) -> Result<fidl_fuchsia_net_dhcp::OptionOverloadValue, Self::IntoError> {
2124        match self {
2125            Overload::File => Ok(fidl_fuchsia_net_dhcp::OptionOverloadValue::File),
2126            Overload::SName => Ok(fidl_fuchsia_net_dhcp::OptionOverloadValue::Sname),
2127            Overload::Both => Ok(fidl_fuchsia_net_dhcp::OptionOverloadValue::Both),
2128        }
2129    }
2130}
2131
2132/// A DHCP Message Type.
2133///
2134/// This enum corresponds to the DHCP Message Type option values
2135/// defined in section 9.4 of RFC 1533.
2136#[derive(FromPrimitive, Copy, Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
2137#[repr(u8)]
2138pub enum MessageType {
2139    DHCPDISCOVER = 1,
2140    DHCPOFFER = 2,
2141    DHCPREQUEST = 3,
2142    DHCPDECLINE = 4,
2143    DHCPACK = 5,
2144    DHCPNAK = 6,
2145    DHCPRELEASE = 7,
2146    DHCPINFORM = 8,
2147}
2148
2149impl From<MessageType> for u8 {
2150    fn from(val: MessageType) -> Self {
2151        val as u8
2152    }
2153}
2154
2155/// Instead of reusing the implementation of `Debug::fmt` here, a cleaner way
2156/// is to derive the 'Display' trait for enums using `enum-display-derive` crate
2157///
2158/// https://docs.rs/enum-display-derive/0.1.0/enum_display_derive/
2159///
2160/// Since addition of this in third_party/rust_crates needs OSRB approval
2161/// it should be done if there is a stronger need for more complex enums.
2162impl fmt::Display for MessageType {
2163    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2164        fmt::Debug::fmt(&self, f)
2165    }
2166}
2167
2168impl TryFrom<u8> for MessageType {
2169    type Error = ProtocolError;
2170
2171    fn try_from(n: u8) -> Result<Self, Self::Error> {
2172        <Self as num_traits::FromPrimitive>::from_u8(n).ok_or(ProtocolError::InvalidMessageType(n))
2173    }
2174}
2175
2176/// Parses DHCP options from `buf` into `options`.
2177fn parse_options<T: Extend<DhcpOption>>(
2178    mut buf: &[u8],
2179    mut options: T,
2180) -> Result<T, ProtocolError> {
2181    loop {
2182        let (raw_opt_code, rest) = buf.split_first().ok_or({
2183            // From RFC 2131 Section 4.1:
2184            //   The last option must always be the 'end' option.
2185            ProtocolError::MissingOption(OptionCode::End)
2186        })?;
2187        buf = rest;
2188        match OptionCode::try_from(*raw_opt_code) {
2189            Ok(OptionCode::End) => {
2190                // End of options reached.
2191                return Ok(options);
2192            }
2193            Ok(OptionCode::Pad) => {}
2194            code => {
2195                let (&opt_len, rest) = buf.split_first().ok_or(ProtocolError::MalformedOption {
2196                    code: *raw_opt_code,
2197                    remaining: buf.len(),
2198                    want: 1,
2199                })?;
2200                buf = rest;
2201                let opt_len = usize::from(opt_len);
2202
2203                // Reaching the end of the buffer means we never encountered an End code.
2204                if buf.len() < opt_len {
2205                    return Err(ProtocolError::MalformedOption {
2206                        code: *raw_opt_code,
2207                        remaining: buf.len(),
2208                        want: opt_len,
2209                    });
2210                };
2211                let (val, rest) = buf.split_at(opt_len);
2212                buf = rest;
2213
2214                // Ignore unknown option codes, hinted at in RFC 2131 section 3.5:
2215                //   ... Other options representing "hints" at configuration parameters are allowed
2216                //   in a DHCPDISCOVER or DHCPREQUEST message.  However, additional options may be
2217                //   ignored by servers...
2218                let code = match code {
2219                    Ok(c) => c,
2220                    Err(ProtocolError::InvalidOptionCode(_)) => continue,
2221                    Err(e) => return Err(e),
2222                };
2223
2224                match DhcpOption::from_raw_parts(code, val) {
2225                    Ok(option) => options.extend(std::iter::once(option)),
2226                    Err(e) => {
2227                        // RFC 2131 does not define how to handle invalid options.
2228                        // In order to match prior art, like ISC and dnsmasq, we strive to
2229                        // be lenient. We throw out as little as necessary and will allow a packet
2230                        // even if a subset of the options are invalid.
2231                        //
2232                        // For example, here is a case where ISC will use the first 4 bytes of an
2233                        // IPv4 Address even if the option had > 4 length.
2234                        // https://github.com/isc-projects/dhcp/blob/31e68e5/server/dhcp.c#L947-L959
2235                        debug!("error while parsing option: {}", e);
2236                        continue;
2237                    }
2238                }
2239            }
2240        }
2241    }
2242}
2243
2244// Ensures slice is non empty. InvalidBufferLength is returned for empty slices.
2245fn nonempty<T>(slice: &[T]) -> Result<&[T], InvalidBufferLengthError> {
2246    if slice.len() == 0 {
2247        return Err(InvalidBufferLengthError(slice.len()));
2248    }
2249    Ok(slice)
2250}
2251
2252fn get_byte_array<const T: usize>(bytes: &[u8]) -> Result<[u8; T], InvalidBufferLengthError> {
2253    bytes
2254        .try_into()
2255        .map_err(|std::array::TryFromSliceError { .. }| InvalidBufferLengthError(bytes.len()))
2256}
2257
2258// Converts input byte slice into a single byte.
2259fn get_byte(bytes: &[u8]) -> Result<u8, InvalidBufferLengthError> {
2260    match bytes {
2261        [b] => Ok(*b),
2262        bytes => Err(InvalidBufferLengthError(bytes.len())),
2263    }
2264}
2265
2266// Converts input byte slice into a nonempty utf8 string.
2267fn bytes_to_nonempty_str(bytes: &[u8]) -> Result<String, ProtocolError> {
2268    // Spec states that strings should not be null terminated, yet receivers
2269    // should be prepared to trim trailing nulls.
2270    // See https://datatracker.ietf.org/doc/html/rfc2132#section-2
2271    std::str::from_utf8(nonempty(bytes)?.into())
2272        .map_err(|e| ProtocolError::Utf8(e.valid_up_to()))
2273        .map(|string| string.trim_end_matches(ASCII_NULL).to_owned())
2274}
2275
2276// Converts input byte slice into an Ipv4Addr.
2277fn bytes_to_addr(bytes: &[u8]) -> Result<Ipv4Addr, InvalidBufferLengthError> {
2278    Ok(Ipv4Addr::from(get_byte_array::<4>(bytes)?))
2279}
2280
2281// Converts an input byte slice into a list of Ipv4Addr.
2282fn bytes_to_addrs<const LOWER_BOUND: usize>(
2283    bytes: &[u8],
2284) -> Result<
2285    AtLeast<LOWER_BOUND, AtMostBytes<{ size_constrained::U8_MAX_AS_USIZE }, Vec<Ipv4Addr>>>,
2286    InvalidBufferLengthError,
2287> {
2288    let vec = bytes
2289        .chunks(IPV4_ADDR_LEN)
2290        .map(bytes_to_addr)
2291        .collect::<Result<Vec<Ipv4Addr>, InvalidBufferLengthError>>()
2292        .map_err(|InvalidBufferLengthError(_)| InvalidBufferLengthError(bytes.len()))?;
2293    vec.try_into().map_err(|(size_constrained::Error::SizeConstraintViolated, _)| {
2294        InvalidBufferLengthError(bytes.len())
2295    })
2296}
2297
2298#[derive(Debug, Error, PartialEq)]
2299enum BooleanConversionError {
2300    #[error("invalid buffer length: {}", _0)]
2301    InvalidBufferLength(usize),
2302    #[error("invalid value: {}", _0)]
2303    InvalidValue(u8),
2304}
2305
2306impl BooleanConversionError {
2307    fn to_protocol(&self, code: OptionCode) -> ProtocolError {
2308        match self {
2309            Self::InvalidBufferLength(len) => ProtocolError::InvalidBufferLength(*len),
2310            Self::InvalidValue(val) => ProtocolError::InvalidOptionValue(code, vec![*val]),
2311        }
2312    }
2313}
2314
2315// Returns a bool from a nonempty byte slice.
2316fn bytes_to_bool(bytes: &[u8]) -> Result<bool, BooleanConversionError> {
2317    let byte = get_byte(bytes)?;
2318    match byte {
2319        0 | 1 => Ok(byte == 1),
2320        b => Err(BooleanConversionError::InvalidValue(b)),
2321    }
2322}
2323
2324// Returns an Ipv4Addr when given a byte buffer in network order whose len >= start + 4.
2325pub fn ip_addr_from_buf_at(buf: &[u8], start: usize) -> Result<Ipv4Addr, ProtocolError> {
2326    let buf = buf.get(start..start + 4).ok_or(ProtocolError::InvalidBufferLength(buf.len()))?;
2327    let buf: [u8; 4] = buf.try_into().map_err(|std::array::TryFromSliceError { .. }| {
2328        ProtocolError::InvalidBufferLength(buf.len())
2329    })?;
2330    Ok(buf.into())
2331}
2332
2333fn buf_to_msg_string(buf: &[u8]) -> Result<String, ProtocolError> {
2334    Ok(std::str::from_utf8(buf)
2335        .map_err(|e| ProtocolError::Utf8(e.valid_up_to()))?
2336        .trim_end_matches(ASCII_NULL)
2337        .to_string())
2338}
2339
2340fn trunc_string_to_n_and_push(s: &str, n: usize, buffer: &mut Vec<u8>) {
2341    if s.len() > n {
2342        let truncated = s.split_at(n);
2343        buffer.extend(truncated.0.as_bytes());
2344        return;
2345    }
2346    buffer.extend(s.as_bytes());
2347    let unused_bytes = n - s.len();
2348    let old_len = buffer.len();
2349    buffer.resize(old_len + unused_bytes, 0);
2350}
2351
2352#[cfg(test)]
2353mod tests {
2354    use super::identifier::ClientIdentifier;
2355    use super::*;
2356    use net_declare::net::prefix_length_v4;
2357    use net_declare::std::ip_v4;
2358    use rand::Rng as _;
2359    use std::str::FromStr;
2360    use test_case::test_case;
2361
2362    const DEFAULT_SUBNET_MASK: PrefixLength<Ipv4> = prefix_length_v4!(24);
2363
2364    fn new_test_msg() -> Message {
2365        Message {
2366            op: OpCode::BOOTREQUEST,
2367            xid: 42,
2368            secs: 1024,
2369            bdcast_flag: false,
2370            ciaddr: Ipv4Addr::UNSPECIFIED,
2371            yiaddr: ip_v4!("192.168.1.1"),
2372            siaddr: Ipv4Addr::UNSPECIFIED,
2373            giaddr: Ipv4Addr::UNSPECIFIED,
2374            chaddr: MacAddr::new([0; 6]),
2375            sname: String::from("relay.example.com"),
2376            file: String::from("boot.img"),
2377            options: Vec::new(),
2378        }
2379    }
2380
2381    #[test]
2382    fn serialize_returns_correct_bytes() {
2383        let mut msg = new_test_msg();
2384        msg.options.push(DhcpOption::SubnetMask(DEFAULT_SUBNET_MASK));
2385
2386        let bytes = msg.serialize();
2387
2388        assert_eq!(bytes.len(), 247);
2389        assert_eq!(bytes[0], 1u8);
2390        assert_eq!(bytes[1], 1u8);
2391        assert_eq!(bytes[2], 6u8);
2392        assert_eq!(bytes[3], 0u8);
2393        assert_eq!(bytes[7], 42u8);
2394        assert_eq!(bytes[8], 4u8);
2395        assert_eq!(bytes[16], 192u8);
2396        assert_eq!(bytes[17], 168u8);
2397        assert_eq!(bytes[18], 1u8);
2398        assert_eq!(bytes[19], 1u8);
2399        assert_eq!(bytes[44], 'r' as u8);
2400        assert_eq!(bytes[60], 'm' as u8);
2401        assert_eq!(bytes[61], 0u8);
2402        assert_eq!(bytes[108], 'b' as u8);
2403        assert_eq!(bytes[115], 'g' as u8);
2404        assert_eq!(bytes[116], 0u8);
2405        assert_eq!(bytes[OPTIONS_START_IDX..OPTIONS_START_IDX + MAGIC_COOKIE.len()], MAGIC_COOKIE);
2406        assert_eq!(bytes[bytes.len() - 1], 255u8);
2407    }
2408
2409    #[test]
2410    fn message_from_buffer_returns_correct_message() {
2411        use std::string::ToString;
2412
2413        let mut buf = Vec::new();
2414        buf.push(1u8);
2415        buf.push(1u8);
2416        buf.push(6u8);
2417        buf.push(0u8);
2418        buf.extend_from_slice(b"\x00\x00\x00\x2A");
2419        buf.extend_from_slice(b"\x04\x00");
2420        buf.extend_from_slice(b"\x00\x00");
2421        buf.extend_from_slice(b"\x00\x00\x00\x00");
2422        buf.extend_from_slice(b"\xC0\xA8\x01\x01");
2423        buf.extend_from_slice(b"\x00\x00\x00\x00");
2424        buf.extend_from_slice(b"\x00\x00\x00\x00");
2425        buf.extend_from_slice(b"\x00\x00\x00\x00\x00\x00");
2426        buf.extend_from_slice(b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00");
2427        buf.extend_from_slice(b"relay.example.com");
2428        let mut old_len = buf.len();
2429        let mut unused_bytes = SNAME_LEN - b"relay.example.com".len();
2430        buf.resize(old_len + unused_bytes, 0u8);
2431        buf.extend_from_slice(b"boot.img");
2432        old_len = buf.len();
2433        unused_bytes = FILE_LEN - b"boot.img".len();
2434        buf.resize(old_len + unused_bytes, 0u8);
2435        buf.extend_from_slice(&MAGIC_COOKIE);
2436        buf.extend_from_slice(b"\x01\x04");
2437        buf.extend_from_slice(&DEFAULT_SUBNET_MASK.get_mask().ipv4_bytes()[..]);
2438        buf.extend_from_slice(b"\x00");
2439        buf.extend_from_slice(b"\x00");
2440        buf.extend_from_slice(b"\x36\x04");
2441        let server_id = ip_v4!("1.2.3.4");
2442        buf.extend_from_slice(&server_id.octets()[..]);
2443        buf.extend_from_slice(b"\xFF");
2444
2445        assert_eq!(
2446            Message::from_buffer(&buf),
2447            Ok(Message {
2448                op: OpCode::BOOTREQUEST,
2449                xid: 42,
2450                secs: 1024,
2451                bdcast_flag: false,
2452                ciaddr: Ipv4Addr::UNSPECIFIED,
2453                yiaddr: ip_v4!("192.168.1.1"),
2454                siaddr: Ipv4Addr::UNSPECIFIED,
2455                giaddr: Ipv4Addr::UNSPECIFIED,
2456                chaddr: MacAddr::new([0; 6]),
2457                sname: "relay.example.com".to_string(),
2458                file: "boot.img".to_string(),
2459                options: vec![
2460                    DhcpOption::SubnetMask(DEFAULT_SUBNET_MASK),
2461                    DhcpOption::ServerIdentifier(server_id),
2462                ],
2463            })
2464        );
2465    }
2466
2467    #[test]
2468    fn serialize_then_deserialize_with_single_option_is_equal_to_starting_value() {
2469        let msg = || {
2470            let mut msg = new_test_msg();
2471            msg.options.push(DhcpOption::SubnetMask(DEFAULT_SUBNET_MASK));
2472            msg
2473        };
2474
2475        assert_eq!(Message::from_buffer(&msg().serialize()), Ok(msg()));
2476    }
2477
2478    #[test]
2479    fn serialize_then_deserialize_with_no_options_is_equal_to_starting_value() {
2480        let msg = new_test_msg();
2481
2482        assert_eq!(Message::from_buffer(&msg.serialize()), Ok(new_test_msg()));
2483    }
2484
2485    #[test]
2486    fn serialize_then_deserialize_with_many_options_is_equal_to_starting_value() {
2487        let msg = || {
2488            let mut msg = new_test_msg();
2489            msg.options.push(DhcpOption::SubnetMask(DEFAULT_SUBNET_MASK));
2490            msg.options.push(DhcpOption::NameServer([ip_v4!("1.2.3.4")].into()));
2491            msg.options.push(DhcpOption::DhcpMessageType(MessageType::DHCPDISCOVER));
2492            msg.options.push(DhcpOption::ParameterRequestList([OptionCode::SubnetMask].into()));
2493            msg.options.push(DhcpOption::PathMtuPlateauTable([1480u16].into()));
2494            msg
2495        };
2496
2497        assert_eq!(Message::from_buffer(&msg().serialize()), Ok(msg()));
2498    }
2499
2500    #[test]
2501    fn strips_null_from_option_strings() {
2502        let ascii_str = String::from("Hello World");
2503        let null_terminated_str = format!("{}{}", ascii_str, ASCII_NULL);
2504        let msg = Message {
2505            options: vec![DhcpOption::MeritDumpFile(null_terminated_str)],
2506            ..new_test_msg()
2507        };
2508        let Message { options: parsed_options, .. } = Message::from_buffer(&msg.serialize())
2509            .expect("Parsing serialized message should succeed.");
2510        assert_eq!(parsed_options, vec![DhcpOption::MeritDumpFile(ascii_str)]);
2511    }
2512
2513    #[test]
2514    fn message_from_too_short_buffer_returns_error() {
2515        let buf = vec![0u8, 0u8, 0u8];
2516
2517        assert_eq!(
2518            Message::from_buffer(&buf),
2519            Err(ProtocolError::InvalidBufferLength(buf.len()).into())
2520        );
2521    }
2522
2523    #[test]
2524    fn serialize_with_valid_option_returns_correct_bytes() {
2525        let opt = DhcpOption::SubnetMask(DEFAULT_SUBNET_MASK);
2526        let mut bytes = Vec::with_capacity(6);
2527        let () = opt.serialize_to(&mut bytes);
2528        assert_eq!(bytes.len(), 6);
2529        assert_eq!(bytes[0], 1);
2530        assert_eq!(bytes[1], 4);
2531        assert_eq!(bytes[2], 255);
2532        assert_eq!(bytes[3], 255);
2533        assert_eq!(bytes[4], 255);
2534        assert_eq!(bytes[5], 0);
2535    }
2536
2537    #[test]
2538    fn serialize_with_fixed_len_option_returns_correct_bytes() {
2539        let opt = DhcpOption::End();
2540        let mut bytes = Vec::with_capacity(1);
2541        let () = opt.serialize_to(&mut bytes);
2542        assert_eq!(bytes.len(), 1);
2543        assert_eq!(bytes[0], 255);
2544    }
2545
2546    #[test]
2547    fn option_from_valid_buffer_has_correct_value() {
2548        let buf = vec![1, 4, 255, 255, 255, 0, 255];
2549        assert_eq!(
2550            parse_options(&buf[..], Vec::new()),
2551            Ok(vec![DhcpOption::SubnetMask(DEFAULT_SUBNET_MASK)])
2552        );
2553    }
2554
2555    #[test]
2556    fn option_from_valid_buffer_with_fixed_length_returns_empty_options() {
2557        let buf = vec![255];
2558        assert_eq!(parse_options(&buf[..], Vec::new()), Ok(Vec::new()));
2559    }
2560
2561    #[test]
2562    fn option_from_valid_buffer_ignores_unknown_opcodes() {
2563        let buf = vec![254, 2, 1, 2, 255];
2564        assert_eq!(parse_options(&buf[..], Vec::new()), Ok(Vec::new()));
2565    }
2566
2567    #[test]
2568    fn option_stops_at_end_of_options() {
2569        let buf = vec![26, 2, 4, 0, 255, 26, 2, 4, 0];
2570        assert_eq!(parse_options(&buf[..], Vec::new()), Ok(vec![DhcpOption::InterfaceMtu(1024)]));
2571    }
2572
2573    #[test]
2574    fn option_from_buffer_with_invalid_length_returns_err() {
2575        let buf = vec![1, 6, 255, 255, 255, 0];
2576        assert_eq!(
2577            parse_options(&buf[..], Vec::new()),
2578            Err(ProtocolError::MalformedOption { code: 1, remaining: 4, want: 6 })
2579        );
2580    }
2581
2582    #[test]
2583    fn option_from_buffer_missing_length_returns_err() {
2584        let buf = vec![1];
2585        assert_eq!(
2586            parse_options(&buf[..], Vec::new()),
2587            Err(ProtocolError::MalformedOption { code: 1, remaining: 0, want: 1 })
2588        );
2589    }
2590
2591    #[test]
2592    fn option_from_buffer_missing_end_option_returns_err() {
2593        assert_eq!(
2594            parse_options(&[], Vec::new()),
2595            Err(ProtocolError::MissingOption(OptionCode::End))
2596        );
2597    }
2598
2599    #[test]
2600    fn get_dhcp_type_with_dhcp_type_option_returns_value() {
2601        let mut msg = new_test_msg();
2602        msg.options.push(DhcpOption::DhcpMessageType(MessageType::DHCPDISCOVER));
2603
2604        assert_eq!(msg.get_dhcp_type(), Ok(MessageType::DHCPDISCOVER));
2605    }
2606
2607    #[test]
2608    fn get_dhcp_type_without_dhcp_type_option_returns_err() {
2609        let msg = new_test_msg();
2610
2611        assert_eq!(
2612            msg.get_dhcp_type(),
2613            Err(ProtocolError::MissingOption(OptionCode::DhcpMessageType).into())
2614        );
2615    }
2616
2617    #[test]
2618    fn buf_into_options_with_invalid_option_parses_other_valid_options() {
2619        let msg = || {
2620            let mut msg = new_test_msg();
2621            msg.options.push(DhcpOption::SubnetMask(DEFAULT_SUBNET_MASK));
2622            msg.options.push(DhcpOption::Router([ip_v4!("192.168.1.1")].into()));
2623            msg.options.push(DhcpOption::DhcpMessageType(MessageType::DHCPDISCOVER));
2624            msg
2625        };
2626
2627        let mut buf = msg().serialize();
2628        // introduce invalid option code in first option
2629        buf[OPTIONS_START_IDX + 4] = 99;
2630
2631        // Expect that everything but the invalid option deserializes.
2632        let mut expected_msg = msg();
2633        assert_eq!(expected_msg.options.remove(0), DhcpOption::SubnetMask(DEFAULT_SUBNET_MASK));
2634        assert_eq!(Message::from_buffer(&buf), Ok(expected_msg));
2635    }
2636
2637    #[test_case(OptionCode::HostName, 0; "Min length 1")]
2638    #[test_case(OptionCode::ClientIdentifier, 1; "Min length 2_1")]
2639    #[test_case(OptionCode::PathMtuPlateauTable, 1; "Min length 2_2")]
2640    #[test_case(OptionCode::Router, 0; "Min length 4")]
2641    #[test_case(OptionCode::PolicyFilter, 4; "Min length 8_1")]
2642    #[test_case(OptionCode::StaticRoute, 4; "Min length 8_2")]
2643    fn parse_options_with_invalid_min_lengths(code: OptionCode, len: usize) {
2644        let option = DhcpOption::from_raw_parts(code, &vec![0; len]);
2645        assert_eq!(Err(ProtocolError::InvalidBufferLength(len)), option)
2646    }
2647
2648    #[test_case(OptionCode::IpForwarding, 0; "Length = 1")]
2649    #[test_case(OptionCode::BootfileName, 0; "Length = 2")]
2650    #[test_case(OptionCode::TimeOffset, 0; "Length = 4")]
2651    fn parse_options_with_invalid_static_length(code: OptionCode, len: usize) {
2652        let option = DhcpOption::from_raw_parts(code, &vec![0; len]);
2653        assert_eq!(Err(ProtocolError::InvalidBufferLength(len)), option)
2654    }
2655
2656    #[test_case(OptionCode::PathMtuPlateauTable, 3; "Min length 2, multiple of 2")]
2657    #[test_case(OptionCode::MobileIpHomeAgent, 5; "Min length 0, multiple of 4")]
2658    #[test_case(OptionCode::PolicyFilter, 4; "PolicyFilter_4: Min length 8, multiple of 8")]
2659    #[test_case(OptionCode::PolicyFilter, 12; "PolicyFilter_12: Min length 8, multiple of 8 - 2")]
2660    #[test_case(OptionCode::StaticRoute, 4; "StaticRoute_4: Min length 8, multiple of 8 - 1")]
2661    #[test_case(OptionCode::StaticRoute, 12; "StaticRoute_12: Min length 8, multiple of 8 - 2")]
2662    fn parse_options_with_invalid_length_multiples(code: OptionCode, len: usize) {
2663        let option = DhcpOption::from_raw_parts(code, &vec![0; len]);
2664        assert_eq!(Err(ProtocolError::InvalidBufferLength(len)), option)
2665    }
2666
2667    #[test_case(OptionCode::IpForwarding)]
2668    #[test_case(OptionCode::NonLocalSourceRouting)]
2669    #[test_case(OptionCode::AllSubnetsLocal)]
2670    #[test_case(OptionCode::PerformMaskDiscovery)]
2671    #[test_case(OptionCode::MaskSupplier)]
2672    #[test_case(OptionCode::PerformRouterDiscovery)]
2673    #[test_case(OptionCode::TrailerEncapsulation)]
2674    #[test_case(OptionCode::EthernetEncapsulation)]
2675    #[test_case(OptionCode::TcpKeepaliveGarbage)]
2676    fn parse_options_with_invalid_flag_value(code: OptionCode) {
2677        let val = vec![2];
2678        let option = DhcpOption::from_raw_parts(code, &val);
2679        assert_eq!(Err(ProtocolError::InvalidOptionValue(code, val)), option)
2680    }
2681
2682    #[test_case(0)]
2683    #[test_case(4)]
2684    fn parse_options_with_invalid_overload_value(overload: u8) {
2685        let code = OptionCode::OptionOverload;
2686        let val = vec![overload];
2687        let option = DhcpOption::from_raw_parts(code, &val);
2688
2689        // Valid values are 1, 2, 3.
2690        assert_eq!(Err(ProtocolError::InvalidOptionValue(code, val)), option);
2691    }
2692
2693    #[test]
2694    fn parse_options_with_invalid_netbios_node_value() {
2695        // Valid values are 1, 2, 4, 8
2696        let code = OptionCode::NetBiosOverTcpipNodeType;
2697        let val = vec![3];
2698        let option = DhcpOption::from_raw_parts(code, &val);
2699        assert_eq!(Err(ProtocolError::InvalidOptionValue(code, val)), option);
2700    }
2701
2702    #[test_case(OptionCode::DefaultIpTtl)]
2703    #[test_case(OptionCode::TcpDefaultTtl)]
2704    fn parse_options_with_invalid_ttl_value(code: OptionCode) {
2705        let val = vec![0];
2706        let option = DhcpOption::from_raw_parts(code, &val);
2707        assert_eq!(Err(ProtocolError::InvalidOptionValue(code, val)), option);
2708    }
2709
2710    #[test_case(OptionCode::MaxDatagramReassemblySize, MIN_MESSAGE_SIZE)]
2711    #[test_case(OptionCode::MaxDhcpMessageSize, MIN_MESSAGE_SIZE)]
2712    #[test_case(OptionCode::InterfaceMtu, MIN_MTU_VAL)]
2713    fn parse_options_with_too_low_value(code: OptionCode, min_size: u16) {
2714        let val = (min_size - 1).to_be_bytes().to_vec();
2715        let option = DhcpOption::from_raw_parts(code, &val);
2716        assert_eq!(Err(ProtocolError::InvalidOptionValue(code, val)), option);
2717    }
2718
2719    #[test]
2720    fn rejects_invalid_subnet_mask() {
2721        let mask = vec![255, 254, 255, 0];
2722
2723        assert_eq!(
2724            DhcpOption::from_raw_parts(OptionCode::SubnetMask, &mask),
2725            Err(ProtocolError::InvalidOptionValue(OptionCode::SubnetMask, mask))
2726        )
2727    }
2728
2729    #[test]
2730    fn parameter_request_list_with_known_and_unknown_options_returns_known_options() {
2731        assert_eq!(
2732            DhcpOption::from_raw_parts(
2733                OptionCode::ParameterRequestList,
2734                &[
2735                    121, /* unrecognized */
2736                    1, 3, 6, 15, 31, 33, 249, /* unrecognized */
2737                    43, 44, 46, 47, 119, /* unrecognized */
2738                    252, /* unrecognized */
2739                ]
2740            ),
2741            Ok(DhcpOption::ParameterRequestList(
2742                [
2743                    OptionCode::SubnetMask,
2744                    OptionCode::Router,
2745                    OptionCode::DomainNameServer,
2746                    OptionCode::DomainName,
2747                    OptionCode::PerformRouterDiscovery,
2748                    OptionCode::StaticRoute,
2749                    OptionCode::VendorSpecificInformation,
2750                    OptionCode::NetBiosOverTcpipNameServer,
2751                    OptionCode::NetBiosOverTcpipNodeType,
2752                    OptionCode::NetBiosOverTcpipScope,
2753                ]
2754                .into()
2755            ))
2756        );
2757    }
2758
2759    fn random_ipv4_generator() -> Ipv4Addr {
2760        let octet1: u8 = rand::thread_rng().gen();
2761        let octet2: u8 = rand::thread_rng().gen();
2762        let octet3: u8 = rand::thread_rng().gen();
2763        let octet4: u8 = rand::thread_rng().gen();
2764        Ipv4Addr::new(octet1, octet2, octet3, octet4)
2765    }
2766
2767    fn test_option_overload(overload: Overload) {
2768        let mut msg = Message {
2769            op: OpCode::BOOTREQUEST,
2770            xid: 0,
2771            secs: 0,
2772            bdcast_flag: false,
2773            ciaddr: Ipv4Addr::UNSPECIFIED,
2774            yiaddr: Ipv4Addr::UNSPECIFIED,
2775            siaddr: Ipv4Addr::UNSPECIFIED,
2776            giaddr: Ipv4Addr::UNSPECIFIED,
2777            chaddr: MacAddr::new([0; 6]),
2778            sname: String::from(""),
2779            file: String::from(""),
2780            options: vec![DhcpOption::OptionOverload(overload)],
2781        }
2782        .serialize();
2783        let ip = random_ipv4_generator();
2784        let first_extra_opt = {
2785            let mut acc = Vec::new();
2786            let () = DhcpOption::RequestedIpAddress(ip).serialize_to(&mut acc);
2787            acc
2788        };
2789        let last_extra_opt = {
2790            let mut acc = Vec::new();
2791            let () = DhcpOption::End().serialize_to(&mut acc);
2792            acc
2793        };
2794        let (extra_opts, start_idx) = match overload {
2795            Overload::SName => ([&first_extra_opt[..], &last_extra_opt[..]].concat(), SNAME_IDX),
2796            Overload::File => ([&first_extra_opt[..], &last_extra_opt[..]].concat(), FILE_IDX),
2797            Overload::Both => {
2798                // Insert enough padding bytes such that extra_opts will straddle both file and
2799                // sname fields.
2800                ([&first_extra_opt[..], &[0u8; SNAME_LEN], &last_extra_opt[..]].concat(), SNAME_IDX)
2801            }
2802        };
2803        let _: std::vec::Splice<'_, _> =
2804            msg.splice(start_idx..start_idx + extra_opts.len(), extra_opts);
2805        assert_eq!(
2806            Message::from_buffer(&msg),
2807            Ok(Message {
2808                op: OpCode::BOOTREQUEST,
2809                xid: 0,
2810                secs: 0,
2811                bdcast_flag: false,
2812                ciaddr: Ipv4Addr::UNSPECIFIED,
2813                yiaddr: Ipv4Addr::UNSPECIFIED,
2814                siaddr: Ipv4Addr::UNSPECIFIED,
2815                giaddr: Ipv4Addr::UNSPECIFIED,
2816                chaddr: MacAddr::new([0; 6]),
2817                sname: String::from(""),
2818                file: String::from(""),
2819                options: vec![
2820                    DhcpOption::OptionOverload(overload),
2821                    DhcpOption::RequestedIpAddress(ip)
2822                ],
2823            })
2824        );
2825    }
2826
2827    #[test]
2828    fn message_with_option_overload_parses_extra_options() {
2829        test_option_overload(Overload::SName);
2830        test_option_overload(Overload::File);
2831        test_option_overload(Overload::Both);
2832    }
2833
2834    #[test]
2835    fn client_identifier_from_str() {
2836        assert_matches::assert_matches!(
2837            ClientIdentifier::from_str("id:1234567890abcd"),
2838            Ok(ClientIdentifier { .. })
2839        );
2840        assert_matches::assert_matches!(
2841            ClientIdentifier::from_str("chaddr:1234567890ab"),
2842            Ok(ClientIdentifier { .. })
2843        );
2844        // incorrect type prefix
2845        assert_matches::assert_matches!(ClientIdentifier::from_str("option:1234567890"), Err(..));
2846        // extra field
2847        assert_matches::assert_matches!(ClientIdentifier::from_str("id:1234567890:extra"), Err(..));
2848        // no type prefix
2849        assert_matches::assert_matches!(ClientIdentifier::from_str("1234567890"), Err(..));
2850        // no delimiter
2851        assert_matches::assert_matches!(ClientIdentifier::from_str("id1234567890"), Err(..));
2852        // incorrect delimiter
2853        assert_matches::assert_matches!(ClientIdentifier::from_str("id-1234567890"), Err(..));
2854        // invalid hex digits
2855        assert_matches::assert_matches!(
2856            ClientIdentifier::from_str("id:1234567890abcdefg"),
2857            Err(..)
2858        );
2859        // odd number of hex digits
2860        assert_matches::assert_matches!(ClientIdentifier::from_str("id:123456789"), Err(..));
2861        // insufficient digits for chaddr
2862        assert_matches::assert_matches!(ClientIdentifier::from_str("chaddr:1234567890"), Err(..));
2863    }
2864}