// Package dhcpv4 provides encoding and decoding of DHCPv4 packets and options.
//
// Example Usage:
//
//   p, err := dhcpv4.New(
//     dhcpv4.WithClientIP(net.IP{192, 168, 0, 1}),
//     dhcpv4.WithMessageType(dhcpv4.MessageTypeInform),
//   )
//   p.UpdateOption(dhcpv4.OptServerIdentifier(net.IP{192, 110, 110, 110}))
//
//   // Retrieve the DHCP Message Type option.
//   m := p.MessageType()
//
//   bytesOnTheWire := p.ToBytes()
//   longSummary := p.Summary()
package dhcpv4

import (
	"bytes"
	"context"
	"errors"
	"fmt"
	"net"
	"strings"
	"time"

	"github.com/insomniacslk/dhcp/iana"
	"github.com/insomniacslk/dhcp/rfc1035label"
	"github.com/u-root/uio/rand"
	"github.com/u-root/uio/uio"
)

const (
	// minPacketLen is the minimum DHCP header length.
	minPacketLen = 236

	// MaxHWAddrLen is the maximum hardware address length of the ClientHWAddr
	// (client hardware address) according to RFC 2131, Section 2. This is the
	// link-layer destination a server must send responses to.
	MaxHWAddrLen = 16

	// MaxMessageSize is the maximum size in bytes that a DHCPv4 packet can hold.
	MaxMessageSize = 576

	// Per RFC 951, the minimum length of a packet is 300 bytes.
	bootpMinLen = 300
)

// RandomTimeout is the amount of time to wait until random number generation
// is canceled.
var RandomTimeout = 2 * time.Minute

// magicCookie is the magic 4-byte value at the beginning of the list of options
// in a DHCPv4 packet.
var magicCookie = [4]byte{99, 130, 83, 99}

// DHCPv4 represents a DHCPv4 packet header and options. See the New* functions
// to build DHCPv4 packets.
type DHCPv4 struct {
	OpCode         OpcodeType
	HWType         iana.HWType
	HopCount       uint8
	TransactionID  TransactionID
	NumSeconds     uint16
	Flags          uint16
	ClientIPAddr   net.IP
	YourIPAddr     net.IP
	ServerIPAddr   net.IP
	GatewayIPAddr  net.IP
	ClientHWAddr   net.HardwareAddr
	ServerHostName string
	BootFileName   string
	Options        Options
}

// Modifier defines the signature for functions that can modify DHCPv4
// structures. This is used to simplify packet manipulation
type Modifier func(d *DHCPv4)

// IPv4AddrsForInterface obtains the currently-configured, non-loopback IPv4
// addresses for iface.
func IPv4AddrsForInterface(iface *net.Interface) ([]net.IP, error) {
	if iface == nil {
		return nil, errors.New("IPv4AddrsForInterface: iface cannot be nil")
	}
	addrs, err := iface.Addrs()
	if err != nil {
		return nil, err
	}
	return GetExternalIPv4Addrs(addrs)
}

// GetExternalIPv4Addrs obtains the currently-configured, non-loopback IPv4
// addresses from `addrs` coming from a particular interface (e.g.
// net.Interface.Addrs).
func GetExternalIPv4Addrs(addrs []net.Addr) ([]net.IP, error) {
	var v4addrs []net.IP
	for _, addr := range addrs {
		var ip net.IP
		switch v := addr.(type) {
		case *net.IPAddr:
			ip = v.IP
		case *net.IPNet:
			ip = v.IP
		}

		if ip == nil || ip.IsLoopback() {
			continue
		}
		ip = ip.To4()
		if ip == nil {
			continue
		}
		v4addrs = append(v4addrs, ip)
	}
	return v4addrs, nil
}

// GenerateTransactionID generates a random 32-bits number suitable for use as
// TransactionID
func GenerateTransactionID() (TransactionID, error) {
	var xid TransactionID
	ctx, cancel := context.WithTimeout(context.Background(), RandomTimeout)
	defer cancel()
	n, err := rand.ReadContext(ctx, xid[:])
	if err != nil {
		return xid, fmt.Errorf("could not get random number: %v", err)
	}
	if n != 4 {
		return xid, errors.New("invalid random sequence for transaction ID: smaller than 32 bits")
	}
	return xid, err
}

