Skip to main content

erbium/dhcp/
dhcppkt.rs

1/*   Copyright 2024 Perry Lorier
2 *
3 *  Licensed under the Apache License, Version 2.0 (the "License");
4 *  you may not use this file except in compliance with the License.
5 *  You may obtain a copy of the License at
6 *
7 *      http://www.apache.org/licenses/LICENSE-2.0
8 *
9 *  Unless required by applicable law or agreed to in writing, software
10 *  distributed under the License is distributed on an "AS IS" BASIS,
11 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 *  See the License for the specific language governing permissions and
13 *  limitations under the License.
14 *
15 *  SPDX-License-Identifier: Apache-2.0
16 *
17 *  Parsing/Serialisation for a DHCP Packet.
18 */
19
20use crate::pktparser;
21use std::collections;
22use std::fmt;
23use std::net;
24
25#[derive(Debug, PartialEq, Eq)]
26pub enum ParseError {
27    UnexpectedEndOfInput,
28    WrongMagic,
29    InvalidPacket,
30}
31
32impl std::error::Error for ParseError {
33    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
34        None
35    }
36}
37
38impl std::fmt::Display for ParseError {
39    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
40        match self {
41            ParseError::UnexpectedEndOfInput => write!(f, "Unexpected End Of Input"),
42            ParseError::WrongMagic => write!(f, "Wrong Magic"),
43            ParseError::InvalidPacket => write!(f, "Invalid Packet"),
44        }
45    }
46}
47
48impl ParseError {
49    pub const fn get_variant_name(&self) -> &'static str {
50        use ParseError::*;
51        match self {
52            UnexpectedEndOfInput => "TRUNCATED_PACKET",
53            WrongMagic => "WRONG_MAGIC",
54            InvalidPacket => "INVALID_PACKET",
55        }
56    }
57}
58
59#[derive(PartialEq, Eq)]
60pub struct DhcpOp(u8);
61pub const OP_BOOTREQUEST: DhcpOp = DhcpOp(1);
62pub const OP_BOOTREPLY: DhcpOp = DhcpOp(2);
63
64impl std::fmt::Display for DhcpOp {
65    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
66        match self {
67            &OP_BOOTREQUEST => write!(f, "BOOTREQUEST"),
68            &OP_BOOTREPLY => write!(f, "BOOTREPLY"),
69            DhcpOp(x) => write!(f, "#{}", x),
70        }
71    }
72}
73
74impl fmt::Debug for DhcpOp {
75    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
76        write!(f, "DhcpOp({})", self)
77    }
78}
79
80#[derive(PartialEq, Eq)]
81pub struct HwType(u8);
82pub const HWTYPE_ETHERNET: HwType = HwType(1);
83
84impl std::fmt::Display for HwType {
85    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
86        match self {
87            &HWTYPE_ETHERNET => write!(f, "Ethernet"),
88            HwType(x) => write!(f, "#{x}"),
89        }
90    }
91}
92
93impl fmt::Debug for HwType {
94    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
95        write!(f, "HwType({})", self)
96    }
97}
98
99#[derive(Copy, Clone, PartialEq, Eq)]
100pub struct MessageType(u8);
101pub const DHCPDISCOVER: MessageType = MessageType(1);
102pub const DHCPOFFER: MessageType = MessageType(2);
103pub const DHCPREQUEST: MessageType = MessageType(3);
104pub const DHCPDECLINE: MessageType = MessageType(4);
105pub const DHCPACK: MessageType = MessageType(5);
106pub const DHCPNAK: MessageType = MessageType(6);
107pub const DHCPRELEASE: MessageType = MessageType(7);
108pub const DHCPINFORM: MessageType = MessageType(8);
109pub const DHCPFORCERENEW: MessageType = MessageType(9);
110
111impl std::fmt::Display for MessageType {
112    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
113        match self {
114            &DHCPDISCOVER => write!(f, "DHCPDISCOVER"),
115            &DHCPOFFER => write!(f, "DHCPOFFER"),
116            &DHCPREQUEST => write!(f, "DHCPREQUEST"),
117            &DHCPDECLINE => write!(f, "DHCPDECLINE"),
118            &DHCPACK => write!(f, "DHCPACK"),
119            &DHCPNAK => write!(f, "DHCPNAK"),
120            &DHCPRELEASE => write!(f, "DHCPRELEASE"),
121            &DHCPINFORM => write!(f, "DHCPINFORM"),
122            &DHCPFORCERENEW => write!(f, "DHCPFORCERENEW"),
123            MessageType(x) => write!(f, "#{x}"),
124        }
125    }
126}
127
128impl fmt::Debug for MessageType {
129    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
130        write!(f, "{}", self)
131    }
132}
133
134impl std::default::Default for MessageType {
135    fn default() -> Self {
136        DHCPNAK
137    }
138}
139
140#[derive(Copy, Clone, PartialEq, Eq, Hash)]
141pub struct DhcpOption(u8);
142pub const OPTION_NETMASK: DhcpOption = DhcpOption(1);
143pub const OPTION_TIMEOFFSET: DhcpOption = DhcpOption(2);
144pub const OPTION_ROUTERADDR: DhcpOption = DhcpOption(3);
145pub const OPTION_TIMESERVER: DhcpOption = DhcpOption(4);
146pub const OPTION_NAMESERVER: DhcpOption = DhcpOption(5);
147pub const OPTION_DOMAINSERVER: DhcpOption = DhcpOption(6);
148pub const OPTION_LOGSERVER: DhcpOption = DhcpOption(7);
149pub const OPTION_QUOTESERVER: DhcpOption = DhcpOption(8);
150pub const OPTION_LPRSERVER: DhcpOption = DhcpOption(9);
151pub const OPTION_IMPRESSSERVER: DhcpOption = DhcpOption(10);
152pub const OPTION_RLPSERVER: DhcpOption = DhcpOption(11);
153pub const OPTION_HOSTNAME: DhcpOption = DhcpOption(12);
154pub const OPTION_DOMAINNAME: DhcpOption = DhcpOption(15);
155pub const OPTION_ROOTPATH: DhcpOption = DhcpOption(17);
156pub const OPTION_EXTFILE: DhcpOption = DhcpOption(18);
157pub const OPTION_FORWARD: DhcpOption = DhcpOption(19);
158pub const OPTION_SRCRT: DhcpOption = DhcpOption(20);
159pub const OPTION_MAXDGASSM: DhcpOption = DhcpOption(21);
160pub const OPTION_TTL: DhcpOption = DhcpOption(23);
161pub const OPTION_MTUTMOUT: DhcpOption = DhcpOption(24);
162pub const OPTION_MTUIF: DhcpOption = DhcpOption(26);
163pub const OPTION_MTUSUB: DhcpOption = DhcpOption(27);
164pub const OPTION_BROADCAST: DhcpOption = DhcpOption(28);
165pub const OPTION_MASKDISCOVERY: DhcpOption = DhcpOption(29);
166pub const OPTION_MASKSUPPLIER: DhcpOption = DhcpOption(30);
167pub const OPTION_RTRDISCOVERY: DhcpOption = DhcpOption(31);
168pub const OPTION_RTRREQ: DhcpOption = DhcpOption(32);
169pub const OPTION_STATICROUTE: DhcpOption = DhcpOption(33);
170pub const OPTION_TRAILERS: DhcpOption = DhcpOption(34);
171pub const OPTION_ARPTIMEOUT: DhcpOption = DhcpOption(35);
172pub const OPTION_ETHERNET: DhcpOption = DhcpOption(36);
173pub const OPTION_TCPTTL: DhcpOption = DhcpOption(37);
174pub const OPTION_TCPKEEPALIVE: DhcpOption = DhcpOption(38);
175pub const OPTION_TCPKEEPALIVEGARBAGE: DhcpOption = DhcpOption(39);
176pub const OPTION_NISDOMAIN: DhcpOption = DhcpOption(40);
177pub const OPTION_NISSERVERS: DhcpOption = DhcpOption(41);
178pub const OPTION_NTPSERVERS: DhcpOption = DhcpOption(42);
179pub const OPTION_VENDOR: DhcpOption = DhcpOption(43);
180pub const OPTION_NETBIOSNAMESRV: DhcpOption = DhcpOption(44);
181pub const OPTION_NETBIOSDISTSRV: DhcpOption = DhcpOption(45);
182pub const OPTION_NETBIOSTYPE: DhcpOption = DhcpOption(46);
183pub const OPTION_NETBIOSSCOPE: DhcpOption = DhcpOption(47);
184pub const OPTION_XWFONTSRVS: DhcpOption = DhcpOption(48);
185pub const OPTION_XWDISPLAY: DhcpOption = DhcpOption(49);
186pub const OPTION_ADDRESSREQUEST: DhcpOption = DhcpOption(50);
187pub const OPTION_LEASETIME: DhcpOption = DhcpOption(51);
188pub const OPTION_MSGTYPE: DhcpOption = DhcpOption(53);
189pub const OPTION_SERVERID: DhcpOption = DhcpOption(54);
190pub const OPTION_PARAMLIST: DhcpOption = DhcpOption(55);
191pub const OPTION_MESSAGE: DhcpOption = DhcpOption(56);
192pub const OPTION_MAXMSGSIZE: DhcpOption = DhcpOption(57);
193pub const OPTION_RENEWALTIME: DhcpOption = DhcpOption(58);
194pub const OPTION_REBINDTIME: DhcpOption = DhcpOption(59);
195pub const OPTION_VENDOR_CLASS: DhcpOption = DhcpOption(60);
196pub const OPTION_CLIENTID: DhcpOption = DhcpOption(61);
197pub const OPTION_NIS3DOMAIN: DhcpOption = DhcpOption(64);
198pub const OPTION_NIS3SERVERS: DhcpOption = DhcpOption(65);
199pub const OPTION_HOMEAGENT: DhcpOption = DhcpOption(68);
200pub const OPTION_SMTP: DhcpOption = DhcpOption(69);
201pub const OPTION_POP3: DhcpOption = DhcpOption(70);
202pub const OPTION_NNTP: DhcpOption = DhcpOption(71);
203pub const OPTION_WWW: DhcpOption = DhcpOption(72);
204pub const OPTION_FINGER: DhcpOption = DhcpOption(73);
205pub const OPTION_IRC: DhcpOption = DhcpOption(74);
206pub const OPTION_STREETTALK: DhcpOption = DhcpOption(75);
207pub const OPTION_STDA: DhcpOption = DhcpOption(76);
208pub const OPTION_USERCLASS: DhcpOption = DhcpOption(77); /* RFC3004 */
209pub const OPTION_FQDN: DhcpOption = DhcpOption(81); /* RFC4702 */
210pub const OPTION_UUID: DhcpOption = DhcpOption(97); /* RFC4578 */
211pub const OPTION_PCODE: DhcpOption = DhcpOption(100); /* RFC4833 */
212pub const OPTION_TCODE: DhcpOption = DhcpOption(101); /* RFC4833 */
213pub const OPTION_AUTOCONF: DhcpOption = DhcpOption(103);
214pub const OPTION_SUBNETSELECT: DhcpOption = DhcpOption(104);
215pub const OPTION_IPV6PREFERRED: DhcpOption = DhcpOption(108);
216pub const OPTION_CAPTIVEPORTAL: DhcpOption = DhcpOption(114); /* RFC8910, previously in RFC7710 defined as 160 */
217pub const OPTION_DOMAINSEARCH: DhcpOption = DhcpOption(119);
218pub const OPTION_SIPSERVERS: DhcpOption = DhcpOption(120);
219pub const OPTION_CIDRROUTE: DhcpOption = DhcpOption(121);
220pub const OPTION_WPAD: DhcpOption = DhcpOption(252);
221
222const OPT_INFO: &[(&str, DhcpOption, DhcpOptionType)] = &[
223    ("netmask", OPTION_NETMASK, DhcpOptionType::Ip),
224    ("time-offset", OPTION_TIMEOFFSET, DhcpOptionType::I32),
225    ("routers", OPTION_ROUTERADDR, DhcpOptionType::IpList),
226    ("time-servers", OPTION_TIMESERVER, DhcpOptionType::IpList),
227    ("name-servers", OPTION_NAMESERVER, DhcpOptionType::IpList),
228    ("dns-servers", OPTION_DOMAINSERVER, DhcpOptionType::IpList),
229    ("log-servers", OPTION_LOGSERVER, DhcpOptionType::IpList),
230    ("quote-servers", OPTION_QUOTESERVER, DhcpOptionType::IpList),
231    ("lpr-servers", OPTION_LPRSERVER, DhcpOptionType::IpList),
232    // 10
233    (
234        "impress-servers",
235        OPTION_IMPRESSSERVER,
236        DhcpOptionType::IpList,
237    ),
238    ("rlp-servers", OPTION_RLPSERVER, DhcpOptionType::IpList),
239    ("host-name", OPTION_HOSTNAME, DhcpOptionType::String),
240    //("bootfile-size", OPTION_BOOTFILESZ, DhcpOptionType::u16),
241    //("merit-dump-file", OPTION_MRTDUMPF, ...)
242    ("domain-name", OPTION_DOMAINNAME, DhcpOptionType::String),
243    //("swap-server", OPTION_SWAPSRV, ...)
244    ("root-path", OPTION_ROOTPATH, DhcpOptionType::String),
245    ("extension-file", OPTION_EXTFILE, DhcpOptionType::String),
246    ("forward", OPTION_FORWARD, DhcpOptionType::Bool),
247    // 20
248    ("source-route", OPTION_SRCRT, DhcpOptionType::Bool),
249    //("policy-filter", OPTION_POLICYFLT, DhcpOptionType::...),
250    (
251        "max-reassembly",
252        OPTION_MAXDGASSM,
253        DhcpOptionType::Seconds16,
254    ),
255    ("default-ttl", OPTION_TTL, DhcpOptionType::U8),
256    ("mtu-timeout", OPTION_MTUTMOUT, DhcpOptionType::Seconds32),
257    //("mtu-plateu", OPTION_MTUPLATEAU, DhcpOptionType::...), [u16]
258    ("mtu", OPTION_MTUIF, DhcpOptionType::U16),
259    ("mtu-subnet", OPTION_MTUSUB, DhcpOptionType::Bool),
260    ("broadcast", OPTION_BROADCAST, DhcpOptionType::Ip),
261    ("mask-discovery", OPTION_MASKDISCOVERY, DhcpOptionType::Bool),
262    // 30
263    ("mask-supplier", OPTION_MASKSUPPLIER, DhcpOptionType::Bool),
264    (
265        "router-discovery",
266        OPTION_RTRDISCOVERY,
267        DhcpOptionType::Bool,
268    ),
269    ("router-request", OPTION_RTRREQ, DhcpOptionType::Ip),
270    (
271        "classful-route",
272        OPTION_STATICROUTE,
273        DhcpOptionType::Unknown,
274    ), // Needs special handling. -- DNI
275    ("trailers", OPTION_TRAILERS, DhcpOptionType::Bool),
276    ("arp-timeout", OPTION_ARPTIMEOUT, DhcpOptionType::Seconds32),
277    ("ethernet", OPTION_ETHERNET, DhcpOptionType::Bool),
278    ("tcp-ttl", OPTION_TCPTTL, DhcpOptionType::U16),
279    (
280        "tcp-keepalive",
281        OPTION_TCPKEEPALIVE,
282        DhcpOptionType::Seconds32,
283    ),
284    (
285        "tcp-keepalive-garbage",
286        OPTION_TCPKEEPALIVEGARBAGE,
287        DhcpOptionType::Bool,
288    ),
289    // 40
290    ("nis-domain", OPTION_NISDOMAIN, DhcpOptionType::String),
291    ("nis-servers", OPTION_NISSERVERS, DhcpOptionType::IpList),
292    ("ntp-servers", OPTION_NTPSERVERS, DhcpOptionType::IpList),
293    // vendor specific options should be handled specially.
294    ("vendor", OPTION_VENDOR, DhcpOptionType::Unknown),
295    (
296        "netbios-namesrv",
297        OPTION_NETBIOSNAMESRV,
298        DhcpOptionType::IpList,
299    ),
300    (
301        "netbios-distsrv",
302        OPTION_NETBIOSDISTSRV,
303        DhcpOptionType::IpList,
304    ),
305    ("netbios-type", OPTION_NETBIOSTYPE, DhcpOptionType::U8), /* enum? */
306    ("netbios-scope", OPTION_NETBIOSSCOPE, DhcpOptionType::String),
307    (
308        "xwindow-font-servers",
309        OPTION_XWFONTSRVS,
310        DhcpOptionType::IpList,
311    ),
312    ("xwindow-display", OPTION_XWDISPLAY, DhcpOptionType::IpList),
313    // 50
314    ("address-request", OPTION_ADDRESSREQUEST, DhcpOptionType::Ip),
315    ("lease-time", OPTION_LEASETIME, DhcpOptionType::Seconds32),
316    // overload, handled specially.
317    // dhcp msg type, handled specially.
318    ("server-id", OPTION_SERVERID, DhcpOptionType::Ip),
319    // parameter list, handled specially.
320    ("message", OPTION_MESSAGE, DhcpOptionType::String),
321    ("max-size", OPTION_MAXMSGSIZE, DhcpOptionType::U16),
322    (
323        "renewal-time",
324        OPTION_RENEWALTIME,
325        DhcpOptionType::Seconds16,
326    ), // seconds
327    ("rebind-time", OPTION_REBINDTIME, DhcpOptionType::Seconds16), // seconds
328    // 60
329    ("class-id", OPTION_VENDOR_CLASS, DhcpOptionType::String),
330    ("client-id", OPTION_CLIENTID, DhcpOptionType::HwAddr),
331    // netware
332    // netware
333    ("nisplus-domain", OPTION_NIS3DOMAIN, DhcpOptionType::String),
334    (
335        "nisplus-servers",
336        OPTION_NIS3SERVERS,
337        DhcpOptionType::IpList,
338    ),
339    //("tftp-server",  OPTION_TFTPSERVER, DhcpOptionType::String), handled specially.
340    //bootfile name, handled specially.
341    (
342        "home-agent-servers",
343        OPTION_HOMEAGENT,
344        DhcpOptionType::IpList,
345    ),
346    ("smtp-servers", OPTION_SMTP, DhcpOptionType::IpList),
347    // 70
348    ("pop3-servers", OPTION_POP3, DhcpOptionType::IpList),
349    ("nntp-servers", OPTION_NNTP, DhcpOptionType::IpList),
350    ("www-servers", OPTION_WWW, DhcpOptionType::IpList),
351    ("finger-servers", OPTION_FINGER, DhcpOptionType::IpList),
352    ("irc-servers", OPTION_IRC, DhcpOptionType::IpList),
353    (
354        "streettalk-servers",
355        OPTION_STREETTALK,
356        DhcpOptionType::IpList,
357    ),
358    ("stda-servers", OPTION_STDA, DhcpOptionType::IpList),
359    ("user-class", OPTION_USERCLASS, DhcpOptionType::String),
360    //("directory-agent", OPTION_DIRECTORY_AGENT, DhcpOptionType::Unknown),
361    //("service-scope", OPTION_SERVICE_SCOPE
362    // 80
363    //("rapid-commit", OPTION_RAPID_COMMIT
364    ("fqdn", OPTION_FQDN, DhcpOptionType::String),
365    // option 82 (relay agent information) needs special handling.
366    // iSNS
367    // NDS Servers
368    // NDS Tree
369    // NDS Context
370    // BCMCS
371    // BCMCS
372    // 90
373    // Authentication, needs special handling
374    // client-last-transaction-time, RFC4388
375    // associated-ip, RFC4388
376    // client-system, RFC4578
377    // client-ndi, RFC4578
378    // ldap, RFC3679
379    //
380    ("uuid", OPTION_UUID, DhcpOptionType::Unknown), //RFC4578
381    // userauth, RFC2485
382    // geoconf civic, RFC4776
383    // 100
384    ("tz-rule", OPTION_PCODE, DhcpOptionType::String),
385    ("tz-name", OPTION_TCODE, DhcpOptionType::String),
386    // uuid/guid
387    ("autoconfig", OPTION_AUTOCONF, DhcpOptionType::Bool),
388    ("subnet-selection", OPTION_SUBNETSELECT, DhcpOptionType::Ip), // RFC3011 -- needs better support
389    (
390        "dns-searches",
391        OPTION_DOMAINSEARCH,
392        DhcpOptionType::DomainList,
393    ), // RFC3397
394    (
395        "ipv6-preferred",
396        OPTION_IPV6PREFERRED,
397        DhcpOptionType::Seconds32,
398    ), // RFC8925
399    (
400        "captive-portal",
401        OPTION_CAPTIVEPORTAL,
402        DhcpOptionType::String,
403    ), // RFC8910
404    ("sip-servers", OPTION_SIPSERVERS, DhcpOptionType::Unknown),   // RFC3361
405    ("routes", OPTION_CIDRROUTE, DhcpOptionType::Routes),
406    //122: Cablelabs Client configuration, RFC3495
407    //123: GeoConf, RFC6225
408    //124: Vendor Identifying Vendor Class -- needs special support, RFC3925
409    //125: Vendor Identifying Vendor Specific Information -- needs special support, RFC3925
410    ("wpad-url", OPTION_WPAD, DhcpOptionType::String),
411];
412
413impl From<u8> for DhcpOption {
414    fn from(v: u8) -> Self {
415        DhcpOption(v)
416    }
417}
418
419#[derive(Copy, Clone)]
420pub enum DhcpOptionType {
421    String,
422    Ip,
423    IpList,
424    I32,
425    U8,
426    U16,
427    U32,
428    Bool,
429    Seconds16,
430    Seconds32,
431    HwAddr,
432    Routes,
433    DomainList,
434    Unknown,
435}
436
437type IpList = Vec<std::net::Ipv4Addr>;
438type U8Str = Vec<u8>;
439
440impl DhcpOptionType {
441    pub fn decode(&self, v: &[u8]) -> Option<DhcpOptionTypeValue> {
442        match *self {
443            DhcpOptionType::String => U8Str::parse_into(v)
444                .map(|x| DhcpOptionTypeValue::String(String::from_utf8_lossy(&x).to_string())),
445            DhcpOptionType::Ip => std::net::Ipv4Addr::parse_into(v).map(DhcpOptionTypeValue::Ip),
446            DhcpOptionType::IpList => IpList::parse_into(v).map(DhcpOptionTypeValue::IpList),
447            DhcpOptionType::I32 => i32::parse_into(v).map(DhcpOptionTypeValue::I32),
448            DhcpOptionType::U8 => u8::parse_into(v).map(DhcpOptionTypeValue::U8),
449            DhcpOptionType::U16 => u16::parse_into(v).map(DhcpOptionTypeValue::U16),
450            DhcpOptionType::U32 => u32::parse_into(v).map(DhcpOptionTypeValue::U32),
451            DhcpOptionType::Bool => u8::parse_into(v).map(DhcpOptionTypeValue::U8), // ?
452            DhcpOptionType::Seconds16 => u16::parse_into(v).map(DhcpOptionTypeValue::U16), // ?
453            DhcpOptionType::Seconds32 => u32::parse_into(v).map(DhcpOptionTypeValue::U32), // ?
454            DhcpOptionType::HwAddr => U8Str::parse_into(v).map(DhcpOptionTypeValue::HwAddr),
455            DhcpOptionType::Routes => Vec::<Route>::parse_into(v).map(DhcpOptionTypeValue::Routes),
456            DhcpOptionType::DomainList => {
457                Vec::<String>::parse_into(v).map(DhcpOptionTypeValue::DomainList)
458            }
459            DhcpOptionType::Unknown => U8Str::parse_into(v).map(DhcpOptionTypeValue::Unknown),
460        }
461    }
462}
463
464#[derive(Debug, Clone)]
465pub enum DhcpOptionTypeValue {
466    String(String),
467    IpList(IpList),
468    Ip(std::net::Ipv4Addr),
469    I32(i32),
470    U8(u8),
471    U16(u16),
472    U32(u32),
473    HwAddr(Vec<u8>),
474    Routes(Vec<Route>),
475    DomainList(Vec<String>),
476    Unknown(Vec<u8>),
477}
478
479impl DhcpOptionTypeValue {
480    pub fn as_bytes(&self) -> Vec<u8> {
481        match self {
482            DhcpOptionTypeValue::String(s) => s.as_bytes().to_vec(),
483            DhcpOptionTypeValue::IpList(v) => {
484                v.iter().map(|x| x.octets()).fold(vec![], |mut acc, v| {
485                    acc.extend(v.iter());
486                    acc
487                })
488            }
489            DhcpOptionTypeValue::Ip(i) => i.octets().to_vec(),
490            DhcpOptionTypeValue::I32(x) => x.to_be_bytes().to_vec(),
491            DhcpOptionTypeValue::U8(x) => x.to_be_bytes().to_vec(),
492            DhcpOptionTypeValue::U16(x) => x.to_be_bytes().to_vec(),
493            DhcpOptionTypeValue::U32(x) => x.to_be_bytes().to_vec(),
494            DhcpOptionTypeValue::HwAddr(x) => x.clone(),
495            DhcpOptionTypeValue::Routes(v) => {
496                let mut o = vec![];
497                for i in v {
498                    o.push(i.prefix.prefixlen);
499                    o.extend(i.prefix.addr.octets().iter());
500                    o.extend(i.nexthop.octets().iter());
501                }
502                o
503            }
504            DhcpOptionTypeValue::Unknown(v) => v.clone(),
505            DhcpOptionTypeValue::DomainList(l) => {
506                let mut o = vec![];
507                for domains in l.iter().map(|d| d.split('.')) {
508                    for label in domains {
509                        o.push(label.len() as u8);
510                        o.extend(label.as_bytes());
511                    }
512                    o.push(0_u8)
513                }
514                o
515            }
516        }
517    }
518}
519
520fn escape_char(&c: &u8) -> String {
521    match c {
522        b' '..=b'~' => char::from(c).to_string(),
523        x => format!("\\x{:0>2x}", x),
524    }
525}
526
527fn escape_str(c: &[u8]) -> String {
528    c.iter().map(escape_char).collect::<Vec<String>>().join("")
529}
530
531impl std::fmt::Display for DhcpOptionTypeValue {
532    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
533        match self {
534            DhcpOptionTypeValue::String(s) => write!(f, "{}", escape_str(s.as_bytes())),
535            DhcpOptionTypeValue::Ip(i) => i.fmt(f),
536            DhcpOptionTypeValue::U8(i) => i.fmt(f),
537            DhcpOptionTypeValue::U16(i) => i.fmt(f),
538            DhcpOptionTypeValue::U32(i) => i.fmt(f),
539            DhcpOptionTypeValue::I32(i) => i.fmt(f),
540            DhcpOptionTypeValue::IpList(l) => write!(
541                f,
542                "{}",
543                l.iter()
544                    .map(|i| format!("{}", i))
545                    .collect::<Vec<String>>()
546                    .join(",")
547            ),
548            DhcpOptionTypeValue::HwAddr(x) => write!(
549                f,
550                "{}",
551                x.iter()
552                    .map(|b| format!("{:0>2x}", b))
553                    .collect::<Vec<String>>()
554                    .join(":")
555            ),
556            DhcpOptionTypeValue::Routes(l) => write!(
557                f,
558                "{}",
559                l.iter()
560                    .map(|i| format!("{}->{}", i.prefix, i.nexthop))
561                    .collect::<Vec<String>>()
562                    .join(",")
563            ),
564            DhcpOptionTypeValue::Unknown(v) => write!(
565                f,
566                "{}",
567                v.iter()
568                    .map(|b| format!("{:0>2x}", b))
569                    .collect::<Vec<_>>()
570                    .join("")
571            ),
572            DhcpOptionTypeValue::DomainList(v) => write!(f, "{}", v.join(",")),
573        }
574    }
575}
576
577impl DhcpOption {
578    pub const fn new(opt: u8) -> Self {
579        DhcpOption(opt)
580    }
581    pub fn get_type(&self) -> Option<DhcpOptionType> {
582        for (_name, option, ty) in OPT_INFO {
583            if option == self {
584                return Some(*ty);
585            }
586        }
587        None
588    }
589}
590
591pub fn name_to_option(lookup_name: &str) -> Option<DhcpOption> {
592    for (name, option, _ty) in OPT_INFO {
593        if *name == lookup_name {
594            return Some(*option);
595        }
596    }
597    None
598}
599
600impl std::fmt::Display for DhcpOption {
601    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
602        for (name, option, _ty) in OPT_INFO {
603            if option == self {
604                return write!(f, "{name}");
605            }
606        }
607        write!(f, "#{}", self.0)
608    }
609}
610
611impl fmt::Debug for DhcpOption {
612    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
613        write!(f, "{}", self)
614    }
615}
616
617pub trait DhcpParse {
618    type Item;
619    fn parse_into(v: &[u8]) -> Option<Self::Item>;
620}
621
622#[derive(Clone, Debug)]
623pub struct Route {
624    pub prefix: erbium_net::Ipv4Subnet,
625    pub nexthop: std::net::Ipv4Addr,
626}
627
628fn parse_ip_from_iter<I>(it: &mut I) -> Option<std::net::Ipv4Addr>
629where
630    I: std::iter::Iterator<Item = u8>,
631{
632    let ip1 = it.next()?;
633    let ip2 = it.next()?;
634    let ip3 = it.next()?;
635    let ip4 = it.next()?;
636    Some(net::Ipv4Addr::new(ip1, ip2, ip3, ip4))
637}
638
639impl DhcpParse for Vec<Route> {
640    type Item = Self;
641    fn parse_into(v: &[u8]) -> Option<Self::Item> {
642        let mut it = v.iter().copied();
643        let mut ret = vec![];
644        while let Some(prefixlen) = it.next() {
645            let prefix =
646                erbium_net::Ipv4Subnet::new(parse_ip_from_iter(&mut it)?, prefixlen).ok()?;
647            let nexthop = parse_ip_from_iter(&mut it)?;
648            ret.push(Route { prefix, nexthop });
649        }
650        Some(ret)
651    }
652}
653
654impl DhcpParse for std::net::Ipv4Addr {
655    type Item = Self;
656    fn parse_into(v: &[u8]) -> Option<Self::Item> {
657        if v.len() != 4 {
658            None
659        } else {
660            Some(std::net::Ipv4Addr::new(v[0], v[1], v[2], v[3]))
661        }
662    }
663}
664
665impl DhcpParse for IpList {
666    type Item = Self;
667    fn parse_into(v: &[u8]) -> Option<Self::Item> {
668        let mut it = v.iter().copied();
669        let mut ret = vec![];
670        while let Some(o1) = it.next() {
671            let o2 = it.next();
672            let o3 = it.next();
673            let o4 = it.next();
674            ret.push(std::net::Ipv4Addr::new(o1, o2?, o3?, o4?));
675        }
676        Some(ret)
677    }
678}
679
680/* HELP WANTED: I can't figure out how to make this a straight &[u8] -> Some(&[u8]) with no copies,
681 * while preserving lifetimes etc.
682 */
683impl DhcpParse for Vec<u8> {
684    type Item = Self;
685    fn parse_into(v: &[u8]) -> Option<Self> {
686        Some(v.to_vec())
687    }
688}
689
690/* This doesn't actually parse into a u64, this just parses as many bytes as it can find into a u64
691 */
692impl DhcpParse for u64 {
693    type Item = Self;
694    fn parse_into(v: &[u8]) -> Option<Self> {
695        Some(v.iter().fold(0_u64, |acc, &v| (acc << 8) + (v as Self)))
696    }
697}
698
699impl DhcpParse for u32 {
700    type Item = Self;
701    fn parse_into(v: &[u8]) -> Option<Self> {
702        Some(v.iter().fold(0_u32, |acc, &v| (acc << 8) + (v as Self)))
703    }
704}
705
706impl DhcpParse for u16 {
707    type Item = Self;
708    fn parse_into(v: &[u8]) -> Option<Self> {
709        Some(v.iter().fold(0_u16, |acc, &v| (acc << 8) + (v as Self)))
710    }
711}
712
713impl DhcpParse for i32 {
714    type Item = Self;
715    fn parse_into(v: &[u8]) -> Option<Self> {
716        Some(v.iter().fold(0_i32, |acc, &v| (acc << 8) + (v as Self)))
717    }
718}
719
720impl DhcpParse for u8 {
721    type Item = Self;
722    fn parse_into(v: &[u8]) -> Option<Self> {
723        if v.len() != 1 {
724            None
725        } else {
726            v.first().copied()
727        }
728    }
729}
730
731impl DhcpParse for std::time::Duration {
732    type Item = Self;
733    fn parse_into(v: &[u8]) -> Option<Self> {
734        u64::parse_into(v).map(std::time::Duration::from_secs)
735    }
736}
737
738impl DhcpParse for MessageType {
739    type Item = Self;
740    fn parse_into(v: &[u8]) -> Option<Self> {
741        if v.len() != 1 {
742            None
743        } else {
744            Some(MessageType(v[0]))
745        }
746    }
747}
748
749impl DhcpParse for Vec<String> {
750    type Item = Self;
751    fn parse_into(v: &[u8]) -> Option<Self> {
752        let mut buf = crate::pktparser::Buffer::new(v);
753        Some(buf.get_domains()?.iter().map(|d| d.join(".")).collect())
754    }
755}
756
757impl DhcpParse for String {
758    type Item = Self;
759    fn parse_into(v: &[u8]) -> Option<Self> {
760        Some(String::from_utf8_lossy(v).to_string())
761    }
762}
763
764#[derive(Debug, Clone, PartialEq, Default, Eq)]
765pub struct DhcpOptions {
766    pub other: collections::HashMap<DhcpOption, Vec<u8>>,
767}
768
769impl DhcpOptions {
770    pub fn get_raw_option(&self, option: &DhcpOption) -> Option<&[u8]> {
771        self.other.get(option).map(|x| x.as_slice())
772    }
773
774    pub fn get_option<T: DhcpParse>(&self, option: &DhcpOption) -> Option<T::Item> {
775        self.get_raw_option(option).and_then(|x| T::parse_into(x))
776    }
777
778    pub fn get_serverid(&self) -> Option<std::net::Ipv4Addr> {
779        self.get_option::<std::net::Ipv4Addr>(&OPTION_SERVERID)
780    }
781
782    pub fn get_clientid(&self) -> Option<Vec<u8>> {
783        self.get_option::<Vec<u8>>(&OPTION_CLIENTID)
784    }
785
786    pub fn get_address_request(&self) -> Option<net::Ipv4Addr> {
787        self.get_option::<std::net::Ipv4Addr>(&OPTION_ADDRESSREQUEST)
788    }
789
790    pub fn get_messagetype(&self) -> Option<MessageType> {
791        self.get_option::<MessageType>(&OPTION_MSGTYPE)
792    }
793
794    pub fn get_hostname(&self) -> Option<String> {
795        self.get_option::<String>(&OPTION_HOSTNAME)
796    }
797
798    #[must_use]
799    pub fn set_raw_option(mut self, option: &DhcpOption, value: &[u8]) -> Self {
800        self.other.insert(*option, value.to_vec());
801        self
802    }
803
804    #[must_use]
805    pub fn set_option<T: Serialise>(self, option: &DhcpOption, value: &T) -> Self {
806        let mut v = Vec::new();
807        value.serialise(&mut v);
808        self.set_raw_option(option, &v)
809    }
810
811    pub fn mutate_option<T: Serialise>(&mut self, option: &DhcpOption, value: &T) {
812        let mut v = Vec::new();
813        value.serialise(&mut v);
814        self.other.insert(*option, v);
815    }
816
817    pub fn mutate_option_value(&mut self, option: &DhcpOption, value: &DhcpOptionTypeValue) {
818        self.other.insert(*option, value.as_bytes());
819    }
820
821    #[must_use]
822    pub fn maybe_set_option<T: Serialise>(self, option: &DhcpOption, value: Option<&T>) -> Self {
823        if let Some(v) = value {
824            self.set_option(option, v)
825        } else {
826            self
827        }
828    }
829
830    #[must_use]
831    pub fn remove_option(mut self, option: &DhcpOption) -> Self {
832        self.other.remove(option);
833        self
834    }
835}
836
837#[derive(PartialEq, Eq)]
838pub struct Dhcp {
839    pub op: DhcpOp,
840    pub htype: HwType,
841    pub hlen: u8,
842    pub hops: u8,
843    pub xid: u32,
844    pub secs: u16,
845    pub flags: u16,
846    pub ciaddr: net::Ipv4Addr,
847    pub yiaddr: net::Ipv4Addr,
848    pub siaddr: net::Ipv4Addr,
849    pub giaddr: net::Ipv4Addr,
850    pub chaddr: Vec<u8>,
851    pub sname: Vec<u8>,
852    pub file: Vec<u8>,
853    pub options: DhcpOptions,
854}
855
856impl std::fmt::Debug for Dhcp {
857    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
858        f.debug_struct("Dhcp")
859            .field("op", &self.op)
860            .field("htype", &self.htype)
861            .field("hlen", &self.hlen)
862            .field("hops", &self.hops)
863            .field("xid", &self.xid)
864            .field("secs", &self.secs)
865            .field("flags", &self.flags)
866            .field("ciaddr", &self.ciaddr)
867            .field("yiaddr", &self.yiaddr)
868            .field("siaddr", &self.siaddr)
869            .field("giaddr", &self.giaddr)
870            .field(
871                "chaddr",
872                &self
873                    .chaddr
874                    .iter()
875                    .map(|&x| format!("{:x?}", x))
876                    .collect::<Vec<String>>()
877                    .join(""),
878            )
879            .field(
880                "sname",
881                &self
882                    .sname
883                    .iter()
884                    .map(|&x| format!("{:x?}", x))
885                    .collect::<Vec<String>>()
886                    .join(""),
887            )
888            .field(
889                "file",
890                &String::from_utf8(self.file.clone())
891                    .or_else::<Result<String, String>, _>(|_| Ok(format!("{:?}", self.file)))
892                    .unwrap(),
893            )
894            .field("options", &self.options)
895            .finish()
896    }
897}
898
899fn null_terminated(mut v: Vec<u8>) -> Vec<u8> {
900    for i in 0..v.len() {
901        if v[i] == 0 {
902            v.truncate(i);
903            break;
904        }
905    }
906    v
907}
908
909pub fn parse_options(mut buf: pktparser::Buffer) -> Result<DhcpOptions, ParseError> {
910    let mut raw_options: collections::HashMap<DhcpOption, Vec<u8>> = collections::HashMap::new();
911    loop {
912        match buf.get_u8() {
913            Some(0) => (),      /* Pad byte */
914            Some(255) => break, /* End Field */
915            Some(x) => {
916                let l = buf.get_u8().ok_or(ParseError::UnexpectedEndOfInput)?;
917                raw_options.entry(DhcpOption(x)).or_default().extend(
918                    buf.get_bytes(l as usize)
919                        .ok_or(ParseError::UnexpectedEndOfInput)?,
920                );
921            }
922            None => return Err(ParseError::UnexpectedEndOfInput),
923        }
924    }
925
926    Ok(DhcpOptions { other: raw_options })
927}
928
929pub fn parse(pkt: &[u8]) -> Result<Dhcp, ParseError> {
930    let mut buf = pktparser::Buffer::new(pkt);
931    let op = buf.get_u8().ok_or(ParseError::UnexpectedEndOfInput)?;
932    let htype = buf.get_u8().ok_or(ParseError::UnexpectedEndOfInput)?;
933    let hlen = buf.get_u8().ok_or(ParseError::UnexpectedEndOfInput)?;
934    let hops = buf.get_u8().ok_or(ParseError::UnexpectedEndOfInput)?;
935    let xid = buf.get_be32().ok_or(ParseError::UnexpectedEndOfInput)?;
936    let secs = buf.get_be16().ok_or(ParseError::UnexpectedEndOfInput)?;
937    let flags = buf.get_be16().ok_or(ParseError::UnexpectedEndOfInput)?;
938    let ciaddr = buf.get_ipv4().ok_or(ParseError::UnexpectedEndOfInput)?;
939    let yiaddr = buf.get_ipv4().ok_or(ParseError::UnexpectedEndOfInput)?;
940    let siaddr = buf.get_ipv4().ok_or(ParseError::UnexpectedEndOfInput)?;
941    let giaddr = buf.get_ipv4().ok_or(ParseError::UnexpectedEndOfInput)?;
942    let chaddr = buf.get_vec(16).ok_or(ParseError::UnexpectedEndOfInput)?;
943    if hlen as usize > chaddr.len() {
944        return Err(ParseError::InvalidPacket);
945    }
946    let sname = null_terminated(buf.get_vec(64).ok_or(ParseError::UnexpectedEndOfInput)?);
947    let file = null_terminated(buf.get_vec(128).ok_or(ParseError::UnexpectedEndOfInput)?);
948    let magic = buf.get_be32().ok_or(ParseError::UnexpectedEndOfInput)?;
949    if magic != 0x6382_5363 {
950        return Err(ParseError::WrongMagic);
951    }
952    let options = parse_options(buf)?;
953
954    Ok(Dhcp {
955        op: DhcpOp(op),
956        htype: HwType(htype),
957        hlen,
958        hops,
959        xid,
960        secs,
961        flags,
962        ciaddr,
963        yiaddr,
964        siaddr,
965        giaddr,
966        chaddr: chaddr[0..hlen as usize].to_vec(),
967        sname,
968        file,
969        options,
970    })
971}
972
973pub trait Serialise {
974    fn serialise(&self, v: &mut Vec<u8>);
975}
976
977impl Serialise for u8 {
978    fn serialise(&self, v: &mut Vec<u8>) {
979        v.push(*self);
980    }
981}
982
983impl Serialise for u16 {
984    fn serialise(&self, v: &mut Vec<u8>) {
985        for b in self.to_be_bytes().iter() {
986            b.serialise(v);
987        }
988    }
989}
990
991impl Serialise for u32 {
992    fn serialise(&self, v: &mut Vec<u8>) {
993        for b in self.to_be_bytes().iter() {
994            b.serialise(v);
995        }
996    }
997}
998
999impl Serialise for net::Ipv4Addr {
1000    fn serialise(&self, v: &mut Vec<u8>) {
1001        for b in self.octets().iter() {
1002            b.serialise(v);
1003        }
1004    }
1005}
1006
1007impl Serialise for DhcpOption {
1008    fn serialise(&self, v: &mut Vec<u8>) {
1009        self.0.serialise(v);
1010    }
1011}
1012
1013impl Serialise for &[u8] {
1014    fn serialise(&self, v: &mut Vec<u8>) {
1015        v.extend(*self);
1016    }
1017}
1018
1019impl Serialise for MessageType {
1020    fn serialise(&self, v: &mut Vec<u8>) {
1021        self.0.serialise(v);
1022    }
1023}
1024
1025impl<T: Serialise> Serialise for Vec<T> {
1026    fn serialise(&self, v: &mut Vec<u8>) {
1027        for i in self {
1028            i.serialise(v);
1029        }
1030    }
1031}
1032
1033impl Serialise for String {
1034    fn serialise(&self, v: &mut Vec<u8>) {
1035        self.as_bytes().serialise(v)
1036    }
1037}
1038
1039impl Serialise for i32 {
1040    fn serialise(&self, v: &mut Vec<u8>) {
1041        for i in self.to_be_bytes().iter() {
1042            i.serialise(v)
1043        }
1044    }
1045}
1046
1047impl Serialise for DhcpOptionTypeValue {
1048    fn serialise(&self, v: &mut Vec<u8>) {
1049        v.extend(self.as_bytes().iter());
1050    }
1051}
1052
1053fn serialise_option<T>(option: DhcpOption, bytes: &[T], v: &mut Vec<u8>)
1054where
1055    T: Serialise,
1056{
1057    option.serialise(v);
1058    (bytes.len() as u8).serialise(v);
1059    for i in bytes.iter() {
1060        i.serialise(v);
1061    }
1062}
1063
1064impl Serialise for DhcpOptions {
1065    fn serialise(&self, v: &mut Vec<u8>) {
1066        for (o, p) in self.other.iter() {
1067            serialise_option(*o, p, v);
1068        }
1069
1070        /* Add end of options marker */
1071        (255_u8).serialise(v);
1072    }
1073}
1074
1075fn serialise_fixed(out: &[u8], l: usize, v: &mut Vec<u8>) {
1076    let mut bytes = Vec::with_capacity(l);
1077    bytes.extend_from_slice(out);
1078    bytes.resize_with(l, Default::default);
1079    for b in &bytes {
1080        b.serialise(v);
1081    }
1082}
1083
1084impl Dhcp {
1085    pub fn serialise(&self) -> Vec<u8> {
1086        let mut v: Vec<u8> = Vec::new();
1087        self.op.0.serialise(&mut v);
1088        self.htype.0.serialise(&mut v);
1089        self.hlen.serialise(&mut v);
1090        self.hops.serialise(&mut v);
1091        self.xid.serialise(&mut v);
1092        self.secs.serialise(&mut v);
1093        self.flags.serialise(&mut v);
1094        self.ciaddr.serialise(&mut v);
1095        self.yiaddr.serialise(&mut v);
1096        self.siaddr.serialise(&mut v);
1097        self.giaddr.serialise(&mut v);
1098
1099        serialise_fixed(&self.chaddr, 16, &mut v);
1100        serialise_fixed(&self.sname, 64, &mut v);
1101        serialise_fixed(&self.file, 128, &mut v);
1102
1103        /* DHCP Magic */
1104        0x6382_5363_u32.serialise(&mut v);
1105
1106        self.options.serialise(&mut v);
1107
1108        v
1109    }
1110
1111    pub fn get_client_id(&self) -> Vec<u8> {
1112        self.options
1113            .get_clientid()
1114            .unwrap_or_else(|| self.chaddr.clone())
1115    }
1116
1117    pub fn get_broadcast_flag(&self) -> bool {
1118        self.flags & 0b1000_0000 != 0
1119    }
1120}
1121
1122#[cfg(test)]
1123fn serialise_one_for_test(opt: DhcpOptionTypeValue) -> Vec<u8> {
1124    let mut v = vec![];
1125    opt.serialise(&mut v);
1126    v
1127}
1128
1129#[test]
1130fn test_type_serialisation() {
1131    assert_eq!(
1132        serialise_one_for_test(DhcpOptionTypeValue::String("test".into())),
1133        vec![116, 101, 115, 116]
1134    );
1135    assert_eq!(
1136        serialise_one_for_test(DhcpOptionTypeValue::Ip("192.0.2.0".parse().unwrap())),
1137        vec![192, 0, 2, 0]
1138    );
1139    assert_eq!(
1140        serialise_one_for_test(DhcpOptionTypeValue::I32(16909060i32)),
1141        vec![1, 2, 3, 4]
1142    );
1143    assert_eq!(
1144        serialise_one_for_test(DhcpOptionTypeValue::U8(42)),
1145        vec![42]
1146    );
1147    assert_eq!(
1148        serialise_one_for_test(DhcpOptionTypeValue::U16(258)),
1149        vec![1, 2],
1150    );
1151    assert_eq!(
1152        serialise_one_for_test(DhcpOptionTypeValue::U32(16909060)),
1153        vec![1, 2, 3, 4]
1154    );
1155    assert_eq!(
1156        serialise_one_for_test(DhcpOptionTypeValue::HwAddr(vec![1, 2, 3, 4, 5, 6])),
1157        vec![1, 2, 3, 4, 5, 6]
1158    );
1159    assert_eq!(
1160        serialise_one_for_test(DhcpOptionTypeValue::IpList(vec![
1161            "192.0.2.0".parse().unwrap(),
1162            "192.0.2.1".parse().unwrap(),
1163            "192.0.2.2".parse().unwrap(),
1164        ])),
1165        vec![192, 0, 2, 0, 192, 0, 2, 1, 192, 0, 2, 2]
1166    );
1167    assert_eq!(
1168        serialise_one_for_test(DhcpOptionTypeValue::Routes(vec![Route {
1169            prefix: erbium_net::Ipv4Subnet::new("192.0.2.0".parse().unwrap(), 24).unwrap(),
1170            nexthop: "192.0.2.254".parse().unwrap(),
1171        }])),
1172        vec![24, 192, 0, 2, 0, 192, 0, 2, 254]
1173    );
1174}
1175
1176#[test]
1177fn test_parse() {
1178    assert_eq!(
1179        format!(
1180            "{}",
1181            DhcpOptionType::String
1182                .decode(&[116, 101, 115, 116])
1183                .unwrap()
1184        ),
1185        "test"
1186    );
1187    assert_eq!(
1188        format!("{}", DhcpOptionType::Ip.decode(&[192, 0, 2, 42]).unwrap()),
1189        "192.0.2.42"
1190    );
1191    assert_eq!(
1192        format!(
1193            "{}",
1194            DhcpOptionType::IpList
1195                .decode(&[192, 0, 2, 12, 192, 0, 2, 17])
1196                .unwrap()
1197        ),
1198        "192.0.2.12,192.0.2.17"
1199    );
1200    assert_eq!(
1201        format!("{}", DhcpOptionType::I32.decode(&[1, 2, 3, 4]).unwrap()),
1202        "16909060",
1203    );
1204    assert_eq!(
1205        format!("{}", DhcpOptionType::U8.decode(&[251]).unwrap()),
1206        "251",
1207    );
1208    assert_eq!(
1209        format!("{}", DhcpOptionType::U16.decode(&[1, 2]).unwrap()),
1210        "258",
1211    );
1212    assert_eq!(
1213        format!("{}", DhcpOptionType::U32.decode(&[1, 2, 3, 4]).unwrap()),
1214        "16909060",
1215    );
1216    assert_eq!(
1217        format!("{}", DhcpOptionType::Bool.decode(&[0]).unwrap()),
1218        "0",
1219    );
1220    assert_eq!(
1221        format!("{}", DhcpOptionType::Bool.decode(&[1]).unwrap()),
1222        "1",
1223    );
1224    assert_eq!(
1225        format!("{}", DhcpOptionType::Seconds16.decode(&[1, 0x2c]).unwrap()),
1226        "300",
1227    );
1228    assert_eq!(
1229        format!(
1230            "{}",
1231            DhcpOptionType::Seconds32
1232                .decode(&[0, 1, 0x51, 0x80])
1233                .unwrap()
1234        ),
1235        "86400",
1236    );
1237    assert_eq!(
1238        format!(
1239            "{}",
1240            DhcpOptionType::HwAddr.decode(&[0, 1, 2, 3, 4, 5]).unwrap()
1241        ),
1242        "00:01:02:03:04:05"
1243    );
1244    assert_eq!(
1245        format!(
1246            "{}",
1247            DhcpOptionType::Routes
1248                .decode(&[
1249                    24, 192, 0, 2, 0, 192, 0, 2, 254, 24, 198, 51, 100, 0, 192, 0, 2, 254
1250                ])
1251                .unwrap()
1252        ),
1253        "192.0.2.0/24->192.0.2.254,198.51.100.0/24->192.0.2.254"
1254    );
1255}