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