// New creates a new DHCPv4 structure and fill it up with default values. It
// won't be a valid DHCPv4 message so you will need to adjust its fields.
// See also NewDiscovery, NewRequest, NewAcknowledge, NewInform and NewRelease.
func New(modifiers ...Modifier) (*DHCPv4, error) {
	xid, err := GenerateTransactionID()
	if err != nil {
		return nil, err
	}
	d := DHCPv4{
		OpCode:        OpcodeBootRequest,
		HWType:        iana.HWTypeEthernet,
		ClientHWAddr:  make(net.HardwareAddr, 6),
		HopCount:      0,
		TransactionID: xid,
		NumSeconds:    0,
		Flags:         0,
		ClientIPAddr:  net.IPv4zero,
		YourIPAddr:    net.IPv4zero,
		ServerIPAddr:  net.IPv4zero,
		GatewayIPAddr: net.IPv4zero,
		Options:       make(Options),
	}
	for _, mod := range modifiers {
		mod(&d)
	}
	return &d, nil
}

// NewDiscoveryForInterface builds a new DHCPv4 Discovery message, with a default
// Ethernet HW type and the hardware address obtained from the specified
// interface.
func NewDiscoveryForInterface(ifname string, modifiers ...Modifier) (*DHCPv4, error) {
	iface, err := net.InterfaceByName(ifname)
	if err != nil {
		return nil, err
	}
	return NewDiscovery(iface.HardwareAddr, modifiers...)
}

// NewDiscovery builds a new DHCPv4 Discovery message, with a default Ethernet
// HW type and specified hardware address.
func NewDiscovery(hwaddr net.HardwareAddr, modifiers ...Modifier) (*DHCPv4, error) {
	return New(PrependModifiers(modifiers,
		WithHwAddr(hwaddr),
		WithRequestedOptions(
			OptionSubnetMask,
			OptionRouter,
			OptionDomainName,
			OptionDomainNameServer,
		),
		WithMessageType(MessageTypeDiscover),
	)...)
}

// NewInformForInterface builds a new DHCPv4 Informational message with default
// Ethernet HW type and the hardware address obtained from the specified
// interface.
func NewInformForInterface(ifname string, needsBroadcast bool) (*DHCPv4, error) {
	// get hw addr
	iface, err := net.InterfaceByName(ifname)
	if err != nil {
		return nil, err
	}

	// Set Client IP as iface's currently-configured IP.
	localIPs, err := IPv4AddrsForInterface(iface)
	if err != nil || len(localIPs) == 0 {
		return nil, fmt.Errorf("could not get local IPs for iface %s", ifname)
	}
	pkt, err := NewInform(iface.HardwareAddr, localIPs[0])
	if err != nil {
		return nil, err
	}

	if needsBroadcast {
		pkt.SetBroadcast()
	} else {
		pkt.SetUnicast()
	}
	return pkt, nil
}

// PrependModifiers prepends other to m.
func PrependModifiers(m []Modifier, other ...Modifier) []Modifier {
	return append(other, m...)
}

// NewInform builds a new DHCPv4 Informational message with the specified
// hardware address.
func NewInform(hwaddr net.HardwareAddr, localIP net.IP, modifiers ...Modifier) (*DHCPv4, error) {
	return New(PrependModifiers(modifiers,
		WithHwAddr(hwaddr),
		WithMessageType(MessageTypeInform),
		WithClientIP(localIP),
	)...)
}

// NewRequestFromOffer builds a DHCPv4 request from an offer.
// It assumes the SELECTING state by default, see Section 4.3.2 in RFC 2131 for more details.
func NewRequestFromOffer(offer *DHCPv4, modifiers ...Modifier) (*DHCPv4, error) {
	return New(PrependModifiers(modifiers,
		WithReply(offer),
		WithMessageType(MessageTypeRequest),
		WithClientIP(offer.ClientIPAddr),
		WithOption(OptRequestedIPAddress(offer.YourIPAddr)),
		// This is usually the server IP.
		WithOptionCopied(offer, OptionServerIdentifier),
		WithRequestedOptions(
			OptionSubnetMask,
			OptionRouter,
			OptionDomainName,
			OptionDomainNameServer,
		),
	)...)
}

