// See https://tools.ietf.org/html/rfc2131 for the DHCP specification.

use bitflags::bitflags;
use byteorder::{ByteOrder, NetworkEndian};
use core::iter;
use heapless::Vec;

use super::{Error, Result};
use crate::wire::arp::Hardware;
use crate::wire::{EthernetAddress, Ipv4Address};

pub const SERVER_PORT: u16 = 67;
pub const CLIENT_PORT: u16 = 68;
pub const MAX_DNS_SERVER_COUNT: usize = 3;

const DHCP_MAGIC_NUMBER: u32 = 0x63825363;

enum_with_unknown! {
    /// The possible opcodes of a DHCP packet.
    pub enum OpCode(u8) {
        Request = 1,
        Reply = 2,
    }
}

enum_with_unknown! {
    /// The possible message types of a DHCP packet.
    pub enum MessageType(u8) {
        Discover = 1,
        Offer = 2,
        Request = 3,
        Decline = 4,
        Ack = 5,
        Nak = 6,
        Release = 7,
        Inform = 8,
    }
}

bitflags! {
    pub struct Flags: u16 {
        const BROADCAST = 0b1000_0000_0000_0000;
    }
}

impl MessageType {
    const fn opcode(&self) -> OpCode {
        match *self {
            MessageType::Discover
            | MessageType::Inform
            | MessageType::Request
            | MessageType::Decline
            | MessageType::Release => OpCode::Request,
            MessageType::Offer | MessageType::Ack | MessageType::Nak => OpCode::Reply,
            MessageType::Unknown(_) => OpCode::Unknown(0),
        }
    }
}

/// A buffer for DHCP options.
#[derive(Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct DhcpOptionWriter<'a> {
    /// The underlying buffer, directly from the DHCP packet representation.
    buffer: &'a mut [u8],
}

impl<'a> DhcpOptionWriter<'a> {
    pub fn new(buffer: &'a mut [u8]) -> Self {
        Self { buffer }
    }

    /// Emit a  [`DhcpOption`] into a [`DhcpOptionWriter`].
    pub fn emit(&mut self, option: DhcpOption<'_>) -> Result<()> {
        if option.data.len() > u8::MAX as _ {
            return Err(Error);
        }

        let total_len = 2 + option.data.len();
        if self.buffer.len() < total_len {
            return Err(Error);
        }

        let (buf, rest) = core::mem::take(&mut self.buffer).split_at_mut(total_len);
        self.buffer = rest;

        buf[0] = option.kind;
        buf[1] = option.data.len() as _;
        buf[2..].copy_from_slice(option.data);

        Ok(())
    }

    pub fn end(&mut self) -> Result<()> {
        if self.buffer.is_empty() {
            return Err(Error);
        }

        self.buffer[0] = field::OPT_END;
        self.buffer = &mut [];
        Ok(())
    }
}

/// A representation of a single DHCP option.
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct DhcpOption<'a> {
    pub kind: u8,
    pub data: &'a [u8],
}

/// A read/write wrapper around a Dynamic Host Configuration Protocol packet buffer.
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct Packet<T: AsRef<[u8]>> {
    buffer: T,
}

pub(crate) mod field {
    #![allow(non_snake_case)]
    #![allow(unused)]

    use crate::wire::field::*;

    pub const OP: usize = 0;
    pub const HTYPE: usize = 1;
    pub const HLEN: usize = 2;
    pub const HOPS: usize = 3;
    pub const XID: Field = 4..8;
    pub const SECS: Field = 8..10;
    pub const FLAGS: Field = 10..12;
    pub const CIADDR: Field = 12..16;
    pub const YIADDR: Field = 16..20;
    pub const SIADDR: Field = 20..24;
    pub const GIADDR: Field = 24..28;
    pub const CHADDR: Field = 28..34;
    pub const SNAME: Field = 34..108;
    pub const FILE: Field = 108..236;
    pub const MAGIC_NUMBER: Field = 236..240;
    pub const OPTIONS: Rest = 240..;

    // Vendor Extensions
    pub const OPT_END: u8 = 255;
    pub const OPT_PAD: u8 = 0;
    pub const OPT_SUBNET_MASK: u8 = 1;
    pub const OPT_TIME_OFFSET: u8 = 2;
    pub const OPT_ROUTER: u8 = 3;
    pub const OPT_TIME_SERVER: u8 = 4;
    pub const OPT_NAME_SERVER: u8 = 5;
    pub const OPT_DOMAIN_NAME_SERVER: u8 = 6;
    pub const OPT_LOG_SERVER: u8 = 7;
    pub const OPT_COOKIE_SERVER: u8 = 8;
    pub const OPT_LPR_SERVER: u8 = 9;
    pub const OPT_IMPRESS_SERVER: u8 = 10;
    pub const OPT_RESOURCE_LOCATION_SERVER: u8 = 11;
    pub const OPT_HOST_NAME: u8 = 12;
    pub const OPT_BOOT_FILE_SIZE: u8 = 13;
    pub const OPT_MERIT_DUMP: u8 = 14;
    pub const OPT_DOMAIN_NAME: u8 = 15;
    pub const OPT_SWAP_SERVER: u8 = 16;
    pub const OPT_ROOT_PATH: u8 = 17;
    pub const OPT_EXTENSIONS_PATH: u8 = 18;

    // IP Layer Parameters per Host
    pub const OPT_IP_FORWARDING: u8 = 19;
    pub const OPT_NON_LOCAL_SOURCE_ROUTING: u8 = 20;
    pub const OPT_POLICY_FILTER: u8 = 21;
    pub const OPT_MAX_DATAGRAM_REASSEMBLY_SIZE: u8 = 22;
    pub const OPT_DEFAULT_TTL: u8 = 23;
    pub const OPT_PATH_MTU_AGING_TIMEOUT: u8 = 24;
    pub const OPT_PATH_MTU_PLATEAU_TABLE: u8 = 25;

