Skip to main content

packet_formats_dhcp/
v6.rs

1// Copyright 2020 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
5//! Parsing and serialization for DHCPv6 messages.
6
7use mdns::protocol::{Domain, ParseError as MdnsParseError};
8use net_types::ip::{IpAddress as _, Ipv6Addr, PrefixTooLongError, Subnet};
9use num_derive::FromPrimitive;
10use packet::records::{
11    ParsedRecord, RecordBuilder, RecordParseResult, RecordSequenceBuilder, Records, RecordsContext,
12    RecordsImpl, RecordsImplLayout,
13};
14use packet::{BufferView, BufferViewMut, InnerPacketBuilder, ParsablePacket, ParseMetadata};
15use std::convert::Infallible as Never;
16use std::slice::Iter;
17use std::{mem, str};
18use thiserror::Error;
19use uuid::Uuid;
20use zerocopy::byteorder::network_endian::{U16, U32};
21use zerocopy::{
22    FromBytes, Immutable, IntoByteSlice, IntoBytes, KnownLayout, Ref, SplitByteSlice, Unaligned,
23};
24
25/// A DHCPv6 packet parsing error.
26#[allow(missing_docs)]
27#[derive(Debug, Error, PartialEq)]
28pub enum ParseError {
29    #[error("invalid message type: {}", _0)]
30    InvalidMessageType(u8),
31    #[error("invalid option code: {}", _0)]
32    InvalidOpCode(u16),
33    #[error("invalid option length {} for option code {:?}", _1, _0)]
34    InvalidOpLen(OptionCode, usize),
35    #[error("invalid status code: {}", _0)]
36    InvalidStatusCode(u16),
37    #[error("invalid error status code: {}", _0)]
38    InvalidErrorStatusCode(u16),
39    #[error("buffer exhausted while more bytes are expected")]
40    BufferExhausted,
41    #[error("failed to parse domain {:?}", _0)]
42    DomainParseError(MdnsParseError),
43    #[error("failed to parse UTF8 string: {:?}", _0)]
44    Utf8Error(#[from] str::Utf8Error),
45    #[error("DHCPv6 option recursion limit exceeded")]
46    OptionRecursionLimitExceeded,
47}
48
49impl From<Never> for ParseError {
50    fn from(err: Never) -> ParseError {
51        match err {}
52    }
53}
54
55/// The maximum allowed recursion depth for DHCPv6 options.
56///
57/// The maximum layer of nesting in practice is:
58/// - Relay Message Option (depth 1)
59/// - IANA/TA/PD options inside the encapsulated message (depth 2)
60/// - IA Addr or IA Prefix options inside IANA/TA/PD (depth 3)
61/// - Status Code options inside IA Addr/Prefix (depth 4)
62const MAX_RECURSION_DEPTH: usize = 4;
63
64/// Context used to track recursion depth during DHCPv6 option parsing.
65#[derive(Clone, Copy, Debug, PartialEq, Eq)]
66struct OptionParseContext {
67    depth: usize,
68}
69
70impl RecordsContext for OptionParseContext {}
71
72/// A DHCPv6 message type as defined in [RFC 8415, Section 7.3].
73///
74/// [RFC 8415, Section 7.3]: https://tools.ietf.org/html/rfc8415#section-7.3
75#[allow(missing_docs)]
76#[derive(Debug, PartialEq, FromPrimitive, IntoBytes, Immutable, Copy, Clone)]
77#[repr(u8)]
78pub enum MessageType {
79    Solicit = 1,
80    Advertise = 2,
81    Request = 3,
82    Confirm = 4,
83    Renew = 5,
84    Rebind = 6,
85    Reply = 7,
86    Release = 8,
87    Decline = 9,
88    Reconfigure = 10,
89    InformationRequest = 11,
90    RelayForw = 12,
91    RelayRepl = 13,
92}
93
94impl From<MessageType> for u8 {
95    fn from(t: MessageType) -> u8 {
96        t as u8
97    }
98}
99
100impl TryFrom<u8> for MessageType {
101    type Error = ParseError;
102
103    fn try_from(b: u8) -> Result<MessageType, ParseError> {
104        <Self as num_traits::FromPrimitive>::from_u8(b).ok_or(ParseError::InvalidMessageType(b))
105    }
106}
107
108/// A DHCPv6 status code as defined in [RFC 8415, Section 21.13].
109///
110/// [RFC 8415, Section 21.13]: https://tools.ietf.org/html/rfc8415#section-21.13
111#[allow(missing_docs)]
112#[derive(Debug, PartialEq, Copy, Clone)]
113pub enum StatusCode {
114    Success,
115    Failure(ErrorStatusCode),
116}
117
118impl From<StatusCode> for u16 {
119    fn from(t: StatusCode) -> u16 {
120        match t {
121            StatusCode::Success => 0,
122            StatusCode::Failure(error_status) => error_status.into(),
123        }
124    }
125}
126
127impl TryFrom<u16> for StatusCode {
128    type Error = ParseError;
129
130    fn try_from(b: u16) -> Result<StatusCode, ParseError> {
131        match b {
132            0 => Ok(Self::Success),
133            b => ErrorStatusCode::try_from(b).map(Self::Failure).map_err(|e| match e {
134                ParseError::InvalidErrorStatusCode(b) => ParseError::InvalidStatusCode(b),
135                e => unreachable!("unexpected error parsing u16 as ErrorStatusCode: {}", e),
136            }),
137        }
138    }
139}
140
141impl StatusCode {
142    /// Converts into either `Ok(())` if the status code is Success or an error
143    /// containing the failure status code.
144    pub fn into_result(self) -> Result<(), ErrorStatusCode> {
145        match self {
146            Self::Success => Ok(()),
147            Self::Failure(error_status) => Err(error_status),
148        }
149    }
150}
151
152/// A DHCPv6 error status code as defined in [RFC 8415, Section 21.13].
153///
154/// [RFC 8415, Section 21.13]: https://tools.ietf.org/html/rfc8415#section-21.13
155#[allow(missing_docs)]
156#[derive(thiserror::Error, Debug, PartialEq, FromPrimitive, IntoBytes, Immutable, Copy, Clone)]
157#[repr(u16)]
158pub enum ErrorStatusCode {
159    #[error("unspecified failure")]
160    UnspecFail = 1,
161    #[error("no addresses available")]
162    NoAddrsAvail = 2,
163    #[error("no binding")]
164    NoBinding = 3,
165    #[error("not on-link")]
166    NotOnLink = 4,
167    #[error("use multicast")]
168    UseMulticast = 5,
169    #[error("no prefixes available")]
170    NoPrefixAvail = 6,
171}
172
173impl From<ErrorStatusCode> for u16 {
174    fn from(code: ErrorStatusCode) -> u16 {
175        code as u16
176    }
177}
178
179impl From<ErrorStatusCode> for StatusCode {
180    fn from(code: ErrorStatusCode) -> Self {
181        Self::Failure(code)
182    }
183}
184
185impl TryFrom<u16> for ErrorStatusCode {
186    type Error = ParseError;
187
188    fn try_from(b: u16) -> Result<Self, ParseError> {
189        <Self as num_traits::FromPrimitive>::from_u16(b)
190            .ok_or(ParseError::InvalidErrorStatusCode(b))
191    }
192}
193
194/// A DHCPv6 option code that identifies a corresponding option.
195///
196/// Options that are not found in this type are currently not supported. An exhaustive list of
197/// option codes can be found [here][option-codes].
198///
199/// [option-codes]: https://www.iana.org/assignments/dhcpv6-parameters/dhcpv6-parameters.xhtml#dhcpv6-parameters-2
200#[allow(missing_docs)]
201#[derive(Debug, PartialEq, FromPrimitive, Clone, Copy)]
202#[repr(u8)]
203pub enum OptionCode {
204    ClientId = 1,
205    ServerId = 2,
206    Iana = 3,
207    IaAddr = 5,
208    Oro = 6,
209    Preference = 7,
210    ElapsedTime = 8,
211    StatusCode = 13,
212    DnsServers = 23,
213    DomainList = 24,
214    IaPd = 25,
215    IaPrefix = 26,
216    InformationRefreshTime = 32,
217    SolMaxRt = 82,
218}
219
220impl From<OptionCode> for u16 {
221    fn from(code: OptionCode) -> u16 {
222        code as u16
223    }
224}
225
226impl TryFrom<u16> for OptionCode {
227    type Error = ParseError;
228
229    fn try_from(n: u16) -> Result<OptionCode, ParseError> {
230        <Self as num_traits::FromPrimitive>::from_u16(n).ok_or(ParseError::InvalidOpCode(n))
231    }
232}
233
234/// A parsed DHCPv6 options.
235///
236/// Options that are not found in this type are currently not supported. An exhaustive list of
237/// options can be found [here][options].
238///
239/// [options]: https://www.iana.org/assignments/dhcpv6-parameters/dhcpv6-parameters.xhtml#dhcpv6-parameters-2
240// TODO(https://fxbug.dev/42155425): replace `ParsedDhcpOption` and `DhcpOption` with a single type.
241#[allow(missing_docs)]
242#[derive(Debug, PartialEq)]
243pub enum ParsedDhcpOption<'a> {
244    // https://tools.ietf.org/html/rfc8415#section-21.2
245    ClientId(&'a Duid),
246    // https://tools.ietf.org/html/rfc8415#section-21.3
247    ServerId(&'a Duid),
248    // https://tools.ietf.org/html/rfc8415#section-21.4
249    // TODO(https://fxbug.dev/42154013): add validation; not all option codes can
250    // be present in an IA_NA option.
251    Iana(IanaData<&'a [u8]>),
252    // https://tools.ietf.org/html/rfc8415#section-21.6
253    // TODO(https://fxbug.dev/42154013): add validation; not all option codes can
254    // be present in an IA Address option.
255    IaAddr(IaAddrData<&'a [u8]>),
256    // https://tools.ietf.org/html/rfc8415#section-21.7
257    // TODO(https://fxbug.dev/42154013): add validation; not all option codes can
258    // be present in an ORO option.
259    // See https://www.iana.org/assignments/dhcpv6-parameters/dhcpv6-parameters.xhtml#dhcpv6-parameters-2
260    Oro(Vec<OptionCode>),
261    // https://tools.ietf.org/html/rfc8415#section-21.8
262    Preference(u8),
263    // https://tools.ietf.org/html/rfc8415#section-21.9
264    ElapsedTime(u16),
265    // https://tools.ietf.org/html/rfc8415#section-21.13
266    StatusCode(U16, &'a str),
267    // https://tools.ietf.org/html/rfc8415#section-21.21
268    // TODO(https://fxbug.dev/42154013): add validation; not all option codes can
269    // be present in an IA_PD option.
270    IaPd(IaPdData<&'a [u8]>),
271    // ttps://tools.ietf.org/html/rfc8415#section-21.22
272    // TODO(https://fxbug.dev/42154013): add validation; not all option codes can
273    // be present in an IA Prefix option.
274    IaPrefix(IaPrefixData<&'a [u8]>),
275    // https://tools.ietf.org/html/rfc8415#section-21.23
276    InformationRefreshTime(u32),
277    // https://tools.ietf.org/html/rfc8415#section-21.24
278    SolMaxRt(U32),
279    // https://tools.ietf.org/html/rfc3646#section-3
280    DnsServers(Vec<Ipv6Addr>),
281    // https://tools.ietf.org/html/rfc3646#section-4
282    DomainList(Vec<checked::Domain>),
283}
284
285/// An overlay representation of an IA_NA option.
286#[derive(Debug, PartialEq)]
287pub struct IanaData<B: SplitByteSlice> {
288    header: Ref<B, IanaHeader>,
289    options: Records<B, ParsedDhcpOptionImpl>,
290}
291
292mod private {
293    /// A `u32` value that is guaranteed to be greater than 0 and less than `u32::MAX`.
294    #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
295    pub struct NonZeroOrMaxU32(u32);
296
297    impl NonZeroOrMaxU32 {
298        /// Constructs a `NonZeroOrMaxU32`.
299        ///
300        /// Returns `None` if `t` is 0 or `u32::MAX`.
301        pub const fn new(t: u32) -> Option<NonZeroOrMaxU32> {
302            if t == 0 || t == u32::MAX {
303                return None;
304            }
305            Some(NonZeroOrMaxU32(t))
306        }
307
308        /// Returns the value.
309        pub fn get(self) -> u32 {
310            let NonZeroOrMaxU32(t) = self;
311            t
312        }
313    }
314}
315
316pub use private::*;
317
318/// A representation of time values for lifetimes to relay the fact that zero is
319/// not a valid time value, and that `Infinity` has special significance, as
320/// described in RFC 8415, [section
321/// 14.2] and [section 7.7].
322///
323/// [section 14.2]: https://datatracker.ietf.org/doc/html/rfc8415#section-14.2
324/// [section 7.7]: https://datatracker.ietf.org/doc/html/rfc8415#section-7.7
325#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
326pub enum NonZeroTimeValue {
327    /// The value is set to a value greater than 0 and less than `Infinity`.
328    Finite(NonZeroOrMaxU32),
329    /// `u32::MAX` representing `Infinity`, as described in
330    /// [RFC 8415, section 7.7].
331    ///
332    /// [RFC 8415, section 7.7]: https://datatracker.ietf.org/doc/html/rfc8415#section-7.7
333    Infinity,
334}
335
336impl From<NonZeroTimeValue> for TimeValue {
337    fn from(v: NonZeroTimeValue) -> TimeValue {
338        TimeValue::NonZero(v)
339    }
340}
341
342/// A representation of time values for lifetimes to relay the fact that certain
343/// values have special significance as described in RFC 8415, [section 14.2]
344/// and [section 7.7].
345///
346/// [section 14.2]: https://datatracker.ietf.org/doc/html/rfc8415#section-14.2
347/// [section 7.7]: https://datatracker.ietf.org/doc/html/rfc8415#section-7.7
348#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
349pub enum TimeValue {
350    /// The value is zero.
351    Zero,
352    /// The value is non-zero.
353    NonZero(NonZeroTimeValue),
354}
355
356impl TimeValue {
357    /// Constructs a new `TimeValue`.
358    pub const fn new(t: u32) -> TimeValue {
359        match t {
360            0 => TimeValue::Zero,
361            u32::MAX => TimeValue::NonZero(NonZeroTimeValue::Infinity),
362            t => TimeValue::NonZero(NonZeroTimeValue::Finite(
363                // should succeed for non zero or u32::MAX values
364                NonZeroOrMaxU32::new(t).unwrap(),
365            )),
366        }
367    }
368}
369
370impl<'a, B: SplitByteSlice> IanaData<B> {
371    /// Constructs a new `IanaData` from a `ByteSlice`.
372    fn new(buf: B, context: OptionParseContext) -> Result<Self, ParseError> {
373        let buf_len = buf.len();
374        let (header, options) =
375            Ref::from_prefix(buf).map_err(Into::into).map_err(|_: zerocopy::SizeError<_, _>| {
376                ParseError::InvalidOpLen(OptionCode::Iana, buf_len)
377            })?;
378        let options = Records::<B, ParsedDhcpOptionImpl>::parse_with_context(options, context)?;
379        Ok(IanaData { header, options })
380    }
381
382    /// Returns the IAID.
383    pub fn iaid(&self) -> u32 {
384        self.header.iaid.get()
385    }
386
387    /// Returns the T1 as `TimeValue` to relay the fact that certain values have
388    /// special significance as described in RFC 8415, [section 14.2] and
389    /// [section 7.7].
390    ///
391    /// [section 14.2]: https://datatracker.ietf.org/doc/html/rfc8415#section-14.2
392    /// [section 7.7]: https://datatracker.ietf.org/doc/html/rfc8415#section-7.7
393    pub fn t1(&self) -> TimeValue {
394        TimeValue::new(self.header.t1.get())
395    }
396
397    /// Returns the T2 as `TimeValue` to relay the fact that certain values have
398    /// special significance as described in RFC 8415, [section 14.2] and
399    /// [section 7.7].
400    ///
401    /// [section 14.2]: https://datatracker.ietf.org/doc/html/rfc8415#section-14.2
402    /// [section 7.7]: https://datatracker.ietf.org/doc/html/rfc8415#section-7.7
403    pub fn t2(&self) -> TimeValue {
404        TimeValue::new(self.header.t2.get())
405    }
406
407    /// Returns an iterator over the options.
408    pub fn iter_options(&'a self) -> impl 'a + Iterator<Item = ParsedDhcpOption<'a>> {
409        self.options.iter()
410    }
411}
412
413/// An overlay for the fixed fields of an IA_NA option.
414#[derive(
415    KnownLayout, FromBytes, IntoBytes, Immutable, Unaligned, Debug, PartialEq, Copy, Clone,
416)]
417#[repr(C)]
418struct IanaHeader {
419    iaid: U32,
420    t1: U32,
421    t2: U32,
422}
423
424/// An overlay representation of an IA Address option.
425#[derive(Debug, PartialEq)]
426pub struct IaAddrData<B: SplitByteSlice> {
427    header: Ref<B, IaAddrHeader>,
428    options: Records<B, ParsedDhcpOptionImpl>,
429}
430
431impl<'a, B: SplitByteSlice> IaAddrData<B> {
432    /// Constructs a new `IaAddrData` from a `ByteSlice`.
433    fn new(buf: B, context: OptionParseContext) -> Result<Self, ParseError> {
434        let buf_len = buf.len();
435        let (header, options) =
436            Ref::from_prefix(buf).map_err(Into::into).map_err(|_: zerocopy::SizeError<_, _>| {
437                ParseError::InvalidOpLen(OptionCode::IaAddr, buf_len)
438            })?;
439        let options = Records::<B, ParsedDhcpOptionImpl>::parse_with_context(options, context)?;
440        Ok(IaAddrData { header, options })
441    }
442
443    /// Returns the address.
444    pub fn addr(&self) -> Ipv6Addr {
445        self.header.addr
446    }
447
448    /// Returns the preferred lifetime as `TimeValue` to relay the fact that
449    /// certain values have special significance as described in
450    /// [RFC 8415, section 7.7].
451    ///
452    /// [section 14.2]: https://datatracker.ietf.org/doc/html/rfc8415#section-14.2
453    /// [section 7.7]: https://datatracker.ietf.org/doc/html/rfc8415#section-7.7
454    pub fn preferred_lifetime(&self) -> TimeValue {
455        TimeValue::new(self.header.preferred_lifetime.get())
456    }
457
458    /// Returns the valid lifetime as `TimeValue` to relay the fact that certain
459    /// values have special significance as described in
460    /// [RFC 8415, section 7.7].
461    ///
462    /// [section 14.2]: https://datatracker.ietf.org/doc/html/rfc8415#section-14.2
463    /// [section 7.7]: https://datatracker.ietf.org/doc/html/rfc8415#section-7.7
464    pub fn valid_lifetime(&self) -> TimeValue {
465        TimeValue::new(self.header.valid_lifetime.get())
466    }
467
468    /// Returns an iterator over the options.
469    pub fn iter_options(&'a self) -> impl 'a + Iterator<Item = ParsedDhcpOption<'a>> {
470        self.options.iter()
471    }
472}
473
474/// An overlay for the fixed fields of an IA Address option.
475#[derive(
476    KnownLayout, FromBytes, IntoBytes, Immutable, Unaligned, Debug, PartialEq, Copy, Clone,
477)]
478#[repr(C)]
479struct IaAddrHeader {
480    addr: Ipv6Addr,
481    preferred_lifetime: U32,
482    valid_lifetime: U32,
483}
484
485/// An overlay for the fixed fields of an IA_PD option.
486#[derive(
487    KnownLayout, FromBytes, IntoBytes, Immutable, Unaligned, Debug, PartialEq, Copy, Clone,
488)]
489#[repr(C)]
490struct IaPdHeader {
491    iaid: U32,
492    t1: U32,
493    t2: U32,
494}
495
496/// An overlay representation of an IA_PD option as per [RFC 8415 section 21.21].
497///
498/// [RFC 8415 section 21.21]: https://datatracker.ietf.org/doc/html/rfc8415#section-21.21
499#[derive(Debug, PartialEq)]
500pub struct IaPdData<B: SplitByteSlice> {
501    header: Ref<B, IaPdHeader>,
502    options: Records<B, ParsedDhcpOptionImpl>,
503}
504
505impl<'a, B: SplitByteSlice> IaPdData<B> {
506    /// Constructs a new `IaPdData` from a `ByteSlice`.
507    fn new(buf: B, context: OptionParseContext) -> Result<Self, ParseError> {
508        let buf_len = buf.len();
509        let (header, options) =
510            Ref::from_prefix(buf).map_err(Into::into).map_err(|_: zerocopy::SizeError<_, _>| {
511                ParseError::InvalidOpLen(OptionCode::IaPd, buf_len)
512            })?;
513        let options = Records::<B, ParsedDhcpOptionImpl>::parse_with_context(options, context)?;
514        Ok(IaPdData { header, options })
515    }
516
517    /// Returns the IAID.
518    pub fn iaid(&self) -> u32 {
519        self.header.iaid.get()
520    }
521
522    /// Returns the T1 as `TimeValue` to relay the fact that certain values have
523    /// special significance as described in RFC 8415, [section 14.2] and
524    /// [section 7.7].
525    ///
526    /// [section 14.2]: https://datatracker.ietf.org/doc/html/rfc8415#section-14.2
527    /// [section 7.7]: https://datatracker.ietf.org/doc/html/rfc8415#section-7.7
528    pub fn t1(&self) -> TimeValue {
529        TimeValue::new(self.header.t1.get())
530    }
531
532    /// Returns the T2 as `TimeValue` to relay the fact that certain values have
533    /// special significance as described in RFC 8415, [section 14.2] and
534    /// [section 7.7].
535    ///
536    /// [section 14.2]: https://datatracker.ietf.org/doc/html/rfc8415#section-14.2
537    /// [section 7.7]: https://datatracker.ietf.org/doc/html/rfc8415#section-7.7
538    pub fn t2(&self) -> TimeValue {
539        TimeValue::new(self.header.t2.get())
540    }
541
542    /// Returns an iterator over the options.
543    pub fn iter_options(&'a self) -> impl 'a + Iterator<Item = ParsedDhcpOption<'a>> {
544        self.options.iter()
545    }
546}
547
548/// An overlay for the fixed fields of an IA Prefix option.
549#[derive(
550    KnownLayout, FromBytes, IntoBytes, Immutable, Unaligned, Debug, PartialEq, Copy, Clone,
551)]
552#[repr(C)]
553struct IaPrefixHeader {
554    preferred_lifetime_secs: U32,
555    valid_lifetime_secs: U32,
556    prefix_length: u8,
557    prefix: Ipv6Addr,
558}
559
560/// An overlay representation of an IA Address option, as per RFC 8415 section 21.22.
561///
562/// [RFC 8415 section 21.22]: https://datatracker.ietf.org/doc/html/rfc8415#section-21.22
563#[derive(Debug, PartialEq)]
564pub struct IaPrefixData<B: SplitByteSlice> {
565    header: Ref<B, IaPrefixHeader>,
566    options: Records<B, ParsedDhcpOptionImpl>,
567}
568
569impl<'a, B: SplitByteSlice> IaPrefixData<B> {
570    /// Constructs a new `IaPrefixData` from a `ByteSlice`.
571    fn new(buf: B, context: OptionParseContext) -> Result<Self, ParseError> {
572        let buf_len = buf.len();
573        let (header, options) =
574            Ref::from_prefix(buf).map_err(Into::into).map_err(|_: zerocopy::SizeError<_, _>| {
575                ParseError::InvalidOpLen(OptionCode::IaPrefix, buf_len)
576            })?;
577        let options = Records::<B, ParsedDhcpOptionImpl>::parse_with_context(options, context)?;
578        Ok(IaPrefixData { header, options })
579    }
580
581    /// Returns the prefix.
582    pub fn prefix(&self) -> Result<Subnet<Ipv6Addr>, PrefixTooLongError> {
583        Subnet::from_host(self.header.prefix, self.header.prefix_length)
584    }
585
586    /// Returns the preferred lifetime as `TimeValue` to relay the fact that
587    /// certain values have special significance as described in
588    /// [RFC 8415, section 7.7].
589    ///
590    /// [section 14.2]: https://datatracker.ietf.org/doc/html/rfc8415#section-14.2
591    /// [section 7.7]: https://datatracker.ietf.org/doc/html/rfc8415#section-7.7
592    pub fn preferred_lifetime(&self) -> TimeValue {
593        TimeValue::new(self.header.preferred_lifetime_secs.get())
594    }
595
596    /// Returns the valid lifetime as `TimeValue` to relay the fact that certain
597    /// values have special significance as described in
598    /// [RFC 8415, section 7.7].
599    ///
600    /// [section 14.2]: https://datatracker.ietf.org/doc/html/rfc8415#section-14.2
601    /// [section 7.7]: https://datatracker.ietf.org/doc/html/rfc8415#section-7.7
602    pub fn valid_lifetime(&self) -> TimeValue {
603        TimeValue::new(self.header.valid_lifetime_secs.get())
604    }
605
606    /// Returns an iterator over the options.
607    pub fn iter_options(&'a self) -> impl 'a + Iterator<Item = ParsedDhcpOption<'a>> {
608        self.options.iter()
609    }
610}
611
612mod checked {
613    use std::str::FromStr;
614
615    use mdns::protocol::{DomainBuilder, EmbeddedPacketBuilder};
616    use packet::BufferViewMut;
617    use zerocopy::SplitByteSliceMut;
618
619    use super::ParseError;
620
621    /// A checked domain that can only be created through the provided constructor.
622    #[derive(Debug, PartialEq)]
623    pub struct Domain {
624        domain: String,
625        builder: DomainBuilder,
626    }
627
628    impl FromStr for Domain {
629        type Err = ParseError;
630
631        /// Constructs a `Domain` from a string.
632        ///
633        /// See `<Domain as TryFrom<String>>::try_from`.
634        fn from_str(s: &str) -> Result<Self, ParseError> {
635            Self::try_from(s.to_string())
636        }
637    }
638
639    impl TryFrom<String> for Domain {
640        type Error = ParseError;
641
642        /// Constructs a `Domain` from a string.
643        ///
644        /// # Errors
645        ///
646        /// If the string is not a valid domain following the definition in [RFC 1035].
647        ///
648        /// [RFC 1035]: https://tools.ietf.org/html/rfc1035
649        fn try_from(domain: String) -> Result<Self, ParseError> {
650            let builder = DomainBuilder::from_str(&domain).map_err(ParseError::DomainParseError)?;
651            Ok(Domain { domain, builder })
652        }
653    }
654
655    impl Domain {
656        pub(crate) fn bytes_len(&self) -> usize {
657            self.builder.bytes_len()
658        }
659
660        pub(crate) fn serialize<B: SplitByteSliceMut, BV: BufferViewMut<B>>(&self, bv: &mut BV) {
661            let () = self.builder.serialize(bv);
662        }
663    }
664}
665
666macro_rules! option_to_code {
667    ($option:ident, $($option_name:ident::$variant:tt($($v:tt)*)),*) => {
668        match $option {
669            $($option_name::$variant($($v)*)=>OptionCode::$variant,)*
670        }
671    }
672}
673
674impl ParsedDhcpOption<'_> {
675    /// Returns the corresponding option code for the calling option.
676    pub fn code(&self) -> OptionCode {
677        option_to_code!(
678            self,
679            ParsedDhcpOption::ClientId(_),
680            ParsedDhcpOption::ServerId(_),
681            ParsedDhcpOption::Iana(_),
682            ParsedDhcpOption::IaAddr(_),
683            ParsedDhcpOption::Oro(_),
684            ParsedDhcpOption::Preference(_),
685            ParsedDhcpOption::ElapsedTime(_),
686            ParsedDhcpOption::StatusCode(_, _),
687            ParsedDhcpOption::IaPd(_),
688            ParsedDhcpOption::IaPrefix(_),
689            ParsedDhcpOption::InformationRefreshTime(_),
690            ParsedDhcpOption::SolMaxRt(_),
691            ParsedDhcpOption::DnsServers(_),
692            ParsedDhcpOption::DomainList(_)
693        )
694    }
695}
696
697/// An ID that uniquely identifies a DHCPv6 client or server, defined in [RFC8415, Section 11].
698///
699/// [RFC8415, Section 11]: https://tools.ietf.org/html/rfc8415#section-11
700type Duid = [u8];
701
702/// An implementation of `RecordsImpl` for `ParsedDhcpOption`.
703///
704/// Options in DHCPv6 messages are sequential, so they are parsed through the
705/// APIs provided in [packet::records].
706///
707/// [packet::records]: https://fuchsia-docs.firebaseapp.com/rust/packet/records/index.html
708#[derive(Debug, PartialEq)]
709enum ParsedDhcpOptionImpl {}
710
711impl RecordsImplLayout for ParsedDhcpOptionImpl {
712    type Context = OptionParseContext;
713
714    type Error = ParseError;
715}
716
717impl RecordsImpl for ParsedDhcpOptionImpl {
718    type Record<'a> = ParsedDhcpOption<'a>;
719
720    /// Tries to parse an option from the beginning of the input buffer. Returns the parsed
721    /// `ParsedDhcpOption` and the remaining buffer. If the buffer is malformed, returns a
722    /// `ParseError`. Option format as defined in [RFC 8415, Section 21.1]:
723    ///
724    /// [RFC 8415, Section 21.1]: https://tools.ietf.org/html/rfc8415#section-21.1
725    fn parse_with_context<'a, BV: BufferView<&'a [u8]>>(
726        data: &mut BV,
727        context: &mut Self::Context,
728    ) -> RecordParseResult<Self::Record<'a>, Self::Error> {
729        let context = OptionParseContext { depth: context.depth + 1 };
730        if context.depth > MAX_RECURSION_DEPTH {
731            return Err(ParseError::OptionRecursionLimitExceeded);
732        }
733
734        if data.len() == 0 {
735            return Ok(ParsedRecord::Done);
736        }
737
738        let opt_code = data.take_obj_front::<U16>().ok_or(ParseError::BufferExhausted)?;
739        let opt_len = data.take_obj_front::<U16>().ok_or(ParseError::BufferExhausted)?;
740        let opt_len = usize::from(opt_len.get());
741        let mut opt_val = data.take_front(opt_len).ok_or(ParseError::BufferExhausted)?;
742
743        let opt_code = match OptionCode::try_from(opt_code.get()) {
744            Ok(opt_code) => opt_code,
745            // TODO(https://fxbug.dev/42134994): surface skipped codes so we know which ones are not
746            // supported.
747            Err(ParseError::InvalidOpCode(_)) => {
748                // Skip unknown option codes to keep useful information.
749                //
750                // https://tools.ietf.org/html/rfc8415#section-16
751                return Ok(ParsedRecord::Skipped);
752            }
753            Err(e) => unreachable!("unexpected error from op code conversion: {}", e),
754        };
755
756        let opt = match opt_code {
757            OptionCode::ClientId => Ok(ParsedDhcpOption::ClientId(opt_val)),
758            OptionCode::ServerId => Ok(ParsedDhcpOption::ServerId(opt_val)),
759            OptionCode::Iana => IanaData::new(opt_val, context).map(ParsedDhcpOption::Iana),
760            OptionCode::IaAddr => IaAddrData::new(opt_val, context).map(ParsedDhcpOption::IaAddr),
761            OptionCode::Oro => {
762                let options = opt_val
763                    // TODO(https://github.com/rust-lang/rust/issues/74985): use slice::as_chunks.
764                    .chunks(2)
765                    .map(|opt| {
766                        let opt: [u8; 2] = opt.try_into().map_err(
767                            |std::array::TryFromSliceError { .. }| {
768                                ParseError::InvalidOpLen(OptionCode::Oro, opt_val.len())
769                            },
770                        )?;
771                        OptionCode::try_from(u16::from_be_bytes(opt))
772                    })
773                    .collect::<Result<_, ParseError>>()?;
774                Ok(ParsedDhcpOption::Oro(options))
775            }
776            OptionCode::Preference => match opt_val {
777                &[b] => Ok(ParsedDhcpOption::Preference(b)),
778                opt_val => Err(ParseError::InvalidOpLen(OptionCode::Preference, opt_val.len())),
779            },
780            OptionCode::ElapsedTime => match opt_val {
781                &[b0, b1] => Ok(ParsedDhcpOption::ElapsedTime(u16::from_be_bytes([b0, b1]))),
782                opt_val => Err(ParseError::InvalidOpLen(OptionCode::ElapsedTime, opt_val.len())),
783            },
784            OptionCode::StatusCode => {
785                let mut opt_val = &mut opt_val;
786                let code = (&mut opt_val).take_obj_front::<U16>().ok_or_else(|| {
787                    ParseError::InvalidOpLen(OptionCode::StatusCode, opt_val.len())
788                })?;
789                let message = str::from_utf8(opt_val)?;
790                Ok(ParsedDhcpOption::StatusCode(*code, message))
791            }
792            OptionCode::IaPd => IaPdData::new(opt_val, context).map(ParsedDhcpOption::IaPd),
793            OptionCode::IaPrefix => {
794                IaPrefixData::new(opt_val, context).map(ParsedDhcpOption::IaPrefix)
795            }
796            OptionCode::InformationRefreshTime => match opt_val {
797                &[b0, b1, b2, b3] => {
798                    Ok(ParsedDhcpOption::InformationRefreshTime(u32::from_be_bytes([
799                        b0, b1, b2, b3,
800                    ])))
801                }
802                opt_val => {
803                    Err(ParseError::InvalidOpLen(OptionCode::InformationRefreshTime, opt_val.len()))
804                }
805            },
806            OptionCode::SolMaxRt => {
807                let mut opt_val = &mut opt_val;
808                let sol_max_rt = (&mut opt_val)
809                    .take_obj_front::<U32>()
810                    .ok_or_else(|| ParseError::InvalidOpLen(OptionCode::SolMaxRt, opt_val.len()))?;
811                Ok(ParsedDhcpOption::SolMaxRt(*sol_max_rt))
812            }
813            OptionCode::DnsServers => {
814                let addresses = opt_val
815                    // TODO(https://github.com/rust-lang/rust/issues/74985): use slice::as_chunks.
816                    .chunks(16)
817                    .map(|opt| {
818                        let opt: [u8; 16] = opt.try_into().map_err(
819                            |std::array::TryFromSliceError { .. }| {
820                                ParseError::InvalidOpLen(OptionCode::DnsServers, opt_val.len())
821                            },
822                        )?;
823                        Ok(Ipv6Addr::from(opt))
824                    })
825                    .collect::<Result<_, ParseError>>()?;
826                Ok(ParsedDhcpOption::DnsServers(addresses))
827            }
828            OptionCode::DomainList => {
829                let mut opt_val = &mut opt_val;
830                let mut domains = Vec::new();
831                while opt_val.len() > 0 {
832                    domains.push(checked::Domain::try_from(
833                        Domain::parse(
834                            &mut opt_val,
835                            // Per RFC 8415 Section 10:
836                            //   A domain name, or list of domain names, in DHCP
837                            //   MUST NOT be stored in compressed form
838                            //
839                            // Pass None to indicate this.
840                            None,
841                        )
842                        .map_err(ParseError::DomainParseError)?
843                        .to_string(),
844                    )?);
845                }
846                Ok(ParsedDhcpOption::DomainList(domains))
847            }
848        }?;
849
850        Ok(ParsedRecord::Parsed(opt))
851    }
852}
853
854/// Generates a random DUID UUID based on the format defined in [RFC 8415, Section 11.5].
855///
856/// [RFC 8415, Section 11.5]: https://tools.ietf.org/html/rfc8415#section-11.5
857pub fn duid_uuid() -> [u8; 18] {
858    let mut duid = [0u8; 18];
859    duid[1] = 4;
860    let uuid = Uuid::new_v4();
861    let uuid = uuid.as_bytes();
862    duid[2..].copy_from_slice(&uuid[..]);
863    duid
864}
865
866/// A serializable DHCPv6 option.
867///
868/// Options that are not found in this type are currently not supported. An exhaustive list of
869/// options can be found [here][options].
870///
871/// [options]: https://www.iana.org/assignments/dhcpv6-parameters/dhcpv6-parameters.xhtml#dhcpv6-parameters-2
872// TODO(https://fxbug.dev/42155425): replace `DhcpOption` and `ParsedDhcpOption` with a single type.
873#[allow(missing_docs)]
874#[derive(Debug)]
875pub enum DhcpOption<'a> {
876    // https://tools.ietf.org/html/rfc8415#section-21.2
877    ClientId(&'a Duid),
878    // https://tools.ietf.org/html/rfc8415#section-21.3
879    ServerId(&'a Duid),
880    // https://tools.ietf.org/html/rfc8415#section-21.4
881    // TODO(https://fxbug.dev/42154013): add validation; not all option codes can
882    // be present in an IA_NA option.
883    Iana(IanaSerializer<'a>),
884    // https://tools.ietf.org/html/rfc8415#section-21.6
885    // TODO(https://fxbug.dev/42154013): add validation; not all option codes can
886    // be present in an IA Address option.
887    IaAddr(IaAddrSerializer<'a>),
888    // https://tools.ietf.org/html/rfc8415#section-21.7
889    // TODO(https://fxbug.dev/42154013): add validation; not all option codes can
890    // be present in an ORO option.
891    // See https://www.iana.org/assignments/dhcpv6-parameters/dhcpv6-parameters.xhtml#dhcpv6-parameters-2
892    Oro(&'a [OptionCode]),
893    // https://tools.ietf.org/html/rfc8415#section-21.8
894    Preference(u8),
895    // https://tools.ietf.org/html/rfc8415#section-21.9
896    ElapsedTime(u16),
897    // https://tools.ietf.org/html/rfc8415#section-21.13
898    StatusCode(u16, &'a str),
899    // https://tools.ietf.org/html/rfc8415#section-21.21
900    IaPd(IaPdSerializer<'a>),
901    // https://tools.ietf.org/html/rfc8415#section-21.22
902    IaPrefix(IaPrefixSerializer<'a>),
903    // https://tools.ietf.org/html/rfc8415#section-21.23
904    InformationRefreshTime(u32),
905    // https://tools.ietf.org/html/rfc8415#section-21.24
906    SolMaxRt(u32),
907    // https://tools.ietf.org/html/rfc3646#section-3
908    DnsServers(&'a [Ipv6Addr]),
909    // https://tools.ietf.org/html/rfc3646#section-4
910    DomainList(&'a [checked::Domain]),
911}
912
913/// Identity Association identifier, as defined in [RFC 8415, Section 4.2].
914///
915/// [RFC 8415, Section 4.2]: https://datatracker.ietf.org/doc/html/rfc8415#section-4.2
916#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq)]
917pub struct IAID(u32);
918
919impl IAID {
920    /// Constructs an `IAID`.
921    pub const fn new(iaid: u32) -> Self {
922        Self(iaid)
923    }
924
925    /// Returns the `u32` value inside `self`.
926    pub fn get(&self) -> u32 {
927        let IAID(iaid) = self;
928        *iaid
929    }
930}
931
932/// A serializer for the IA_NA DHCPv6 option.
933#[derive(Debug)]
934pub struct IanaSerializer<'a> {
935    header: IanaHeader,
936    options: RecordSequenceBuilder<DhcpOption<'a>, Iter<'a, DhcpOption<'a>>>,
937}
938
939impl<'a> IanaSerializer<'a> {
940    /// Constructs a new `IanaSerializer`.
941    pub fn new(iaid: IAID, t1: u32, t2: u32, options: &'a [DhcpOption<'a>]) -> IanaSerializer<'a> {
942        IanaSerializer {
943            header: IanaHeader { iaid: U32::new(iaid.get()), t1: U32::new(t1), t2: U32::new(t2) },
944            options: RecordSequenceBuilder::new(options.iter()),
945        }
946    }
947}
948
949/// A serializer for the IA Address DHCPv6 option.
950#[derive(Debug)]
951pub struct IaAddrSerializer<'a> {
952    header: IaAddrHeader,
953    options: RecordSequenceBuilder<DhcpOption<'a>, Iter<'a, DhcpOption<'a>>>,
954}
955
956impl<'a> IaAddrSerializer<'a> {
957    /// Constructs a new `IaAddrSerializer`.
958    pub fn new(
959        addr: Ipv6Addr,
960        preferred_lifetime: u32,
961        valid_lifetime: u32,
962        options: &'a [DhcpOption<'a>],
963    ) -> IaAddrSerializer<'a> {
964        IaAddrSerializer {
965            header: IaAddrHeader {
966                addr,
967                preferred_lifetime: U32::new(preferred_lifetime),
968                valid_lifetime: U32::new(valid_lifetime),
969            },
970            options: RecordSequenceBuilder::new(options.iter()),
971        }
972    }
973}
974
975/// A serializer for the IA_PD DHCPv6 option.
976#[derive(Debug)]
977pub struct IaPdSerializer<'a> {
978    header: IaPdHeader,
979    options: RecordSequenceBuilder<DhcpOption<'a>, Iter<'a, DhcpOption<'a>>>,
980}
981
982impl<'a> IaPdSerializer<'a> {
983    /// Constructs a new `IaPdSerializer`.
984    pub fn new(iaid: IAID, t1: u32, t2: u32, options: &'a [DhcpOption<'a>]) -> IaPdSerializer<'a> {
985        IaPdSerializer {
986            header: IaPdHeader { iaid: U32::new(iaid.get()), t1: U32::new(t1), t2: U32::new(t2) },
987            options: RecordSequenceBuilder::new(options.iter()),
988        }
989    }
990}
991
992/// A serializer for the IA Prefix DHCPv6 option.
993#[derive(Debug)]
994pub struct IaPrefixSerializer<'a> {
995    header: IaPrefixHeader,
996    options: RecordSequenceBuilder<DhcpOption<'a>, Iter<'a, DhcpOption<'a>>>,
997}
998
999impl<'a> IaPrefixSerializer<'a> {
1000    /// Constructs a new `IaPrefixSerializer`.
1001    pub fn new(
1002        preferred_lifetime_secs: u32,
1003        valid_lifetime_secs: u32,
1004        prefix: Subnet<Ipv6Addr>,
1005        options: &'a [DhcpOption<'a>],
1006    ) -> IaPrefixSerializer<'a> {
1007        IaPrefixSerializer {
1008            header: IaPrefixHeader {
1009                preferred_lifetime_secs: U32::new(preferred_lifetime_secs),
1010                valid_lifetime_secs: U32::new(valid_lifetime_secs),
1011                prefix_length: prefix.prefix(),
1012                prefix: prefix.network(),
1013            },
1014            options: RecordSequenceBuilder::new(options.iter()),
1015        }
1016    }
1017}
1018
1019impl DhcpOption<'_> {
1020    /// Returns the corresponding option code for the calling option.
1021    pub fn code(&self) -> OptionCode {
1022        option_to_code!(
1023            self,
1024            DhcpOption::ClientId(_),
1025            DhcpOption::ServerId(_),
1026            DhcpOption::Iana(_),
1027            DhcpOption::IaAddr(_),
1028            DhcpOption::Oro(_),
1029            DhcpOption::Preference(_),
1030            DhcpOption::ElapsedTime(_),
1031            DhcpOption::StatusCode(_, _),
1032            DhcpOption::IaPd(_),
1033            DhcpOption::IaPrefix(_),
1034            DhcpOption::InformationRefreshTime(_),
1035            DhcpOption::SolMaxRt(_),
1036            DhcpOption::DnsServers(_),
1037            DhcpOption::DomainList(_)
1038        )
1039    }
1040}
1041
1042impl<'a> RecordBuilder for DhcpOption<'a> {
1043    /// Calculates the serialized length of the option based on option format
1044    /// defined in [RFC 8415, Section 21.1].
1045    ///
1046    /// For variable length options that exceeds the size limit (`u16::MAX`),
1047    /// fallback to a default value.
1048    ///
1049    /// [RFC 8415, Section 21.1]: https://tools.ietf.org/html/rfc8415#section-21.1
1050    fn serialized_len(&self) -> usize {
1051        4 + match self {
1052            DhcpOption::ClientId(duid) | DhcpOption::ServerId(duid) => {
1053                u16::try_from(duid.len()).unwrap_or(18).into()
1054            }
1055            DhcpOption::Iana(IanaSerializer { header, options }) => {
1056                u16::try_from(header.as_bytes().len() + options.serialized_len())
1057                    .expect("overflows")
1058                    .into()
1059            }
1060            DhcpOption::IaAddr(IaAddrSerializer { header, options }) => {
1061                u16::try_from(header.as_bytes().len() + options.serialized_len())
1062                    .expect("overflows")
1063                    .into()
1064            }
1065            DhcpOption::Oro(opts) => u16::try_from(2 * opts.len()).unwrap_or(0).into(),
1066            DhcpOption::Preference(v) => std::mem::size_of_val(v),
1067            DhcpOption::ElapsedTime(v) => std::mem::size_of_val(v),
1068            DhcpOption::StatusCode(v, message) => std::mem::size_of_val(v) + message.len(),
1069            DhcpOption::IaPd(IaPdSerializer { header, options }) => {
1070                u16::try_from(header.as_bytes().len() + options.serialized_len())
1071                    .expect("overflows")
1072                    .into()
1073            }
1074            DhcpOption::IaPrefix(IaPrefixSerializer { header, options }) => {
1075                u16::try_from(header.as_bytes().len() + options.serialized_len())
1076                    .expect("overflows")
1077                    .into()
1078            }
1079            DhcpOption::InformationRefreshTime(v) => std::mem::size_of_val(v),
1080            DhcpOption::SolMaxRt(v) => std::mem::size_of_val(v),
1081            DhcpOption::DnsServers(recursive_name_servers) => {
1082                u16::try_from(16 * recursive_name_servers.len()).unwrap_or(0).into()
1083            }
1084            DhcpOption::DomainList(domains) => {
1085                u16::try_from(domains.iter().fold(0, |tot, domain| tot + domain.bytes_len()))
1086                    .unwrap_or(0)
1087                    .into()
1088            }
1089        }
1090    }
1091
1092    /// Serializes an option and appends to input buffer based on option format
1093    /// defined in [RFC 8415, Section 21.1].
1094    ///
1095    /// For variable length options that exceeds the size limit (`u16::MAX`),
1096    /// fallback to use a default value instead, so it is impossible for future
1097    /// changes to introduce DoS vulnerabilities even if they accidentally allow
1098    /// such options to be injected.
1099    ///
1100    /// # Panics
1101    ///
1102    /// `serialize_into` panics if `buf` is too small to hold the serialized
1103    /// form of `self`.
1104    ///
1105    /// [RFC 8415, Section 21.1]: https://tools.ietf.org/html/rfc8415#section-21.1
1106    fn serialize_into(&self, mut buf: &mut [u8]) {
1107        // Implements BufferViewMut, giving us write_obj_front.
1108        let mut buf = &mut buf;
1109        let () = buf.write_obj_front(&U16::new(self.code().into())).expect("buffer is too small");
1110
1111        match self {
1112            DhcpOption::ClientId(duid) | DhcpOption::ServerId(duid) => {
1113                match u16::try_from(duid.len()) {
1114                    Ok(len) => {
1115                        let () = buf.write_obj_front(&U16::new(len)).expect("buffer is too small");
1116                        let () = buf.write_obj_front(*duid).expect("buffer is too small");
1117                    }
1118                    Err(std::num::TryFromIntError { .. }) => {
1119                        // Do not panic, so DUIDs with length exceeding u16 won't introduce DoS
1120                        // vulnerability.
1121                        let duid = duid_uuid();
1122                        let len = u16::try_from(duid.len()).expect("uuid length is too long");
1123                        let () = buf.write_obj_front(&U16::new(len)).expect("buffer is too small");
1124                        let () = buf.write_obj_front(&duid).expect("buffer is too small");
1125                    }
1126                }
1127            }
1128            DhcpOption::Iana(IanaSerializer { header, options }) => {
1129                let len = u16::try_from(header.as_bytes().len() + options.serialized_len())
1130                    .expect("overflows");
1131                let () = buf.write_obj_front(&U16::new(len)).expect("buffer is too small");
1132                let () = buf.write_obj_front(header).expect("buffer is too small");
1133                let () = options.serialize_into(buf);
1134            }
1135            DhcpOption::IaAddr(IaAddrSerializer { header, options }) => {
1136                let len = u16::try_from(header.as_bytes().len() + options.serialized_len())
1137                    .expect("overflows");
1138                let () = buf.write_obj_front(&U16::new(len)).expect("buffer is too small");
1139                let () = buf.write_obj_front(header).expect("buffer is too small");
1140                let () = options.serialize_into(buf);
1141            }
1142            DhcpOption::Oro(requested_opts) => {
1143                let (requested_opts, len) = u16::try_from(2 * requested_opts.len()).map_or_else(
1144                    |std::num::TryFromIntError { .. }| {
1145                        // Do not panic, so OROs with size exceeding u16 won't introduce DoS
1146                        // vulnerability.
1147                        (&[][..], 0)
1148                    },
1149                    |len| (*requested_opts, len),
1150                );
1151                let () = buf.write_obj_front(&U16::new(len)).expect("buffer is too small");
1152                for opt_code in requested_opts.iter() {
1153                    let () = buf
1154                        .write_obj_front(&u16::from(*opt_code).to_be_bytes())
1155                        .expect("buffer is too small");
1156                }
1157            }
1158            DhcpOption::Preference(pref_val) => {
1159                let () = buf.write_obj_front(&U16::new(1)).expect("buffer is too small");
1160                let () = buf.write_obj_front(pref_val).expect("buffer is too small");
1161            }
1162            DhcpOption::ElapsedTime(elapsed_time) => {
1163                let () = buf
1164                    .write_obj_front(&U16::new(
1165                        mem::size_of_val(elapsed_time).try_into().expect("overflows"),
1166                    ))
1167                    .expect("buffer is too small");
1168                let () =
1169                    buf.write_obj_front(&U16::new(*elapsed_time)).expect("buffer is too small");
1170            }
1171            DhcpOption::StatusCode(code, message) => {
1172                let opt_len = u16::try_from(2 + message.len()).expect("overflows");
1173                let () = buf.write_obj_front(&U16::new(opt_len)).expect("buffer is too small");
1174                let () = buf.write_obj_front(&U16::new(*code)).expect("buffer is too small");
1175                let () = buf.write_obj_front(message.as_bytes()).expect("buffer is too small");
1176            }
1177            DhcpOption::IaPd(IaPdSerializer { header, options }) => {
1178                let len = u16::try_from(header.as_bytes().len() + options.serialized_len())
1179                    .expect("overflows");
1180                let () = buf.write_obj_front(&U16::new(len)).expect("buffer is too small");
1181                buf.write_obj_front(header).expect("buffer is too small");
1182                let () = options.serialize_into(buf);
1183            }
1184            DhcpOption::IaPrefix(IaPrefixSerializer { header, options }) => {
1185                let len = u16::try_from(header.as_bytes().len() + options.serialized_len())
1186                    .expect("overflows");
1187                let () = buf.write_obj_front(&U16::new(len)).expect("buffer is too small");
1188                buf.write_obj_front(header).expect("buffer is too small");
1189                let () = options.serialize_into(buf);
1190            }
1191            DhcpOption::InformationRefreshTime(information_refresh_time) => {
1192                let () = buf
1193                    .write_obj_front(&U16::new(
1194                        mem::size_of_val(information_refresh_time).try_into().expect("overflows"),
1195                    ))
1196                    .expect("buffer is too small");
1197                let () = buf
1198                    .write_obj_front(&U32::new(*information_refresh_time))
1199                    .expect("buffer is too small");
1200            }
1201            DhcpOption::SolMaxRt(sol_max_rt) => {
1202                let () = buf
1203                    .write_obj_front(&U16::new(
1204                        mem::size_of_val(sol_max_rt).try_into().expect("overflows"),
1205                    ))
1206                    .expect("buffer is too small");
1207                let () = buf.write_obj_front(&U32::new(*sol_max_rt)).expect("buffer is too small");
1208            }
1209            DhcpOption::DnsServers(recursive_name_servers) => {
1210                let (recursive_name_servers, len) =
1211                    u16::try_from(16 * recursive_name_servers.len()).map_or_else(
1212                        |std::num::TryFromIntError { .. }| {
1213                            // Do not panic, so DnsServers with size exceeding `u16` won't introduce
1214                            // DoS vulnerability.
1215                            (&[][..], 0)
1216                        },
1217                        |len| (*recursive_name_servers, len),
1218                    );
1219                let () = buf.write_obj_front(&U16::new(len)).expect("buffer is too small");
1220                recursive_name_servers.iter().for_each(|server_addr| {
1221                    let () = buf.write_obj_front(server_addr.bytes()).expect("buffer is too small");
1222                })
1223            }
1224            DhcpOption::DomainList(domains) => {
1225                let (domains, len) =
1226                    u16::try_from(domains.iter().map(|domain| domain.bytes_len()).sum::<usize>())
1227                        .map_or_else(
1228                            |std::num::TryFromIntError { .. }| {
1229                                // Do not panic, so DomainList with size exceeding `u16` won't
1230                                // introduce DoS vulnerability.
1231                                (&[][..], 0)
1232                            },
1233                            |len| (*domains, len),
1234                        );
1235                let () = buf.write_obj_front(&U16::new(len)).expect("buffer is too small");
1236                domains.iter().for_each(|domain| {
1237                    domain.serialize(&mut buf);
1238                })
1239            }
1240        }
1241    }
1242}
1243
1244/// A transaction ID defined in [RFC 8415, Section 8].
1245///
1246/// [RFC 8415, Section 8]: https://tools.ietf.org/html/rfc8415#section-8
1247type TransactionId = [u8; 3];
1248
1249/// A DHCPv6 message as defined in [RFC 8415, Section 8].
1250///
1251/// [RFC 8415, Section 8]: https://tools.ietf.org/html/rfc8415#section-8
1252#[derive(Debug)]
1253pub struct Message<'a, B> {
1254    msg_type: MessageType,
1255    transaction_id: &'a TransactionId,
1256    options: Records<B, ParsedDhcpOptionImpl>,
1257}
1258
1259impl<'a, B: SplitByteSlice> Message<'a, B> {
1260    /// Returns the message type.
1261    pub fn msg_type(&self) -> MessageType {
1262        self.msg_type
1263    }
1264
1265    /// Returns the transaction ID.
1266    pub fn transaction_id(&self) -> &TransactionId {
1267        &self.transaction_id
1268    }
1269
1270    /// Returns an iterator over the options.
1271    pub fn options<'b: 'a>(&'b self) -> impl 'b + Iterator<Item = ParsedDhcpOption<'a>> {
1272        self.options.iter()
1273    }
1274}
1275
1276impl<'a, B: 'a + SplitByteSlice + IntoByteSlice<'a>> ParsablePacket<B, ()> for Message<'a, B> {
1277    type Error = ParseError;
1278
1279    fn parse_metadata(&self) -> ParseMetadata {
1280        let Self { msg_type, transaction_id, options } = self;
1281        ParseMetadata::from_packet(
1282            0,
1283            mem::size_of_val(msg_type) + mem::size_of_val(transaction_id) + options.bytes().len(),
1284            0,
1285        )
1286    }
1287
1288    fn parse<BV: BufferView<B>>(mut buf: BV, _args: ()) -> Result<Self, ParseError> {
1289        let msg_type =
1290            MessageType::try_from(buf.take_byte_front().ok_or(ParseError::BufferExhausted)?)?;
1291        let transaction_id = Ref::into_ref(
1292            buf.take_obj_front::<TransactionId>().ok_or(ParseError::BufferExhausted)?,
1293        );
1294        let options = Records::<_, ParsedDhcpOptionImpl>::parse_with_context(
1295            buf.take_rest_front(),
1296            OptionParseContext { depth: 0 },
1297        )?;
1298        Ok(Message { msg_type, transaction_id, options })
1299    }
1300}
1301
1302/// A `DHCPv6Message` builder.
1303///
1304/// DHCPv6 messages are serialized through [packet::serialize::InnerPacketBuilder] because it won't
1305/// encapsulate any other packets.
1306///
1307/// [packet::serialize::InnerPacketBuilder]: https://fuchsia-docs.firebaseapp.com/rust/packet/serialize/trait.InnerPacketBuilder.html
1308#[derive(Debug)]
1309pub struct MessageBuilder<'a> {
1310    msg_type: MessageType,
1311    transaction_id: TransactionId,
1312    options: RecordSequenceBuilder<DhcpOption<'a>, Iter<'a, DhcpOption<'a>>>,
1313}
1314
1315impl<'a> MessageBuilder<'a> {
1316    /// Constructs a new `MessageBuilder`.
1317    pub fn new(
1318        msg_type: MessageType,
1319        transaction_id: TransactionId,
1320        options: &'a [DhcpOption<'a>],
1321    ) -> MessageBuilder<'a> {
1322        MessageBuilder {
1323            msg_type,
1324            transaction_id,
1325            options: RecordSequenceBuilder::new(options.iter()),
1326        }
1327    }
1328}
1329
1330impl InnerPacketBuilder for MessageBuilder<'_> {
1331    /// Calculates the serialized length of the DHCPv6 message based on format defined in
1332    /// [RFC 8415, Section 8].
1333    ///
1334    /// [RFC 8415, Section 8]: https://tools.ietf.org/html/rfc8415#section-8
1335    fn bytes_len(&self) -> usize {
1336        let Self { msg_type, transaction_id, options } = self;
1337        mem::size_of_val(msg_type) + mem::size_of_val(transaction_id) + options.serialized_len()
1338    }
1339
1340    /// Serializes DHCPv6 message based on format defined in [RFC 8415, Section 8].
1341    ///
1342    /// # Panics
1343    ///
1344    /// If buffer is too small. This means `record_length` is not correctly implemented.
1345    ///
1346    /// [RFC 8415, Section 8]: https://tools.ietf.org/html/rfc8415#section-8
1347    fn serialize(&self, mut buffer: &mut [u8]) {
1348        let Self { msg_type, transaction_id, options } = self;
1349        // Implements BufferViewMut, giving us write_obj_front.
1350        let mut buffer = &mut buffer;
1351        let () = buffer.write_obj_front(msg_type).expect("buffer is too small");
1352        let () = buffer.write_obj_front(transaction_id).expect("buffer is too small");
1353        let () = options.serialize_into(buffer);
1354    }
1355}
1356
1357#[cfg(test)]
1358mod tests {
1359    use super::*;
1360    use assert_matches::assert_matches;
1361    use net_declare::{net_ip_v6, net_subnet_v6};
1362    use std::str::FromStr;
1363    use test_case::test_case;
1364
1365    fn test_buf_with_no_options() -> Vec<u8> {
1366        let builder = MessageBuilder::new(MessageType::Solicit, [1, 2, 3], &[]);
1367        let mut buf = vec![0; builder.bytes_len()];
1368        let () = builder.serialize(&mut buf);
1369        buf
1370    }
1371
1372    #[test]
1373    fn test_message_serialization() {
1374        let iaaddr_options = [DhcpOption::StatusCode(0, "Success.")];
1375        let iana_options = [
1376            DhcpOption::Preference(42),
1377            DhcpOption::IaAddr(IaAddrSerializer::new(
1378                Ipv6Addr::from([0, 1, 2, 3, 4, 5, 6, 107, 108, 109, 110, 111, 212, 213, 214, 215]),
1379                3600,
1380                7200,
1381                &iaaddr_options,
1382            )),
1383        ];
1384        let iaprefix_options = [DhcpOption::StatusCode(0, "Success.")];
1385        let iapd_options = [DhcpOption::IaPrefix(IaPrefixSerializer::new(
1386            9999,
1387            6666,
1388            net_subnet_v6!("abcd:1234::/56"),
1389            &iaprefix_options,
1390        ))];
1391        let dns_servers = [
1392            Ipv6Addr::from([0, 1, 2, 3, 4, 5, 6, 107, 108, 109, 110, 111, 212, 213, 214, 215]),
1393            Ipv6Addr::from([10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25]),
1394        ];
1395        let domains = [
1396            checked::Domain::from_str("fuchsia.dev").expect("failed to construct test domain"),
1397            checked::Domain::from_str("www.google.com").expect("failed to construct test domain"),
1398        ];
1399        let options = [
1400            DhcpOption::ClientId(&[4, 5, 6]),
1401            DhcpOption::ServerId(&[8]),
1402            DhcpOption::Iana(IanaSerializer::new(IAID::new(42), 3000, 6500, &iana_options)),
1403            DhcpOption::Oro(&[OptionCode::ClientId, OptionCode::ServerId]),
1404            DhcpOption::Preference(42),
1405            DhcpOption::ElapsedTime(3600),
1406            DhcpOption::StatusCode(0, "Success."),
1407            DhcpOption::IaPd(IaPdSerializer::new(IAID::new(44), 5000, 7000, &iapd_options)),
1408            DhcpOption::InformationRefreshTime(86400),
1409            DhcpOption::SolMaxRt(86400),
1410            DhcpOption::DnsServers(&dns_servers),
1411            DhcpOption::DomainList(&domains),
1412        ];
1413        let builder = MessageBuilder::new(MessageType::Solicit, [1, 2, 3], &options);
1414        assert_eq!(builder.bytes_len(), 256);
1415        let mut buf = vec![0; builder.bytes_len()];
1416        let () = builder.serialize(&mut buf);
1417
1418        #[rustfmt::skip]
1419        assert_eq!(
1420            buf[..],
1421            [
1422                1, // message type
1423                1, 2, 3, // transaction id
1424                0, 1, 0, 3, 4, 5, 6, // option - client ID
1425                0, 2, 0, 1, 8, // option - server ID
1426                // option - IA_NA
1427                0, 3, 0, 59, 0, 0, 0, 42, 0, 0, 11, 184, 0, 0, 25, 100, 0, 7, 0, 1, 42, 0, 5, 0, 38, 0, 1, 2, 3, 4, 5, 6, 107, 108, 109, 110, 111, 212, 213, 214, 215, 0, 0, 14, 16, 0, 0, 28, 32, 0, 13, 0, 10, 0, 0, 83, 117, 99, 99, 101, 115, 115, 46,
1428                0, 6, 0, 4, 0, 1, 0, 2, // option - ORO
1429                0, 7, 0, 1, 42, // option - preference
1430                0, 8, 0, 2, 14, 16, // option - elapsed time
1431                // option - status code
1432                0, 13, 0, 10, 0, 0, 83, 117, 99, 99, 101, 115, 115, 46,
1433
1434                // option - IA_PD
1435                0, 25, 0, 55,
1436                // IA_PD - IAID
1437                0, 0, 0, 44,
1438                // IA_PD - T1
1439                0, 0, 19, 136,
1440                // IA_PD - T2
1441                0, 0, 27, 88,
1442                // IA_PD - Options
1443                //   IA Prefix
1444                0, 26, 0, 39,
1445                //   IA Prefix - Preferred lifetime
1446                0, 0, 39, 15,
1447                //   IA Prefix - Valid lifetime
1448                0, 0, 26, 10,
1449                //   IA Prefix - Prefix Length
1450                56,
1451                //   IA Prefix - IPv6 Prefix
1452                0xab, 0xcd, 0x12, 0x34, 0x00, 0x00, 0x00, 0x00,
1453                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1454                //   IA Prefix - Options
1455                //     Status Code
1456                0, 13, 0, 10, 0, 0, 83, 117, 99, 99, 101, 115, 115, 46,
1457
1458                0, 32, 0, 4, 0, 1, 81, 128, // option - information refresh time
1459                0, 82, 0, 4, 0, 1, 81, 128, // option - SOL_MAX_RT
1460                // option - Dns servers
1461                0, 23, 0, 32,
1462                0, 1, 2, 3, 4, 5, 6, 107, 108, 109, 110, 111, 212, 213, 214, 215,
1463                10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,
1464                // option - Dns domains
1465                0, 24, 0, 29,
1466                7, 102, 117, 99, 104, 115, 105, 97, 3, 100, 101, 118, 0,
1467                3, 119, 119, 119, 6, 103, 111, 111, 103, 108, 101, 3, 99, 111, 109, 0
1468            ],
1469        );
1470    }
1471
1472    #[test]
1473    fn test_message_serialization_parsing_roundtrip() {
1474        let iaaddr_suboptions = [DhcpOption::StatusCode(0, "Success.")];
1475        let iana_suboptions = [
1476            DhcpOption::Preference(42),
1477            DhcpOption::IaAddr(IaAddrSerializer::new(
1478                Ipv6Addr::from([0, 1, 2, 3, 4, 5, 6, 107, 108, 109, 110, 111, 212, 213, 214, 215]),
1479                7200,
1480                9000,
1481                &iaaddr_suboptions,
1482            )),
1483        ];
1484        let iaprefix_options = [DhcpOption::StatusCode(0, "Success.")];
1485        let iapd_options = [DhcpOption::IaPrefix(IaPrefixSerializer::new(
1486            89658902,
1487            82346231,
1488            net_subnet_v6!("1234:5678:1231::/48"),
1489            &iaprefix_options,
1490        ))];
1491        let dns_servers = [net_ip_v6!("::")];
1492        let domains = [
1493            checked::Domain::from_str("fuchsia.dev").expect("failed to construct test domain"),
1494            checked::Domain::from_str("www.google.com").expect("failed to construct test domain"),
1495        ];
1496        let options = [
1497            DhcpOption::ClientId(&[4, 5, 6]),
1498            DhcpOption::ServerId(&[8]),
1499            DhcpOption::Iana(IanaSerializer::new(IAID::new(1234), 7000, 8800, &iana_suboptions)),
1500            DhcpOption::Oro(&[OptionCode::ClientId, OptionCode::ServerId]),
1501            DhcpOption::Preference(42),
1502            DhcpOption::ElapsedTime(3600),
1503            DhcpOption::StatusCode(0, "Success."),
1504            DhcpOption::IaPd(IaPdSerializer::new(IAID::new(1412), 6513, 9876, &iapd_options)),
1505            DhcpOption::InformationRefreshTime(86400),
1506            DhcpOption::SolMaxRt(86400),
1507            DhcpOption::DnsServers(&dns_servers),
1508            DhcpOption::DomainList(&domains),
1509        ];
1510        let builder = MessageBuilder::new(MessageType::Solicit, [1, 2, 3], &options);
1511        let mut buf = vec![0; builder.bytes_len()];
1512        let () = builder.serialize(&mut buf);
1513
1514        let mut buf = &buf[..];
1515        let msg = Message::parse(&mut buf, ()).expect("parse should succeed");
1516        assert_eq!(msg.msg_type, MessageType::Solicit);
1517        assert_eq!(msg.transaction_id, &[1, 2, 3]);
1518        let got_options: Vec<_> = msg.options.iter().collect();
1519
1520        let iana_buf = [
1521            0, 0, 4, 210, 0, 0, 27, 88, 0, 0, 34, 96, 0, 7, 0, 1, 42, 0, 5, 0, 38, 0, 1, 2, 3, 4,
1522            5, 6, 107, 108, 109, 110, 111, 212, 213, 214, 215, 0, 0, 28, 32, 0, 0, 35, 40, 0, 13,
1523            0, 10, 0, 0, 83, 117, 99, 99, 101, 115, 115, 46,
1524        ];
1525        let iapd_buf = [
1526            // IA_PD - IAID
1527            0, 0, 5, 132, // IA_PD - T1
1528            0, 0, 25, 113, // IA_PD - T2
1529            0, 0, 38, 148, // IA_PD - Options
1530            //   IA Prefix
1531            0, 26, 0, 39, //   IA Prefix - Preferred lifetime
1532            5, 88, 22, 22, //   IA Prefix - Valid lifetime
1533            4, 232, 128, 247, //   IA Prefix - Prefix Length
1534            48,  //   IA Prefix - IPv6 Prefix
1535            0x12, 0x34, 0x56, 0x78, 0x12, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1536            0x00, 0x00, //   IA Prefix - Options
1537            0, 13, 0, 10, 0, 0, 83, 117, 99, 99, 101, 115, 115, 46,
1538        ];
1539        let options = [
1540            ParsedDhcpOption::ClientId(&[4, 5, 6]),
1541            ParsedDhcpOption::ServerId(&[8]),
1542            ParsedDhcpOption::Iana(
1543                IanaData::new(&iana_buf[..], OptionParseContext { depth: 1 })
1544                    .expect("construction failed"),
1545            ),
1546            ParsedDhcpOption::Oro(vec![OptionCode::ClientId, OptionCode::ServerId]),
1547            ParsedDhcpOption::Preference(42),
1548            ParsedDhcpOption::ElapsedTime(3600),
1549            ParsedDhcpOption::StatusCode(U16::new(0), "Success."),
1550            ParsedDhcpOption::IaPd(
1551                IaPdData::new(&iapd_buf[..], OptionParseContext { depth: 1 })
1552                    .expect("IA_PD construction failed"),
1553            ),
1554            ParsedDhcpOption::InformationRefreshTime(86400),
1555            ParsedDhcpOption::SolMaxRt(U32::new(86400)),
1556            ParsedDhcpOption::DnsServers(vec![Ipv6Addr::from([0; 16])]),
1557            ParsedDhcpOption::DomainList(vec![
1558                checked::Domain::from_str("fuchsia.dev").expect("failed to construct test domain"),
1559                checked::Domain::from_str("www.google.com")
1560                    .expect("failed to construct test domain"),
1561            ]),
1562        ];
1563        assert_eq!(got_options, options);
1564    }
1565
1566    // We're forced into an `as` cast because From::from is not a const fn.
1567    const OVERFLOW_LENGTH: usize = u16::MAX as usize + 1;
1568
1569    #[test]
1570    fn test_message_serialization_duid_too_long() {
1571        let options = [DhcpOption::ClientId(&[0u8; OVERFLOW_LENGTH])];
1572        let builder = MessageBuilder::new(MessageType::Solicit, [1, 2, 3], &options);
1573        let mut buf = vec![0; builder.bytes_len()];
1574        let () = builder.serialize(&mut buf);
1575
1576        assert_eq!(buf.len(), 26);
1577        assert_eq!(
1578            buf[..8],
1579            [
1580                1, // message type
1581                1, 2, 3, // transaction id
1582                0, 1, 0, 18, // option - client ID (code and len only)
1583            ],
1584        );
1585
1586        // Make sure the buffer is still parsable.
1587        let mut buf = &buf[..];
1588        let _: Message<'_, _> = Message::parse(&mut buf, ()).expect("parse should succeed");
1589    }
1590
1591    #[test]
1592    fn test_message_serialization_oro_too_long() {
1593        let options = [DhcpOption::Oro(&[OptionCode::Preference; OVERFLOW_LENGTH][..])];
1594        let builder = MessageBuilder::new(MessageType::Solicit, [1, 2, 3], &options);
1595        let mut buf = vec![0; builder.bytes_len()];
1596        let () = builder.serialize(&mut buf);
1597
1598        assert_eq!(
1599            buf[..],
1600            [
1601                1, // message type
1602                1, 2, 3, // transaction id
1603                0, 6, 0, 0, // option - ORO
1604            ],
1605        );
1606
1607        // Make sure the buffer is still parsable.
1608        let mut buf = &buf[..];
1609        let _: Message<'_, _> = Message::parse(&mut buf, ()).expect("parse should succeed");
1610    }
1611
1612    #[test]
1613    fn test_option_serialization_parsing_roundtrip() {
1614        let mut buf = [0u8; 6];
1615        let option = DhcpOption::ElapsedTime(42);
1616
1617        option.serialize_into(&mut buf);
1618        assert_eq!(buf, [0, 8, 0, 2, 0, 42]);
1619
1620        let options = Records::<_, ParsedDhcpOptionImpl>::parse_with_context(
1621            &buf[..],
1622            OptionParseContext { depth: 0 },
1623        )
1624        .expect("parse should succeed");
1625        let options: Vec<ParsedDhcpOption<'_>> = options.iter().collect();
1626        assert_eq!(options[..], [ParsedDhcpOption::ElapsedTime(42)]);
1627    }
1628
1629    #[test]
1630    fn test_buffer_too_short() {
1631        let buf = [];
1632        assert_matches!(Message::parse(&mut &buf[..], ()), Err(ParseError::BufferExhausted));
1633
1634        let buf = [
1635            1, // valid message type
1636            0, // transaction id is too short
1637        ];
1638        assert_matches!(Message::parse(&mut &buf[..], ()), Err(ParseError::BufferExhausted));
1639
1640        let buf = [
1641            1, // valid message type
1642            1, 2, 3, // valid transaction id
1643            0, // option code is too short
1644        ];
1645        assert_matches!(Message::parse(&mut &buf[..], ()), Err(ParseError::BufferExhausted));
1646
1647        let buf = [
1648            1, // valid message type
1649            1, 2, 3, // valida transaction id
1650            0, 1, // valid option code
1651            0, // option length is too short
1652        ];
1653        assert_matches!(Message::parse(&mut &buf[..], ()), Err(ParseError::BufferExhausted));
1654
1655        // option value too short
1656        let buf = [
1657            1, // valid message type
1658            1, 2, 3, // valid transaction id
1659            0, 1, // valid option code
1660            0, 100, // valid option length
1661            1, 2, // option value is too short
1662        ];
1663        assert_matches!(Message::parse(&mut &buf[..], ()), Err(ParseError::BufferExhausted));
1664    }
1665
1666    #[test]
1667    fn test_invalid_message_type() {
1668        let mut buf = test_buf_with_no_options();
1669        // 0 is an invalid message type.
1670        buf[0] = 0;
1671        assert_matches!(Message::parse(&mut &buf[..], ()), Err(ParseError::InvalidMessageType(0)));
1672    }
1673
1674    #[test]
1675    fn test_skip_invalid_op_code() {
1676        let mut buf = test_buf_with_no_options();
1677        buf.extend_from_slice(&[
1678            0, 0, // opt code = 0, invalid op code
1679            0, 1, // valid opt length
1680            0, // valid opt value
1681            0, 1, 0, 3, 4, 5, 6, // option - client ID
1682        ]);
1683        let mut buf = &buf[..];
1684        let msg = Message::parse(&mut buf, ()).expect("parse should succeed");
1685        let got_options: Vec<_> = msg.options.iter().collect();
1686        assert_eq!(got_options, [ParsedDhcpOption::ClientId(&[4, 5, 6])]);
1687    }
1688
1689    #[test]
1690    fn test_iana_no_suboptions_serialization_parsing_roundtrip() {
1691        let mut buf = [0u8; 16];
1692        let option = DhcpOption::Iana(IanaSerializer::new(IAID::new(3456), 1024, 54321, &[]));
1693
1694        option.serialize_into(&mut buf);
1695        assert_eq!(buf, [0, 3, 0, 12, 0, 0, 13, 128, 0, 0, 4, 0, 0, 0, 212, 49]);
1696
1697        let options = Records::<_, ParsedDhcpOptionImpl>::parse_with_context(
1698            &buf[..],
1699            OptionParseContext { depth: 0 },
1700        )
1701        .expect("parse should succeed");
1702        let options: Vec<ParsedDhcpOption<'_>> = options.iter().collect();
1703        let iana_buf = [0, 0, 13, 128, 0, 0, 4, 0, 0, 0, 212, 49];
1704        assert_eq!(
1705            options[..],
1706            [ParsedDhcpOption::Iana(
1707                IanaData::new(&iana_buf[..], OptionParseContext { depth: 1 })
1708                    .expect("construction failed")
1709            )]
1710        );
1711    }
1712
1713    // IA_NA must have option length >= 12, according to [RFC 8145, Section 21.4].
1714    //
1715    // [RFC 8145, Section 21.4]: https://tools.ietf.org/html/rfc8415#section-21.4
1716    #[test]
1717    fn test_iana_invalid_opt_len() {
1718        let mut buf = test_buf_with_no_options();
1719        buf.extend_from_slice(&[
1720            0, 3, // opt code = 3, IA_NA
1721            0, 8, // invalid opt length, must be >= 12
1722            0, 0, 0, 0, 0, 0, 0, 0,
1723        ]);
1724        assert_matches!(
1725            Message::parse(&mut &buf[..], ()),
1726            Err(ParseError::InvalidOpLen(OptionCode::Iana, 8))
1727        );
1728    }
1729
1730    #[test]
1731    fn test_iaaddr_no_suboptions_serialization_parsing_roundtrip() {
1732        let mut buf = [0u8; 28];
1733        let option = DhcpOption::IaAddr(IaAddrSerializer::new(
1734            Ipv6Addr::from([10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25]),
1735            0,
1736            0,
1737            &[],
1738        ));
1739
1740        option.serialize_into(&mut buf);
1741        assert_eq!(
1742            buf,
1743            [
1744                0, 5, 0, 24, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 0, 0,
1745                0, 0, 0, 0, 0, 0
1746            ]
1747        );
1748
1749        let options = Records::<_, ParsedDhcpOptionImpl>::parse_with_context(
1750            &buf[..],
1751            OptionParseContext { depth: 0 },
1752        )
1753        .expect("parse should succeed");
1754        let options: Vec<ParsedDhcpOption<'_>> = options.iter().collect();
1755        let iaaddr_buf = [
1756            10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 0, 0, 0, 0, 0, 0, 0, 0,
1757        ];
1758        assert_eq!(
1759            options[..],
1760            [ParsedDhcpOption::IaAddr(
1761                IaAddrData::new(&iaaddr_buf[..], OptionParseContext { depth: 1 })
1762                    .expect("construction failed")
1763            )]
1764        );
1765    }
1766
1767    // IA Address must have option length >= 24, according to [RFC 8145, Section 21.6].
1768    //
1769    // [RFC 8145, Section 21.6]: https://tools.ietf.org/html/rfc8415#section-21.6
1770    #[test]
1771    fn test_iaaddr_invalid_opt_len() {
1772        let mut buf = test_buf_with_no_options();
1773        buf.extend_from_slice(&[
1774            0, 5, // opt code = 5, IA Address
1775            0, 8, // invalid opt length, must be >= 24
1776            0, 0, 0, 0, 0, 0, 0, 0,
1777        ]);
1778        assert_matches!(
1779            Message::parse(&mut &buf[..], ()),
1780            Err(ParseError::InvalidOpLen(OptionCode::IaAddr, 8))
1781        );
1782    }
1783
1784    // Oro must have a even option length, according to [RFC 8415, Section 21.7].
1785    //
1786    // [RFC 8415, Section 21.7]: https://tools.ietf.org/html/rfc8415#section-21.7
1787    #[test]
1788    fn test_invalid_oro_opt_len() {
1789        let mut buf = test_buf_with_no_options();
1790        buf.extend_from_slice(&[
1791            0, 6, // opt code = 6, ORO
1792            0, 1, // invalid opt length, must be even
1793            0,
1794        ]);
1795        assert_matches!(
1796            Message::parse(&mut &buf[..], ()),
1797            Err(ParseError::InvalidOpLen(OptionCode::Oro, 1))
1798        );
1799    }
1800
1801    // Preference must have option length 1, according to [RFC8415, Section 21.8].
1802    //
1803    // [RFC8415, Section 21.8]: https://tools.ietf.org/html/rfc8415#section-21.8
1804    #[test]
1805    fn test_invalid_preference_opt_len() {
1806        let mut buf = test_buf_with_no_options();
1807        buf.extend_from_slice(&[
1808            0, 7, // opt code = 7, preference
1809            0, 2, // invalid opt length, must be even
1810            0, 0,
1811        ]);
1812        assert_matches!(
1813            Message::parse(&mut &buf[..], ()),
1814            Err(ParseError::InvalidOpLen(OptionCode::Preference, 2))
1815        );
1816    }
1817
1818    // Elapsed time must have option length 2, according to [RFC 8145, Section 21.9].
1819    //
1820    // [RFC 8145, Section 21.9]: https://tools.ietf.org/html/rfc8415#section-21.9
1821    #[test]
1822    fn test_elapsed_time_invalid_opt_len() {
1823        let mut buf = test_buf_with_no_options();
1824        buf.extend_from_slice(&[
1825            0, 8, // opt code = 8, elapsed time
1826            0, 3, // invalid opt length, must be even
1827            0, 0, 0,
1828        ]);
1829        assert_matches!(
1830            Message::parse(&mut &buf[..], ()),
1831            Err(ParseError::InvalidOpLen(OptionCode::ElapsedTime, 3))
1832        );
1833    }
1834
1835    // Status code must have option length >= 2, according to [RFC 8145, Section 21.13].
1836    //
1837    // [RFC 8145, Section 21.13]: https://tools.ietf.org/html/rfc8415#section-21.13
1838    #[test]
1839    fn test_status_code_invalid_opt_len() {
1840        let mut buf = test_buf_with_no_options();
1841        buf.extend_from_slice(&[
1842            0, 13, // opt code = 13, status code
1843            0, 1, // invalid opt length, must be >= 2
1844            0, 0, 0,
1845        ]);
1846        assert_matches!(
1847            Message::parse(&mut &buf[..], ()),
1848            Err(ParseError::InvalidOpLen(OptionCode::StatusCode, 1))
1849        );
1850    }
1851    // Information refresh time must have option length 4, according to [RFC 8145, Section 21.23].
1852    //
1853    // [RFC 8145, Section 21.23]: https://tools.ietf.org/html/rfc8415#section-21.23
1854    #[test]
1855    fn test_information_refresh_time_invalid_opt_len() {
1856        let mut buf = test_buf_with_no_options();
1857        buf.extend_from_slice(&[
1858            0, 32, // opt code = 32, information refresh time
1859            0, 3, // invalid opt length, must be 4
1860            0, 0, 0,
1861        ]);
1862        assert_matches!(
1863            Message::parse(&mut &buf[..], ()),
1864            Err(ParseError::InvalidOpLen(OptionCode::InformationRefreshTime, 3))
1865        );
1866    }
1867
1868    // SOL_MAX_RT must have option length 4, according to [RFC 8145, Section 21.24].
1869    //
1870    // [RFC 8145, Section 21.24]: https://tools.ietf.org/html/rfc8415#section-21.24
1871    #[test]
1872    fn test_sol_max_rt_invalid_opt_len() {
1873        let mut buf = test_buf_with_no_options();
1874        buf.extend_from_slice(&[
1875            0, 82, // opt code = 82, SOL_MAX_RT
1876            0, 3, // invalid opt length, must be 4
1877            0, 0, 0,
1878        ]);
1879        assert_matches!(
1880            Message::parse(&mut &buf[..], ()),
1881            Err(ParseError::InvalidOpLen(OptionCode::SolMaxRt, 3))
1882        );
1883    }
1884    // Option length of Dns servers must be multiples of 16, according to [RFC 3646, Section 3].
1885    //
1886    // [RFC 3646, Section 3]: https://tools.ietf.org/html/rfc3646#section-3
1887    #[test]
1888    fn test_dns_servers_invalid_opt_len() {
1889        let mut buf = test_buf_with_no_options();
1890        buf.extend_from_slice(&[
1891            0, 23, // opt code = 23, dns servers
1892            0, 17, // invalid opt length, must be multiple of 16
1893            0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
1894        ]);
1895        assert_matches!(
1896            Message::parse(&mut &buf[..], ()),
1897            Err(ParseError::InvalidOpLen(OptionCode::DnsServers, 17))
1898        );
1899    }
1900
1901    #[test_case(TimeValue::new(0), TimeValue::Zero)]
1902    #[test_case(TimeValue::new(5), TimeValue::NonZero(NonZeroTimeValue::Finite(NonZeroOrMaxU32::new(5).expect("should succeed for non zero or u32::MAX values"))))]
1903    #[test_case(TimeValue::new(u32::MAX), TimeValue::NonZero(NonZeroTimeValue::Infinity))]
1904    fn test_time_value_new(time_value: TimeValue, expected_variant: TimeValue) {
1905        assert_eq!(time_value, expected_variant);
1906    }
1907
1908    #[test_case(
1909       NonZeroTimeValue::Finite(
1910                    NonZeroOrMaxU32::new(1)
1911                        .expect("should succeed for non zero or u32::MAX values")
1912                ))]
1913    #[test_case(NonZeroTimeValue::Infinity)]
1914    fn test_time_value_ord(non_zero_tv: NonZeroTimeValue) {
1915        assert!(TimeValue::Zero < TimeValue::NonZero(non_zero_tv));
1916    }
1917
1918    #[test]
1919    fn test_non_zero_time_value_ord() {
1920        assert!(
1921            NonZeroTimeValue::Finite(
1922                NonZeroOrMaxU32::new(u32::MAX - 1)
1923                    .expect("should succeed for non zero or u32::MAX values")
1924            ) < NonZeroTimeValue::Infinity
1925        );
1926    }
1927
1928    #[test_case(0, None)]
1929    #[test_case(60, Some(NonZeroOrMaxU32::new(60).unwrap()))]
1930    #[test_case(u32::MAX, None)]
1931    fn test_non_zero_or_max_u32_new(t: u32, expected: Option<NonZeroOrMaxU32>) {
1932        assert_eq!(NonZeroOrMaxU32::new(t), expected);
1933    }
1934
1935    #[test_case(1)]
1936    #[test_case(4321)]
1937    #[test_case(u32::MAX - 1)]
1938    fn test_non_zero_or_max_u32_get(t: u32) {
1939        assert_eq!(
1940            NonZeroOrMaxU32::new(t).expect("should succeed for non-zero or u32::MAX values").get(),
1941            t
1942        );
1943    }
1944
1945    #[test]
1946    fn test_option_recursion_limit() {
1947        let lvl5 = [DhcpOption::StatusCode(0, "Success.")];
1948        let lvl4 = [DhcpOption::Iana(IanaSerializer::new(IAID::new(5), 0, 0, &lvl5))];
1949        let lvl3 = [DhcpOption::Iana(IanaSerializer::new(IAID::new(4), 0, 0, &lvl4))];
1950        let lvl2 = [DhcpOption::Iana(IanaSerializer::new(IAID::new(3), 0, 0, &lvl3))];
1951        let lvl1 = [DhcpOption::Iana(IanaSerializer::new(IAID::new(2), 0, 0, &lvl2))];
1952
1953        let builder = MessageBuilder::new(MessageType::Solicit, [1, 2, 3], &lvl1);
1954        let mut buf = vec![0; builder.bytes_len()];
1955        let () = builder.serialize(&mut buf);
1956
1957        let mut buf = &buf[..];
1958        assert_matches!(
1959            Message::parse(&mut buf, ()),
1960            Err(ParseError::OptionRecursionLimitExceeded)
1961        );
1962    }
1963}