// NewRenewFromAck builds a DHCPv4 RENEW-style request from the ACK of a lease. RENEW requests have
// minor changes to their options compared to SELECT requests as specified by RFC 2131, section 4.3.2.
func NewRenewFromAck(ack *DHCPv4, modifiers ...Modifier) (*DHCPv4, error) {
	return New(PrependModifiers(modifiers,
		WithReply(ack),
		WithMessageType(MessageTypeRequest),
		// The client IP must be filled in with the IP offered to the client
		WithClientIP(ack.YourIPAddr),
		// The renewal request must use unicast
		WithBroadcast(false),
		WithRequestedOptions(
			OptionSubnetMask,
			OptionRouter,
			OptionDomainName,
			OptionDomainNameServer,
		),
	)...)
}

// NewReplyFromRequest builds a DHCPv4 reply from a request.
func NewReplyFromRequest(request *DHCPv4, modifiers ...Modifier) (*DHCPv4, error) {
	return New(PrependModifiers(modifiers,
		WithReply(request),
		WithGatewayIP(request.GatewayIPAddr),
		WithOptionCopied(request, OptionRelayAgentInformation),

		// RFC 6842 states the Client Identifier option must be copied
		// from the request if a client specified it.
		WithOptionCopied(request, OptionClientIdentifier),
	)...)
}

// NewReleaseFromACK creates a DHCPv4 Release message from ACK.
// default Release message without any Modifer is created as following:
//  - option Message Type is Release
//  - ClientIP is set to ack.YourIPAddr
//  - ClientHWAddr is set to ack.ClientHWAddr
//  - Unicast
//  - option Server Identifier is set to ack's ServerIdentifier
func NewReleaseFromACK(ack *DHCPv4, modifiers ...Modifier) (*DHCPv4, error) {
	return New(PrependModifiers(modifiers,
		WithMessageType(MessageTypeRelease),
		WithClientIP(ack.YourIPAddr),
		WithHwAddr(ack.ClientHWAddr),
		WithBroadcast(false),
		WithOptionCopied(ack, OptionServerIdentifier),
	)...)
}

// FromBytes decodes a DHCPv4 packet from a sequence of bytes, and returns an
// error if the packet is not valid.
func FromBytes(q []byte) (*DHCPv4, error) {
	var p DHCPv4
	buf := uio.NewBigEndianBuffer(q)

	p.OpCode = OpcodeType(buf.Read8())
	p.HWType = iana.HWType(buf.Read8())

	hwAddrLen := buf.Read8()

	p.HopCount = buf.Read8()
	buf.ReadBytes(p.TransactionID[:])
	p.NumSeconds = buf.Read16()
	p.Flags = buf.Read16()

	p.ClientIPAddr = net.IP(buf.CopyN(net.IPv4len))
	p.YourIPAddr = net.IP(buf.CopyN(net.IPv4len))
	p.ServerIPAddr = net.IP(buf.CopyN(net.IPv4len))
	p.GatewayIPAddr = net.IP(buf.CopyN(net.IPv4len))

	if hwAddrLen > 16 {
		hwAddrLen = 16
	}
	// Always read 16 bytes, but only use hwaddrlen of them.
	p.ClientHWAddr = make(net.HardwareAddr, 16)
	buf.ReadBytes(p.ClientHWAddr)
	p.ClientHWAddr = p.ClientHWAddr[:hwAddrLen]

	var sname [64]byte
	buf.ReadBytes(sname[:])
	length := strings.Index(string(sname[:]), "\x00")
	if length == -1 {
		length = 64
	}
	p.ServerHostName = string(sname[:length])

	var file [128]byte
	buf.ReadBytes(file[:])
	length = strings.Index(string(file[:]), "\x00")
	if length == -1 {
		length = 128
	}
	p.BootFileName = string(file[:length])

	var cookie [4]byte
	buf.ReadBytes(cookie[:])

	if err := buf.Error(); err != nil {
		return nil, err
	}
	if cookie != magicCookie {
		return nil, fmt.Errorf("malformed DHCP packet: got magic cookie %v, want %v", cookie[:], magicCookie[:])
	}

	p.Options = make(Options)
	if err := p.Options.fromBytesCheckEnd(buf.Data(), true); err != nil {
		return nil, err
	}
	return &p, nil
}