    // IP Layer Parameters per Interface
    pub const OPT_INTERFACE_MTU: u8 = 26;
    pub const OPT_ALL_SUBNETS_ARE_LOCAL: u8 = 27;
    pub const OPT_BROADCAST_ADDRESS: u8 = 28;
    pub const OPT_PERFORM_MASK_DISCOVERY: u8 = 29;
    pub const OPT_MASK_SUPPLIER: u8 = 30;
    pub const OPT_PERFORM_ROUTER_DISCOVERY: u8 = 31;
    pub const OPT_ROUTER_SOLICITATION_ADDRESS: u8 = 32;
    pub const OPT_STATIC_ROUTE: u8 = 33;

    // Link Layer Parameters per Interface
    pub const OPT_TRAILER_ENCAPSULATION: u8 = 34;
    pub const OPT_ARP_CACHE_TIMEOUT: u8 = 35;
    pub const OPT_ETHERNET_ENCAPSULATION: u8 = 36;

    // TCP Parameters
    pub const OPT_TCP_DEFAULT_TTL: u8 = 37;
    pub const OPT_TCP_KEEPALIVE_INTERVAL: u8 = 38;
    pub const OPT_TCP_KEEPALIVE_GARBAGE: u8 = 39;

    // Application and Service Parameters
    pub const OPT_NIS_DOMAIN: u8 = 40;
    pub const OPT_NIS_SERVERS: u8 = 41;
    pub const OPT_NTP_SERVERS: u8 = 42;
    pub const OPT_VENDOR_SPECIFIC_INFO: u8 = 43;
    pub const OPT_NETBIOS_NAME_SERVER: u8 = 44;
    pub const OPT_NETBIOS_DISTRIBUTION_SERVER: u8 = 45;
    pub const OPT_NETBIOS_NODE_TYPE: u8 = 46;
    pub const OPT_NETBIOS_SCOPE: u8 = 47;
    pub const OPT_X_WINDOW_FONT_SERVER: u8 = 48;
    pub const OPT_X_WINDOW_DISPLAY_MANAGER: u8 = 49;
    pub const OPT_NIS_PLUS_DOMAIN: u8 = 64;
    pub const OPT_NIS_PLUS_SERVERS: u8 = 65;
    pub const OPT_MOBILE_IP_HOME_AGENT: u8 = 68;
    pub const OPT_SMTP_SERVER: u8 = 69;
    pub const OPT_POP3_SERVER: u8 = 70;
    pub const OPT_NNTP_SERVER: u8 = 71;
    pub const OPT_WWW_SERVER: u8 = 72;
    pub const OPT_FINGER_SERVER: u8 = 73;
    pub const OPT_IRC_SERVER: u8 = 74;
    pub const OPT_STREETTALK_SERVER: u8 = 75;
    pub const OPT_STDA_SERVER: u8 = 76;

    // DHCP Extensions
    pub const OPT_REQUESTED_IP: u8 = 50;
    pub const OPT_IP_LEASE_TIME: u8 = 51;
    pub const OPT_OPTION_OVERLOAD: u8 = 52;
    pub const OPT_TFTP_SERVER_NAME: u8 = 66;
    pub const OPT_BOOTFILE_NAME: u8 = 67;
    pub const OPT_DHCP_MESSAGE_TYPE: u8 = 53;
    pub const OPT_SERVER_IDENTIFIER: u8 = 54;
    pub const OPT_PARAMETER_REQUEST_LIST: u8 = 55;
    pub const OPT_MESSAGE: u8 = 56;
    pub const OPT_MAX_DHCP_MESSAGE_SIZE: u8 = 57;
    pub const OPT_RENEWAL_TIME_VALUE: u8 = 58;
    pub const OPT_REBINDING_TIME_VALUE: u8 = 59;
    pub const OPT_VENDOR_CLASS_ID: u8 = 60;
    pub const OPT_CLIENT_ID: u8 = 61;
}

impl<T: AsRef<[u8]>> Packet<T> {
    /// Imbue a raw octet buffer with DHCP packet structure.
    pub const fn new_unchecked(buffer: T) -> Packet<T> {
        Packet { buffer }
    }

    /// Shorthand for a combination of [new_unchecked] and [check_len].
    ///
    /// [new_unchecked]: #method.new_unchecked
    /// [check_len]: #method.check_len
    pub fn new_checked(buffer: T) -> Result<Packet<T>> {
        let packet = Self::new_unchecked(buffer);
        packet.check_len()?;
        Ok(packet)
    }

    /// Ensure that no accessor method will panic if called.
    /// Returns `Err(Error)` if the buffer is too short.
    ///
    /// [set_header_len]: #method.set_header_len
    pub fn check_len(&self) -> Result<()> {
        let len = self.buffer.as_ref().len();
        if len < field::MAGIC_NUMBER.end {
            Err(Error)
        } else {
            Ok(())
        }
    }

    /// Consume the packet, returning the underlying buffer.
    pub fn into_inner(self) -> T {
        self.buffer
    }

    /// Returns the operation code of this packet.
    pub fn opcode(&self) -> OpCode {
        let data = self.buffer.as_ref();
        OpCode::from(data[field::OP])
    }

    /// Returns the hardware protocol type (e.g. ethernet).
    pub fn hardware_type(&self) -> Hardware {
        let data = self.buffer.as_ref();
        Hardware::from(u16::from(data[field::HTYPE]))
    }

    /// Returns the length of a hardware address in bytes (e.g. 6 for ethernet).
    pub fn hardware_len(&self) -> u8 {
        self.buffer.as_ref()[field::HLEN]
    }

    /// Returns the transaction ID.
    ///
    /// The transaction ID (called `xid` in the specification) is a random number used to
    /// associate messages and responses between client and server. The number is chosen by
    /// the client.
    pub fn transaction_id(&self) -> u32 {
        let field = &self.buffer.as_ref()[field::XID];
        NetworkEndian::read_u32(field)
    }

    /// Returns the hardware address of the client (called `chaddr` in the specification).
    ///
    /// Only ethernet is supported by `smoltcp`, so this functions returns
    /// an `EthernetAddress`.
    pub fn client_hardware_address(&self) -> EthernetAddress {
        let field = &self.buffer.as_ref()[field::CHADDR];
        EthernetAddress::from_bytes(field)
    }

