dhcparse/
dhcpv4.rs

1/*!
2DHCPv4 definitions and parser.
3
4# Examples
5
6Basic usage:
7
8```
9# const EXAMPLE_DISCOVER_MSG: [u8; 250] = [
10#     /* op */ 2, /* htype */ 1, /* hlen */ 6, /* hops */ 0,
11#     /* xid */ 0xC7, 0xF5, 0xA0, 0xA7, /* secs */ 0, 0, /* flags */ 0x00, 0x00,
12#     /* ciaddr */ 0x00, 0x00, 0x00, 0x00, /* yiaddr */ 0x00, 0x00, 0x00, 0x00,
13#     /* siaddr */ 0x00, 0x00, 0x00, 0x00, /* giaddr */ 0x00, 0x00, 0x00, 0x00,
14#     /* chaddr */ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
15#     /* sname */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
16#     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
17#     0, 0, 0, 0, 0, 0, 0, 0, 0, /* file */ /* msg type */ 53, 1, 1, /* end */ 255, 0,
18#     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
19#     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
20#     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
21#     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
22#     0, 0, 0, /* magic */ 99, 130, 83, 99, /* options */
23#     /* option overload */ 52, 1, 0b01, /* requested addr */ 50, 4, 192, 168, 1, 100, /* end */ 255,
24# ];
25use dhcparse::{v4_options, dhcpv4::{Message, MessageType}};
26use std::net::Ipv4Addr;
27
28let mut msg = Message::new(EXAMPLE_DISCOVER_MSG)?;
29
30// Read a field
31assert_eq!(msg.chaddr()?, [0x01, 0x02, 0x03, 0x04, 0x05, 0x06]);
32
33// Set a field
34*msg.giaddr_mut() = Ipv4Addr::new(192, 168, 1, 50).into();
35
36// Parse a set of options
37assert_eq!(
38    v4_options!(msg; MessageType required, ServerIdentifier, RequestedIpAddress)?,
39    (
40        MessageType::DISCOVER,
41        None,
42        Some(&Ipv4Addr::new(192, 168, 1, 100).into())
43    )
44);
45# Ok::<(), dhcparse::Error>(())
46```
47
48Constructing a new message:
49
50```
51use dhcparse::dhcpv4::{DhcpOption, Encode as _, Encoder, Message, MessageType, OpCode};
52// Create a copy of an empty message with the message type option added
53let mut msg = Encoder
54    .append_options([DhcpOption::MessageType(MessageType::DISCOVER)])
55    .encode_to_owned(&Message::default())?;
56msg.set_op(OpCode::BootRequest);
57
58assert_eq!(msg.options()?.count(), 1);
59# Ok::<(), dhcparse::Error>(())
60```
61
62See:
63 * [RFC2131]: Dynamic Host Configuration Protocol
64 * [RFC2132]: DHCP Options and BOOTP Vendor Extensions
65
66[RFC2131]: https://datatracker.ietf.org/doc/html/rfc2131
67[RFC2132]: https://datatracker.ietf.org/doc/html/rfc2132
68 */
69
70use bitflags::bitflags;
71use byteorder::{ByteOrder, NetworkEndian};
72use core::convert::{TryFrom, TryInto};
73use core::iter::FusedIterator;
74use core::{fmt, mem, slice};
75use memchr::memchr;
76use ref_cast::RefCast;
77
78use crate::{Cursor, Error};
79
80pub mod relay;
81
82/// The 'DHCP server' UDP port.
83pub const SERVER_PORT: u16 = 67;
84/// The 'DHCP client' UDP port.
85pub const CLIENT_PORT: u16 = 68;
86/// The minimum DHCP message size all agents are required to support.
87pub const MAX_MESSAGE_SIZE: usize = 576;
88
89/// An IPv4 address.
90///
91/// This is similar to [`std::net::Ipv4Addr`], but has an explicit
92/// representation.
93#[derive(Clone, Copy, PartialEq, Eq, RefCast, Debug)]
94#[repr(transparent)]
95pub struct Addr(pub [u8; 4]);
96
97impl<'a> TryFrom<&'a [u8]> for &'a Addr {
98    type Error = Error;
99
100    #[inline]
101    fn try_from(b: &'a [u8]) -> Result<Self, Self::Error> {
102        b[..4]
103            .try_into()
104            .map(Addr::ref_cast)
105            .map_err(|_| Error::Malformed)
106    }
107}
108
109#[cfg(feature = "std")]
110impl From<Addr> for std::net::Ipv4Addr {
111    #[inline]
112    fn from(Addr(x): Addr) -> Self {
113        x.into()
114    }
115}
116
117#[cfg(feature = "std")]
118impl From<std::net::Ipv4Addr> for Addr {
119    #[inline]
120    fn from(x: std::net::Ipv4Addr) -> Self {
121        Addr(x.octets())
122    }
123}
124
125/// The packet op code/message type.
126#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
127pub enum OpCode {
128    /// Signifies that the message is sent from a client to a server.
129    BootRequest = 1,
130    /// Signifies that the message is sent from a server to a client.
131    BootReply,
132}
133
134impl TryFrom<u8> for OpCode {
135    type Error = Error;
136
137    fn try_from(value: u8) -> Result<Self, Self::Error> {
138        Ok(match value {
139            1 => OpCode::BootRequest,
140            2 => OpCode::BootReply,
141            _ => return Err(Error::Malformed),
142        })
143    }
144}
145
146/// DHCP message type.
147#[derive(Clone, Copy, PartialEq, Eq, Hash)]
148pub struct MessageType(u8);
149
150impl MessageType {
151    pub const DISCOVER: MessageType = MessageType(1);
152    pub const OFFER: MessageType = MessageType(2);
153    pub const REQUEST: MessageType = MessageType(3);
154    pub const DECLINE: MessageType = MessageType(4);
155    pub const ACK: MessageType = MessageType(5);
156    pub const NAK: MessageType = MessageType(6);
157    pub const RELEASE: MessageType = MessageType(7);
158    pub const INFORM: MessageType = MessageType(8);
159}
160
161impl From<u8> for MessageType {
162    fn from(x: u8) -> Self {
163        Self(x)
164    }
165}
166
167impl From<MessageType> for u8 {
168    fn from(MessageType(x): MessageType) -> Self {
169        x
170    }
171}
172
173impl fmt::Debug for MessageType {
174    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
175        let name = match *self {
176            MessageType::DISCOVER => "DISCOVER",
177            MessageType::OFFER => "OFFER",
178            MessageType::REQUEST => "REQUEST",
179            MessageType::DECLINE => "DECLINE",
180            MessageType::ACK => "ACK",
181            MessageType::NAK => "NAK",
182            MessageType::RELEASE => "RELEASE",
183            MessageType::INFORM => "INFORM",
184            Self(x) => return f.debug_tuple("MessageType").field(&x).finish(),
185        };
186        f.write_str(name)
187    }
188}
189
190bitflags! {
191    /// DHCPv4 flags.
192    ///
193    /// This is the type of the 'flags' field.
194    #[repr(transparent)]
195    pub struct Flags: u16 {
196        const BROADCAST = 1 << 15;
197    }
198
199    /// The possible values of the 'option overload' option.
200    #[repr(transparent)]
201    pub struct OptionOverload: u8 {
202        /// The 'file' field is used to hold options.
203        const FILE = 0b01;
204        /// The 'sname' field is used to hold options.
205        const SNAME = 0b10;
206    }
207}
208
209/// A DHCPv4 option.
210#[derive(Clone, Copy, PartialEq, Eq, Debug)]
211#[non_exhaustive]
212pub enum DhcpOption<'a> {
213    /// 0 Padding
214    ///
215    /// Can be used to cause subsequent fields to align on word
216    /// boundaries.
217    Pad,
218    /// 255 End
219    ///
220    /// Marks the end of valid information in the 'options' field.
221    End,
222    /// 1 Subnet Mask.
223    SubnetMask(&'a Addr),
224    /// 2 Time Offset
225    TimeOffset(i32),
226    /// 3 Router
227    Router(&'a [Addr]),
228    /// 4 Time Server
229    TimeServer(&'a [Addr]),
230    /// 5 Name Server
231    NameServer(&'a [Addr]),
232    /// 6 Domain Name Server
233    DomainNameServer(&'a [Addr]),
234    /// 7 Log Server
235    LogServer(&'a [Addr]),
236    /// 8 Cookie Server
237    CookieServer(&'a [Addr]),
238    /// 9 LPR Server
239    LprServer(&'a [Addr]),
240    /// 10 Impress Server
241    ImpressServer(&'a [Addr]),
242    /// 11 Resource Location Server
243    ResourceLocationServer(&'a [Addr]),
244    /// 12 Host Name
245    HostName(&'a [u8]),
246    /// 13 Boot File Size
247    BootFileSize(u16),
248    /// 14 Merit Dump File
249    MeritDumpFile(&'a [u8]),
250    /// 15 Domain Name
251    DomainName(&'a [u8]),
252    /// 16 Swap Server
253    SwapServer(&'a Addr),
254    /// 17 Root Path
255    RootPath(&'a [u8]),
256    /// 18 Extensions Path
257    ExtensionsPath(&'a [u8]),
258    /// 19 IP Forwarding Enable/Disable
259    IpForwarding(bool),
260    /// 20 Non-Local Source Routing Enable/Disable
261    NonLocalSrcRouting(bool),
262    /// 21 Policy Filter
263    PolicyFilter(&'a [[Addr; 2]]),
264    /// 22 Maximum Datagram Reassembly Size
265    ///
266    /// The minimum legal value is [`MAX_MESSAGE_SIZE`].
267    MaximumDatagramSize(u16),
268    /// 50 Requested IP Address
269    RequestedIpAddress(&'a Addr),
270    /// 51 IP Address Lease Time
271    ///
272    /// The time is in units of seconds.
273    AddressLeaseTime(u32),
274    /// 52 Option Overload
275    OptionOverload(OptionOverload),
276    /// 53 DHCP Message Type
277    ///
278    /// This option is required.
279    MessageType(MessageType),
280    /// 54 Server Identifier
281    ServerIdentifier(&'a Addr),
282    /// 55 Parameter Request List
283    ParameterRequestList(&'a [u8]),
284    /// 56 Message
285    Message(&'a [u8]),
286    /// 57 Maximum DHCP Message Size
287    ///
288    /// The minimum legal value is [`MAX_MESSAGE_SIZE`].
289    MaximumMessageSize(u16),
290    /// 60 Vendor class identifier
291    VendorClassIdentifier(&'a [u8]),
292    /// 61 Client-identifier
293    ///
294    /// The first byte is the type field which should correspond to
295    /// 'htype' in case the client identifier is a hardware address,
296    /// or be zero.
297    ClientIdentifier(&'a [u8]),
298    /// 82 Relay Agent Information
299    RelayAgentInformation(relay::RelayAgentInformation<'a>),
300    /// Unrecognized option.
301    Unknown(u8, &'a [u8]),
302}
303
304#[inline]
305fn read_str(b: &[u8]) -> Result<&[u8], Error> {
306    if b.is_empty() {
307        return Err(Error::Malformed);
308    }
309    Ok(b)
310}
311
312#[inline]
313fn read_addrs(b: &[u8]) -> Result<&[Addr], Error> {
314    if b.len() < 4 || b.len() % mem::size_of::<Addr>() != 0 {
315        return Err(Error::Malformed);
316    }
317    // Safety: Ok, since Addr has same representation as [u8; 4].
318    Ok(unsafe {
319        slice::from_raw_parts(
320            b as *const [u8] as *const Addr,
321            b.len() / mem::size_of::<Addr>(),
322        )
323    })
324}
325
326#[inline]
327fn read_addr_pairs(b: &[u8]) -> Result<&[[Addr; 2]], Error> {
328    if b.len() < 8 || b.len() % mem::size_of::<[Addr; 2]>() != 0 {
329        return Err(Error::Malformed);
330    }
331    // Safety: Ok, since Addr has same representation as [u8; 4].
332    Ok(unsafe {
333        slice::from_raw_parts(
334            b as *const [u8] as *const [Addr; 2],
335            b.len() / mem::size_of::<[Addr; 2]>(),
336        )
337    })
338}
339
340impl<'a> DhcpOption<'a> {
341    /// Returns the tag of this parameter.
342    pub fn code(&self) -> u8 {
343        use DhcpOption::*;
344        match *self {
345            Pad => 0,
346            End => 255,
347            SubnetMask(_) => 1,
348            TimeOffset(_) => 2,
349            Router(_) => 3,
350            TimeServer(_) => 4,
351            NameServer(_) => 5,
352            DomainNameServer(_) => 6,
353            LogServer(_) => 7,
354            CookieServer(_) => 8,
355            LprServer(_) => 9,
356            ImpressServer(_) => 10,
357            ResourceLocationServer(_) => 11,
358            HostName(_) => 12,
359            BootFileSize(_) => 13,
360            MeritDumpFile(_) => 14,
361            DomainName(_) => 15,
362            SwapServer(_) => 16,
363            RootPath(_) => 17,
364            ExtensionsPath(_) => 18,
365            IpForwarding(_) => 19,
366            NonLocalSrcRouting(_) => 20,
367            PolicyFilter(_) => 21,
368            MaximumDatagramSize(_) => 22,
369            RequestedIpAddress(_) => 50,
370            AddressLeaseTime(_) => 51,
371            OptionOverload(_) => 52,
372            MessageType(_) => 53,
373            ServerIdentifier(_) => 54,
374            ParameterRequestList(_) => 55,
375            Message(_) => 56,
376            MaximumMessageSize(_) => 57,
377            VendorClassIdentifier(_) => 60,
378            ClientIdentifier(_) => 61,
379            RelayAgentInformation(_) => 82,
380            Unknown(code, _) => code,
381        }
382    }
383
384    fn read(buf: &'a [u8]) -> Result<(Self, usize), Error> {
385        use DhcpOption::*;
386        let (tag, b) = match *buf {
387            [0, ..] => return Ok((Pad, 1)),
388            [255, ..] => return Ok((End, 1)),
389            [tag, len, ref rest @ ..] => (tag, rest.get(..len.into()).ok_or(Error::Malformed)?),
390            _ => return Err(Error::Underflow),
391        };
392        Ok((
393            match tag {
394                0 | 255 => unreachable!(),
395                1 => SubnetMask(b.try_into()?),
396                2 => {
397                    if b.len() != 4 {
398                        return Err(Error::Malformed);
399                    }
400                    TimeOffset(NetworkEndian::read_i32(b))
401                }
402                3 => Router(read_addrs(b)?),
403                4 => TimeServer(read_addrs(b)?),
404                5 => NameServer(read_addrs(b)?),
405                6 => DomainNameServer(read_addrs(b)?),
406                7 => LogServer(read_addrs(b)?),
407                8 => CookieServer(read_addrs(b)?),
408                9 => LprServer(read_addrs(b)?),
409                10 => ImpressServer(read_addrs(b)?),
410                11 => ResourceLocationServer(read_addrs(b)?),
411                12 => HostName(read_str(b)?),
412                13 => {
413                    if b.len() != 2 {
414                        return Err(Error::Malformed);
415                    }
416                    BootFileSize(NetworkEndian::read_u16(b))
417                }
418                14 => MeritDumpFile(read_str(b)?),
419                15 => DomainName(read_str(b)?),
420                16 => SwapServer(b.try_into()?),
421                17 => RootPath(read_str(b)?),
422                18 => ExtensionsPath(read_str(b)?),
423                19 => match *b {
424                    [x] => IpForwarding(x == 1),
425                    _ => return Err(Error::Malformed),
426                },
427                20 => match *b {
428                    [x] => NonLocalSrcRouting(x == 1),
429                    _ => return Err(Error::Malformed),
430                },
431                21 => PolicyFilter(read_addr_pairs(b)?),
432                22 => {
433                    if b.len() != 2 {
434                        return Err(Error::Malformed);
435                    }
436                    MaximumDatagramSize(NetworkEndian::read_u16(b))
437                }
438                50 => RequestedIpAddress(b.try_into()?),
439                51 => {
440                    if b.len() != 4 {
441                        return Err(Error::Malformed);
442                    }
443                    AddressLeaseTime(NetworkEndian::read_u32(b))
444                }
445                52 => match *b {
446                    [x] => {
447                        OptionOverload(self::OptionOverload::from_bits(x).ok_or(Error::Malformed)?)
448                    }
449                    _ => return Err(Error::Malformed),
450                },
451                53 => match *b {
452                    [x] => MessageType(x.into()),
453                    _ => return Err(Error::Malformed),
454                },
455                54 => ServerIdentifier(b.try_into()?),
456                55 => ParameterRequestList(read_str(b)?),
457                56 => Message(read_str(b)?),
458                57 => {
459                    if b.len() != 2 {
460                        return Err(Error::Malformed);
461                    }
462                    MaximumMessageSize(NetworkEndian::read_u16(b))
463                }
464                60 => VendorClassIdentifier(read_str(b)?),
465                61 => {
466                    if b.len() < 2 {
467                        return Err(Error::Malformed);
468                    }
469                    ClientIdentifier(read_str(b)?)
470                }
471                82 => RelayAgentInformation(relay::RelayAgentInformation::new(b)?),
472                _ => Unknown(tag, b),
473            },
474            2 + b.len(),
475        ))
476    }
477
478    fn write<'buf>(&self, cursor: &mut Cursor<'buf>) -> Result<(), Error> {
479        use DhcpOption::*;
480        cursor.write_u8(self.code())?;
481        match *self {
482            Pad | End => Ok(()),
483            SubnetMask(addr)
484            | SwapServer(addr)
485            | RequestedIpAddress(addr)
486            | ServerIdentifier(addr) => {
487                cursor.write_u8(addr.0.len() as u8)?;
488                cursor.write(&addr.0)
489            }
490            TimeOffset(i) => {
491                cursor.write_u8(4)?;
492                cursor.write(&i.to_be_bytes())
493            }
494            Router(addrs)
495            | TimeServer(addrs)
496            | NameServer(addrs)
497            | DomainNameServer(addrs)
498            | LogServer(addrs)
499            | CookieServer(addrs)
500            | LprServer(addrs)
501            | ImpressServer(addrs)
502            | ResourceLocationServer(addrs) => {
503                // Safety: Addr has the same representation as [u8; 4]
504                let xs = unsafe {
505                    slice::from_raw_parts(
506                        addrs as *const [Addr] as *const u8,
507                        addrs.len() * mem::size_of::<Addr>(),
508                    )
509                };
510                cursor.write_u8(xs.len().try_into().map_err(|_| Error::TooLong)?)?;
511                cursor.write(xs)
512            }
513            HostName(xs)
514            | MeritDumpFile(xs)
515            | DomainName(xs)
516            | RootPath(xs)
517            | ExtensionsPath(xs)
518            | RelayAgentInformation(relay::RelayAgentInformation(xs))
519            | ParameterRequestList(xs)
520            | Message(xs)
521            | VendorClassIdentifier(xs)
522            | ClientIdentifier(xs)
523            | Unknown(_, xs) => {
524                cursor.write_u8(xs.len().try_into().map_err(|_| Error::TooLong)?)?;
525                cursor.write(xs)
526            }
527            BootFileSize(x) | MaximumDatagramSize(x) | MaximumMessageSize(x) => {
528                cursor.write_u8(2)?;
529                cursor.write(&x.to_be_bytes())
530            }
531            IpForwarding(x) | NonLocalSrcRouting(x) => {
532                cursor.write_u8(1)?;
533                cursor.write_u8(if x { 1 } else { 0 })
534            }
535            PolicyFilter(addr_pairs) => {
536                // Safety: Addr has the same representation as [u8; 4]
537                let xs = unsafe {
538                    slice::from_raw_parts(
539                        addr_pairs as *const [[Addr; 2]] as *const u8,
540                        addr_pairs.len() * mem::size_of::<[Addr; 2]>(),
541                    )
542                };
543                cursor.write_u8(xs.len().try_into().map_err(|_| Error::TooLong)?)?;
544                cursor.write(xs)
545            }
546            AddressLeaseTime(i) => {
547                cursor.write_u8(4)?;
548                cursor.write(&i.to_be_bytes())
549            }
550            OptionOverload(x) => {
551                cursor.write_u8(1)?;
552                cursor.write_u8(x.bits())
553            }
554            MessageType(self::MessageType(x)) => {
555                cursor.write_u8(1)?;
556                cursor.write_u8(x)
557            }
558        }
559    }
560
561    /// Returns the byte size of this option when serialized.
562    fn size(&self) -> usize {
563        use DhcpOption::*;
564        if matches!(*self, Pad | End) {
565            return 1;
566        }
567        2 + match *self {
568            Pad | End => unreachable!(),
569            IpForwarding(_) | NonLocalSrcRouting(_) | OptionOverload(_) | MessageType(_) => 1,
570            SubnetMask(addr)
571            | SwapServer(addr)
572            | RequestedIpAddress(addr)
573            | ServerIdentifier(addr) => mem::size_of_val(addr),
574            TimeOffset(_) | AddressLeaseTime(_) => 4,
575            Router(addrs)
576            | TimeServer(addrs)
577            | NameServer(addrs)
578            | DomainNameServer(addrs)
579            | LogServer(addrs)
580            | CookieServer(addrs)
581            | LprServer(addrs)
582            | ImpressServer(addrs)
583            | ResourceLocationServer(addrs) => mem::size_of_val(addrs),
584            HostName(xs)
585            | MeritDumpFile(xs)
586            | DomainName(xs)
587            | RootPath(xs)
588            | ExtensionsPath(xs)
589            | RelayAgentInformation(relay::RelayAgentInformation(xs))
590            | ParameterRequestList(xs)
591            | Message(xs)
592            | VendorClassIdentifier(xs)
593            | ClientIdentifier(xs)
594            | Unknown(_, xs) => mem::size_of_val(xs),
595            BootFileSize(_) | MaximumDatagramSize(_) | MaximumMessageSize(_) => 2,
596            PolicyFilter(addr_pairs) => mem::size_of_val(addr_pairs),
597        }
598    }
599}
600
601const SNAME_FIELD_OFFSET: usize = 44;
602const FILE_FIELD_OFFSET: usize = SNAME_FIELD_OFFSET + 64;
603const OPTIONS_FIELD_OFFSET: usize = FILE_FIELD_OFFSET + 128;
604const MAGIC_COOKIE: [u8; 4] = [99, 130, 83, 99];
605
606/// Immutable DHCPv4 option iterator.
607///
608/// This `struct` is created by the [`options`] method on [`Message`].
609/// See its documentation for more.
610///
611/// [`options`]: Message::options
612#[derive(Clone, Debug)]
613pub struct Options<'a> {
614    /// The entire DHCP message.
615    b: &'a [u8],
616    overload: OptionOverload,
617    /// The current position in `b`.
618    cursor: usize,
619}
620
621impl<'a> Options<'a> {
622    #[inline]
623    fn new(b: &'a [u8]) -> Result<Self, Error> {
624        if b[OPTIONS_FIELD_OFFSET..][..MAGIC_COOKIE.len()] != MAGIC_COOKIE {
625            return Err(Error::Malformed);
626        }
627
628        Ok(Self {
629            b,
630            overload: OptionOverload::empty(),
631            cursor: OPTIONS_FIELD_OFFSET + MAGIC_COOKIE.len(),
632        })
633    }
634}
635
636impl<'a> Iterator for Options<'a> {
637    type Item = Result<(DhcpOption<'a>, (usize, usize)), Error>;
638
639    fn next(&mut self) -> Option<Self::Item> {
640        loop {
641            let b = if self.cursor > OPTIONS_FIELD_OFFSET {
642                &self.b[self.cursor..]
643            } else if self.cursor >= FILE_FIELD_OFFSET {
644                &self.b[FILE_FIELD_OFFSET..][..128][self.cursor - FILE_FIELD_OFFSET..]
645            } else {
646                &self.b[SNAME_FIELD_OFFSET..][..64][self.cursor - SNAME_FIELD_OFFSET..]
647            };
648            match DhcpOption::read(b) {
649                Ok((DhcpOption::Pad, len)) => self.cursor += len,
650                Ok((DhcpOption::End, _)) => {
651                    self.cursor = if self.cursor > OPTIONS_FIELD_OFFSET
652                        && self.overload.contains(OptionOverload::FILE)
653                    {
654                        FILE_FIELD_OFFSET // The 'file' field MUST be interpreted next
655                    } else if self.cursor >= FILE_FIELD_OFFSET
656                        && self.overload.contains(OptionOverload::SNAME)
657                    {
658                        SNAME_FIELD_OFFSET // ...followed by the 'sname' field
659                    } else {
660                        break None;
661                    }
662                }
663                Ok((option, len)) => {
664                    let bnd = (self.cursor, len);
665                    self.cursor += len;
666                    if let DhcpOption::OptionOverload(x) = option {
667                        self.overload = x;
668                    }
669                    break Some(Ok((option, bnd)));
670                }
671                Err(e) => break Some(Err(e)),
672            }
673        }
674    }
675}
676
677impl FusedIterator for Options<'_> {}
678
679/// A read/write wrapper around a Dynamic Host Protocol version 4
680/// message buffer.
681#[derive(Clone, Debug)]
682pub struct Message<T>(T);
683
684impl<T: AsRef<[u8]>> AsRef<[u8]> for Message<T> {
685    #[inline]
686    fn as_ref(&self) -> &[u8] {
687        self.0.as_ref()
688    }
689}
690
691impl<T: AsMut<[u8]>> AsMut<[u8]> for Message<T> {
692    #[inline]
693    fn as_mut(&mut self) -> &mut [u8] {
694        self.0.as_mut()
695    }
696}
697
698impl<T> Message<T> {
699    /// Consumes the view and returns the underlying buffer.
700    #[inline]
701    pub fn into_inner(self) -> T {
702        let Self(inner) = self;
703        inner
704    }
705}
706
707impl Default for Message<[u8; 241]> {
708    /// Returns a DHCPv4 message with all fields set to zero and zero options.
709    ///
710    /// This is intended for bootstrapping a message with [`Encoder::encode`].
711    fn default() -> Self {
712        let mut buf = [0; 241];
713        buf[OPTIONS_FIELD_OFFSET..][..MAGIC_COOKIE.len()].copy_from_slice(&MAGIC_COOKIE);
714        buf[OPTIONS_FIELD_OFFSET + MAGIC_COOKIE.len()] = DhcpOption::End.code();
715        Self(buf)
716    }
717}
718
719impl<T: AsRef<[u8]>> Message<T> {
720    /// Constructs a new view from an underlying message buffer.
721    ///
722    /// # Errors
723    ///
724    /// If the length of the buffer is smaller than any valid message,
725    /// then an error is returned.
726    #[inline]
727    pub fn new(b: T) -> Result<Self, Error> {
728        if b.as_ref().len() < OPTIONS_FIELD_OFFSET + MAGIC_COOKIE.len() + 4 {
729            return Err(Error::Underflow);
730        }
731        Ok(Self(b))
732    }
733
734    /// Gets the 'op' field (message type).
735    #[inline]
736    pub fn op(&self) -> Result<OpCode, Error> {
737        self.as_ref()[0].try_into()
738    }
739
740    /// Gets the hardware len of the message (length of `chaddr`).
741    #[inline]
742    pub fn hlen(&self) -> u8 {
743        self.as_ref()[2]
744    }
745
746    /// Gets the 'hops' field.
747    #[inline]
748    pub fn hops(&self) -> u8 {
749        self.as_ref()[3]
750    }
751
752    /// Gets the transaction ID.
753    #[inline]
754    pub fn xid(&self) -> u32 {
755        NetworkEndian::read_u32(&self.as_ref()[4..])
756    }
757
758    /// Gets the 'secs' field.
759    ///
760    /// This is the number of seconds elapsed since client began
761    /// address acquisition or renewal process.
762    #[inline]
763    pub fn secs(&self) -> u16 {
764        NetworkEndian::read_u16(&self.as_ref()[8..])
765    }
766
767    /// Gets the 'flags' field.
768    #[inline]
769    pub fn flags(&self) -> Flags {
770        Flags::from_bits_truncate(NetworkEndian::read_u16(&self.as_ref()[10..]))
771    }
772
773    /// Gets the 'ciaddr' field (client address).
774    #[inline]
775    pub fn ciaddr(&self) -> &Addr {
776        self.as_ref()[12..].try_into().unwrap()
777    }
778
779    /// Gets the 'yiaddr' field (your address).
780    #[inline]
781    pub fn yiaddr(&self) -> &Addr {
782        self.as_ref()[16..].try_into().unwrap()
783    }
784
785    /// Gets the 'siaddr' field (IP address of next server to use in bootstrap).
786    #[inline]
787    pub fn siaddr(&self) -> &Addr {
788        self.as_ref()[20..].try_into().unwrap()
789    }
790
791    /// Gets the 'giaddr' field (the gateway IP or unspecified).
792    #[inline]
793    pub fn giaddr(&self) -> &Addr {
794        self.as_ref()[24..].try_into().unwrap()
795    }
796
797    /// Returns the 'chaddr' field (client hardware address).
798    ///
799    /// # Errors
800    ///
801    /// An error is returned if the value of the 'hlen' field exceeds
802    /// the maximum length of 16.
803    #[inline]
804    pub fn chaddr(&self) -> Result<&[u8], Error> {
805        self.as_ref()[28..][..16]
806            .get(..self.hlen() as usize)
807            .ok_or(Error::Malformed)
808    }
809
810    /// Returns the optional server host name.
811    pub fn sname(&self) -> Result<&[u8], Error> {
812        let data = self.as_ref();
813        memchr(0, &data[SNAME_FIELD_OFFSET..][..64])
814            .map(|nul_pos| &data[SNAME_FIELD_OFFSET..][..nul_pos])
815            .ok_or(Error::BadNull)
816    }
817
818    /// Returns the boot file name.
819    pub fn file(&self) -> Result<&[u8], Error> {
820        let data = self.as_ref();
821        memchr(0, &data[FILE_FIELD_OFFSET..][..128])
822            .map(|nul_pos| &data[FILE_FIELD_OFFSET..][..nul_pos])
823            .ok_or(Error::BadNull)
824    }
825
826    /// Returns an iterator over the DHCP options.
827    ///
828    /// The ['pad'](DhcpOption::Pad) and ['end'](DhcpOption::End)
829    /// options are excluded. If at some point the message is
830    /// malformed, the corresponding error will be repeated endlessly.
831    #[inline]
832    pub fn options(&self) -> Result<Options<'_>, Error> {
833        Options::new(self.as_ref())
834    }
835}
836
837impl<T: AsMut<[u8]>> Message<T> {
838    /// Sets the 'op' field.
839    #[inline]
840    pub fn set_op(&mut self, op: OpCode) {
841        self.as_mut()[0] = op as u8;
842    }
843
844    /// Returns a mutable reference to the 'hops' field.
845    pub fn hops_mut(&mut self) -> &mut u8 {
846        &mut self.as_mut()[3]
847    }
848
849    /// Sets the 'xid' field.
850    pub fn set_xid(&mut self, xid: u32) {
851        NetworkEndian::write_u32(&mut self.as_mut()[4..], xid);
852    }
853
854    /// Sets the 'secs' field.
855    pub fn set_secs(&mut self, secs: u16) {
856        NetworkEndian::write_u16(&mut self.as_mut()[8..], secs);
857    }
858
859    /// Sets the 'flags' field.
860    #[inline]
861    pub fn set_flags(&mut self, flags: Flags) {
862        NetworkEndian::write_u16(&mut self.as_mut()[10..], flags.bits());
863    }
864
865    /// Returns a mutable reference to the 'ciaddr' field.
866    #[inline]
867    pub fn ciaddr_mut(&mut self) -> &mut Addr {
868        Addr::ref_cast_mut((&mut self.as_mut()[12..][..4]).try_into().unwrap())
869    }
870
871    /// Returns a mutable reference to the 'yiaddr' field.
872    #[inline]
873    pub fn yiaddr_mut(&mut self) -> &mut Addr {
874        Addr::ref_cast_mut((&mut self.as_mut()[16..][..4]).try_into().unwrap())
875    }
876
877    /// Returns a mutable reference to the 'siaddr' field.
878    #[inline]
879    pub fn siaddr_mut(&mut self) -> &mut Addr {
880        Addr::ref_cast_mut((&mut self.as_mut()[20..][..4]).try_into().unwrap())
881    }
882
883    /// Returns a mutable reference to the 'giaddr' field.
884    #[inline]
885    pub fn giaddr_mut(&mut self) -> &mut Addr {
886        Addr::ref_cast_mut((&mut self.as_mut()[24..][..4]).try_into().unwrap())
887    }
888
889    /// Sets the 'chaddr' field.
890    ///
891    /// This setter also updates the length stored in the 'hlen' field.
892    ///
893    /// # Errors
894    ///
895    /// If the provided hardware address is longer than the maximum
896    /// length of 16 bytes, then an error is returned.
897    #[inline]
898    pub fn set_chaddr(&mut self, chaddr: &[u8]) -> Result<(), Error> {
899        self.as_mut()[28..][..16]
900            .get_mut(..chaddr.len())
901            .ok_or(Error::TooLong)?
902            .copy_from_slice(chaddr);
903        self.as_mut()[2] = chaddr.len() as u8;
904        Ok(())
905    }
906}
907
908#[doc(hidden)]
909pub mod _get_options {
910    /// Dummy identifier to only allow the keyword `required` in [`v4_options`].
911    #[allow(non_upper_case_globals)]
912    pub const required: () = ();
913}
914
915/// Convenience macro for parsing a set of DHCPv4 options from a message.
916///
917/// It takes as arguments a [`Message`] and a set of [`DhcpOption`]
918/// variant names and returns either a parse error or a tuple
919/// containing the data of the respective options. Each option name
920/// may optionally be followed by the keyword `required`. In that case
921/// it is an error if the option is missing and the data will not be
922/// wrapped in an [`Option`].
923///
924/// For getting *all* occurances of an option, one has to fall back on
925/// [`Message::options`] with a custom reducer.
926///
927/// # Example
928///
929/// ```no_run
930/// use std::net::Ipv4Addr;
931/// use dhcparse::{v4_options, dhcpv4::{Message, MessageType}};
932/// # let buf: &[u8] = todo!();
933/// let msg = Message::new(buf)?;
934/// assert_eq!(
935///     v4_options!(msg; MessageType required, RequestedIpAddress)?,
936///     (
937///         MessageType::DISCOVER,
938///         Some(&Ipv4Addr::new(192, 168, 1, 100).into())
939///     )
940/// );
941/// # Ok::<(), dhcparse::Error>(())
942/// ```
943#[macro_export]
944macro_rules! v4_options {
945    ($msg:expr; $($opt:ident $($required:ident)? ),*) => ('outer: loop {
946        use ::core::{result::Result::*, option::Option::*};
947        let mut count = 0;
948        $(#[allow(non_snake_case)] let mut $opt = None; count += 1;)*
949        for x in match $msg.options() {
950            Ok(x) => x,
951            Err(e) => break Err(e),
952        } {
953            match x {
954                $(Ok(($crate::dhcpv4::DhcpOption::$opt(data), _))
955                    if $opt.is_none() => { $opt = Some(data); },)*
956                Ok(_) => continue,
957                Err(e) => break 'outer Err(e),
958            }
959            count -= 1;
960            if count == 0 { break; }
961        }
962        break Ok(($({
963            let x = $opt;
964            $(
965                $crate::dhcpv4::_get_options::$required;
966                let x = if let Some(x) = x {
967                    x
968                } else {
969                    break Err($crate::Error::MissingRequired);
970                };
971            )?
972            x
973        }),*));
974    })
975}
976
977mod private {
978    pub trait Sealed {}
979
980    impl Sealed for super::Encoder {}
981    impl<Prev, I> Sealed for super::AppendOptions<Prev, I> {}
982    impl<Prev> Sealed for super::SetOption<'_, Prev> {}
983    impl<Prev, F> Sealed for super::FilterOptions<Prev, F> {}
984}
985
986/// An interface for DHCPv4 message transforms.
987pub trait Encode: private::Sealed {
988    /// Reencodes the given DHCPv4 message using this transform.
989    ///
990    /// The return value, if successful, is a sliced reference to the
991    /// written bytes.
992    fn encode<'dst, T: AsRef<[u8]>>(
993        mut self,
994        src: &Message<T>,
995        dst: &'dst mut [u8],
996    ) -> Result<Message<&'dst mut [u8]>, Error>
997    where
998        Self: Sized,
999    {
1000        let data = src.as_ref();
1001        dst[..data.len()].copy_from_slice(data);
1002        let mut cursor = Cursor {
1003            buffer: dst,
1004            index: OPTIONS_FIELD_OFFSET + MAGIC_COOKIE.len(),
1005        };
1006
1007        src.options()?.try_for_each(|x| {
1008            let (option, bnd) = x?;
1009            self.write_option(&mut cursor, data, (&option, bnd))
1010        })?;
1011        self.write_new_options(&mut cursor)?;
1012        DhcpOption::End.write(&mut cursor)?;
1013
1014        let len = cursor.index;
1015        Ok(Message::new(&mut dst[..len]).unwrap())
1016    }
1017
1018    /// Reencodes the given DHCPv4 message to a new owned buffer.
1019    #[cfg(feature = "std")]
1020    fn encode_to_owned<T: AsRef<[u8]>>(self, src: &Message<T>) -> Result<Message<Vec<u8>>, Error>
1021    where
1022        Self: Sized,
1023    {
1024        let mut buf = vec![0; src.as_ref().len() + self.max_size_diff()];
1025        let len = self.encode(src, &mut buf)?.as_ref().len();
1026        buf.truncate(len);
1027        Ok(Message::new(buf).unwrap())
1028    }
1029
1030    #[doc(hidden)]
1031    fn write_option<'old, 'new>(
1032        &mut self,
1033        cursor: &mut Cursor<'new>,
1034        src: &'old [u8],
1035        old_option: (&DhcpOption<'old>, (usize, usize)),
1036    ) -> Result<(), Error>;
1037
1038    #[doc(hidden)]
1039    fn write_new_options(self, cursor: &mut Cursor) -> Result<(), Error>;
1040
1041    /// Returns the largest byte size increase this encoder can incur
1042    /// when reencoding any message.
1043    fn max_size_diff(&self) -> usize;
1044
1045    /// Returns a new transform that additionally appends the given options.
1046    #[inline]
1047    fn append_options<I>(self, options: I) -> AppendOptions<Self, I>
1048    where
1049        Self: Sized,
1050    {
1051        AppendOptions {
1052            prev: self,
1053            options,
1054        }
1055    }
1056
1057    /// Returns a new transform that additionally sets the given option.
1058    ///
1059    /// For setting options that need more than 255 bytes of data, use
1060    /// a combination of [`append_options`] and [`filter_options`].
1061    ///
1062    /// [`append_options`]: Self::append_options
1063    /// [`filter_options`]: Self::filter_options
1064    #[inline]
1065    fn set_option(self, option: DhcpOption<'_>) -> SetOption<'_, Self>
1066    where
1067        Self: Sized,
1068    {
1069        SetOption {
1070            prev: self,
1071            replaced: false,
1072            option,
1073        }
1074    }
1075
1076    /// Returns a new transform that initially discards any options
1077    /// for which the function returns true.
1078    #[inline]
1079    fn filter_options<F>(self, f: F) -> FilterOptions<Self, F>
1080    where
1081        FilterOptions<Self, F>: Encode,
1082        Self: Sized,
1083    {
1084        FilterOptions { prev: self, f }
1085    }
1086}
1087
1088/// A base transform that just copies the old message.
1089#[derive(Debug)]
1090pub struct Encoder;
1091
1092impl Encode for Encoder {
1093    #[inline]
1094    fn write_option<'old, 'new>(
1095        &mut self,
1096        cursor: &mut Cursor<'new>,
1097        src: &'old [u8],
1098        (old_option, (start, len)): (&DhcpOption<'old>, (usize, usize)),
1099    ) -> Result<(), Error> {
1100        if let DhcpOption::OptionOverload(_) = old_option {
1101            return Ok(());
1102        }
1103        if cursor.index == start {
1104            // Since we copied old data the option has already been written
1105            cursor.index += len;
1106            Ok(())
1107        } else {
1108            cursor.write(&src[start..][..len])
1109        }
1110    }
1111
1112    #[inline]
1113    fn write_new_options(self, _cursor: &mut Cursor) -> Result<(), Error> {
1114        Ok(())
1115    }
1116
1117    #[inline]
1118    fn max_size_diff(&self) -> usize {
1119        // Since we do not use the sname/file fields when reencoding
1120        // messages, any options in those fields would take up space
1121        // at the end instead.
1122        /* sname field */
1123        64 + /* file field */ 128 - /* end in both fields */ 2 - /* option overload option */ 3
1124    }
1125}
1126
1127/// A transform that appends the specified options.
1128#[derive(Debug)]
1129pub struct AppendOptions<Prev, I> {
1130    prev: Prev,
1131    options: I,
1132}
1133
1134impl<'a, Prev: Encode, I> Encode for AppendOptions<Prev, I>
1135where
1136    I: IntoIterator<Item = DhcpOption<'a>> + Clone,
1137{
1138    #[inline]
1139    fn write_option<'old, 'new>(
1140        &mut self,
1141        cursor: &mut Cursor<'new>,
1142        src: &'old [u8],
1143        (old_option, bnd): (&DhcpOption<'old>, (usize, usize)),
1144    ) -> Result<(), Error> {
1145        self.prev.write_option(cursor, src, (old_option, bnd))
1146    }
1147
1148    #[inline]
1149    fn write_new_options(self, cursor: &mut Cursor) -> Result<(), Error> {
1150        self.prev.write_new_options(cursor)?;
1151        self.options
1152            .into_iter()
1153            .try_for_each(|option| option.write(cursor))
1154    }
1155
1156    #[inline]
1157    fn max_size_diff(&self) -> usize {
1158        self.prev.max_size_diff()
1159            + self
1160                .options
1161                .clone()
1162                .into_iter()
1163                .map(|option| option.size())
1164                .sum::<usize>()
1165    }
1166}
1167
1168/// A transform that sets a given option.
1169#[derive(Debug)]
1170pub struct SetOption<'a, Prev> {
1171    prev: Prev,
1172    replaced: bool,
1173    option: DhcpOption<'a>,
1174}
1175
1176impl<'a, Prev: Encode> Encode for SetOption<'a, Prev> {
1177    #[inline]
1178    fn write_option<'old, 'new>(
1179        &mut self,
1180        cursor: &mut Cursor<'new>,
1181        src: &'old [u8],
1182        (old_option, bnd): (&DhcpOption<'old>, (usize, usize)),
1183    ) -> Result<(), Error> {
1184        if old_option.code() == self.option.code() {
1185            if !self.replaced {
1186                self.option.write(cursor)?;
1187            }
1188            self.replaced = true;
1189            Ok(())
1190        } else {
1191            self.prev.write_option(cursor, src, (old_option, bnd))
1192        }
1193    }
1194
1195    #[inline]
1196    fn write_new_options(self, cursor: &mut Cursor) -> Result<(), Error> {
1197        self.prev.write_new_options(cursor)?;
1198        if !self.replaced {
1199            self.option.write(cursor)?;
1200        }
1201        Ok(())
1202    }
1203
1204    #[inline]
1205    fn max_size_diff(&self) -> usize {
1206        self.prev.max_size_diff() + self.option.size()
1207    }
1208}
1209
1210/// A transform that filters options.
1211#[derive(Debug)]
1212pub struct FilterOptions<Prev, F> {
1213    prev: Prev,
1214    f: F,
1215}
1216
1217impl<Prev: Encode, F> Encode for FilterOptions<Prev, F>
1218where
1219    for<'a> F: Fn(DhcpOption<'a>) -> bool,
1220{
1221    #[inline]
1222    fn write_option<'old, 'new>(
1223        &mut self,
1224        cursor: &mut Cursor<'new>,
1225        src: &'old [u8],
1226        (old_option, bnd): (&DhcpOption<'old>, (usize, usize)),
1227    ) -> Result<(), Error> {
1228        if (self.f)(*old_option) {
1229            self.prev.write_option(cursor, src, (old_option, bnd))
1230        } else {
1231            Ok(())
1232        }
1233    }
1234
1235    #[inline]
1236    fn write_new_options(self, cursor: &mut Cursor) -> Result<(), Error> {
1237        self.prev.write_new_options(cursor)
1238    }
1239
1240    #[inline]
1241    fn max_size_diff(&self) -> usize {
1242        self.prev.max_size_diff()
1243    }
1244}
1245
1246#[cfg(test)]
1247mod tests {
1248    use super::*;
1249
1250    /// An example DHCPOFFER message.
1251    const EX_MSG: [u8; 244] = [
1252        /* op */ 2, /* htype */ 1, /* hlen */ 6, /* hops */ 0,
1253        /* xid */ 0xC7, 0xF5, 0xA0, 0xA7, /* secs */ 1, 2, /* flags */ 0x80, 0x00,
1254        /* ciaddr */ 0x00, 0x00, 0x00, 0x00, /* yiaddr */ 0x12, 0x34, 0x56, 0x78,
1255        /* siaddr */ 0x00, 0x00, 0x00, 0x00, /* giaddr */ 0x11, 0x22, 0x33, 0x44,
1256        /* chaddr */ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1257        /* sname */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1258        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1259        0, 0, 0, 0, 0, 0, 0, 0, 0, /* file */ /* msg type */ 53, 1, 2, /* end */ 255, 0,
1260        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1261        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1262        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1263        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1264        0, 0, 0, /* magic */ 99, 130, 83, 99, /* options */
1265        /* option overload */ 52, 1, 0b01, /* end */ 255,
1266    ];
1267
1268    #[test]
1269    fn it_works() -> Result<(), Error> {
1270        use std::net::Ipv4Addr;
1271
1272        let view = Message::new(EX_MSG)?;
1273        assert_eq!(view.op()?, OpCode::BootReply);
1274        assert_eq!(view.hlen(), 6);
1275        assert_eq!(view.secs(), 0x0102);
1276        assert_eq!(view.flags(), Flags::BROADCAST);
1277        assert_eq!(view.yiaddr(), &Ipv4Addr::new(0x12, 0x34, 0x56, 0x78).into());
1278        assert_eq!(view.siaddr(), &Ipv4Addr::UNSPECIFIED.into());
1279        assert_eq!(view.giaddr(), &Ipv4Addr::new(0x11, 0x22, 0x33, 0x44).into());
1280        assert_eq!(view.chaddr()?, [0x01, 0x02, 0x03, 0x04, 0x05, 0x06]);
1281        Ok(())
1282    }
1283
1284    #[test]
1285    fn can_set_giaddr() -> Result<(), Error> {
1286        use std::net::Ipv4Addr;
1287
1288        let mut view = Message::new(EX_MSG)?;
1289        let new_giaddr = Ipv4Addr::new(0x31, 0x41, 0x59, 0x26);
1290        *view.giaddr_mut() = new_giaddr.into();
1291        assert_eq!(view.giaddr(), &new_giaddr.into());
1292        Ok(())
1293    }
1294
1295    #[test]
1296    fn read_options() -> Result<(), Error> {
1297        let view = Message::new(EX_MSG)?;
1298        assert_eq!(
1299            view.options()?.collect::<Result<Vec<_>, _>>()?,
1300            [
1301                (DhcpOption::OptionOverload(OptionOverload::FILE), (240, 3)),
1302                (DhcpOption::MessageType(MessageType::OFFER), (108, 3))
1303            ]
1304        );
1305        Ok(())
1306    }
1307
1308    #[test]
1309    fn write_options() -> Result<(), Error> {
1310        let view = Message::new(EX_MSG)?;
1311        let mut out = [0; MAX_MESSAGE_SIZE];
1312
1313        // Try to replace an option
1314        assert_eq!(
1315            Encoder
1316                .set_option(DhcpOption::MessageType(MessageType::REQUEST))
1317                .encode(&view, &mut out)?
1318                .options()?
1319                .map(|x| x.unwrap().0)
1320                .find(|x| matches!(x, DhcpOption::MessageType(_))),
1321            Some(DhcpOption::MessageType(MessageType::REQUEST)),
1322        );
1323
1324        // Add a new option
1325        assert_eq!(
1326            Encoder
1327                .set_option(DhcpOption::IpForwarding(true))
1328                .encode(&view, &mut out)?
1329                .options()?
1330                .map(|x| x.unwrap().0)
1331                .find(|x| matches!(x, DhcpOption::IpForwarding(_))),
1332            Some(DhcpOption::IpForwarding(true)),
1333        );
1334
1335        Ok(())
1336    }
1337
1338    #[test]
1339    fn construct_new_msg() -> Result<(), Error> {
1340        let client_id = [/* type field */ 6, 0x06, 0, 0, 0, 0, 0];
1341        let chaddr = &client_id[1..];
1342        let mut msg = Encoder
1343            .append_options([
1344                DhcpOption::MessageType(MessageType::DISCOVER),
1345                DhcpOption::ClientIdentifier(&client_id),
1346            ])
1347            .encode_to_owned(&Message::default())?;
1348        msg.set_chaddr(chaddr)?;
1349
1350        assert_eq!(msg.chaddr()?, chaddr);
1351        assert_eq!(
1352            msg.options()?.collect::<Result<Vec<_>, _>>()?,
1353            [
1354                (DhcpOption::MessageType(MessageType::DISCOVER), (240, 3)),
1355                (DhcpOption::ClientIdentifier(&client_id), (243, 9))
1356            ]
1357        );
1358        Ok(())
1359    }
1360
1361    #[test]
1362    fn option_size_includes_code_and_len() {
1363        assert_eq!(DhcpOption::End.size(), 1);
1364        assert_eq!(DhcpOption::MessageType(MessageType::DISCOVER).size(), 3);
1365    }
1366}