// FlagsToString returns a human-readable representation of the flags field.
func (d *DHCPv4) FlagsToString() string {
	flags := ""
	if d.IsBroadcast() {
		flags += "Broadcast"
	} else {
		flags += "Unicast"
	}
	if d.Flags&0xfe != 0 {
		flags += " (reserved bits not zeroed)"
	}
	return flags
}

// IsBroadcast indicates whether the packet is a broadcast packet.
func (d *DHCPv4) IsBroadcast() bool {
	return d.Flags&0x8000 == 0x8000
}

// SetBroadcast sets the packet to be a broadcast packet.
func (d *DHCPv4) SetBroadcast() {
	d.Flags |= 0x8000
}

// IsUnicast indicates whether the packet is a unicast packet.
func (d *DHCPv4) IsUnicast() bool {
	return d.Flags&0x8000 == 0
}

// SetUnicast sets the packet to be a unicast packet.
func (d *DHCPv4) SetUnicast() {
	d.Flags &= ^uint16(0x8000)
}

// GetOneOption returns the option that matches the given option code.
//
// According to RFC 3396, options that are specified more than once are
// concatenated, and hence this should always just return one option.
func (d *DHCPv4) GetOneOption(code OptionCode) []byte {
	return d.Options.Get(code)
}

// DeleteOption deletes an existing option with the given option code.
func (d *DHCPv4) DeleteOption(code OptionCode) {
	if d.Options != nil {
		d.Options.Del(code)
	}
}

// UpdateOption replaces an existing option with the same option code with the
// given one, adding it if not already present.
func (d *DHCPv4) UpdateOption(opt Option) {
	if d.Options == nil {
		d.Options = make(Options)
	}
	d.Options.Update(opt)
}

// String implements fmt.Stringer.
func (d *DHCPv4) String() string {
	return fmt.Sprintf("DHCPv4(xid=%s hwaddr=%s msg_type=%s, your_ip=%s, server_ip=%s)",
		d.TransactionID, d.ClientHWAddr, d.MessageType(), d.YourIPAddr, d.ServerIPAddr)
}

// SummaryWithVendor prints a summary of the packet, interpreting the
// vendor-specific info option using the given parser (can be nil).
func (d *DHCPv4) SummaryWithVendor(vendorDecoder OptionDecoder) string {
	ret := fmt.Sprintf(
		"DHCPv4 Message\n"+
			"  opcode: %s\n"+
			"  hwtype: %s\n"+
			"  hopcount: %v\n"+
			"  transaction ID: %s\n"+
			"  num seconds: %v\n"+
			"  flags: %v (0x%02x)\n"+
			"  client IP: %s\n"+
			"  your IP: %s\n"+
			"  server IP: %s\n"+
			"  gateway IP: %s\n"+
			"  client MAC: %s\n"+
			"  server hostname: %s\n"+
			"  bootfile name: %s\n",
		d.OpCode,
		d.HWType,
		d.HopCount,
		d.TransactionID,
		d.NumSeconds,
		d.FlagsToString(),
		d.Flags,
		d.ClientIPAddr,
		d.YourIPAddr,
		d.ServerIPAddr,
		d.GatewayIPAddr,
		d.ClientHWAddr,
		d.ServerHostName,
		d.BootFileName,
	)
	ret += "  options:\n"
	ret += d.Options.Summary(vendorDecoder)
	return ret
}

// Summary prints detailed information about the packet.
func (d *DHCPv4) Summary() string {
	return d.SummaryWithVendor(nil)
}