    /// Returns the value of the `hops` field.
    ///
    /// The `hops` field is set to zero by clients and optionally used by relay agents.
    pub fn hops(&self) -> u8 {
        self.buffer.as_ref()[field::HOPS]
    }

    /// Returns the value of the `secs` field.
    ///
    /// The secs field is filled by clients and describes the number of seconds elapsed
    /// since client began process.
    pub fn secs(&self) -> u16 {
        let field = &self.buffer.as_ref()[field::SECS];
        NetworkEndian::read_u16(field)
    }

    /// Returns the value of the `magic cookie` field in the DHCP options.
    ///
    /// This field should be always be `0x63825363`.
    pub fn magic_number(&self) -> u32 {
        let field = &self.buffer.as_ref()[field::MAGIC_NUMBER];
        NetworkEndian::read_u32(field)
    }

    /// Returns the Ipv4 address of the client, zero if not set.
    ///
    /// This corresponds to the `ciaddr` field in the DHCP specification. According to it,
    /// this field is “only filled in if client is in `BOUND`, `RENEW` or `REBINDING` state
    /// and can respond to ARP requests”.
    pub fn client_ip(&self) -> Ipv4Address {
        let field = &self.buffer.as_ref()[field::CIADDR];
        Ipv4Address::from_bytes(field)
    }

    /// Returns the value of the `yiaddr` field, zero if not set.
    pub fn your_ip(&self) -> Ipv4Address {
        let field = &self.buffer.as_ref()[field::YIADDR];
        Ipv4Address::from_bytes(field)
    }

    /// Returns the value of the `siaddr` field, zero if not set.
    pub fn server_ip(&self) -> Ipv4Address {
        let field = &self.buffer.as_ref()[field::SIADDR];
        Ipv4Address::from_bytes(field)
    }

    /// Returns the value of the `giaddr` field, zero if not set.
    pub fn relay_agent_ip(&self) -> Ipv4Address {
        let field = &self.buffer.as_ref()[field::GIADDR];
        Ipv4Address::from_bytes(field)
    }

    pub fn flags(&self) -> Flags {
        let field = &self.buffer.as_ref()[field::FLAGS];
        Flags::from_bits_truncate(NetworkEndian::read_u16(field))
    }

    /// Return an iterator over the options.
    #[inline]
    pub fn options(&self) -> impl Iterator<Item = DhcpOption<'_>> + '_ {
        let mut buf = &self.buffer.as_ref()[field::OPTIONS];
        iter::from_fn(move || {
            loop {
                match buf.first().copied() {
                    // No more options, return.
                    None => return None,
                    Some(field::OPT_END) => return None,

                    // Skip padding.
                    Some(field::OPT_PAD) => buf = &buf[1..],
                    Some(kind) => {
                        if buf.len() < 2 {
                            return None;
                        }

                        let len = buf[1] as usize;

                        if buf.len() < 2 + len {
                            return None;
                        }

                        let opt = DhcpOption {
                            kind,
                            data: &buf[2..2 + len],
                        };

                        buf = &buf[2 + len..];
                        return Some(opt);
                    }
                }
            }
        })
    }

    pub fn get_sname(&self) -> Result<&str> {
        let data = &self.buffer.as_ref()[field::SNAME];
        let len = data.iter().position(|&x| x == 0).ok_or(Error)?;
        if len == 0 {
            return Err(Error);
        }

        let data = core::str::from_utf8(&data[..len]).map_err(|_| Error)?;
        Ok(data)
    }

    pub fn get_boot_file(&self) -> Result<&str> {
        let data = &self.buffer.as_ref()[field::FILE];
        let len = data.iter().position(|&x| x == 0).ok_or(Error)?;
        if len == 0 {
            return Err(Error);
        }
        let data = core::str::from_utf8(&data[..len]).map_err(|_| Error)?;
        Ok(data)
    }
}

impl<T: AsRef<[u8]> + AsMut<[u8]>> Packet<T> {
    /// Sets the optional `sname` (“server name”) and `file` (“boot file name”) fields to zero.
    ///
    /// The fields are not commonly used, so we set their value always to zero. **This method
    /// must be called when creating a packet, otherwise the emitted values for these fields
    /// are undefined!**
    pub fn set_sname_and_boot_file_to_zero(&mut self) {
        let data = self.buffer.as_mut();
        for byte in &mut data[field::SNAME] {
            *byte = 0;
        }
        for byte in &mut data[field::FILE] {
            *byte = 0;
        }
    }

    /// Sets the `OpCode` for the packet.
    pub fn set_opcode(&mut self, value: OpCode) {
        let data = self.buffer.as_mut();
        data[field::OP] = value.into();
    }

    /// Sets the hardware address type (only ethernet is supported).
    pub fn set_hardware_type(&mut self, value: Hardware) {
        let data = self.buffer.as_mut();
        let number: u16 = value.into();
        assert!(number <= u16::from(u8::max_value())); // TODO: Replace with TryFrom when it's stable
        data[field::HTYPE] = number as u8;
    }

    /// Sets the hardware address length.
    ///
    /// Only ethernet is supported, so this field should be set to the value `6`.
    pub fn set_hardware_len(&mut self, value: u8) {
        self.buffer.as_mut()[field::HLEN] = value;
    }

    /// Sets the transaction ID.
    ///
    /// The transaction ID (called `xid` in the specification) is a random number used to
    /// associate messages and responses between client and server. The number is chosen by
    /// the client.
    pub fn set_transaction_id(&mut self, value: u32) {
        let field = &mut self.buffer.as_mut()[field::XID];
        NetworkEndian::write_u32(field, value)
    }

    /// Sets the ethernet address of the client.
    ///
    /// Sets the `chaddr` field.
    pub fn set_client_hardware_address(&mut self, value: EthernetAddress) {
        let field = &mut self.buffer.as_mut()[field::CHADDR];
        field.copy_from_slice(value.as_bytes());
    }

    /// Sets the hops field.
    ///
    /// The `hops` field is set to zero by clients and optionally used by relay agents.
    pub fn set_hops(&mut self, value: u8) {
        self.buffer.as_mut()[field::HOPS] = value;
    }

