Skip to main content

dhcp_client_core/
parse.rs

1// Copyright 2023 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 of DHCP messages
6
7use dhcp_protocol::{AtLeast, AtMostBytes};
8use diagnostics_traits::Inspector;
9use packet::{InnerPacketBuilder, ParseBuffer as _, Serializer};
10use packet_formats::ip::IpPacket as _;
11use std::net::Ipv4Addr;
12use std::num::{NonZeroU16, NonZeroU32, TryFromIntError};
13
14use crate::inspect::Counter;
15
16#[derive(thiserror::Error, Debug)]
17pub(crate) enum ParseError {
18    #[error("parsing IPv4 packet: {0}")]
19    Ipv4(packet_formats::error::ParseError),
20    #[error("IPv4 packet protocol was not UDP")]
21    NotUdp,
22    #[error("parsing UDP datagram: {0}")]
23    Udp(packet_formats::error::ParseError),
24    #[error("incoming packet destined for wrong port: {0}")]
25    WrongPort(NonZeroU16),
26    #[error("incoming packet has wrong source address: {0}")]
27    WrongSource(std::net::SocketAddr),
28    #[error("parsing DHCP message: {0}")]
29    Dhcp(dhcp_protocol::ProtocolError),
30}
31
32/// Parses a DHCP message from the bytes of an IP packet. This function does not
33/// expect to parse a packet with link-layer headers; the buffer may only
34/// include bytes for the IP layer and above.
35/// NOTE: does not handle IP fragmentation.
36pub(crate) fn parse_dhcp_message_from_ip_packet(
37    mut bytes: &[u8],
38    expected_dst_port: NonZeroU16,
39) -> Result<(net_types::ip::Ipv4Addr, dhcp_protocol::Message), ParseError> {
40    let ip_packet =
41        bytes.parse::<packet_formats::ipv4::Ipv4Packet<_>>().map_err(ParseError::Ipv4)?;
42
43    let src_addr = ip_packet.src_ip();
44
45    match ip_packet.proto() {
46        packet_formats::ip::Ipv4Proto::Proto(packet_formats::ip::IpProto::Udp) => (),
47        packet_formats::ip::Ipv4Proto::Proto(packet_formats::ip::IpProto::Tcp)
48        | packet_formats::ip::Ipv4Proto::Icmp
49        | packet_formats::ip::Ipv4Proto::Igmp
50        | packet_formats::ip::Ipv4Proto::Proto(packet_formats::ip::IpProto::Reserved)
51        | packet_formats::ip::Ipv4Proto::Other(_) => return Err(ParseError::NotUdp),
52    };
53    let mut ip_packet_body = ip_packet.body();
54
55    let udp_packet = ip_packet_body
56        .parse_with::<_, packet_formats::udp::UdpPacket<_>>(packet_formats::udp::UdpParseArgs::new(
57            ip_packet.src_ip(),
58            ip_packet.dst_ip(),
59        ))
60        .map_err(ParseError::Udp)?;
61    let dst_port = udp_packet.dst_port();
62    if dst_port != expected_dst_port {
63        return Err(ParseError::WrongPort(dst_port));
64    }
65    dhcp_protocol::Message::from_buffer(udp_packet.body())
66        .map(|msg| (src_addr, msg))
67        .map_err(ParseError::Dhcp)
68}
69
70const DEFAULT_TTL: u8 = 64;
71
72/// Serializes a DHCP message to the bytes of an IP packet. Includes IP header
73/// but not link-layer headers.
74pub(crate) fn serialize_dhcp_message_to_ip_packet(
75    message: dhcp_protocol::Message,
76    src_ip: impl Into<net_types::ip::Ipv4Addr>,
77    src_port: NonZeroU16,
78    dst_ip: impl Into<net_types::ip::Ipv4Addr>,
79    dst_port: NonZeroU16,
80) -> impl AsRef<[u8]> {
81    let message = message.serialize();
82    let src_ip = src_ip.into();
83    let dst_ip = dst_ip.into();
84
85    let udp_builder =
86        packet_formats::udp::UdpPacketBuilder::new(src_ip, dst_ip, Some(src_port), dst_port);
87
88    let ipv4_builder = packet_formats::ipv4::Ipv4PacketBuilder::new(
89        src_ip,
90        dst_ip,
91        DEFAULT_TTL,
92        packet_formats::ip::Ipv4Proto::Proto(packet_formats::ip::IpProto::Udp),
93    );
94
95    match message.into_serializer().wrap_in(udp_builder).wrap_in(ipv4_builder).serialize_vec_outer()
96    {
97        Ok(buf) => buf,
98        Err(e) => {
99            let (e, _serializer) = e;
100            match e {
101                packet::SerializeError::SizeLimitExceeded => {
102                    unreachable!("no MTU constraints on serializer")
103                }
104            }
105        }
106    }
107}
108
109#[derive(derive_builder::Builder, Debug, PartialEq)]
110#[builder(private, build_fn(error = "CommonIncomingMessageError"))]
111struct CommonIncomingMessageFields {
112    message_type: dhcp_protocol::MessageType,
113    #[builder(setter(custom), default)]
114    server_identifier: Option<net_types::SpecifiedAddr<net_types::ip::Ipv4Addr>>,
115    #[builder(setter(custom), default)]
116    yiaddr: Option<net_types::SpecifiedAddr<net_types::ip::Ipv4Addr>>,
117    #[builder(setter(strip_option), default)]
118    ip_address_lease_time_secs: Option<NonZeroU32>,
119    // While it's nonsensical to have a 0-valued lease time, it's somewhat more
120    // reasonable to set the renewal time value to 0 (prompting the client to
121    // begin the renewal process immediately).
122    #[builder(setter(strip_option), default)]
123    renewal_time_value_secs: Option<u32>,
124    // Same holds for the rebinding time.
125    #[builder(setter(strip_option), default)]
126    rebinding_time_value_secs: Option<u32>,
127    #[builder(default)]
128    parameters: Vec<dhcp_protocol::DhcpOption>,
129    #[builder(setter(strip_option), default)]
130    message: Option<String>,
131    #[builder(setter(strip_option), default)]
132    client_identifier: Option<AtLeast<2, AtMostBytes<{ dhcp_protocol::U8_MAX_AS_USIZE }, Vec<u8>>>>,
133    #[builder(setter(custom))]
134    seen_option_codes: OptionCodeSet,
135}
136
137#[derive(thiserror::Error, Debug, PartialEq)]
138pub(crate) enum CommonIncomingMessageError {
139    #[error("got op = {0}, want op = BOOTREPLY")]
140    NotBootReply(dhcp_protocol::OpCode),
141    #[error("server identifier was the unspecified address")]
142    UnspecifiedServerIdentifier,
143    #[error("missing: {0}")]
144    BuilderMissingField(&'static str),
145    #[error("option's inclusion violates protocol: {0:?}")]
146    IllegallyIncludedOption(dhcp_protocol::OptionCode),
147}
148
149impl From<derive_builder::UninitializedFieldError> for CommonIncomingMessageError {
150    fn from(value: derive_builder::UninitializedFieldError) -> Self {
151        // `derive_builder::UninitializedFieldError` cannot be destructured
152        // because its fields are private.
153        Self::BuilderMissingField(value.field_name())
154    }
155}
156
157/// Counters for reasons an incoming message was discarded.
158#[derive(Default, Debug)]
159pub(crate) struct CommonIncomingMessageErrorCounters {
160    /// The incoming message was a BOOTREQUEST rather than a BOOTREPLY.
161    pub(crate) not_boot_reply: Counter,
162    /// The incoming message provided the unspecified address as the server
163    /// identifier.
164    pub(crate) unspecified_server_identifier: Counter,
165    /// The parser was unable to populate a required field while consuming the
166    /// message.
167    pub(crate) parser_missing_field: Counter,
168    /// The incoming message included an option that was illegal to include
169    /// according to spec.
170    pub(crate) illegally_included_option: Counter,
171}
172
173impl CommonIncomingMessageErrorCounters {
174    /// Records the counters.
175    fn record(&self, inspector: &mut impl Inspector) {
176        let Self {
177            not_boot_reply,
178            unspecified_server_identifier,
179            parser_missing_field,
180            illegally_included_option,
181        } = self;
182        inspector.record_usize("NotBootReply", not_boot_reply.load());
183        inspector.record_usize("UnspecifiedServerIdentifier", unspecified_server_identifier.load());
184        inspector.record_usize("ParserMissingField", parser_missing_field.load());
185        inspector.record_usize("IllegallyIncludedOption", illegally_included_option.load());
186    }
187
188    /// Increments the counter corresponding to the error.
189    fn increment(&self, error: &CommonIncomingMessageError) {
190        let Self {
191            not_boot_reply,
192            unspecified_server_identifier,
193            parser_missing_field,
194            illegally_included_option,
195        } = self;
196        match error {
197            CommonIncomingMessageError::NotBootReply(_) => not_boot_reply.increment(),
198            CommonIncomingMessageError::UnspecifiedServerIdentifier => {
199                unspecified_server_identifier.increment()
200            }
201            CommonIncomingMessageError::BuilderMissingField(_) => parser_missing_field.increment(),
202            CommonIncomingMessageError::IllegallyIncludedOption(_) => {
203                illegally_included_option.increment()
204            }
205        }
206    }
207}
208
209impl CommonIncomingMessageFieldsBuilder {
210    fn ignore_unused_result(&mut self) {}
211
212    fn add_requested_parameter(&mut self, option: dhcp_protocol::DhcpOption) {
213        let parameters = self.parameters.get_or_insert_with(Default::default);
214        parameters.push(option)
215    }
216
217    fn add_seen_option_and_return_whether_newly_added(
218        &mut self,
219        option_code: dhcp_protocol::OptionCode,
220    ) -> bool {
221        self.seen_option_codes.get_or_insert_with(Default::default).insert(option_code)
222    }
223
224    fn server_identifier(&mut self, addr: Ipv4Addr) -> Result<(), CommonIncomingMessageError> {
225        self.server_identifier = Some(Some(
226            net_types::SpecifiedAddr::new(net_types::ip::Ipv4Addr::from(addr))
227                .ok_or(CommonIncomingMessageError::UnspecifiedServerIdentifier)?,
228        ));
229        Ok(())
230    }
231
232    fn yiaddr(&mut self, addr: Ipv4Addr) {
233        match net_types::SpecifiedAddr::new(net_types::ip::Ipv4Addr::from(addr)) {
234            None => {
235                // Unlike with the Server Identifier option, it is not an error
236                // to set `yiaddr` to the unspecified address, as there is no
237                // other way to indicate its absence (it has its own field in
238                // the DHCP message rather than appearing in the list of
239                // options).
240            }
241            Some(specified_addr) => {
242                self.yiaddr = Some(Some(specified_addr));
243            }
244        }
245    }
246}
247
248/// Represents a `Map<OptionCode, T>` as an array of booleans.
249#[derive(Clone, PartialEq, Debug)]
250pub struct OptionCodeMap<T> {
251    inner: [Option<T>; dhcp_protocol::U8_MAX_AS_USIZE],
252}
253
254impl<T: Copy> OptionCodeMap<T> {
255    /// Constructs an empty `OptionCodeMap`.
256    pub fn new() -> Self {
257        OptionCodeMap { inner: [None; dhcp_protocol::U8_MAX_AS_USIZE] }
258    }
259
260    /// Puts `(option_code, value)` into the map, returning the previously-associated
261    /// value if is one.
262    pub fn put(&mut self, option_code: dhcp_protocol::OptionCode, value: T) -> Option<T> {
263        std::mem::replace(&mut self.inner[usize::from(u8::from(option_code))], Some(value))
264    }
265
266    /// Gets the value associated with `option_code` from the map, if there is one.
267    pub fn get(&self, option_code: dhcp_protocol::OptionCode) -> Option<T> {
268        self.inner[usize::from(u8::from(option_code))]
269    }
270
271    /// Checks if `option_code` is present in the map.
272    pub fn contains(&self, option_code: dhcp_protocol::OptionCode) -> bool {
273        self.get(option_code).is_some()
274    }
275
276    pub(crate) fn iter(&self) -> impl Iterator<Item = (dhcp_protocol::OptionCode, T)> + '_ {
277        self.inner.iter().enumerate().filter_map(|(index, value)| {
278            let option_code = u8::try_from(index)
279                .ok()
280                .and_then(|i| dhcp_protocol::OptionCode::try_from(i).ok())?;
281            let value = *value.as_ref()?;
282            Some((option_code, value))
283        })
284    }
285
286    pub(crate) fn iter_keys(&self) -> impl Iterator<Item = dhcp_protocol::OptionCode> + '_ {
287        self.iter().map(|(key, _)| key)
288    }
289}
290
291impl<V: Copy> FromIterator<(dhcp_protocol::OptionCode, V)> for OptionCodeMap<V> {
292    fn from_iter<T: IntoIterator<Item = (dhcp_protocol::OptionCode, V)>>(iter: T) -> Self {
293        let mut map = Self::new();
294        for (option_code, value) in iter {
295            let _: Option<_> = map.put(option_code, value);
296        }
297        map
298    }
299}
300
301impl<T: Copy> Default for OptionCodeMap<T> {
302    fn default() -> Self {
303        Self::new()
304    }
305}
306
307impl OptionCodeMap<OptionRequested> {
308    fn iter_required(&self) -> impl Iterator<Item = dhcp_protocol::OptionCode> + '_ {
309        self.iter().filter_map(|(key, val)| match val {
310            OptionRequested::Required => Some(key),
311            OptionRequested::Optional => None,
312        })
313    }
314
315    /// Converts `self` into the representation required for
316    /// `DhcpOption::ParameterRequestList`.
317    ///
318    /// Returns None if `self` is empty.
319    pub(crate) fn try_to_parameter_request_list(
320        &self,
321    ) -> Option<
322        AtLeast<1, AtMostBytes<{ dhcp_protocol::U8_MAX_AS_USIZE }, Vec<dhcp_protocol::OptionCode>>>,
323    > {
324        match AtLeast::try_from(self.iter_keys().collect::<Vec<_>>()) {
325            Ok(parameters) => Some(parameters),
326            Err((dhcp_protocol::SizeConstrainedError::SizeConstraintViolated, parameters)) => {
327                // This can only have happened because parameters is empty.
328                assert_eq!(parameters, Vec::new());
329                // Thus, we must omit the ParameterRequestList option.
330                None
331            }
332        }
333    }
334}
335
336/// Represents a set of OptionCodes as an array of booleans.
337pub type OptionCodeSet = OptionCodeMap<()>;
338
339impl OptionCodeSet {
340    /// Inserts `option_code` into the set, returning whether it was newly added.
341    pub fn insert(&mut self, option_code: dhcp_protocol::OptionCode) -> bool {
342        self.put(option_code, ()).is_none()
343    }
344}
345
346impl FromIterator<dhcp_protocol::OptionCode> for OptionCodeSet {
347    fn from_iter<T: IntoIterator<Item = dhcp_protocol::OptionCode>>(iter: T) -> Self {
348        let mut set = Self::new();
349        for code in iter {
350            let _: bool = set.insert(code);
351        }
352        set
353    }
354}
355
356/// Denotes whether a requested option is required or optional.
357#[derive(Copy, Clone, PartialEq, Debug)]
358pub enum OptionRequested {
359    /// The option is required; incoming DHCPOFFERs and DHCPACKs lacking this
360    /// option will be discarded.
361    Required,
362    /// The option is optional.
363    Optional,
364}
365
366fn collect_common_fields<T: Copy>(
367    requested_parameters: &OptionCodeMap<T>,
368    dhcp_protocol::Message {
369        op,
370        xid: _,
371        secs: _,
372        bdcast_flag: _,
373        ciaddr: _,
374        yiaddr,
375        siaddr: _,
376        giaddr: _,
377        chaddr: _,
378        sname: _,
379        file: _,
380        options,
381    }: dhcp_protocol::Message,
382) -> Result<CommonIncomingMessageFields, CommonIncomingMessageError> {
383    use dhcp_protocol::DhcpOption;
384
385    match op {
386        dhcp_protocol::OpCode::BOOTREQUEST => {
387            return Err(CommonIncomingMessageError::NotBootReply(op));
388        }
389        dhcp_protocol::OpCode::BOOTREPLY => (),
390    };
391
392    let mut builder = CommonIncomingMessageFieldsBuilder::default();
393    builder.yiaddr(yiaddr);
394
395    for option in options {
396        let code = option.code();
397        let newly_seen = builder.add_seen_option_and_return_whether_newly_added(code);
398        if !newly_seen && !option_can_be_duplicated(code) {
399            // Adhere to a principle of maximum conformance: ignore options
400            // that the server unexpectedly repeated rather than rejecting the
401            // message entirely. Should the multiple instance of this option
402            // have different values, we'll use the first instance's value.
403            log::warn!("DHCP option {code} was unexpectedly repeated: {option:?}");
404            continue;
405        }
406
407        // From RFC 2131 section 4.3.1:
408        // """
409        // Option                    DHCPOFFER    DHCPACK               DHCPNAK
410        // ------                    ---------    -------               -------
411        // Requested IP address      MUST NOT     MUST NOT              MUST NOT
412        // IP address lease time     MUST         MUST (DHCPREQUEST)    MUST NOT
413        //                                        MUST NOT (DHCPINFORM)
414        // Use 'file'/'sname' fields MAY          MAY                   MUST NOT
415        // DHCP message type         DHCPOFFER    DHCPACK               DHCPNAK
416        // Parameter request list    MUST NOT     MUST NOT              MUST NOT
417        // Message                   SHOULD       SHOULD                SHOULD
418        // Client identifier         MUST NOT     MUST NOT              MAY
419        // Vendor class identifier   MAY          MAY                   MAY
420        // Server identifier         MUST         MUST                  MUST
421        // Maximum message size      MUST NOT     MUST NOT              MUST NOT
422        // All others                MAY          MAY                   MUST NOT
423        //
424        //            Table 3:  Fields and options used by DHCP servers
425        // """
426
427        match &option {
428            DhcpOption::IpAddressLeaseTime(value) => match NonZeroU32::try_from(*value) {
429                Err(e) => {
430                    let _: TryFromIntError = e;
431                    log::warn!("dropping 0 lease time");
432                }
433                Ok(value) => {
434                    builder.ip_address_lease_time_secs(value).ignore_unused_result();
435                }
436            },
437            DhcpOption::DhcpMessageType(message_type) => {
438                builder.message_type(*message_type).ignore_unused_result()
439            }
440            DhcpOption::ServerIdentifier(value) => {
441                builder.server_identifier(*value)?;
442            }
443            DhcpOption::Message(message) => builder.message(message.clone()).ignore_unused_result(),
444            DhcpOption::RenewalTimeValue(value) => {
445                builder.renewal_time_value_secs(*value).ignore_unused_result()
446            }
447            DhcpOption::RebindingTimeValue(value) => {
448                builder.rebinding_time_value_secs(*value).ignore_unused_result()
449            }
450            DhcpOption::ClientIdentifier(value) => {
451                builder.client_identifier(value.clone()).ignore_unused_result();
452            }
453            DhcpOption::ParameterRequestList(_)
454            | DhcpOption::RequestedIpAddress(_)
455            | DhcpOption::MaxDhcpMessageSize(_) => {
456                return Err(CommonIncomingMessageError::IllegallyIncludedOption(option.code()));
457            }
458            DhcpOption::Pad()
459            | DhcpOption::End()
460            | DhcpOption::SubnetMask(_)
461            | DhcpOption::TimeOffset(_)
462            | DhcpOption::Router(_)
463            | DhcpOption::TimeServer(_)
464            | DhcpOption::NameServer(_)
465            | DhcpOption::DomainNameServer(_)
466            | DhcpOption::LogServer(_)
467            | DhcpOption::CookieServer(_)
468            | DhcpOption::LprServer(_)
469            | DhcpOption::ImpressServer(_)
470            | DhcpOption::ResourceLocationServer(_)
471            | DhcpOption::HostName(_)
472            | DhcpOption::BootFileSize(_)
473            | DhcpOption::MeritDumpFile(_)
474            | DhcpOption::DomainName(_)
475            | DhcpOption::SwapServer(_)
476            | DhcpOption::RootPath(_)
477            | DhcpOption::ExtensionsPath(_)
478            | DhcpOption::IpForwarding(_)
479            | DhcpOption::NonLocalSourceRouting(_)
480            | DhcpOption::PolicyFilter(_)
481            | DhcpOption::MaxDatagramReassemblySize(_)
482            | DhcpOption::DefaultIpTtl(_)
483            | DhcpOption::PathMtuAgingTimeout(_)
484            | DhcpOption::PathMtuPlateauTable(_)
485            | DhcpOption::InterfaceMtu(_)
486            | DhcpOption::AllSubnetsLocal(_)
487            | DhcpOption::BroadcastAddress(_)
488            | DhcpOption::PerformMaskDiscovery(_)
489            | DhcpOption::MaskSupplier(_)
490            | DhcpOption::PerformRouterDiscovery(_)
491            | DhcpOption::RouterSolicitationAddress(_)
492            | DhcpOption::StaticRoute(_)
493            | DhcpOption::TrailerEncapsulation(_)
494            | DhcpOption::ArpCacheTimeout(_)
495            | DhcpOption::EthernetEncapsulation(_)
496            | DhcpOption::TcpDefaultTtl(_)
497            | DhcpOption::TcpKeepaliveInterval(_)
498            | DhcpOption::TcpKeepaliveGarbage(_)
499            | DhcpOption::NetworkInformationServiceDomain(_)
500            | DhcpOption::NetworkInformationServers(_)
501            | DhcpOption::NetworkTimeProtocolServers(_)
502            | DhcpOption::VendorSpecificInformation(_)
503            | DhcpOption::NetBiosOverTcpipNameServer(_)
504            | DhcpOption::NetBiosOverTcpipDatagramDistributionServer(_)
505            | DhcpOption::NetBiosOverTcpipNodeType(_)
506            | DhcpOption::NetBiosOverTcpipScope(_)
507            | DhcpOption::XWindowSystemFontServer(_)
508            | DhcpOption::XWindowSystemDisplayManager(_)
509            | DhcpOption::NetworkInformationServicePlusDomain(_)
510            | DhcpOption::NetworkInformationServicePlusServers(_)
511            | DhcpOption::MobileIpHomeAgent(_)
512            | DhcpOption::SmtpServer(_)
513            | DhcpOption::Pop3Server(_)
514            | DhcpOption::NntpServer(_)
515            | DhcpOption::DefaultWwwServer(_)
516            | DhcpOption::DefaultFingerServer(_)
517            | DhcpOption::DefaultIrcServer(_)
518            | DhcpOption::StreetTalkServer(_)
519            | DhcpOption::StreetTalkDirectoryAssistanceServer(_)
520            | DhcpOption::OptionOverload(_)
521            | DhcpOption::TftpServerName(_)
522            | DhcpOption::BootfileName(_)
523            | DhcpOption::VendorClassIdentifier(_) => (),
524        };
525
526        if requested_parameters.contains(option.code()) {
527            builder.add_requested_parameter(option);
528        }
529    }
530    builder.build()
531}
532
533/// Returns whether the option is allowed to be specified multiple times.
534///
535/// Note: The DHCP RFC doesn't take a stance on whether options may or may not
536/// be repeated. We take a pragmatic approach and allow options to be repeated
537/// where appropriate (e.g. options that have list semantics). Repeats are
538/// rejected on options for which they are nonsensical (e.g. the
539/// DhcpMessageType).
540fn option_can_be_duplicated(code: dhcp_protocol::OptionCode) -> bool {
541    use dhcp_protocol::OptionCode::*;
542    match code {
543        SubnetMask
544        | TimeOffset
545        | HostName
546        | BootFileSize
547        | MeritDumpFile
548        | DomainName
549        | SwapServer
550        | RootPath
551        | ExtensionsPath
552        | IpForwarding
553        | NonLocalSourceRouting
554        | MaxDatagramReassemblySize
555        | DefaultIpTtl
556        | PathMtuAgingTimeout
557        | InterfaceMtu
558        | AllSubnetsLocal
559        | BroadcastAddress
560        | PerformMaskDiscovery
561        | MaskSupplier
562        | PerformRouterDiscovery
563        | RouterSolicitationAddress
564        | TrailerEncapsulation
565        | ArpCacheTimeout
566        | EthernetEncapsulation
567        | TcpDefaultTtl
568        | TcpKeepaliveInterval
569        | TcpKeepaliveGarbage
570        | NetworkInformationServiceDomain
571        | NetBiosOverTcpipNodeType
572        | NetBiosOverTcpipScope
573        | RequestedIpAddress
574        | IpAddressLeaseTime
575        | OptionOverload
576        | DhcpMessageType
577        | ServerIdentifier
578        | Message
579        | MaxDhcpMessageSize
580        | RenewalTimeValue
581        | RebindingTimeValue
582        | VendorClassIdentifier
583        | ClientIdentifier
584        | NetworkInformationServicePlusDomain
585        | TftpServerName
586        | BootfileName
587        | End => false,
588        Pad
589        | Router
590        | TimeServer
591        | NameServer
592        | DomainNameServer
593        | LogServer
594        | CookieServer
595        | LprServer
596        | ImpressServer
597        | ResourceLocationServer
598        | PolicyFilter
599        | PathMtuPlateauTable
600        | StaticRoute
601        | NetworkInformationServers
602        | NetworkTimeProtocolServers
603        | VendorSpecificInformation
604        | NetBiosOverTcpipNameServer
605        | NetBiosOverTcpipDatagramDistributionServer
606        | XWindowSystemFontServer
607        | XWindowSystemDisplayManager
608        | NetworkInformationServicePlusServers
609        | MobileIpHomeAgent
610        | SmtpServer
611        | Pop3Server
612        | NntpServer
613        | DefaultWwwServer
614        | DefaultFingerServer
615        | DefaultIrcServer
616        | StreetTalkServer
617        | StreetTalkDirectoryAssistanceServer
618        | ParameterRequestList => true,
619    }
620}
621
622/// Reasons that an incoming DHCP message might be discarded during Selecting
623/// state.
624#[derive(thiserror::Error, Debug, PartialEq)]
625pub(crate) enum SelectingIncomingMessageError {
626    #[error("{0}")]
627    CommonError(#[from] CommonIncomingMessageError),
628    /// Note that `NoServerIdentifier` is intentionally distinct from
629    /// `CommonIncomingMessageError::UnspecifiedServerIdentifier`, as the latter
630    /// refers to the Server Identifier being explicitly populated as the
631    /// unspecified address, rather than simply omitted.
632    #[error("no server identifier")]
633    NoServerIdentifier,
634    #[error("got DHCP message type = {0}, wanted DHCPOFFER")]
635    NotDhcpOffer(dhcp_protocol::MessageType),
636    #[error("yiaddr was the unspecified address")]
637    UnspecifiedYiaddr,
638    #[error("missing required option: {0:?}")]
639    MissingRequiredOption(dhcp_protocol::OptionCode),
640}
641
642/// Counters for reasons a message was discarded while receiving in Selecting
643/// state.
644#[derive(Default, Debug)]
645pub(crate) struct SelectingIncomingMessageErrorCounters {
646    /// Common reasons across all states.
647    pub(crate) common: CommonIncomingMessageErrorCounters,
648    /// The message omitted the Server Identifier option.
649    pub(crate) no_server_identifier: Counter,
650    /// The message was not a DHCPOFFER.
651    pub(crate) not_dhcp_offer: Counter,
652    /// The message had yiaddr set to the unspecified address.
653    pub(crate) unspecified_yiaddr: Counter,
654    /// The message was missing a required option.
655    pub(crate) missing_required_option: Counter,
656}
657
658impl SelectingIncomingMessageErrorCounters {
659    /// Records the counters.
660    pub(crate) fn record(&self, inspector: &mut impl Inspector) {
661        let Self {
662            common,
663            no_server_identifier,
664            not_dhcp_offer,
665            unspecified_yiaddr,
666            missing_required_option,
667        } = self;
668        common.record(inspector);
669        inspector.record_usize("NoServerIdentifier", no_server_identifier.load());
670        inspector.record_usize("NotDhcpOffer", not_dhcp_offer.load());
671        inspector.record_usize("UnspecifiedYiaddr", unspecified_yiaddr.load());
672        inspector.record_usize("MissingRequiredOption", missing_required_option.load());
673    }
674
675    /// Increments the counter corresponding to the error.
676    pub(crate) fn increment(&self, error: &SelectingIncomingMessageError) {
677        let Self {
678            common,
679            no_server_identifier,
680            not_dhcp_offer,
681            unspecified_yiaddr,
682            missing_required_option,
683        } = self;
684        match error {
685            SelectingIncomingMessageError::CommonError(common_incoming_message_error) => {
686                common.increment(common_incoming_message_error)
687            }
688            SelectingIncomingMessageError::NoServerIdentifier => no_server_identifier.increment(),
689            SelectingIncomingMessageError::NotDhcpOffer(_) => not_dhcp_offer.increment(),
690            SelectingIncomingMessageError::UnspecifiedYiaddr => unspecified_yiaddr.increment(),
691            SelectingIncomingMessageError::MissingRequiredOption(_) => {
692                missing_required_option.increment()
693            }
694        }
695    }
696}
697
698/// Extracts the fields from a DHCP message incoming during Selecting state that
699/// should be used during Requesting state.
700pub(crate) fn fields_to_retain_from_selecting(
701    requested_parameters: &OptionCodeMap<OptionRequested>,
702    message: dhcp_protocol::Message,
703) -> Result<FieldsFromOfferToUseInRequest, SelectingIncomingMessageError> {
704    let CommonIncomingMessageFields {
705        message_type,
706        server_identifier,
707        yiaddr,
708        ip_address_lease_time_secs,
709        renewal_time_value_secs: _,
710        rebinding_time_value_secs: _,
711        parameters: _,
712        seen_option_codes,
713        message: _,
714        client_identifier: _,
715    } = collect_common_fields(requested_parameters, message)?;
716
717    match message_type {
718        dhcp_protocol::MessageType::DHCPOFFER => (),
719        dhcp_protocol::MessageType::DHCPDISCOVER
720        | dhcp_protocol::MessageType::DHCPREQUEST
721        | dhcp_protocol::MessageType::DHCPDECLINE
722        | dhcp_protocol::MessageType::DHCPACK
723        | dhcp_protocol::MessageType::DHCPNAK
724        | dhcp_protocol::MessageType::DHCPRELEASE
725        | dhcp_protocol::MessageType::DHCPINFORM => {
726            return Err(SelectingIncomingMessageError::NotDhcpOffer(message_type));
727        }
728    };
729
730    if let Some(missing_option_code) =
731        requested_parameters.iter_required().find(|code| !seen_option_codes.contains(*code))
732    {
733        return Err(SelectingIncomingMessageError::MissingRequiredOption(missing_option_code));
734    }
735
736    Ok(FieldsFromOfferToUseInRequest {
737        server_identifier: server_identifier
738            .ok_or(SelectingIncomingMessageError::NoServerIdentifier)?,
739        ip_address_lease_time_secs,
740        ip_address_to_request: yiaddr.ok_or(SelectingIncomingMessageError::UnspecifiedYiaddr)?,
741    })
742}
743
744#[derive(Debug, Clone, Copy, PartialEq)]
745/// Fields from a DHCPOFFER that should be used while building a DHCPREQUEST.
746pub(crate) struct FieldsFromOfferToUseInRequest {
747    pub(crate) server_identifier: net_types::SpecifiedAddr<net_types::ip::Ipv4Addr>,
748    pub(crate) ip_address_lease_time_secs: Option<NonZeroU32>,
749    pub(crate) ip_address_to_request: net_types::SpecifiedAddr<net_types::ip::Ipv4Addr>,
750}
751
752impl FieldsFromOfferToUseInRequest {
753    pub(crate) fn record(&self, inspector: &mut impl Inspector) {
754        let Self { server_identifier, ip_address_lease_time_secs, ip_address_to_request } = self;
755        inspector.record_ip_addr("ServerIdentifier", **server_identifier);
756        if let Some(value) = ip_address_lease_time_secs {
757            inspector.record_uint("IpAddressLeaseTimeSecs", value.get());
758        }
759        inspector.record_ip_addr("IpAddressToRequest", **ip_address_to_request);
760    }
761}
762
763#[derive(Debug, PartialEq)]
764// `ServerIdentifier`` is generic in order to allow for it to be optional for
765// DHCPACKs received while in RENEWING state (since we already know the
766// identifier of the server we're directly communicating with) but required in
767// REBINDING state (because we've broadcast the request and need to record
768// which server to send renewal requests to in the future).
769pub(crate) enum IncomingResponseToRequest<ServerIdentifier> {
770    Ack(FieldsToRetainFromAck<ServerIdentifier>),
771    Nak(FieldsToRetainFromNak),
772}
773
774/// Reasons that an incoming response to a DHCPREQUEST might be discarded.
775#[derive(thiserror::Error, Debug, PartialEq)]
776pub(crate) enum IncomingResponseToRequestError {
777    #[error("{0}")]
778    CommonError(#[from] CommonIncomingMessageError),
779    #[error("got DHCP message type = {0}, wanted DHCPACK or DHCPNAK")]
780    NotDhcpAckOrNak(dhcp_protocol::MessageType),
781    #[error("yiaddr was the unspecified address")]
782    UnspecifiedYiaddr,
783    #[error("no server identifier")]
784    NoServerIdentifier,
785    #[error("missing required option: {0:?}")]
786    MissingRequiredOption(dhcp_protocol::OptionCode),
787}
788
789/// Counters for reasons a message was discarded while receiving in Requesting
790/// state.
791#[derive(Default, Debug)]
792pub(crate) struct IncomingResponseToRequestErrorCounters {
793    /// Common reasons across all states.
794    pub(crate) common: CommonIncomingMessageErrorCounters,
795    /// The message was not a DHCPACK or DHCPNAK.
796    pub(crate) not_dhcp_ack_or_nak: Counter,
797    /// The message had yiaddr set to the unspecified address.
798    pub(crate) unspecified_yiaddr: Counter,
799    /// The message had no server identifier.
800    pub(crate) no_server_identifier: Counter,
801    /// The message was missing a required option.
802    pub(crate) missing_required_option: Counter,
803}
804
805impl IncomingResponseToRequestErrorCounters {
806    /// Records the counters.
807    pub(crate) fn record(&self, inspector: &mut impl Inspector) {
808        let Self {
809            common,
810            not_dhcp_ack_or_nak,
811            unspecified_yiaddr,
812            no_server_identifier,
813            missing_required_option,
814        } = self;
815        common.record(inspector);
816        inspector.record_usize("NotDhcpAckOrNak", not_dhcp_ack_or_nak.load());
817        inspector.record_usize("UnspecifiedYiaddr", unspecified_yiaddr.load());
818        inspector.record_usize("NoServerIdentifier", no_server_identifier.load());
819        inspector.record_usize("MissingRequiredOption", missing_required_option.load());
820    }
821
822    /// Increments the counter corresponding to the error.
823    pub(crate) fn increment(&self, error: &IncomingResponseToRequestError) {
824        let Self {
825            common,
826            not_dhcp_ack_or_nak,
827            unspecified_yiaddr,
828            no_server_identifier,
829            missing_required_option,
830        } = self;
831        match error {
832            IncomingResponseToRequestError::CommonError(common_incoming_message_error) => {
833                common.increment(common_incoming_message_error)
834            }
835            IncomingResponseToRequestError::NotDhcpAckOrNak(_) => not_dhcp_ack_or_nak.increment(),
836            IncomingResponseToRequestError::UnspecifiedYiaddr => unspecified_yiaddr.increment(),
837            IncomingResponseToRequestError::NoServerIdentifier => no_server_identifier.increment(),
838            IncomingResponseToRequestError::MissingRequiredOption(_) => {
839                missing_required_option.increment()
840            }
841        }
842    }
843}
844
845#[derive(Debug, PartialEq)]
846pub(crate) struct FieldsToRetainFromAck<ServerIdentifier> {
847    pub(crate) yiaddr: net_types::SpecifiedAddr<net_types::ip::Ipv4Addr>,
848    pub(crate) server_identifier: ServerIdentifier,
849    // Strictly according to RFC 2131, the IP Address Lease Time MUST be
850    // included in the DHCPACK. However, we've observed DHCP servers in the
851    // field that fail to set the lease time (https://fxbug.dev/486403240).
852    // Thus, we treat IP Address Lease Time as optional for DHCPACK.
853    pub(crate) ip_address_lease_time_secs: Option<NonZeroU32>,
854    pub(crate) renewal_time_value_secs: Option<u32>,
855    pub(crate) rebinding_time_value_secs: Option<u32>,
856    // Note: Options with list semantics may be repeated.
857    pub(crate) parameters: Vec<dhcp_protocol::DhcpOption>,
858}
859
860impl<ServerIdentifier> FieldsToRetainFromAck<ServerIdentifier> {
861    pub(crate) fn map_server_identifier<T, E>(
862        self,
863        f: impl FnOnce(ServerIdentifier) -> Result<T, E>,
864    ) -> Result<FieldsToRetainFromAck<T>, E> {
865        let Self {
866            yiaddr,
867            server_identifier,
868            ip_address_lease_time_secs,
869            renewal_time_value_secs,
870            rebinding_time_value_secs,
871            parameters,
872        } = self;
873        Ok(FieldsToRetainFromAck {
874            yiaddr,
875            server_identifier: f(server_identifier)?,
876            ip_address_lease_time_secs,
877            renewal_time_value_secs,
878            rebinding_time_value_secs,
879            parameters,
880        })
881    }
882}
883
884#[derive(Debug, PartialEq)]
885pub(crate) struct FieldsToRetainFromNak {
886    pub(crate) server_identifier: net_types::SpecifiedAddr<net_types::ip::Ipv4Addr>,
887    pub(crate) message: Option<String>,
888    pub(crate) client_identifier: Option<
889        AtLeast<
890            { dhcp_protocol::CLIENT_IDENTIFIER_MINIMUM_LENGTH },
891            AtMostBytes<{ dhcp_protocol::U8_MAX_AS_USIZE }, Vec<u8>>,
892        >,
893    >,
894}
895
896pub(crate) fn fields_to_retain_from_response_to_request(
897    requested_parameters: &OptionCodeMap<OptionRequested>,
898    message: dhcp_protocol::Message,
899) -> Result<
900    IncomingResponseToRequest<
901        // Strictly according to RFC 2131, the Server Identifier MUST be included in
902        // the DHCPACK. However, we've observed DHCP servers in the field fail to
903        // set the Server Identifier, instead expecting the client to remember it
904        // from the DHCPOFFER (https://fxbug.dev/42064504). Thus, we treat Server
905        // Identifier as optional for DHCPACK.
906        Option<net_types::SpecifiedAddr<net_types::ip::Ipv4Addr>>,
907    >,
908    IncomingResponseToRequestError,
909> {
910    let CommonIncomingMessageFields {
911        message_type,
912        server_identifier,
913        yiaddr,
914        ip_address_lease_time_secs,
915        renewal_time_value_secs,
916        rebinding_time_value_secs,
917        parameters,
918        seen_option_codes,
919        message,
920        client_identifier,
921    } = collect_common_fields(requested_parameters, message)?;
922
923    match message_type {
924        dhcp_protocol::MessageType::DHCPACK => {
925            // Only enforce required parameters for ACKs, since NAKs aren't
926            // expected to include any configuration at all.
927
928            if let Some(missing_option_code) =
929                requested_parameters.iter_required().find(|code| !seen_option_codes.contains(*code))
930            {
931                return Err(IncomingResponseToRequestError::MissingRequiredOption(
932                    missing_option_code,
933                ));
934            }
935            Ok(IncomingResponseToRequest::Ack(FieldsToRetainFromAck {
936                yiaddr: yiaddr.ok_or(IncomingResponseToRequestError::UnspecifiedYiaddr)?,
937                server_identifier,
938                ip_address_lease_time_secs: ip_address_lease_time_secs,
939                renewal_time_value_secs,
940                rebinding_time_value_secs,
941                parameters,
942            }))
943        }
944        dhcp_protocol::MessageType::DHCPNAK => {
945            Ok(IncomingResponseToRequest::Nak(FieldsToRetainFromNak {
946                server_identifier: server_identifier
947                    .ok_or(IncomingResponseToRequestError::NoServerIdentifier)?,
948                message,
949                client_identifier,
950            }))
951        }
952        dhcp_protocol::MessageType::DHCPDISCOVER
953        | dhcp_protocol::MessageType::DHCPOFFER
954        | dhcp_protocol::MessageType::DHCPREQUEST
955        | dhcp_protocol::MessageType::DHCPDECLINE
956        | dhcp_protocol::MessageType::DHCPRELEASE
957        | dhcp_protocol::MessageType::DHCPINFORM => {
958            Err(IncomingResponseToRequestError::NotDhcpAckOrNak(message_type))
959        }
960    }
961}
962
963#[cfg(test)]
964mod test {
965    use super::*;
966    use assert_matches::assert_matches;
967    use dhcp_protocol::{CLIENT_PORT, SERVER_PORT};
968    use net_declare::net::prefix_length_v4;
969    use net_declare::{net_ip_v4, net_mac, std_ip_v4};
970    use net_types::ip::{Ip, Ipv4, PrefixLength};
971    use std::net::Ipv4Addr;
972    use test_case::test_case;
973
974    #[test]
975    fn serialize_parse_roundtrip() {
976        let make_message = || dhcp_protocol::Message {
977            op: dhcp_protocol::OpCode::BOOTREQUEST,
978            xid: 124,
979            secs: 99,
980            bdcast_flag: false,
981            ciaddr: net_ip_v4!("1.2.3.4").into(),
982            yiaddr: net_ip_v4!("5.6.7.8").into(),
983            siaddr: net_ip_v4!("9.10.11.12").into(),
984            giaddr: net_ip_v4!("13.14.15.16").into(),
985            chaddr: net_mac!("17:18:19:20:21:22"),
986            sname: "this is a sname".to_owned(),
987            file: "this is the boot filename".to_owned(),
988            options: vec![
989                dhcp_protocol::DhcpOption::DhcpMessageType(
990                    dhcp_protocol::MessageType::DHCPDISCOVER,
991                ),
992                dhcp_protocol::DhcpOption::RequestedIpAddress(net_ip_v4!("5.6.7.8").into()),
993            ],
994        };
995        let packet = serialize_dhcp_message_to_ip_packet(
996            make_message(),
997            Ipv4Addr::UNSPECIFIED,
998            CLIENT_PORT,
999            Ipv4Addr::BROADCAST,
1000            SERVER_PORT,
1001        );
1002        let (src_addr, parsed_message) =
1003            parse_dhcp_message_from_ip_packet(packet.as_ref(), SERVER_PORT).unwrap();
1004
1005        assert_eq!(net_types::ip::Ipv4::UNSPECIFIED_ADDRESS, src_addr);
1006        assert_eq!(make_message(), parsed_message);
1007    }
1008
1009    #[test]
1010    fn nonsense() {
1011        assert_matches!(
1012            parse_dhcp_message_from_ip_packet(
1013                &[0xD, 0xE, 0xA, 0xD, 0xB, 0xE, 0xE, 0xF],
1014                NonZeroU16::new(1).unwrap()
1015            ),
1016            Err(ParseError::Ipv4(parse_error)) => {
1017                assert_eq!(parse_error, packet_formats::error::ParseError::Format)
1018            }
1019        )
1020    }
1021
1022    #[test]
1023    fn not_udp() {
1024        let src_ip = Ipv4Addr::UNSPECIFIED.into();
1025        let dst_ip = Ipv4Addr::BROADCAST.into();
1026        let tcp_builder: packet_formats::tcp::TcpSegmentBuilder<net_types::ip::Ipv4Addr> =
1027            packet_formats::tcp::TcpSegmentBuilder::new(
1028                src_ip,
1029                dst_ip,
1030                CLIENT_PORT,
1031                SERVER_PORT,
1032                0,
1033                None,
1034                0,
1035            );
1036        let ipv4_builder = packet_formats::ipv4::Ipv4PacketBuilder::new(
1037            src_ip,
1038            dst_ip,
1039            DEFAULT_TTL,
1040            packet_formats::ip::Ipv4Proto::Proto(packet_formats::ip::IpProto::Tcp),
1041        );
1042        let bytes = vec![1, 2, 3, 4, 5]
1043            .into_serializer()
1044            .wrap_in(tcp_builder)
1045            .wrap_in(ipv4_builder)
1046            .serialize_vec_outer()
1047            .expect("serialize error");
1048
1049        assert_matches!(
1050            parse_dhcp_message_from_ip_packet(bytes.as_ref(), NonZeroU16::new(1).unwrap()),
1051            Err(ParseError::NotUdp)
1052        );
1053    }
1054
1055    #[test]
1056    fn wrong_port() {
1057        let src_ip = Ipv4Addr::UNSPECIFIED.into();
1058        let dst_ip = Ipv4Addr::BROADCAST.into();
1059
1060        let udp_builder: packet_formats::udp::UdpPacketBuilder<net_types::ip::Ipv4Addr> =
1061            packet_formats::udp::UdpPacketBuilder::new(
1062                src_ip,
1063                dst_ip,
1064                Some(CLIENT_PORT),
1065                SERVER_PORT,
1066            );
1067        let ipv4_builder = packet_formats::ipv4::Ipv4PacketBuilder::new(
1068            src_ip,
1069            dst_ip,
1070            DEFAULT_TTL,
1071            packet_formats::ip::Ipv4Proto::Proto(packet_formats::ip::IpProto::Udp),
1072        );
1073
1074        let bytes = "hello_world"
1075            .bytes()
1076            .collect::<Vec<_>>()
1077            .into_serializer()
1078            .wrap_in(udp_builder)
1079            .wrap_in(ipv4_builder)
1080            .serialize_vec_outer()
1081            .expect("serialize error");
1082
1083        let result = parse_dhcp_message_from_ip_packet(bytes.as_ref(), CLIENT_PORT);
1084        assert_matches!(result, Err(ParseError::WrongPort(port)) => assert_eq!(port, SERVER_PORT));
1085    }
1086
1087    struct VaryingOfferFields {
1088        op: dhcp_protocol::OpCode,
1089        yiaddr: Ipv4Addr,
1090        message_type: Option<dhcp_protocol::MessageType>,
1091        server_identifier: Option<Ipv4Addr>,
1092        subnet_mask: Option<PrefixLength<Ipv4>>,
1093        lease_length_secs: Option<u32>,
1094        include_duplicate_option: bool,
1095    }
1096
1097    const SERVER_IP: Ipv4Addr = std_ip_v4!("192.168.1.1");
1098    const TEST_SUBNET_MASK: PrefixLength<Ipv4> = prefix_length_v4!(24);
1099    const LEASE_LENGTH_SECS: u32 = 100;
1100    const LEASE_LENGTH_SECS_NONZERO: NonZeroU32 = NonZeroU32::new(LEASE_LENGTH_SECS).unwrap();
1101    const YIADDR: Ipv4Addr = std_ip_v4!("192.168.1.5");
1102
1103    #[test_case(VaryingOfferFields {
1104        op: dhcp_protocol::OpCode::BOOTREPLY,
1105        yiaddr: YIADDR,
1106        message_type: Some(dhcp_protocol::MessageType::DHCPOFFER),
1107        server_identifier: Some(SERVER_IP),
1108        subnet_mask: Some(TEST_SUBNET_MASK),
1109        lease_length_secs: Some(LEASE_LENGTH_SECS),
1110        include_duplicate_option: false,
1111    } => Ok(FieldsFromOfferToUseInRequest {
1112        server_identifier: net_types::ip::Ipv4Addr::from(SERVER_IP)
1113            .try_into()
1114            .expect("should be specified"),
1115        ip_address_lease_time_secs: Some(LEASE_LENGTH_SECS_NONZERO),
1116        ip_address_to_request: net_types::ip::Ipv4Addr::from(YIADDR)
1117            .try_into()
1118            .expect("should be specified"),
1119    }); "accepts good offer with lease time")]
1120    #[test_case(VaryingOfferFields {
1121        op: dhcp_protocol::OpCode::BOOTREPLY,
1122        yiaddr: YIADDR,
1123        message_type: Some(dhcp_protocol::MessageType::DHCPOFFER),
1124        server_identifier: Some(SERVER_IP),
1125        subnet_mask: Some(TEST_SUBNET_MASK),
1126        lease_length_secs: None,
1127        include_duplicate_option: false,
1128    } => Ok(FieldsFromOfferToUseInRequest {
1129        server_identifier: net_types::ip::Ipv4Addr::from(SERVER_IP)
1130            .try_into()
1131            .expect("should be specified"),
1132        ip_address_lease_time_secs: None,
1133        ip_address_to_request: net_types::ip::Ipv4Addr::from(YIADDR)
1134            .try_into()
1135            .expect("should be specified"),
1136    }); "accepts good offer without lease time")]
1137    #[test_case(VaryingOfferFields {
1138        op: dhcp_protocol::OpCode::BOOTREPLY,
1139        yiaddr: YIADDR,
1140        message_type: Some(dhcp_protocol::MessageType::DHCPOFFER),
1141        server_identifier: Some(Ipv4Addr::UNSPECIFIED),
1142        subnet_mask: Some(TEST_SUBNET_MASK),
1143        lease_length_secs: Some(LEASE_LENGTH_SECS),
1144        include_duplicate_option: false,
1145    } => Err(SelectingIncomingMessageError::CommonError(
1146        CommonIncomingMessageError::UnspecifiedServerIdentifier,
1147    )); "rejects offer with unspecified server identifier")]
1148    #[test_case(VaryingOfferFields {
1149        op: dhcp_protocol::OpCode::BOOTREPLY,
1150        yiaddr: YIADDR,
1151        message_type: Some(dhcp_protocol::MessageType::DHCPOFFER),
1152        server_identifier: Some(SERVER_IP),
1153        subnet_mask: None,
1154        lease_length_secs: Some(LEASE_LENGTH_SECS),
1155        include_duplicate_option: false,
1156    } => Err(SelectingIncomingMessageError::MissingRequiredOption(
1157        dhcp_protocol::OptionCode::SubnetMask,
1158    )); "rejects offer without required subnet mask")]
1159    #[test_case(VaryingOfferFields {
1160        op: dhcp_protocol::OpCode::BOOTREPLY,
1161        yiaddr: YIADDR,
1162        message_type: Some(dhcp_protocol::MessageType::DHCPOFFER),
1163        server_identifier: None,
1164        subnet_mask: Some(TEST_SUBNET_MASK),
1165        lease_length_secs: Some(LEASE_LENGTH_SECS),
1166        include_duplicate_option: false,
1167    } => Err(SelectingIncomingMessageError::NoServerIdentifier); "rejects offer with no server identifier option")]
1168    #[test_case(VaryingOfferFields {
1169        op: dhcp_protocol::OpCode::BOOTREPLY,
1170        yiaddr: Ipv4Addr::UNSPECIFIED,
1171        message_type: Some(dhcp_protocol::MessageType::DHCPOFFER),
1172        server_identifier: Some(SERVER_IP),
1173        subnet_mask: Some(TEST_SUBNET_MASK),
1174        lease_length_secs: Some(LEASE_LENGTH_SECS),
1175        include_duplicate_option: false,
1176    } => Err(SelectingIncomingMessageError::UnspecifiedYiaddr) ; "rejects offer with unspecified yiaddr")]
1177    #[test_case(VaryingOfferFields {
1178        op: dhcp_protocol::OpCode::BOOTREQUEST,
1179        yiaddr: YIADDR,
1180        message_type: Some(dhcp_protocol::MessageType::DHCPOFFER),
1181        server_identifier: Some(SERVER_IP),
1182        subnet_mask: Some(TEST_SUBNET_MASK),
1183        lease_length_secs: Some(LEASE_LENGTH_SECS),
1184        include_duplicate_option: false,
1185    } => Err(SelectingIncomingMessageError::CommonError(
1186        CommonIncomingMessageError::NotBootReply(dhcp_protocol::OpCode::BOOTREQUEST),
1187    )); "rejects offer that isn't a bootreply")]
1188    #[test_case(VaryingOfferFields {
1189        op: dhcp_protocol::OpCode::BOOTREPLY,
1190        yiaddr: YIADDR,
1191        message_type: Some(dhcp_protocol::MessageType::DHCPACK),
1192        server_identifier: Some(SERVER_IP),
1193        subnet_mask: Some(TEST_SUBNET_MASK),
1194        lease_length_secs: Some(LEASE_LENGTH_SECS),
1195        include_duplicate_option: false,
1196    } => Err(
1197        SelectingIncomingMessageError::NotDhcpOffer(dhcp_protocol::MessageType::DHCPACK),
1198    ); "rejects offer with wrong DHCP message type")]
1199    #[test_case(VaryingOfferFields {
1200        op: dhcp_protocol::OpCode::BOOTREPLY,
1201        yiaddr: YIADDR,
1202        message_type: None,
1203        server_identifier: Some(SERVER_IP),
1204        subnet_mask: Some(TEST_SUBNET_MASK),
1205        lease_length_secs: Some(LEASE_LENGTH_SECS),
1206        include_duplicate_option: false,
1207    } => Err(SelectingIncomingMessageError::CommonError(
1208        CommonIncomingMessageError::BuilderMissingField("message_type"),
1209    )); "rejects offer with no DHCP message type option")]
1210    #[test_case(VaryingOfferFields {
1211        op: dhcp_protocol::OpCode::BOOTREPLY,
1212        yiaddr: YIADDR,
1213        message_type: Some(dhcp_protocol::MessageType::DHCPOFFER),
1214        server_identifier: Some(SERVER_IP),
1215        subnet_mask: Some(TEST_SUBNET_MASK),
1216        lease_length_secs: Some(LEASE_LENGTH_SECS),
1217        include_duplicate_option: true,
1218    } => Ok(FieldsFromOfferToUseInRequest {
1219        server_identifier: net_types::ip::Ipv4Addr::from(SERVER_IP)
1220            .try_into()
1221            .expect("should be specified"),
1222        ip_address_lease_time_secs: Some(LEASE_LENGTH_SECS_NONZERO),
1223        ip_address_to_request: net_types::ip::Ipv4Addr::from(YIADDR)
1224            .try_into()
1225            .expect("should be specified"),
1226    }); "accepts good offer with duplicate options")]
1227    fn fields_from_offer_to_use_in_request(
1228        offer_fields: VaryingOfferFields,
1229    ) -> Result<FieldsFromOfferToUseInRequest, SelectingIncomingMessageError> {
1230        use super::fields_to_retain_from_selecting as fields;
1231        use dhcp_protocol::DhcpOption;
1232
1233        let VaryingOfferFields {
1234            op,
1235            yiaddr,
1236            message_type,
1237            server_identifier,
1238            subnet_mask,
1239            lease_length_secs,
1240            include_duplicate_option,
1241        } = offer_fields;
1242
1243        let message = dhcp_protocol::Message {
1244            op,
1245            xid: 1,
1246            secs: 0,
1247            bdcast_flag: false,
1248            ciaddr: Ipv4Addr::UNSPECIFIED,
1249            yiaddr,
1250            siaddr: Ipv4Addr::UNSPECIFIED,
1251            giaddr: Ipv4Addr::UNSPECIFIED,
1252            chaddr: net_mac!("01:02:03:04:05:06"),
1253            sname: String::new(),
1254            file: String::new(),
1255            options: message_type
1256                .map(DhcpOption::DhcpMessageType)
1257                .into_iter()
1258                .chain(server_identifier.map(DhcpOption::ServerIdentifier))
1259                .chain(subnet_mask.map(DhcpOption::SubnetMask))
1260                .chain(lease_length_secs.map(DhcpOption::IpAddressLeaseTime))
1261                .chain(
1262                    include_duplicate_option
1263                        .then_some([
1264                            dhcp_protocol::DhcpOption::DomainName("example.com".to_owned()),
1265                            dhcp_protocol::DhcpOption::DomainName("example.com".to_owned()),
1266                        ])
1267                        .into_iter()
1268                        .flatten(),
1269                )
1270                .collect(),
1271        };
1272
1273        fields(
1274            &std::iter::once((dhcp_protocol::OptionCode::SubnetMask, OptionRequested::Required))
1275                .collect(),
1276            message,
1277        )
1278    }
1279
1280    struct VaryingReplyToRequestFields {
1281        op: dhcp_protocol::OpCode,
1282        yiaddr: Ipv4Addr,
1283        message_type: Option<dhcp_protocol::MessageType>,
1284        server_identifier: Option<Ipv4Addr>,
1285        subnet_mask: Option<PrefixLength<Ipv4>>,
1286        lease_length_secs: Option<u32>,
1287        renewal_time_secs: Option<u32>,
1288        rebinding_time_secs: Option<u32>,
1289        message: Option<String>,
1290        include_duplicate_option: bool,
1291    }
1292
1293    const DOMAIN_NAME: &str = "example.com";
1294    const MESSAGE: &str = "message explaining why the DHCPNAK was sent";
1295    const RENEWAL_TIME_SECS: u32 = LEASE_LENGTH_SECS / 2;
1296    const REBINDING_TIME_SECS: u32 = LEASE_LENGTH_SECS * 3 / 4;
1297
1298    #[test_case(
1299        VaryingReplyToRequestFields {
1300            op: dhcp_protocol::OpCode::BOOTREPLY,
1301            yiaddr: YIADDR,
1302            message_type: Some(dhcp_protocol::MessageType::DHCPACK),
1303            server_identifier: Some(SERVER_IP),
1304            subnet_mask: Some(TEST_SUBNET_MASK),
1305            lease_length_secs: Some(LEASE_LENGTH_SECS),
1306            renewal_time_secs: None,
1307            rebinding_time_secs: None,
1308            message: None,
1309            include_duplicate_option: false,
1310        } => Ok(IncomingResponseToRequest::Ack(FieldsToRetainFromAck {
1311            yiaddr: net_types::ip::Ipv4Addr::from(YIADDR)
1312                .try_into()
1313                .expect("should be specified"),
1314            server_identifier: Some(
1315                net_types::ip::Ipv4Addr::from(SERVER_IP)
1316                    .try_into()
1317                    .expect("should be specified"),
1318            ),
1319            ip_address_lease_time_secs: Some(LEASE_LENGTH_SECS_NONZERO),
1320            parameters: vec![
1321                dhcp_protocol::DhcpOption::SubnetMask(TEST_SUBNET_MASK),
1322                dhcp_protocol::DhcpOption::DomainName(DOMAIN_NAME.to_owned())
1323            ],
1324            renewal_time_value_secs: None,
1325            rebinding_time_value_secs: None,
1326        })); "accepts good DHCPACK")]
1327    #[test_case(VaryingReplyToRequestFields {
1328        op: dhcp_protocol::OpCode::BOOTREPLY,
1329        yiaddr: YIADDR,
1330        message_type: Some(dhcp_protocol::MessageType::DHCPACK),
1331        server_identifier: None,
1332        subnet_mask: Some(TEST_SUBNET_MASK),
1333        lease_length_secs: Some(LEASE_LENGTH_SECS),
1334        renewal_time_secs: None,
1335        rebinding_time_secs: None,
1336        message: None,
1337        include_duplicate_option: false,
1338    } => Ok(IncomingResponseToRequest::Ack(FieldsToRetainFromAck {
1339        yiaddr: net_types::ip::Ipv4Addr::from(YIADDR)
1340            .try_into()
1341            .expect("should be specified"),
1342        server_identifier: None,
1343        ip_address_lease_time_secs: Some(LEASE_LENGTH_SECS_NONZERO),
1344        parameters: vec![
1345            dhcp_protocol::DhcpOption::SubnetMask(TEST_SUBNET_MASK),
1346            dhcp_protocol::DhcpOption::DomainName(DOMAIN_NAME.to_owned())
1347        ],
1348        renewal_time_value_secs: None,
1349        rebinding_time_value_secs: None,
1350    })); "accepts DHCPACK with no server identifier")]
1351    #[test_case(VaryingReplyToRequestFields {
1352        op: dhcp_protocol::OpCode::BOOTREPLY,
1353        yiaddr: YIADDR,
1354        message_type: Some(dhcp_protocol::MessageType::DHCPACK),
1355        server_identifier: Some(SERVER_IP),
1356        subnet_mask: Some(TEST_SUBNET_MASK),
1357        lease_length_secs: Some(LEASE_LENGTH_SECS),
1358        renewal_time_secs: Some(RENEWAL_TIME_SECS),
1359        rebinding_time_secs: Some(REBINDING_TIME_SECS),
1360        message: None,
1361        include_duplicate_option: false,
1362    } => Ok(IncomingResponseToRequest::Ack(FieldsToRetainFromAck {
1363        yiaddr: net_types::ip::Ipv4Addr::from(YIADDR)
1364            .try_into()
1365            .expect("should be specified"),
1366        server_identifier: Some(
1367            net_types::ip::Ipv4Addr::from(SERVER_IP)
1368                .try_into()
1369                .expect("should be specified"),
1370        ),
1371        ip_address_lease_time_secs: Some(LEASE_LENGTH_SECS_NONZERO),
1372        parameters: vec![
1373            dhcp_protocol::DhcpOption::SubnetMask(TEST_SUBNET_MASK),
1374            dhcp_protocol::DhcpOption::DomainName(DOMAIN_NAME.to_owned())
1375        ],
1376        renewal_time_value_secs: Some(RENEWAL_TIME_SECS),
1377        rebinding_time_value_secs: Some(REBINDING_TIME_SECS),
1378    })); "accepts DHCPACK with renew and rebind times")]
1379    #[test_case(VaryingReplyToRequestFields {
1380        op: dhcp_protocol::OpCode::BOOTREPLY,
1381        yiaddr: Ipv4Addr::UNSPECIFIED,
1382        message_type: Some(dhcp_protocol::MessageType::DHCPNAK),
1383        server_identifier: Some(SERVER_IP),
1384        subnet_mask: None,
1385        lease_length_secs: None,
1386        renewal_time_secs: None,
1387        rebinding_time_secs: None,
1388        message: Some(MESSAGE.to_owned()),
1389        include_duplicate_option: false,
1390    } => Ok(IncomingResponseToRequest::Nak(FieldsToRetainFromNak {
1391        server_identifier: net_types::ip::Ipv4Addr::from(SERVER_IP)
1392            .try_into()
1393            .expect("should be specified"),
1394        message: Some(MESSAGE.to_owned()),
1395        client_identifier: None,
1396    })); "accepts good DHCPNAK")]
1397    #[test_case(VaryingReplyToRequestFields {
1398        op: dhcp_protocol::OpCode::BOOTREPLY,
1399        yiaddr: YIADDR,
1400        message_type: Some(dhcp_protocol::MessageType::DHCPACK),
1401        server_identifier: Some(SERVER_IP),
1402        subnet_mask: Some(TEST_SUBNET_MASK),
1403        lease_length_secs: None,
1404        renewal_time_secs: None,
1405        rebinding_time_secs: None,
1406        message: None,
1407        include_duplicate_option: false,
1408    } => Ok(IncomingResponseToRequest::Ack(FieldsToRetainFromAck {
1409        yiaddr: net_types::ip::Ipv4Addr::from(YIADDR).try_into().expect("should be specified"),
1410        server_identifier: Some(
1411            net_types::ip::Ipv4Addr::from(SERVER_IP).try_into().expect("should be specified")
1412        ),
1413        ip_address_lease_time_secs: None,
1414        parameters: vec![
1415            dhcp_protocol::DhcpOption::SubnetMask(TEST_SUBNET_MASK),
1416            dhcp_protocol::DhcpOption::DomainName(DOMAIN_NAME.to_owned())
1417        ],
1418        renewal_time_value_secs: None,
1419        rebinding_time_value_secs: None,
1420    })); "accepts DHCPACK with no lease time")]
1421    #[test_case(
1422        VaryingReplyToRequestFields {
1423            op: dhcp_protocol::OpCode::BOOTREPLY,
1424            yiaddr: YIADDR,
1425            message_type: Some(dhcp_protocol::MessageType::DHCPACK),
1426            server_identifier: Some(SERVER_IP),
1427            subnet_mask: None,
1428            lease_length_secs: Some(LEASE_LENGTH_SECS),
1429            renewal_time_secs: None,
1430            rebinding_time_secs: None,
1431            message: None,
1432            include_duplicate_option: false,
1433        } => Err(IncomingResponseToRequestError::MissingRequiredOption(
1434            dhcp_protocol::OptionCode::SubnetMask
1435        )); "rejects DHCPACK without required subnet mask")]
1436    #[test_case(VaryingReplyToRequestFields {
1437        op: dhcp_protocol::OpCode::BOOTREPLY,
1438        yiaddr: YIADDR,
1439        message_type: Some(dhcp_protocol::MessageType::DHCPACK),
1440        server_identifier: Some(Ipv4Addr::UNSPECIFIED),
1441        subnet_mask: Some(TEST_SUBNET_MASK),
1442        lease_length_secs: Some(LEASE_LENGTH_SECS),
1443        renewal_time_secs: Some(RENEWAL_TIME_SECS),
1444        rebinding_time_secs: Some(REBINDING_TIME_SECS),
1445        message: None,
1446        include_duplicate_option: false,
1447    } => Err(IncomingResponseToRequestError::CommonError(
1448        CommonIncomingMessageError::UnspecifiedServerIdentifier,
1449    )); "rejects DHCPACK with unspecified server identifier")]
1450    #[test_case(VaryingReplyToRequestFields {
1451        op: dhcp_protocol::OpCode::BOOTREPLY,
1452        yiaddr: Ipv4Addr::UNSPECIFIED,
1453        message_type: Some(dhcp_protocol::MessageType::DHCPACK),
1454        server_identifier: Some(SERVER_IP),
1455        subnet_mask: Some(TEST_SUBNET_MASK),
1456        lease_length_secs: Some(LEASE_LENGTH_SECS),
1457        renewal_time_secs: Some(RENEWAL_TIME_SECS),
1458        rebinding_time_secs: Some(REBINDING_TIME_SECS),
1459        message: None,
1460        include_duplicate_option: false,
1461    } => Err(IncomingResponseToRequestError::UnspecifiedYiaddr); "rejects DHCPACK with unspecified yiaddr")]
1462    #[test_case(VaryingReplyToRequestFields {
1463        op: dhcp_protocol::OpCode::BOOTREPLY,
1464        yiaddr: Ipv4Addr::UNSPECIFIED,
1465        message_type: Some(dhcp_protocol::MessageType::DHCPNAK),
1466        server_identifier: Some(Ipv4Addr::UNSPECIFIED),
1467        subnet_mask: None,
1468        lease_length_secs: None,
1469        renewal_time_secs: None,
1470        rebinding_time_secs: None,
1471        message: Some(MESSAGE.to_owned()),
1472        include_duplicate_option: false,
1473    } => Err(IncomingResponseToRequestError::CommonError(
1474        CommonIncomingMessageError::UnspecifiedServerIdentifier,
1475    )); "rejects DHCPNAK with unspecified server identifier")]
1476    #[test_case(VaryingReplyToRequestFields {
1477        op: dhcp_protocol::OpCode::BOOTREPLY,
1478        yiaddr: Ipv4Addr::UNSPECIFIED,
1479        message_type: Some(dhcp_protocol::MessageType::DHCPNAK),
1480        server_identifier: None,
1481        subnet_mask: None,
1482        lease_length_secs: None,
1483        renewal_time_secs: None,
1484        rebinding_time_secs: None,
1485        message: Some(MESSAGE.to_owned()),
1486        include_duplicate_option: false,
1487    } => Err(IncomingResponseToRequestError::NoServerIdentifier) ; "rejects DHCPNAK with no server identifier")]
1488    #[test_case(VaryingReplyToRequestFields {
1489        op: dhcp_protocol::OpCode::BOOTREQUEST,
1490        yiaddr: Ipv4Addr::UNSPECIFIED,
1491        message_type: Some(dhcp_protocol::MessageType::DHCPNAK),
1492        server_identifier: Some(SERVER_IP),
1493        subnet_mask: None,
1494        lease_length_secs: None,
1495        renewal_time_secs: None,
1496        rebinding_time_secs: None,
1497        message: Some(MESSAGE.to_owned()),
1498        include_duplicate_option: false,
1499    } => Err(IncomingResponseToRequestError::CommonError(
1500        CommonIncomingMessageError::NotBootReply(dhcp_protocol::OpCode::BOOTREQUEST),
1501    )) ; "rejects non-bootreply")]
1502    #[test_case(VaryingReplyToRequestFields {
1503        op: dhcp_protocol::OpCode::BOOTREPLY,
1504        yiaddr: Ipv4Addr::UNSPECIFIED,
1505        message_type: Some(dhcp_protocol::MessageType::DHCPOFFER),
1506        server_identifier: Some(SERVER_IP),
1507        subnet_mask: Some(TEST_SUBNET_MASK),
1508        lease_length_secs: None,
1509        renewal_time_secs: None,
1510        rebinding_time_secs: None,
1511        message: Some(MESSAGE.to_owned()),
1512        include_duplicate_option: false,
1513    } => Err(IncomingResponseToRequestError::NotDhcpAckOrNak(
1514        dhcp_protocol::MessageType::DHCPOFFER,
1515    )) ; "rejects non-DHCPACK or DHCPNAK")]
1516    #[test_case(VaryingReplyToRequestFields {
1517        op: dhcp_protocol::OpCode::BOOTREPLY,
1518        yiaddr: Ipv4Addr::UNSPECIFIED,
1519        message_type: None,
1520        server_identifier: Some(SERVER_IP),
1521        subnet_mask: None,
1522        lease_length_secs: None,
1523        renewal_time_secs: None,
1524        rebinding_time_secs: None,
1525        message: Some(MESSAGE.to_owned()),
1526        include_duplicate_option: false,
1527    } => Err(IncomingResponseToRequestError::CommonError(
1528        CommonIncomingMessageError::BuilderMissingField("message_type"),
1529    )) ; "rejects missing DHCP message type")]
1530    #[test_case( VaryingReplyToRequestFields {
1531            op: dhcp_protocol::OpCode::BOOTREPLY,
1532            yiaddr: YIADDR,
1533            message_type: Some(dhcp_protocol::MessageType::DHCPACK),
1534            server_identifier: Some(SERVER_IP),
1535            subnet_mask: Some(TEST_SUBNET_MASK),
1536            lease_length_secs: Some(LEASE_LENGTH_SECS),
1537            renewal_time_secs: Some(RENEWAL_TIME_SECS),
1538            rebinding_time_secs: Some(REBINDING_TIME_SECS),
1539            message: None,
1540            include_duplicate_option: true,
1541        } => Ok(IncomingResponseToRequest::Ack(FieldsToRetainFromAck {
1542            yiaddr: net_types::ip::Ipv4Addr::from(YIADDR)
1543                .try_into()
1544                .expect("should be specified"),
1545            server_identifier: Some(
1546                net_types::ip::Ipv4Addr::from(SERVER_IP)
1547                    .try_into()
1548                    .expect("should be specified"),
1549            ),
1550            ip_address_lease_time_secs: Some(LEASE_LENGTH_SECS_NONZERO),
1551            parameters: vec![
1552                dhcp_protocol::DhcpOption::SubnetMask(TEST_SUBNET_MASK),
1553                dhcp_protocol::DhcpOption::DomainName(DOMAIN_NAME.to_owned())
1554            ],
1555            renewal_time_value_secs: Some(RENEWAL_TIME_SECS),
1556            rebinding_time_value_secs: Some(REBINDING_TIME_SECS),
1557        })); "accepts good DHCPACK with duplicate option")]
1558    fn fields_to_retain_during_requesting(
1559        incoming_fields: VaryingReplyToRequestFields,
1560    ) -> Result<
1561        IncomingResponseToRequest<Option<net_types::SpecifiedAddr<net_types::ip::Ipv4Addr>>>,
1562        IncomingResponseToRequestError,
1563    > {
1564        use super::fields_to_retain_from_response_to_request as fields;
1565        use dhcp_protocol::DhcpOption;
1566
1567        let VaryingReplyToRequestFields {
1568            op,
1569            yiaddr,
1570            message_type,
1571            server_identifier,
1572            subnet_mask,
1573            lease_length_secs,
1574            renewal_time_secs,
1575            rebinding_time_secs,
1576            message,
1577            include_duplicate_option,
1578        } = incoming_fields;
1579
1580        let message = dhcp_protocol::Message {
1581            op,
1582            xid: 1,
1583            secs: 0,
1584            bdcast_flag: false,
1585            ciaddr: Ipv4Addr::UNSPECIFIED,
1586            yiaddr,
1587            siaddr: Ipv4Addr::UNSPECIFIED,
1588            giaddr: Ipv4Addr::UNSPECIFIED,
1589            chaddr: net_mac!("01:02:03:04:05:06"),
1590            sname: String::new(),
1591            file: String::new(),
1592            options: std::iter::empty()
1593                .chain(message_type.map(DhcpOption::DhcpMessageType))
1594                .chain(server_identifier.map(DhcpOption::ServerIdentifier))
1595                .chain(subnet_mask.map(DhcpOption::SubnetMask))
1596                .chain(lease_length_secs.map(DhcpOption::IpAddressLeaseTime))
1597                .chain(renewal_time_secs.map(DhcpOption::RenewalTimeValue))
1598                .chain(rebinding_time_secs.map(DhcpOption::RebindingTimeValue))
1599                .chain(message.map(DhcpOption::Message))
1600                // Include a parameter that the client didn't request so that we can
1601                // assert that the client ignored it.
1602                .chain(std::iter::once(dhcp_protocol::DhcpOption::InterfaceMtu(1)))
1603                // Include a parameter that the client did request so that we can
1604                // check that it's included in the acquired parameters map.
1605                .chain(std::iter::once(dhcp_protocol::DhcpOption::DomainName(
1606                    DOMAIN_NAME.to_owned(),
1607                )))
1608                .chain(
1609                    include_duplicate_option
1610                        .then_some(dhcp_protocol::DhcpOption::DomainName(DOMAIN_NAME.to_owned())),
1611                )
1612                .collect(),
1613        };
1614
1615        fields(
1616            &[
1617                (dhcp_protocol::OptionCode::SubnetMask, OptionRequested::Required),
1618                (dhcp_protocol::OptionCode::DomainName, OptionRequested::Optional),
1619            ]
1620            .into_iter()
1621            .collect(),
1622            message,
1623        )
1624    }
1625}