// IsOptionRequested returns true if that option is within the requested
// options of the DHCPv4 message.
func (d *DHCPv4) IsOptionRequested(requested OptionCode) bool {
	rq := d.ParameterRequestList()
	if rq == nil {
		// RFC2131§3.5
		// Not all clients require initialization of all parameters [...]
		// Two techniques are used to reduce the number of parameters transmitted from
		// the server to the client. [...] Second, in its initial DHCPDISCOVER or
		// DHCPREQUEST message, a client may provide the server with a list of specific
		// parameters the client is interested in.
		// We interpret this to say that all available parameters should be sent if
		// the parameter request list is not sent at all.
		return true
	}

	for _, o := range rq {
		if o.Code() == requested.Code() {
			return true
		}
	}
	return false
}

// In case somebody forgets to set an IP, just write 0s as default values.
func writeIP(b *uio.Lexer, ip net.IP) {
	var zeros [net.IPv4len]byte
	if ip == nil {
		b.WriteBytes(zeros[:])
	} else {
		// Converting IP to 4 byte format
		ip = ip.To4()
		b.WriteBytes(ip[:net.IPv4len])
	}
}

// ToBytes writes the packet to binary.
func (d *DHCPv4) ToBytes() []byte {
	buf := uio.NewBigEndianBuffer(make([]byte, 0, minPacketLen))
	buf.Write8(uint8(d.OpCode))
	buf.Write8(uint8(d.HWType))

	// HwAddrLen
	hlen := uint8(len(d.ClientHWAddr))
	buf.Write8(hlen)
	buf.Write8(d.HopCount)
	buf.WriteBytes(d.TransactionID[:])
	buf.Write16(d.NumSeconds)
	buf.Write16(d.Flags)

	writeIP(buf, d.ClientIPAddr)
	writeIP(buf, d.YourIPAddr)
	writeIP(buf, d.ServerIPAddr)
	writeIP(buf, d.GatewayIPAddr)
	copy(buf.WriteN(16), d.ClientHWAddr)

	var sname [64]byte
	copy(sname[:63], []byte(d.ServerHostName))
	buf.WriteBytes(sname[:])

	var file [128]byte
	copy(file[:127], []byte(d.BootFileName))
	buf.WriteBytes(file[:])

	// The magic cookie.
	buf.WriteBytes(magicCookie[:])

	// Write all options.
	d.Options.Marshal(buf)

	// Finish the options.
	buf.Write8(OptionEnd.Code())

	// DHCP is based on BOOTP, and BOOTP messages have a minimum length of
	// 300 bytes per RFC 951. This not stated explicitly, but if you sum up
	// all the bytes in the message layout, you'll get 300 bytes.
	//
	// Some DHCP servers and relay agents care about this BOOTP legacy B.S.
	// and "conveniently" drop messages that are less than 300 bytes long.
	if buf.Len() < bootpMinLen {
		buf.WriteBytes(bytes.Repeat([]byte{OptionPad.Code()}, bootpMinLen-buf.Len()))
	}

	return buf.Data()
}

// GetBroadcastAddress returns the DHCPv4 Broadcast Address value in d.
//
// The broadcast address option is described in RFC 2132, Section 5.3.
func (d *DHCPv4) BroadcastAddress() net.IP {
	return GetIP(OptionBroadcastAddress, d.Options)
}

// RequestedIPAddress returns the DHCPv4 Requested IP Address value in d.
//
// The requested IP address option is described by RFC 2132, Section 9.1.
func (d *DHCPv4) RequestedIPAddress() net.IP {
	return GetIP(OptionRequestedIPAddress, d.Options)
}

// ServerIdentifier returns the DHCPv4 Server Identifier value in d.
//
// The server identifier option is described by RFC 2132, Section 9.7.
func (d *DHCPv4) ServerIdentifier() net.IP {
	return GetIP(OptionServerIdentifier, d.Options)
}

// Router parses the DHCPv4 Router option if present.
//
// The Router option is described by RFC 2132, Section 3.5.
func (d *DHCPv4) Router() []net.IP {
	return GetIPs(OptionRouter, d.Options)
}

// ClasslessStaticRoute parses the DHCPv4 Classless Static Route option if present.
//
// The Classless Static Route option is described by RFC 3442.
func (d *DHCPv4) ClasslessStaticRoute() []*Route {
	v := d.Options.Get(OptionClasslessStaticRoute)
	if v == nil {
		return nil
	}
	var routes Routes
	if err := routes.FromBytes(v); err != nil {
		return nil
	}
	return routes
}