    /// Sets the `secs` field.
    ///
    /// The secs field is filled by clients and describes the number of seconds elapsed
    /// since client began process.
    pub fn set_secs(&mut self, value: u16) {
        let field = &mut self.buffer.as_mut()[field::SECS];
        NetworkEndian::write_u16(field, value);
    }

    /// Sets the value of the `magic cookie` field in the DHCP options.
    ///
    /// This field should be always be `0x63825363`.
    pub fn set_magic_number(&mut self, value: u32) {
        let field = &mut self.buffer.as_mut()[field::MAGIC_NUMBER];
        NetworkEndian::write_u32(field, value);
    }

    /// Sets the Ipv4 address of the client.
    ///
    /// This corresponds to the `ciaddr` field in the DHCP specification. According to it,
    /// this field is “only filled in if client is in `BOUND`, `RENEW` or `REBINDING` state
    /// and can respond to ARP requests”.
    pub fn set_client_ip(&mut self, value: Ipv4Address) {
        let field = &mut self.buffer.as_mut()[field::CIADDR];
        field.copy_from_slice(value.as_bytes());
    }

    /// Sets the value of the `yiaddr` field.
    pub fn set_your_ip(&mut self, value: Ipv4Address) {
        let field = &mut self.buffer.as_mut()[field::YIADDR];
        field.copy_from_slice(value.as_bytes());
    }

    /// Sets the value of the `siaddr` field.
    pub fn set_server_ip(&mut self, value: Ipv4Address) {
        let field = &mut self.buffer.as_mut()[field::SIADDR];
        field.copy_from_slice(value.as_bytes());
    }

    /// Sets the value of the `giaddr` field.
    pub fn set_relay_agent_ip(&mut self, value: Ipv4Address) {
        let field = &mut self.buffer.as_mut()[field::GIADDR];
        field.copy_from_slice(value.as_bytes());
    }

    /// Sets the flags to the specified value.
    pub fn set_flags(&mut self, val: Flags) {
        let field = &mut self.buffer.as_mut()[field::FLAGS];
        NetworkEndian::write_u16(field, val.bits());
    }
}

impl<'a, T: AsRef<[u8]> + AsMut<[u8]> + ?Sized> Packet<&'a mut T> {
    /// Return a pointer to the options.
    #[inline]
    pub fn options_mut(&mut self) -> DhcpOptionWriter<'_> {
        DhcpOptionWriter::new(&mut self.buffer.as_mut()[field::OPTIONS])
    }
}

/// A high-level representation of a Dynamic Host Configuration Protocol packet.
///
/// DHCP messages have the following layout (see [RFC 2131](https://tools.ietf.org/html/rfc2131)
/// for details):
///
/// ```no_rust
/// 0                   1                   2                   3
/// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/// | message_type  | htype (N/A)   |   hlen (N/A)  |   hops        |
/// +---------------+---------------+---------------+---------------+
/// |                       transaction_id                          |
/// +-------------------------------+-------------------------------+
/// |           secs                |           flags               |
/// +-------------------------------+-------------------------------+
/// |                           client_ip                           |
/// +---------------------------------------------------------------+
/// |                            your_ip                            |
/// +---------------------------------------------------------------+
/// |                           server_ip                           |
/// +---------------------------------------------------------------+
/// |                        relay_agent_ip                         |
/// +---------------------------------------------------------------+
/// |                                                               |
/// |                    client_hardware_address                    |
/// |                                                               |
/// |                                                               |
/// +---------------------------------------------------------------+
/// |                                                               |
/// |                          sname  (N/A)                         |
/// +---------------------------------------------------------------+
/// |                                                               |
/// |                          file    (N/A)                        |
/// +---------------------------------------------------------------+
/// |                                                               |
/// |                          options                              |
/// +---------------------------------------------------------------+
/// ```
///
/// It is assumed that the access layer is Ethernet, so `htype` (the field representing the
/// hardware address type) is always set to `1`, and `hlen` (which represents the hardware address
/// length) is set to `6`.
///
/// The `options` field has a variable length.
#[derive(Debug, PartialEq, Eq, Clone)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct Repr<'a> {
    /// This field is also known as `op` in the RFC. It indicates the type of DHCP message this
    /// packet represents.
    pub message_type: MessageType,
    /// This field is also known as `xid` in the RFC. It is a random number chosen by the client,
    /// used by the client and server to associate messages and responses between a client and a
    /// server.
    pub transaction_id: u32,
    /// seconds elapsed since client began address acquisition or renewal
    /// process the DHCPREQUEST message MUST use the same value in the DHCP
    /// message header's 'secs' field and be sent to the same IP broadcast
    /// address as the original DHCPDISCOVER message.
    pub secs: u16,
    /// This field is also known as `chaddr` in the RFC and for networks where the access layer is
    /// ethernet, it is the client MAC address.
    pub client_hardware_address: EthernetAddress,
    /// This field is also known as `ciaddr` in the RFC. It is only filled in if client is in
    /// BOUND, RENEW or REBINDING state and can respond to ARP requests.
    pub client_ip: Ipv4Address,
    /// This field is also known as `yiaddr` in the RFC.
    pub your_ip: Ipv4Address,
    /// This field is also known as `siaddr` in the RFC. It may be set by the server in DHCPOFFER
    /// and DHCPACK messages, and represent the address of the next server to use in bootstrap.
    pub server_ip: Ipv4Address,
    /// Default gateway
    pub router: Option<Ipv4Address>,
    /// This field comes from a corresponding DhcpOption.
    pub subnet_mask: Option<Ipv4Address>,
    /// This field is also known as `giaddr` in the RFC. In order to allow DHCP clients on subnets
    /// not directly served by DHCP servers to communicate with DHCP servers, DHCP relay agents can
    /// be installed on these subnets. The DHCP client broadcasts on the local link; the relay
    /// agent receives the broadcast and transmits it to one or more DHCP servers using unicast.
    /// The relay agent stores its own IP address in the `relay_agent_ip` field of the DHCP packet.
    /// The DHCP server uses the `relay_agent_ip` to determine the subnet on which the relay agent
    /// received the broadcast, and allocates an IP address on that subnet. When the DHCP server
    /// replies to the client, it sends the reply to the `relay_agent_ip` address, again using
    /// unicast. The relay agent then retransmits the response on the local network
    pub relay_agent_ip: Ipv4Address,
    /// Broadcast flags. It can be set in DHCPDISCOVER, DHCPINFORM and DHCPREQUEST message if the
    /// client requires the response to be broadcasted.
    pub broadcast: bool,
    /// The "requested IP address" option. It can be used by clients in DHCPREQUEST or DHCPDISCOVER
    /// messages, or by servers in DHCPDECLINE messages.
    pub requested_ip: Option<Ipv4Address>,
    /// The "client identifier" option.
    ///
    /// The 'client identifier' is an opaque key, not to be interpreted by the server; for example,
    /// the 'client identifier' may contain a hardware address, identical to the contents of the
    /// 'chaddr' field, or it may contain another type of identifier, such as a DNS name.  The
    /// 'client identifier' chosen by a DHCP client MUST be unique to that client within the subnet
    /// to which the client is attached. If the client uses a 'client identifier' in one message,
    /// it MUST use that same identifier in all subsequent messages, to ensure that all servers
    /// correctly identify the client.
    pub client_identifier: Option<EthernetAddress>,
    /// The "server identifier" option. It is used both to identify a DHCP server
    /// in a DHCP message and as a destination address from clients to servers.
    pub server_identifier: Option<Ipv4Address>,
    /// The parameter request list informs the server about which configuration parameters
    /// the client is interested in.
    pub parameter_request_list: Option<&'a [u8]>,
    /// DNS servers
    pub dns_servers: Option<Vec<Ipv4Address, MAX_DNS_SERVER_COUNT>>,
    /// The maximum size dhcp packet the interface can receive
    pub max_size: Option<u16>,
    /// The DHCP IP lease duration, specified in seconds.
    pub lease_duration: Option<u32>,
    /// The DHCP IP renew duration (T1 interval), in seconds, if specified in the packet.
    pub renew_duration: Option<u32>,
    /// The DHCP IP rebind duration (T2 interval), in seconds, if specified in the packet.
    pub rebind_duration: Option<u32>,
    /// When returned from [`Repr::parse`], this field will be `None`.
    /// However, when calling [`Repr::emit`], this field should contain only
    /// additional DHCP options not known to smoltcp.
    pub additional_options: &'a [DhcpOption<'a>],
}

