Skip to main content

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