// NTPServers parses the DHCPv4 NTP Servers option if present.
//
// The NTP servers option is described by RFC 2132, Section 8.3.
func (d *DHCPv4) NTPServers() []net.IP {
	return GetIPs(OptionNTPServers, d.Options)
}

// DNS parses the DHCPv4 Domain Name Server option if present.
//
// The DNS server option is described by RFC 2132, Section 3.8.
func (d *DHCPv4) DNS() []net.IP {
	return GetIPs(OptionDomainNameServer, d.Options)
}

// DomainName parses the DHCPv4 Domain Name option if present.
//
// The Domain Name option is described by RFC 2132, Section 3.17.
func (d *DHCPv4) DomainName() string {
	return GetString(OptionDomainName, d.Options)
}

// HostName parses the DHCPv4 Host Name option if present.
//
// The Host Name option is described by RFC 2132, Section 3.14.
func (d *DHCPv4) HostName() string {
	name := GetString(OptionHostName, d.Options)
	return strings.TrimRight(name, "\x00")
}

// RootPath parses the DHCPv4 Root Path option if present.
//
// The Root Path option is described by RFC 2132, Section 3.19.
func (d *DHCPv4) RootPath() string {
	return GetString(OptionRootPath, d.Options)
}

// BootFileNameOption parses the DHCPv4 Bootfile Name option if present.
//
// The Bootfile Name option is described by RFC 2132, Section 9.5.
func (d *DHCPv4) BootFileNameOption() string {
	name := GetString(OptionBootfileName, d.Options)
	return strings.TrimRight(name, "\x00")
}

// TFTPServerName parses the DHCPv4 TFTP Server Name option if present.
//
// The TFTP Server Name option is described by RFC 2132, Section 9.4.
func (d *DHCPv4) TFTPServerName() string {
	name := GetString(OptionTFTPServerName, d.Options)
	return strings.TrimRight(name, "\x00")
}

// ClassIdentifier parses the DHCPv4 Class Identifier option if present.
//
// The Vendor Class Identifier option is described by RFC 2132, Section 9.13.
func (d *DHCPv4) ClassIdentifier() string {
	return GetString(OptionClassIdentifier, d.Options)
}

// ClientArch returns the Client System Architecture Type option.
func (d *DHCPv4) ClientArch() []iana.Arch {
	v := d.Options.Get(OptionClientSystemArchitectureType)
	if v == nil {
		return nil
	}
	var archs iana.Archs
	if err := archs.FromBytes(v); err != nil {
		return nil
	}
	return archs
}

// DomainSearch returns the domain search list if present.
//
// The domain search option is described by RFC 3397, Section 2.
func (d *DHCPv4) DomainSearch() *rfc1035label.Labels {
	v := d.Options.Get(OptionDNSDomainSearchList)
	if v == nil {
		return nil
	}
	labels, err := rfc1035label.FromBytes(v)
	if err != nil {
		return nil
	}
	return labels
}

// IPAddressLeaseTime returns the IP address lease time or the given
// default duration if not present.
//
// The IP address lease time option is described by RFC 2132, Section 9.2.
func (d *DHCPv4) IPAddressLeaseTime(def time.Duration) time.Duration {
	v := d.Options.Get(OptionIPAddressLeaseTime)
	if v == nil {
		return def
	}
	var dur Duration
	if err := dur.FromBytes(v); err != nil {
		return def
	}
	return time.Duration(dur)
}

// IPAddressRenewalTime returns the IP address renewal time or the given
// default duration if not present.
//
// The IP address renewal time option is described by RFC 2132, Section 9.11.
func (d *DHCPv4) IPAddressRenewalTime(def time.Duration) time.Duration {
	v := d.Options.Get(OptionRenewTimeValue)
	if v == nil {
		return def
	}
	var dur Duration
	if err := dur.FromBytes(v); err != nil {
		return def
	}
	return time.Duration(dur)
}