impl<'a> Repr<'a> {
    /// Return the length of a packet that will be emitted from this high-level representation.
    pub fn buffer_len(&self) -> usize {
        let mut len = field::OPTIONS.start;
        // message type and end-of-options options
        len += 3 + 1;
        if self.requested_ip.is_some() {
            len += 6;
        }
        if self.client_identifier.is_some() {
            len += 9;
        }
        if self.server_identifier.is_some() {
            len += 6;
        }
        if self.max_size.is_some() {
            len += 4;
        }
        if self.router.is_some() {
            len += 6;
        }
        if self.subnet_mask.is_some() {
            len += 6;
        }
        if self.lease_duration.is_some() {
            len += 6;
        }
        if let Some(dns_servers) = &self.dns_servers {
            len += 2;
            len += dns_servers.iter().count() * core::mem::size_of::<u32>();
        }
        if let Some(list) = self.parameter_request_list {
            len += list.len() + 2;
        }
        for opt in self.additional_options {
            len += 2 + opt.data.len()
        }

        len
    }

    /// Parse a DHCP packet and return a high-level representation.
    pub fn parse<T>(packet: &'a Packet<&'a T>) -> Result<Self>
    where
        T: AsRef<[u8]> + ?Sized,
    {
        packet.check_len()?;
        let transaction_id = packet.transaction_id();
        let client_hardware_address = packet.client_hardware_address();
        let client_ip = packet.client_ip();
        let your_ip = packet.your_ip();
        let server_ip = packet.server_ip();
        let relay_agent_ip = packet.relay_agent_ip();
        let secs = packet.secs();

        // only ethernet is supported right now
        match packet.hardware_type() {
            Hardware::Ethernet => {
                if packet.hardware_len() != 6 {
                    return Err(Error);
                }
            }
            Hardware::Unknown(_) => return Err(Error), // unimplemented
        }

        if packet.magic_number() != DHCP_MAGIC_NUMBER {
            return Err(Error);
        }

        let mut message_type = Err(Error);
        let mut requested_ip = None;
        let mut client_identifier = None;
        let mut server_identifier = None;
        let mut router = None;
        let mut subnet_mask = None;
        let mut parameter_request_list = None;
        let mut dns_servers = None;
        let mut max_size = None;
        let mut lease_duration = None;
        let mut renew_duration = None;
        let mut rebind_duration = None;

        for option in packet.options() {
            let data = option.data;
            match (option.kind, data.len()) {
                (field::OPT_DHCP_MESSAGE_TYPE, 1) => {
                    let value = MessageType::from(data[0]);
                    if value.opcode() == packet.opcode() {
                        message_type = Ok(value);
                    }
                }
                (field::OPT_REQUESTED_IP, 4) => {
                    requested_ip = Some(Ipv4Address::from_bytes(data));
                }
                (field::OPT_CLIENT_ID, 7) => {
                    let hardware_type = Hardware::from(u16::from(data[0]));
                    if hardware_type != Hardware::Ethernet {
                        return Err(Error);
                    }
                    client_identifier = Some(EthernetAddress::from_bytes(&data[1..]));
                }
                (field::OPT_SERVER_IDENTIFIER, 4) => {
                    server_identifier = Some(Ipv4Address::from_bytes(data));
                }
                (field::OPT_ROUTER, 4) => {
                    router = Some(Ipv4Address::from_bytes(data));
                }
                (field::OPT_SUBNET_MASK, 4) => {
                    subnet_mask = Some(Ipv4Address::from_bytes(data));
                }
                (field::OPT_MAX_DHCP_MESSAGE_SIZE, 2) => {
                    max_size = Some(u16::from_be_bytes([data[0], data[1]]));
                }
                (field::OPT_RENEWAL_TIME_VALUE, 4) => {
                    renew_duration = Some(u32::from_be_bytes([data[0], data[1], data[2], data[3]]))
                }
                (field::OPT_REBINDING_TIME_VALUE, 4) => {
                    rebind_duration = Some(u32::from_be_bytes([data[0], data[1], data[2], data[3]]))
                }
                (field::OPT_IP_LEASE_TIME, 4) => {
                    lease_duration = Some(u32::from_be_bytes([data[0], data[1], data[2], data[3]]))
                }
                (field::OPT_PARAMETER_REQUEST_LIST, _) => {
                    parameter_request_list = Some(data);
                }
                (field::OPT_DOMAIN_NAME_SERVER, _) => {
                    let mut servers = Vec::new();
                    const IP_ADDR_BYTE_LEN: usize = 4;
                    let mut addrs = data.chunks_exact(IP_ADDR_BYTE_LEN);
                    for chunk in &mut addrs {
                        // We ignore push failures because that will only happen
                        // if we attempt to push more than 4 addresses, and the only
                        // solution to that is to support more addresses.
                        servers.push(Ipv4Address::from_bytes(chunk)).ok();
                    }
                    dns_servers = Some(servers);

                    if !addrs.remainder().is_empty() {
                        net_trace!("DHCP domain name servers contained invalid address");
                    }
                }
                _ => {}
            }
        }

        let broadcast = packet.flags().contains(Flags::BROADCAST);

        Ok(Repr {
            secs,
            transaction_id,
            client_hardware_address,
            client_ip,
            your_ip,
            server_ip,
            relay_agent_ip,
            broadcast,
            requested_ip,
            server_identifier,
            router,
            subnet_mask,
            client_identifier,
            parameter_request_list,
            dns_servers,
            max_size,
            lease_duration,
            renew_duration,
            rebind_duration,
            message_type: message_type?,
            additional_options: &[],
        })
    }

