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