// IPAddressRebindingTime returns the IP address rebinding time or the given
// default duration if not present.
//
// The IP address rebinding time option is described by RFC 2132, Section 9.12.
func (d *DHCPv4) IPAddressRebindingTime(def time.Duration) time.Duration {
	v := d.Options.Get(OptionRebindingTimeValue)
	if v == nil {
		return def
	}
	var dur Duration
	if err := dur.FromBytes(v); err != nil {
		return def
	}
	return time.Duration(dur)
}

// IPv6OnlyPreferred returns the V6ONLY_WAIT duration, and a boolean
// indicating whether this option was present.
//
// The IPv6-Only Preferred option is described by RFC 8925, Section 3.1.
func (d *DHCPv4) IPv6OnlyPreferred() (time.Duration, bool) {
	v := d.Options.Get(OptionIPv6OnlyPreferred)
	if v == nil {
		return 0, false
	}
	var dur Duration
	if err := dur.FromBytes(v); err != nil {
		return 0, false
	}
	return time.Duration(dur), true
}

// MaxMessageSize returns the DHCP Maximum Message Size if present.
//
// The Maximum DHCP Message Size option is described by RFC 2132, Section 9.10.
func (d *DHCPv4) MaxMessageSize() (uint16, error) {
	return GetUint16(OptionMaximumDHCPMessageSize, d.Options)
}

// AutoConfigure returns the value of the AutoConfigure option, and a
// boolean indicating if it was present.
//
// The AutoConfigure option is described by RFC 2563, Section 2.
func (d *DHCPv4) AutoConfigure() (AutoConfiguration, bool) {
	v, err := GetByte(OptionAutoConfigure, d.Options)
	return AutoConfiguration(v), err == nil
}

// MessageType returns the DHCPv4 Message Type option.
func (d *DHCPv4) MessageType() MessageType {
	v := d.Options.Get(OptionDHCPMessageType)
	if v == nil {
		return MessageTypeNone
	}
	var m MessageType
	if err := m.FromBytes(v); err != nil {
		return MessageTypeNone
	}
	return m
}

// Message returns the DHCPv4 (Error) Message option.
//
// The message options is described in RFC 2132, Section 9.9.
func (d *DHCPv4) Message() string {
	return GetString(OptionMessage, d.Options)
}

// ParameterRequestList returns the DHCPv4 Parameter Request List.
//
// The parameter request list option is described by RFC 2132, Section 9.8.
func (d *DHCPv4) ParameterRequestList() OptionCodeList {
	v := d.Options.Get(OptionParameterRequestList)
	if v == nil {
		return nil
	}
	var codes OptionCodeList
	if err := codes.FromBytes(v); err != nil {
		return nil
	}
	return codes
}

// RelayAgentInfo returns options embedded by the relay agent.
//
// The relay agent info option is described by RFC 3046.
func (d *DHCPv4) RelayAgentInfo() *RelayOptions {
	v := d.Options.Get(OptionRelayAgentInformation)
	if v == nil {
		return nil
	}
	var relayOptions RelayOptions
	if err := relayOptions.FromBytes(v); err != nil {
		return nil
	}
	return &relayOptions
}

// SubnetMask returns a subnet mask option contained if present.
//
// The subnet mask option is described by RFC 2132, Section 3.3.
func (d *DHCPv4) SubnetMask() net.IPMask {
	v := d.Options.Get(OptionSubnetMask)
	if v == nil {
		return nil
	}
	var im IPMask
	if err := im.FromBytes(v); err != nil {
		return nil
	}
	return net.IPMask(im)
}

// UserClass returns the user class if present.
//
// The user class information option is defined by RFC 3004.
func (d *DHCPv4) UserClass() []string {
	v := d.Options.Get(OptionUserClassInformation)
	if v == nil {
		return nil
	}
	var uc Strings
	if err := uc.FromBytes(v); err != nil {
		return []string{GetString(OptionUserClassInformation, d.Options)}
	}
	return uc
}

// VIVC returns the vendor-identifying vendor class option if present.
func (d *DHCPv4) VIVC() VIVCIdentifiers {
	v := d.Options.Get(OptionVendorIdentifyingVendorClass)
	if v == nil {
		return nil
	}
	var ids VIVCIdentifiers
	if err := ids.FromBytes(v); err != nil {
		return nil
	}
	return ids
}