    /// Emit a high-level representation into a Dynamic Host
    /// Configuration Protocol packet.
    pub fn emit<T>(&self, packet: &mut Packet<&mut T>) -> Result<()>
    where
        T: AsRef<[u8]> + AsMut<[u8]> + ?Sized,
    {
        packet.set_sname_and_boot_file_to_zero();
        packet.set_opcode(self.message_type.opcode());
        packet.set_hardware_type(Hardware::Ethernet);
        packet.set_hardware_len(6);
        packet.set_transaction_id(self.transaction_id);
        packet.set_client_hardware_address(self.client_hardware_address);
        packet.set_hops(0);
        packet.set_secs(self.secs);
        packet.set_magic_number(0x63825363);
        packet.set_client_ip(self.client_ip);
        packet.set_your_ip(self.your_ip);
        packet.set_server_ip(self.server_ip);
        packet.set_relay_agent_ip(self.relay_agent_ip);

        let mut flags = Flags::empty();
        if self.broadcast {
            flags |= Flags::BROADCAST;
        }
        packet.set_flags(flags);

        {
            let mut options = packet.options_mut();

            options.emit(DhcpOption {
                kind: field::OPT_DHCP_MESSAGE_TYPE,
                data: &[self.message_type.into()],
            })?;

            if let Some(val) = &self.client_identifier {
                let mut data = [0; 7];
                data[0] = u16::from(Hardware::Ethernet) as u8;
                data[1..].copy_from_slice(val.as_bytes());

                options.emit(DhcpOption {
                    kind: field::OPT_CLIENT_ID,
                    data: &data,
                })?;
            }

            if let Some(val) = &self.server_identifier {
                options.emit(DhcpOption {
                    kind: field::OPT_SERVER_IDENTIFIER,
                    data: val.as_bytes(),
                })?;
            }

            if let Some(val) = &self.router {
                options.emit(DhcpOption {
                    kind: field::OPT_ROUTER,
                    data: val.as_bytes(),
                })?;
            }
            if let Some(val) = &self.subnet_mask {
                options.emit(DhcpOption {
                    kind: field::OPT_SUBNET_MASK,
                    data: val.as_bytes(),
                })?;
            }
            if let Some(val) = &self.requested_ip {
                options.emit(DhcpOption {
                    kind: field::OPT_REQUESTED_IP,
                    data: val.as_bytes(),
                })?;
            }
            if let Some(val) = &self.max_size {
                options.emit(DhcpOption {
                    kind: field::OPT_MAX_DHCP_MESSAGE_SIZE,
                    data: &val.to_be_bytes(),
                })?;
            }
            if let Some(val) = &self.lease_duration {
                options.emit(DhcpOption {
                    kind: field::OPT_IP_LEASE_TIME,
                    data: &val.to_be_bytes(),
                })?;
            }
            if let Some(val) = &self.parameter_request_list {
                options.emit(DhcpOption {
                    kind: field::OPT_PARAMETER_REQUEST_LIST,
                    data: val,
                })?;
            }

            if let Some(dns_servers) = &self.dns_servers {
                const IP_SIZE: usize = core::mem::size_of::<u32>();
                let mut servers = [0; MAX_DNS_SERVER_COUNT * IP_SIZE];

                let data_len = dns_servers
                    .iter()
                    .enumerate()
                    .inspect(|(i, ip)| {
                        servers[(i * IP_SIZE)..((i + 1) * IP_SIZE)].copy_from_slice(ip.as_bytes());
                    })
                    .count()
                    * IP_SIZE;
                options.emit(DhcpOption {
                    kind: field::OPT_DOMAIN_NAME_SERVER,
                    data: &servers[..data_len],
                })?;
            }

            for option in self.additional_options {
                options.emit(*option)?;
            }

            options.end()?;
        }

        Ok(())
    }
}

#[cfg(test)]
mod test {
    use super::*;
    use crate::wire::Ipv4Address;

    const MAGIC_COOKIE: u32 = 0x63825363;

    static DISCOVER_BYTES: &[u8] = &[
        0x01, 0x01, 0x06, 0x00, 0x00, 0x00, 0x3d, 0x1d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0b,
        0x82, 0x01, 0xfc, 0x42, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63, 0x82, 0x53, 0x63,
        0x35, 0x01, 0x01, 0x3d, 0x07, 0x01, 0x00, 0x0b, 0x82, 0x01, 0xfc, 0x42, 0x32, 0x04, 0x00,
        0x00, 0x00, 0x00, 0x39, 0x2, 0x5, 0xdc, 0x37, 0x04, 0x01, 0x03, 0x06, 0x2a, 0xff, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    ];

    static ACK_DNS_SERVER_BYTES: &[u8] = &[
        0x02, 0x01, 0x06, 0x00, 0xcc, 0x34, 0x75, 0xab, 0x00, 0x00, 0x80, 0x00, 0x0a, 0xff, 0x06,
        0x91, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0xff, 0x06, 0xfe, 0x34, 0x17,
        0xeb, 0xc9, 0xaa, 0x2f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63, 0x82, 0x53, 0x63,
        0x35, 0x01, 0x05, 0x36, 0x04, 0xa3, 0x01, 0x4a, 0x16, 0x01, 0x04, 0xff, 0xff, 0xff, 0x00,
        0x2b, 0x05, 0xdc, 0x03, 0x4e, 0x41, 0x50, 0x0f, 0x15, 0x6e, 0x61, 0x74, 0x2e, 0x70, 0x68,
        0x79, 0x73, 0x69, 0x63, 0x73, 0x2e, 0x6f, 0x78, 0x2e, 0x61, 0x63, 0x2e, 0x75, 0x6b, 0x00,
        0x03, 0x04, 0x0a, 0xff, 0x06, 0xfe, 0x06, 0x10, 0xa3, 0x01, 0x4a, 0x06, 0xa3, 0x01, 0x4a,
        0x07, 0xa3, 0x01, 0x4a, 0x03, 0xa3, 0x01, 0x4a, 0x04, 0x2c, 0x10, 0xa3, 0x01, 0x4a, 0x03,
        0xa3, 0x01, 0x4a, 0x04, 0xa3, 0x01, 0x4a, 0x06, 0xa3, 0x01, 0x4a, 0x07, 0x2e, 0x01, 0x08,
        0xff,
    ];

    static ACK_LEASE_TIME_BYTES: &[u8] = &[
        0x02, 0x01, 0x06, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x0a, 0x22, 0x10, 0x0b, 0x0a, 0x22, 0x10, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x04, 0x91,
        0x62, 0xd2, 0xa8, 0x6f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63, 0x82, 0x53, 0x63,
        0x35, 0x01, 0x05, 0x36, 0x04, 0x0a, 0x22, 0x10, 0x0a, 0x33, 0x04, 0x00, 0x00, 0x02, 0x56,
        0x01, 0x04, 0xff, 0xff, 0xff, 0x00, 0x03, 0x04, 0x0a, 0x22, 0x10, 0x0a, 0xff, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    ];

    const IP_NULL: Ipv4Address = Ipv4Address([0, 0, 0, 0]);
    const CLIENT_MAC: EthernetAddress = EthernetAddress([0x0, 0x0b, 0x82, 0x01, 0xfc, 0x42]);
    const DHCP_SIZE: u16 = 1500;

    #[test]
    fn test_deconstruct_discover() {
        let packet = Packet::new_unchecked(DISCOVER_BYTES);
        assert_eq!(packet.magic_number(), MAGIC_COOKIE);
        assert_eq!(packet.opcode(), OpCode::Request);
        assert_eq!(packet.hardware_type(), Hardware::Ethernet);
        assert_eq!(packet.hardware_len(), 6);
        assert_eq!(packet.hops(), 0);
        assert_eq!(packet.transaction_id(), 0x3d1d);
        assert_eq!(packet.secs(), 0);
        assert_eq!(packet.client_ip(), IP_NULL);
        assert_eq!(packet.your_ip(), IP_NULL);
        assert_eq!(packet.server_ip(), IP_NULL);
        assert_eq!(packet.relay_agent_ip(), IP_NULL);
        assert_eq!(packet.client_hardware_address(), CLIENT_MAC);

        let mut options = packet.options();
        assert_eq!(
            options.next(),
            Some(DhcpOption {
                kind: field::OPT_DHCP_MESSAGE_TYPE,
                data: &[0x01]
            })
        );
        assert_eq!(
            options.next(),
            Some(DhcpOption {
                kind: field::OPT_CLIENT_ID,
                data: &[0x01, 0x00, 0x0b, 0x82, 0x01, 0xfc, 0x42],
            })
        );
        assert_eq!(
            options.next(),
            Some(DhcpOption {
                kind: field::OPT_REQUESTED_IP,
                data: &[0x00, 0x00, 0x00, 0x00],
            })
        );
        assert_eq!(
            options.next(),
            Some(DhcpOption {
                kind: field::OPT_MAX_DHCP_MESSAGE_SIZE,
                data: &DHCP_SIZE.to_be_bytes(),
            })
        );
        assert_eq!(
            options.next(),
            Some(DhcpOption {
                kind: field::OPT_PARAMETER_REQUEST_LIST,
                data: &[1, 3, 6, 42]
            })
        );
        assert_eq!(options.next(), None);
    }

    #[test]
    fn test_construct_discover() {
        let mut bytes = vec![0xa5; 276];
        let mut packet = Packet::new_unchecked(&mut bytes);
        packet.set_magic_number(MAGIC_COOKIE);
        packet.set_sname_and_boot_file_to_zero();
        packet.set_opcode(OpCode::Request);
        packet.set_hardware_type(Hardware::Ethernet);
        packet.set_hardware_len(6);
        packet.set_hops(0);
        packet.set_transaction_id(0x3d1d);
        packet.set_secs(0);
        packet.set_flags(Flags::empty());
        packet.set_client_ip(IP_NULL);
        packet.set_your_ip(IP_NULL);
        packet.set_server_ip(IP_NULL);
        packet.set_relay_agent_ip(IP_NULL);
        packet.set_client_hardware_address(CLIENT_MAC);

        let mut options = packet.options_mut();

        options
            .emit(DhcpOption {
                kind: field::OPT_DHCP_MESSAGE_TYPE,
                data: &[0x01],
            })
            .unwrap();
        options
            .emit(DhcpOption {
                kind: field::OPT_CLIENT_ID,
                data: &[0x01, 0x00, 0x0b, 0x82, 0x01, 0xfc, 0x42],
            })
            .unwrap();
        options
            .emit(DhcpOption {
                kind: field::OPT_REQUESTED_IP,
                data: &[0x00, 0x00, 0x00, 0x00],
            })
            .unwrap();
        options
            .emit(DhcpOption {
                kind: field::OPT_MAX_DHCP_MESSAGE_SIZE,
                data: &DHCP_SIZE.to_be_bytes(),
            })
            .unwrap();
        options
            .emit(DhcpOption {
                kind: field::OPT_PARAMETER_REQUEST_LIST,
                data: &[1, 3, 6, 42],
            })
            .unwrap();
        options.end().unwrap();

        let packet = &mut packet.into_inner()[..];
        for byte in &mut packet[269..276] {
            *byte = 0; // padding bytes
        }

        assert_eq!(packet, DISCOVER_BYTES);
    }

    const fn offer_repr() -> Repr<'static> {
        Repr {
            message_type: MessageType::Offer,
            transaction_id: 0x3d1d,
            client_hardware_address: CLIENT_MAC,
            client_ip: IP_NULL,
            your_ip: IP_NULL,
            server_ip: IP_NULL,
            router: Some(IP_NULL),
            subnet_mask: Some(IP_NULL),
            relay_agent_ip: IP_NULL,
            secs: 0,
            broadcast: false,
            requested_ip: None,
            client_identifier: Some(CLIENT_MAC),
            server_identifier: None,
            parameter_request_list: None,
            dns_servers: None,
            max_size: None,
            renew_duration: None,
            rebind_duration: None,
            lease_duration: Some(0xffff_ffff), // Infinite lease
            additional_options: &[],
        }
    }

    const fn discover_repr() -> Repr<'static> {
        Repr {
            message_type: MessageType::Discover,
            transaction_id: 0x3d1d,
            client_hardware_address: CLIENT_MAC,
            client_ip: IP_NULL,
            your_ip: IP_NULL,
            server_ip: IP_NULL,
            router: None,
            subnet_mask: None,
            relay_agent_ip: IP_NULL,
            broadcast: false,
            secs: 0,
            max_size: Some(DHCP_SIZE),
            renew_duration: None,
            rebind_duration: None,
            lease_duration: None,
            requested_ip: Some(IP_NULL),
            client_identifier: Some(CLIENT_MAC),
            server_identifier: None,
            parameter_request_list: Some(&[1, 3, 6, 42]),
            dns_servers: None,
            additional_options: &[],
        }
    }

    #[test]
    fn test_parse_discover() {
        let packet = Packet::new_unchecked(DISCOVER_BYTES);
        let repr = Repr::parse(&packet).unwrap();
        assert_eq!(repr, discover_repr());
    }

    #[test]
    fn test_emit_discover() {
        let repr = discover_repr();
        let mut bytes = vec![0xa5; repr.buffer_len()];
        let mut packet = Packet::new_unchecked(&mut bytes);
        repr.emit(&mut packet).unwrap();
        let packet = &*packet.into_inner();
        let packet_len = packet.len();
        assert_eq!(packet, &DISCOVER_BYTES[..packet_len]);
        for byte in &DISCOVER_BYTES[packet_len..] {
            assert_eq!(*byte, 0); // padding bytes
        }
    }

    #[test]
    fn test_emit_offer() {
        let repr = offer_repr();
        let mut bytes = vec![0xa5; repr.buffer_len()];
        let mut packet = Packet::new_unchecked(&mut bytes);
        repr.emit(&mut packet).unwrap();
    }

    #[test]
    fn test_emit_offer_dns() {
        let repr = {
            let mut repr = offer_repr();
            repr.dns_servers = Some(
                Vec::from_slice(&[
                    Ipv4Address([163, 1, 74, 6]),
                    Ipv4Address([163, 1, 74, 7]),
                    Ipv4Address([163, 1, 74, 3]),
                ])
                .unwrap(),
            );
            repr
        };
        let mut bytes = vec![0xa5; repr.buffer_len()];
        let mut packet = Packet::new_unchecked(&mut bytes);
        repr.emit(&mut packet).unwrap();

        let packet = Packet::new_unchecked(&bytes);
        let repr_parsed = Repr::parse(&packet).unwrap();

        assert_eq!(
            repr_parsed.dns_servers,
            Some(
                Vec::from_slice(&[
                    Ipv4Address([163, 1, 74, 6]),
                    Ipv4Address([163, 1, 74, 7]),
                    Ipv4Address([163, 1, 74, 3]),
                ])
                .unwrap()
            )
        );
    }

    #[test]
    fn test_emit_dhcp_option() {
        static DATA: &[u8] = &[1, 3, 6];
        let dhcp_option = DhcpOption {
            kind: field::OPT_PARAMETER_REQUEST_LIST,
            data: DATA,
        };

        let mut bytes = vec![0xa5; 5];
        let mut writer = DhcpOptionWriter::new(&mut bytes);
        writer.emit(dhcp_option).unwrap();

        assert_eq!(
            &bytes[0..2],
            &[field::OPT_PARAMETER_REQUEST_LIST, DATA.len() as u8]
        );
        assert_eq!(&bytes[2..], DATA);
    }

    #[test]
    fn test_parse_ack_dns_servers() {
        let packet = Packet::new_unchecked(ACK_DNS_SERVER_BYTES);
        let repr = Repr::parse(&packet).unwrap();

        // The packet described by ACK_BYTES advertises 4 DNS servers
        // Here we ensure that we correctly parse the first 3 into our fixed
        // length-3 array (see issue #305)
        assert_eq!(
            repr.dns_servers,
            Some(
                Vec::from_slice(&[
                    Ipv4Address([163, 1, 74, 6]),
                    Ipv4Address([163, 1, 74, 7]),
                    Ipv4Address([163, 1, 74, 3])
                ])
                .unwrap()
            )
        );
    }

    #[test]
    fn test_parse_ack_lease_duration() {
        let packet = Packet::new_unchecked(ACK_LEASE_TIME_BYTES);
        let repr = Repr::parse(&packet).unwrap();

        // Verify that the lease time in the ACK is properly parsed. The packet contains a lease
        // duration of 598s.
        assert_eq!(repr.lease_duration, Some(598));
    }
}
