Package org.dhcp4java

Source Code of org.dhcp4java.DHCPPacket

/*
*  This file is part of dhcp4java, a DHCP API for the Java language.
*  (c) 2006 Stephan Hadinger
*
*  This library is free software; you can redistribute it and/or
*  modify it under the terms of the GNU Lesser General Public
*  License as published by the Free Software Foundation; either
*  version 2.1 of the License, or (at your option) any later version.
*
*  This library is distributed in the hope that it will be useful,
*  but WITHOUT ANY WARRANTY; without even the implied warranty of
*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
*  Lesser General Public License for more details.
*
*  You should have received a copy of the GNU Lesser General Public
*  License along with this library; if not, write to the Free Software
*  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
*/
package org.dhcp4java;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.Serializable;
import java.net.DatagramPacket;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import static org.dhcp4java.DHCPConstants.*;

/**
* The basic class for manipulating DHCP packets.
*
* @author Stephan Hadinger
* @version 1.00
*
* <p>There are two basic ways to build a new DHCPPacket object.
* <p>First one is to build an object from scratch using the constructor and setters.
* If you need to set repeatedly the same set of parameters and options,
* you can create a "master" object and clone it many times.
*
* <pre>
* DHCPPacket discover = new DHCPPacket();
* discover.setOp(DHCPPacket.BOOTREQUEST);
* discover.setHtype(DHCPPacket.HTYPE_ETHER);
* discover.setHlen((byte) 6);
* discover.setHops((byte) 0);
* discover.setXid( (new Random()).nextInt() );
* ...
* </pre>
* Second is to decode a DHCP datagram received from the network.
* In this case, the object is created through a factory.
*
* <p>Example: simple DHCP sniffer
* <pre>
* DatagramSocket socket = new DatagramSocket(67);
* while (true) {
*     DatagramPacket pac = new DatagramPacket(new byte[1500], 1500);
*     socket.receive(pac);
*     DHCPPacket dhcp = DHCPPacket.getPacket(pac);
*     System.out.println(dhcp.toString());
* }
* </pre>
* In this second way, beware that a <tt>BadPacketExpcetion</tt> is thrown
* if the datagram contains invalid DHCP data.
*
*
* <p><b>Getters and Setters</b>: methods are provided with high-level data structures
* wherever it is possible (String, InetAddress...). However there are also low-overhead
* version (suffix <tt>Raw</tt>) dealing directly with <tt>byte[]</tt> for maximum performance.
* They are useful in servers for copying parameters in a servers from a request to a response without
* any type conversion. All parameters are copies, you may modify them as you like without
* any side-effect on the <tt>DHCPPacket</tt> object.
*
* <h4>DHCP datagram format description:</h4>
* <blockquote><table cellspacing=2>
*   <tr><th>Field</th><th>Octets</th><th>Description</th></tr>
*   <tr><td valign=top><tt>op</tt></td><td valign=top>1</td>
*             <td>Message op code / message type.<br>
*             use constants
*             <tt>BOOTREQUEST</tt>,
*             <tt>BOOTREPLY</tt></td></tr>
*   <tr><td valign=top><tt>htype</tt></td>
*             <td valign=top>1</td><td>Hardware address type, see ARP section in
*             "Assigned Numbers" RFC<br>
*             use constants
*             <tt>HTYPE_ETHER</tt>,
*             <tt>HTYPE_IEEE802</tt>,
*             <tt>HTYPE_FDDI</tt></td></tr>
*   <tr><td valign=top><tt>hlen</tt></td><td>1</td><td>Hardware address length
*             (e.g.  '6' for ethernet).</td></tr>
*   <tr><td valign=top><tt>hops</tt></td><td valign=top>1</td><td>Client sets to zero, optionally used
*             by relay agents when booting via a relay agent.</td></tr>
*   <tr><td valign=top><tt>xid</tt></td><td valign=top>4</td>
*             <td>Transaction ID, a random number chosen by the
*             client, used by the client and server to associate
*             messages and responses between a client and a
*             server.</td></tr>
*   <tr><td valign=top><tt>secs</tt></td><td valign=top>2</td>
*             <td>Filled in by client, seconds elapsed since client
*             began address acquisition or renewal process.</td></tr>
*   <tr><td valign=top><tt>flags</tt></td><td valign=top>2</td>
*             <td>Flags (see below).</td></tr>
*   <tr><td valign=top><tt>ciaddr</tt></td><td valign=top>4</td>
*             <td>Client IP address; only filled in if client is in
*             BOUND, RENEW or REBINDING state and can respond
*             to ARP requests.</td></tr>
*   <tr><td valign=top><tt>yiaddr</tt></td><td valign=top>4</td>
*             <td>'your' (client) IP address.</td></tr>
*   <tr><td valign=top><tt>siaddr</tt></td><td valign=top>4</td>
*             <td>IP address of next server to use in bootstrap;
*             returned in DHCPOFFER, DHCPACK by server.</td></tr>
*   <tr><td valign=top><tt>giaddr</tt></td><td valign=top>4</td>
*             <td>Relay agent IP address, used in booting via a
*             relay agent.</td></tr>
*   <tr><td valign=top><tt>chaddr</tt></td><td valign=top>16</td>
*             <td>Client hardware address.</td></tr>
*   <tr><td valign=top><tt>sname</tt></td><td valign=top>64</td>
*             <td>Optional server host name, null terminated string.</td></tr>
*   <tr><td valign=top><tt>file</tt></td><td valign=top>128</td>
*             <td>Boot file name, null terminated string; "generic"
*             name or null in DHCPDISCOVER, fully qualified
*             directory-path name in DHCPOFFER.</td></tr>
*   <tr><td valign=top><tt>isDhcp</tt></td><td valign=top>4</td>
*             <td>Controls whether the packet is BOOTP or DHCP.
*             DHCP contains the "magic cookie" of 4 bytes.
*             0x63 0x82 0x53 0x63.</td></tr>
*   <tr><td valign=top><tt>DHO_*code*</tt></td><td valign=top>*</td>
*             <td>Optional parameters field.  See the options
*             documents for a list of defined options. See below.</td></tr>
*   <tr><td valign=top><tt>padding</tt></td><td valign=top>*</td>
*             <td>Optional padding at the end of the packet.</td></tr>
* </table></blockquote>
*
* <h4>DHCP Option</h4>
*
* The following options are codes are supported:
* <pre>
* DHO_SUBNET_MASK(1)
* DHO_TIME_OFFSET(2)
* DHO_ROUTERS(3)
* DHO_TIME_SERVERS(4)
* DHO_NAME_SERVERS(5)
* DHO_DOMAIN_NAME_SERVERS(6)
* DHO_LOG_SERVERS(7)
* DHO_COOKIE_SERVERS(8)
* DHO_LPR_SERVERS(9)
* DHO_IMPRESS_SERVERS(10)
* DHO_RESOURCE_LOCATION_SERVERS(11)
* DHO_HOST_NAME(12)
* DHO_BOOT_SIZE(13)
* DHO_MERIT_DUMP(14)
* DHO_DOMAIN_NAME(15)
* DHO_SWAP_SERVER(16)
* DHO_ROOT_PATH(17)
* DHO_EXTENSIONS_PATH(18)
* DHO_IP_FORWARDING(19)
* DHO_NON_LOCAL_SOURCE_ROUTING(20)
* DHO_POLICY_FILTER(21)
* DHO_MAX_DGRAM_REASSEMBLY(22)
* DHO_DEFAULT_IP_TTL(23)
* DHO_PATH_MTU_AGING_TIMEOUT(24)
* DHO_PATH_MTU_PLATEAU_TABLE(25)
* DHO_INTERFACE_MTU(26)
* DHO_ALL_SUBNETS_LOCAL(27)
* DHO_BROADCAST_ADDRESS(28)
* DHO_PERFORM_MASK_DISCOVERY(29)
* DHO_MASK_SUPPLIER(30)
* DHO_ROUTER_DISCOVERY(31)
* DHO_ROUTER_SOLICITATION_ADDRESS(32)
* DHO_STATIC_ROUTES(33)
* DHO_TRAILER_ENCAPSULATION(34)
* DHO_ARP_CACHE_TIMEOUT(35)
* DHO_IEEE802_3_ENCAPSULATION(36)
* DHO_DEFAULT_TCP_TTL(37)
* DHO_TCP_KEEPALIVE_INTERVAL(38)
* DHO_TCP_KEEPALIVE_GARBAGE(39)
* DHO_NIS_SERVERS(41)
* DHO_NTP_SERVERS(42)
* DHO_VENDOR_ENCAPSULATED_OPTIONS(43)
* DHO_NETBIOS_NAME_SERVERS(44)
* DHO_NETBIOS_DD_SERVER(45)
* DHO_NETBIOS_NODE_TYPE(46)
* DHO_NETBIOS_SCOPE(47)
* DHO_FONT_SERVERS(48)
* DHO_X_DISPLAY_MANAGER(49)
* DHO_DHCP_REQUESTED_ADDRESS(50)
* DHO_DHCP_LEASE_TIME(51)
* DHO_DHCP_OPTION_OVERLOAD(52)
* DHO_DHCP_MESSAGE_TYPE(53)
* DHO_DHCP_SERVER_IDENTIFIER(54)
* DHO_DHCP_PARAMETER_REQUEST_LIST(55)
* DHO_DHCP_MESSAGE(56)
* DHO_DHCP_MAX_MESSAGE_SIZE(57)
* DHO_DHCP_RENEWAL_TIME(58)
* DHO_DHCP_REBINDING_TIME(59)
* DHO_VENDOR_CLASS_IDENTIFIER(60)
* DHO_DHCP_CLIENT_IDENTIFIER(61)
* DHO_NWIP_DOMAIN_NAME(62)
* DHO_NWIP_SUBOPTIONS(63)
* DHO_NIS_DOMAIN(64)
* DHO_NIS_SERVER(65)
* DHO_TFTP_SERVER(66)
* DHO_BOOTFILE(67)
* DHO_MOBILE_IP_HOME_AGENT(68)
* DHO_SMTP_SERVER(69)
* DHO_POP3_SERVER(70)
* DHO_NNTP_SERVER(71)
* DHO_WWW_SERVER(72)
* DHO_FINGER_SERVER(73)
* DHO_IRC_SERVER(74)
* DHO_STREETTALK_SERVER(75)
* DHO_STDA_SERVER(76)
* DHO_USER_CLASS(77)
* DHO_FQDN(81)
* DHO_DHCP_AGENT_OPTIONS(82)
* DHO_NDS_SERVERS(85)
* DHO_NDS_TREE_NAME(86)
* DHO_USER_AUTHENTICATION_PROTOCOL(98)
* DHO_AUTO_CONFIGURE(116)
* DHO_NAME_SERVICE_SEARCH(117)
* DHO_SUBNET_SELECTION(118)
* </pre>
*
* <p>These options can be set and get through basic low-level <tt>getOptionRaw</tt> and
* <tt>setOptionRaw</tt> passing <tt>byte[]</tt> structures. Using these functions, data formats
* are under your responsibility. Arrays are always passed by copies (clones) so you can modify
* them freely without side-effects. These functions allow maximum performance, especially
* when copying options from a request datagram to a response datagram.
*
* <h4>Special case: DHO_DHCP_MESSAGE_TYPE</h4>
* The DHCP Message Type (option 53) is supported for the following values
* <pre>
* DHCPDISCOVER(1)
* DHCPOFFER(2)
* DHCPREQUEST(3)
* DHCPDECLINE(4)
* DHCPACK(5)
* DHCPNAK(6)
* DHCPRELEASE(7)
* DHCPINFORM(8)
* DHCPFORCERENEW(9)
* DHCPLEASEQUERY(13)
* </pre>
*
* <h4>DHCP option formats</h4>
*
* A limited set of higher level data-structures are supported. Type checking is enforced
* according to rfc 2132. Check corresponding methods for a list of option codes allowed for
* each datatype.
*
* <blockquote>
* <br>Inet (4 bytes - IPv4 address)
* <br>Inets (X*4 bytes - list of IPv4 addresses)
* <br>Short (2 bytes - short)
* <br>Shorts (X*2 bytes - list of shorts)
* <br>Byte (1 byte)
* <br>Bytes (X bytes - list of 1 byte parameters)
* <br>String (X bytes - ASCII string)
* <br>
* </blockquote>
*
*
* <p><b>Note</b>: this class is not synchronized for maximum performance.
* However, it is unlikely that the same <tt>DHCPPacket</tt> is used in two different
* threads in real life DHPC servers or clients. Multi-threading acces
* to an instance of this class is at your own risk.
*
* <p><b>Limitations</b>: this class doesn't support spanned options or options longer than 256 bytes.
* It does not support options stored in <tt>sname</tt> or <tt>file</tt> fields.
*
* <p>This API is originally a port from my PERL
* <tt><a href="http://search.cpan.org/~shadinger/">Net::DHCP</a></tt> api.
*
* <p><b>Future extensions</b>: IPv6 support, extended data structure TODO...
*
*/
public class DHCPPacket implements Cloneable, Serializable {
  private static final long   serialVersionUID = 1L;

    private static final Logger logger = Logger.getLogger(DHCPPacket.class.getName().toLowerCase());
   
    // ----------------------------------------------------------------------
    // user defined comment
    private String  comment;   // Free user-defined comment

    // ----------------------------------------------------------------------
    // static structure of the packet
    private byte    op;        // Op code
    private byte    htype;     // HW address Type
    private byte    hlen;      // hardware address length
    private byte    hops;      // Hw options
    private int     xid;       // transaction id
    private short   secs;      // elapsed time from trying to boot
    private short   flags;     // flags
    private byte[]  ciaddr;    // client IP
    private byte[]  yiaddr;    // your client IP
    private byte[]  siaddr;    // Server IP
    private byte[]  giaddr;    // relay agent IP
    private byte[]  chaddr;    // Client HW address
    private byte[]  sname;     // Optional server host name
    private byte[]  file;      // Boot file name
   
    // ----------------------------------------------------------------------
    // options part of the packet

    // DHCP options
    // Invariant 1: K is identical to V.getCode()
    // Invariant 2: V.value is never <tt>null</tt>
    // Invariant 3; K is not 0 (PAD) and not -1 (END)
    private Map<Byte, DHCPOption> options;
    private boolean               isDhcp;    // well-formed DHCP Packet ?
    private boolean               truncated; // are the option truncated
    // ----------------------------------------------------------------------
    // extra bytes for padding
    private byte[]      padding;  // end of packet padding
   
    // ----------------------------------------------------------------------
    // Address/port address of the machine, which this datagram is being sent to
    // or received from.
    private InetAddress address;
    private int         port;
   
   
    /**
     * Constructor for the <tt>DHCPPacket</tt> class.
     *
     * <p>This creates an empty <tt>DHCPPacket</tt> datagram.
     * All data is default values and the packet is still lacking key data
     * to be sent on the wire.
     */
    public DHCPPacket() {
        this.comment = "";
        this.op      = BOOTREPLY;
        this.htype   = HTYPE_ETHER;
        this.hlen    = 6;
        this.ciaddr  = new byte4];
        this.yiaddr  = new byte4];
        this.siaddr  = new byte4];
        this.giaddr  = new byte4];
        this.chaddr  = new byte[ 16];
        this.sname   = new byte[ 64];
        this.file    = new byte[128];
        this.padding = new byte[0];
        this.isDhcp  = true;
        this.options = new LinkedHashMap<Byte, DHCPOption>();
    }

    /**
     * Factory for creating <tt>DHCPPacket</tt> objects by parsing a
     * <tt>DatagramPacket</tt> object.
     *
     * @param datagram the UDP datagram received to be parsed
     * @return the newly create <tt>DHCPPacket</tt> instance
     * @throws DHCPBadPacketException the datagram is malformed and cannot be parsed properly.
     * @throws IllegalArgumentException datagram is <tt>null</tt>
     * @throws IOException
     */
    public static DHCPPacket getPacket(DatagramPacket datagram) throws DHCPBadPacketException {
      if (datagram == null) {
            throw new IllegalArgumentException("datagram is null");
        }
        DHCPPacket packet = new DHCPPacket();
        // all parameters are checked in marshall()
        packet.marshall(datagram.getData(), datagram.getOffset(), datagram.getLength(),
                        datagram.getAddress(), datagram.getPort(),
                        true);    // strict mode by default
        return packet;
    }

    /**
     * Factory for creating <tt>DHCPPacket</tt> objects by parsing a
     * <tt>byte[]</tt> e.g. from a datagram.
     *
     * <p>This method allows you to specify non-strict mode which is much more
     * tolerant for packet options. By default, any problem seen during DHCP option
     * parsing causes a DHCPBadPacketException to be thrown.
     *
     * @param buf buffer for holding the incoming datagram.
     * @param offset the offset for the buffer.
     * @param length the number of bytes to read.
     * @param strict do we parse in strict mode?
     * @return the newly create <tt>DHCPPacket</tt> instance
     * @throws DHCPBadPacketException the datagram is malformed.
     */
    public static DHCPPacket getPacket(byte[] buf, int offset, int length, boolean strict) throws DHCPBadPacketException {
        DHCPPacket packet = new DHCPPacket();
        // all parameters are checked in marshall()
        packet.marshall(buf, offset, length, null, 0, strict);
        return packet;
    }

    /**
     * Returns a copy of this <tt>DHCPPacket</tt>.
     *
     * <p>The <tt>truncated</tt> flag is reset.
     *
     * @return a copy of the <tt>DHCPPacket</tt> instance.
     */
    @Override
    public DHCPPacket clone() {
        try {
            DHCPPacket p = (DHCPPacket) super.clone();

            // specifically cloning arrays to avoid side-effects
            p.ciaddr = this.ciaddr.clone();
            p.yiaddr = this.yiaddr.clone();
            p.siaddr = this.siaddr.clone();
            p.giaddr = this.giaddr.clone();
            p.chaddr = this.chaddr.clone();
            p.sname  = this.sname .clone();
            p.file   = this.file  .clone();
            //p.options = this.options.clone();
            p.options = new LinkedHashMap<Byte, DHCPOption>(this.options);
            p.padding = this.padding.clone();

            p.truncated = false;    // freshly new object, it is not considered as corrupt

            return p;
        } catch (CloneNotSupportedException e) {
            // this shouldn't happen, since we are Cloneable
            throw new InternalError();
        }
    }
    /**
     * Returns true if 2 instances of <tt>DHCPPacket</tt> represent the same DHCP packet.
     *
     * <p>This is a field by field comparison, except <tt>truncated</tt> which is ignored.
     */
    @Override
    public boolean equals(Object o) {
        if (o == this) {
            return true;
        }
        if (!(o instanceof DHCPPacket)) {
            return false;
        }

        DHCPPacket p = (DHCPPacket) o;
        boolean    b;

        b  = (this.comment.equals(p.comment));
        b &= (this.op    == p.op);
        b &= (this.htype == p.htype);
        b &= (this.hlen  == p.hlen);
        b &= (this.hops  == p.hops);
        b &= (this.xid   == p.xid);
        b &= (this.secs  == p.secs);
        b &= (this.flags == p.flags);
        b &= (Arrays.equals(this.ciaddr, p.ciaddr));
        b &= (Arrays.equals(this.yiaddr, p.yiaddr));
        b &= (Arrays.equals(this.siaddr, p.siaddr));
        b &= (Arrays.equals(this.giaddr, p.giaddr));
        b &= (Arrays.equals(this.chaddr, p.chaddr));
        b &= (Arrays.equals(this.sname,  p.sname));
        b &= (Arrays.equals(this.file,   p.file));
        b &= (this.options.equals(p.options));
        b &= (this.isDhcp == p.isDhcp);
        // we deliberately ignore "truncated" since it is reset when cloning
        b &= (Arrays.equals(this.padding, p.padding));
        b &= (equalsStatic (this.address, p.address));
        b &= (this.port == p.port);

        return b;
    }
   
    /**
     * Returns a hash code value for the object.
     */
  @Override
  public int hashCode() {
    int h = -1;
    h ^= this.comment.hashCode();
    h += this.op;
    h += this.htype;
    h += this.hlen;
    h += this.hops;
    h += this.xid;
    h += this.secs;
    h ^= this.flags;
    h ^= Arrays.hashCode(this.ciaddr);
    h ^= Arrays.hashCode(this.yiaddr);
    h ^= Arrays.hashCode(this.siaddr);
    h ^= Arrays.hashCode(this.giaddr);
    h ^= Arrays.hashCode(this.chaddr);
    h ^= Arrays.hashCode(this.sname);
    h ^= Arrays.hashCode(this.file);
    h ^= this.options.hashCode();
    h += this.isDhcp ? 1 : 0;
//    h += this.truncated ? 1 : 0;
    h ^= Arrays.hashCode(this.padding);
    h ^= (this.address != null) ? this.address.hashCode() : 0;
    h += this.port;
    return h;
  }

  private static boolean equalsStatic(Object a, Object b) {
        return ((a == null) ? (b == null) : a.equals(b));
    }

    /**
     * Assert all the invariants of the object. For debug purpose only.
     *
     */
    private void assertInvariants() {
        assert(this.comment != null);
        assert(this.ciaddr != null);
        assert(this.ciaddr.length == 4);
        assert(this.yiaddr != null);
        assert(this.yiaddr.length == 4);
        assert(this.siaddr != null);
        assert(this.siaddr.length == 4);
        assert(this.giaddr != null);
        assert(this.giaddr.length == 4);
        // strings
        assert(this.chaddr != null);
        assert(this.chaddr.length == 16);
        assert(this.sname != null);
        assert(this.sname.length == 64);
        assert(this.file != null);
        assert(this.file.length == 128);
        assert(this.padding != null);    // length is free for padding
        // options
        assert(this.options != null);
        for (Map.Entry<Byte, DHCPOption> mapEntry : this.options.entrySet()) {
            Byte       key = mapEntry.getKey();
            DHCPOption opt = mapEntry.getValue();

            assert(key != null);
            assert(key != DHO_PAD);
            assert(key != DHO_END);
            assert(opt != null);
            assert(opt.getCode() == key);
            assert(opt.getValueFast() != null);
        }
    }
    /**
     * Convert a specified byte array containing a DHCP message into a
     * DHCPMessage object.
     *
     * @return a DHCPMessage object with information from byte array.
     * @param  buffer  byte array to convert to a DHCPMessage object
     * @param  offset starting offset for the buffer
     * @param  length length of the buffer
     * @param  address0 the address from which the packet was sent, or <tt>null</tt>
     * @param  port0 the port from which the packet was sent
     * @param  strict do we read in strict mode?
     * @throws IllegalArgumentException if buffer is <tt>null</tt>...
     * @throws IndexOutOfBoundsException offset..offset+length is out of buffer bounds
     * @throws DHCPBadPacketException datagram is malformed
     */
    protected DHCPPacket marshall(byte[] buffer, int offset, int length,
                                  InetAddress address0, int port0, boolean strict) {
        // do some basic sanity checks
        // ibuff, offset & length are valid?
        if (buffer == null) {
            throw new IllegalArgumentException("null buffer not allowed");
        }
        if (offset < 0) {
            throw new IndexOutOfBoundsException("negative offset not allowed");
        }
        if (length < 0) {
            throw new IllegalArgumentException("negative length not allowed");
        }
        if (buffer.length < offset + length) {
            throw new IndexOutOfBoundsException("offset+length exceeds buffer length");
        }
       
        // absolute minimum size for a valid packet
        if (length < _BOOTP_ABSOLUTE_MIN_LEN) {
            throw new DHCPBadPacketException("DHCP Packet too small (" + length +
                    ") absolute minimum is " + _BOOTP_ABSOLUTE_MIN_LEN);
        }
        // maximum size for a valid DHCP packet
        if (length > _DHCP_MAX_MTU) {
            throw new DHCPBadPacketException("DHCP Packet too big (" + length +
                    ") max MTU is " + _DHCP_MAX_MTU);
        }
       
        // copy address and port
        this.address = address0; // no need to clone, InetAddress is immutable
        this.port    = port0;
       
        try {
            // turn buffer into a readable stream
            ByteArrayInputStream inBStream = new ByteArrayInputStream(buffer, offset, length);
            DataInputStream      inStream  = new DataInputStream(inBStream);

            // parse static part of packet
            this.op    = inStream.readByte ();
            this.htype = inStream.readByte ();
            this.hlen  = inStream.readByte ();
            this.hops  = inStream.readByte ();
            this.xid   = inStream.readInt  ();
            this.secs  = inStream.readShort();
            this.flags = inStream.readShort();
            inStream.readFully(this.ciaddr, 0,   4);
            inStream.readFully(this.yiaddr, 0,   4);
            inStream.readFully(this.siaddr, 0,   4);
            inStream.readFully(this.giaddr, 0,   4);
            inStream.readFully(this.chaddr, 016);
            inStream.readFully(this.sname,  064);
            inStream.readFully(this.file,   0, 128);

            // check for DHCP MAGIC_COOKIE
            this.isDhcp = true;
            inBStream.mark(4);        // read ahead 4 bytes
            if (inStream.readInt() != _MAGIC_COOKIE) {
                this.isDhcp = false;
                inBStream.reset();    // re-read the 4 bytes
            }

            if (this.isDhcp) {    // is it a full DHCP packet or a simple BOOTP?
                // DHCP Packet: parsing options
                int type = 0;

                while (true) {
                    int r = inBStream.read();
                    if (r < 0) { break; } // EOF

                    type = (byte) r;

                    if (type == DHO_PAD) { continue; } // skip Padding
                    if (type == DHO_END) { break;    } // break if end of options

                    r = inBStream.read();
                    if (r < 0) { break; } // EOF

                    int    len      = Math.min(r, inBStream.available());
                    byte[] unit_opt = new byte[len];
                    inBStream.read(unit_opt);

                    this.setOption(new DHCPOption((byte) type, unit_opt))// store option
                }
                this.truncated = (type != DHO_END); // truncated options?
                if (strict && this.truncated) {
                  throw new DHCPBadPacketException("Packet seams to be truncated");
                }
            }

            // put the remaining in padding
            this.padding = new byte[inBStream.available()];
            inBStream.read(this.padding);
            // final verifications (if assertions are activated)
            this.assertInvariants();

            return this;
        } catch (IOException e) {
            // unlikely with ByteArrayInputStream
            throw new DHCPBadPacketException("IOException: "+e.toString(), e);
        }
    }

    /**
     * Converts the object to a byte array ready to be sent on the wire.
     *
     * <p>Default max size of resulting packet is 576, which is the maximum
     * size a client can accept without explicit notice (option XXX)
     *
     * @return a byte array with information from DHCPMessage object.
     * @throws DHCPBadPacketException the datagram would be malformed (too small, too big...)
     */
    public byte[] serialize() {
      int minLen = _BOOTP_ABSOLUTE_MIN_LEN;
     
        if (this.isDhcp) {
          // most other DHCP software seems to ensure that the BOOTP 'vend'
          // field is padded to at least 64 bytes
          minLen += _BOOTP_VEND_SIZE;
        }
     
      return serialize(minLen, _DHCP_DEFAULT_MAX_LEN);
    }

    /**
     * Converts the object to a byte array ready to be sent on the wire.
     *
     * @param maxSize the maximum buffer size in bytes
     * @return a byte array with information from DHCPMessage object.
     * @throws DHCPBadPacketException the datagram would be malformed (too small, too big...)
     */
    public byte[] serialize(int minSize, int maxSize) {
        this.assertInvariants();
        // prepare output buffer, pre-sized to maximum buffer length
        // default buffer is half the maximum size of possible packet
        // (this seams reasonable for most uses, worst case only doubles the buffer size once
        ByteArrayOutputStream outBStream = new ByteArrayOutputStream(_DHCP_MAX_MTU / 2);
        DataOutputStream      outStream  = new DataOutputStream(outBStream);
        try {
            outStream.writeByte (this.op);
            outStream.writeByte (this.htype);
            outStream.writeByte (this.hlen);
            outStream.writeByte (this.hops);
            outStream.writeInt  (this.xid);
            outStream.writeShort(this.secs);
            outStream.writeShort(this.flags);
            outStream.write(this.ciaddr, 0,   4);
            outStream.write(this.yiaddr, 0,   4);
            outStream.write(this.siaddr, 0,   4);
            outStream.write(this.giaddr, 0,   4);
            outStream.write(this.chaddr, 016);
            outStream.write(this.sname,  064);
            outStream.write(this.file,   0, 128);

            if (this.isDhcp) {
                // DHCP and not BOOTP -> magic cookie required
                outStream.writeInt(_MAGIC_COOKIE);

                // parse output options in creation order (LinkedHashMap)
                for (DHCPOption opt : this.getOptionsCollection()) {
                    assert (opt != null);
                    assert (opt.getCode() != DHO_PAD);
                    assert (opt.getCode() != DHO_END);
                    assert (opt.getValueFast() != null);
                    int size = opt.getValueFast().length;
                    assert (size >= 0);
                    if (size > 255) {
                      throw new DHCPBadPacketException("Options larger than 255 bytes are not yet supported");
                    }
                    outStream.writeByte(opt.getCode());        // output option code
                    outStream.writeByte(size);    // output option length
                    outStream.write(opt.getValueFast());    // output option data
                }
                // mark end of options
                outStream.writeByte(DHO_END);
            }

            // write padding
            outStream.write(this.padding);

            // add padding if the packet is too small
            int min_padding = minSize - outBStream.size();
            if (min_padding > 0) {
                byte[] add_padding = new byte[min_padding];
                outStream.write(add_padding);
            }

            // final packet is here
            byte[] data = outBStream.toByteArray();

            // do some post sanity checks
            if (data.length > _DHCP_MAX_MTU) {
                throw new DHCPBadPacketException("serialize: packet too big (" + data.length + " greater than max MAX_MTU (" + _DHCP_MAX_MTU + ')');
            }

            return data;
        } catch (IOException e) {
            // nomrally impossible with ByteArrayOutputStream
            logger.log(Level.SEVERE, "Unexpected Exception", e);
            throw new DHCPBadPacketException("IOException raised: "+e.toString());
        }
    }

    // ========================================================================
    // debug functions

    /**
     * Returns a detailed string representation of the DHCP datagram.
     *
     * <p>This multi-line string details: the static, options and padding parts
     * of the object. This is useful for debugging, but not efficient.
     *
     * @return a string representation of the object.
     */
    @Override
    public String toString() {
        StringBuilder buffer = new StringBuilder(); // output buffer

        try {
            buffer.append(this.isDhcp ? "DHCP Packet" : "BOOTP Packet")
                  .append("\ncomment=")
                  .append(this.comment)
                  .append("\naddress=")
                  .append(this.address != null ? this.address.getHostAddress() : "")
                  .append('(')
                  .append(this.port)
                  .append(')')
                  .append("\nop=");

            final Object bootName = _BOOT_NAMES.get(this.op);
            if (bootName != null) {
                buffer.append(bootName)
                      .append('(')
                      .append(this.op)
                      .append(')');
            } else {
                buffer.append(this.op);
            }

            buffer.append("\nhtype=");

            final Object htypeName = _HTYPE_NAMES.get(this.htype);
            if (htypeName != null) {
                buffer.append(htypeName)
                      .append('(')
                      .append(this.htype)
                      .append(')');
            } else {
                buffer.append(this.htype);
            }

            buffer.append("\nhlen=")
                  .append(this.hlen)
                  .append("\nhops=")
                  .append(this.hops)
                  .append("\nxid=0x");
            appendHex(buffer, this.xid);
            buffer.append("\nsecs=")
                  .append(this.secs)
                  .append("\nflags=0x")
                  .append(Integer.toHexString(this.flags))
                  .append("\nciaddr=");
            appendHostAddress(buffer, InetAddress.getByAddress(this.ciaddr));
            buffer.append("\nyiaddr=");
            appendHostAddress(buffer, InetAddress.getByAddress(this.yiaddr));
            buffer.append("\nsiaddr=");
            appendHostAddress(buffer, InetAddress.getByAddress(this.siaddr));
            buffer.append("\ngiaddr=");
            appendHostAddress(buffer, InetAddress.getByAddress(this.giaddr));
            buffer.append("\nchaddr=0x");
            this.appendChaddrAsHex(buffer);
            buffer.append("\nsname=")
                  .append(this.getSname())
                  .append("\nfile=")
                  .append(this.getFile());

            if (this.isDhcp) {
                buffer.append("\nOptions follows:");

                // parse options in creation order (LinkedHashMap)
                for (DHCPOption opt : this.getOptionsCollection()) {
                    buffer.append('\n');
                    opt.append(buffer);
                }
            }

            // padding
            buffer.append("\npadding[")
                  .append(this.padding.length)
                  .append("]=");
            appendHex(buffer, this.padding);
        } catch (Exception e) {
            // what to do ???
        }

        return buffer.toString();
    }

    // ========================================================================
    // getters and setters

    /**
     * Returns the comment associated to this packet.
     *
     * <p>This field can be used freely and has no influence on the real network datagram.
     * It can be used to store a transaction number or any other information
     *
     * @return the _comment field.
     */
    public String getComment() {
        return this.comment;
    }
    /**
     * Sets the comment associated to this packet.
     *
     * <p>This field can be used freely and has no influence on the real network datagram.
     * It can be used to store a transaction number or any other information
     *
     * @param comment The comment to set.
     */
    public void setComment(String comment) {
        this.comment = comment;
    }
    /**
     * Returns the chaddr field (Client hardware address - typically MAC address).
     *
     * <p>Returns the byte[16] raw buffer. Only the first <tt>hlen</tt> bytes are valid.
     *
     * @return the chaddr field.
     */
    public byte[] getChaddr() {
        return this.chaddr.clone();
    }

    /**
     * Appends the chaddr field (Client hardware address - typically MAC address) as
     * a hex string to this string buffer.
     *
     * <p>Only first <tt>hlen</tt> bytes are appended, as uppercase hex string.
     *
     * @param buffer this string buffer
     * @return the string buffer.
     */
    private StringBuilder appendChaddrAsHex(StringBuilder buffer) {
        appendHex(buffer, this.chaddr, 0, this.hlen & 0xFF);
        return buffer;
    }
   
    /**
     * Return the hardware address (@MAC) as an <tt>HardwareAddress</tt> object.
     *
     * @return the <tt>HardwareAddress</tt> object
     */
    public HardwareAddress getHardwareAddress() {
      int len = this.hlen & 0xff;
      if (len > 16) {
        len = 16;
      }
      byte[] buf = new byte[len];
      System.arraycopy(this.chaddr, 0, buf, 0, len);
      return new HardwareAddress(this.htype, buf);
    }

    /**
     * Returns the chaddr field (Client hardware address - typically MAC address) as
     * a hex string.
     *
     * <p>Only first <tt>hlen</tt> bytes are printed, as uppercase hex string.
     *
     * @return the chaddr field as hex string.
     */
    public String getChaddrAsHex() {
        return this.appendChaddrAsHex(new StringBuilder(this.hlen & 0xFF)).toString();
    }
    /**
     * Sets the chaddr field (Client hardware address - typically MAC address).
     *
     * <p>The buffer length should be between 0 and 16, otherwise an
     * <tt>IllegalArgumentException</tt> is thrown.
     *
     * <p>If chaddr is null, the field is filled with zeros.
     *
     * @param chaddr The chaddr to set.
     * @throws IllegalArgumentException chaddr buffer is longer than 16 bytes.
     */
    public void setChaddr(byte[] chaddr) {
        if (chaddr != null) {
            if (chaddr.length > this.chaddr.length) {
                throw new IllegalArgumentException("chaddr is too long: " + chaddr.length +
                                                   ", max is: " + this.chaddr.length);
            }
            Arrays.fill(this.chaddr, (byte) 0);
            System.arraycopy(chaddr, 0, this.chaddr, 0, chaddr.length);
        } else {
            Arrays.fill(this.chaddr, (byte) 0);
        }
    }
    /**
     * Sets the chaddr field - from an hex String.
     *
     * @param hex the chaddr in hex format
     */
    public void setChaddrHex(String hex) {
        this.setChaddr(hex2Bytes(hex));
    }
    /**
     * Returns the ciaddr field (Client IP Address).
     *
     * @return the ciaddr field converted to <tt>InetAddress</tt> object.
     */
    public InetAddress getCiaddr() {
        try {
            return InetAddress.getByAddress(this.getCiaddrRaw());
        } catch (UnknownHostException e) {
            logger.log(Level.SEVERE, "Unexpected UnknownHostException", e);
            return null;    // normaly impossible
        }
    }

    /**
     * Returns the ciaddr field (Client IP Address).
     *
     * <p>This is the low-level maximum performance getter for this field.
     *
     * @return Returns the ciaddr as raw byte[4].
     */
    public byte[] getCiaddrRaw() {
        return this.ciaddr.clone();
    }

    /**
     * Sets the ciaddr field (Client IP Address).
     *
     * <p>Ths <tt>ciaddr</tt> field must be of <tt>Inet4Address</tt> class or
     * an <tt>IllegalArgumentException</tt> is thrown.
     *
     * @param ciaddr The ciaddr to set.
     */
    public void setCiaddr(InetAddress ciaddr) {
        if (!(ciaddr instanceof Inet4Address)) {
            throw new IllegalArgumentException("Inet4Address required");
        }
        this.setCiaddrRaw(ciaddr.getAddress());
    }
    /**
     * Sets the ciaddr field (Client IP Address).
     *
     * @param ciaddr The ciaddr to set.
     * @throws UnknownHostException
     */
    public void setCiaddr(String ciaddr) throws UnknownHostException {
        this.setCiaddr(InetAddress.getByName(ciaddr));
    }
    /**
     * Sets the ciaddr field (Client IP Address).
     *
     * <p><tt>ciaddr</tt> must be a 4 bytes array, or an <tt>IllegalArgumentException</tt>
     * is thrown.
     *
     * <p>This is the low-level maximum performance setter for this field.
     * The array is internally copied so any further modification to <tt>ciaddr</tt>
     * parameter has no side effect.
     *
     * @param ciaddr The ciaddr to set.
     */
    public void setCiaddrRaw(byte[] ciaddr) {
        if (ciaddr.length != 4) {
            throw new IllegalArgumentException("4-byte array required");
        }
        System.arraycopy(ciaddr, 0, this.ciaddr, 0, 4);
    }
    /**
     * Returns the file field (Boot File Name).
     *
     * <p>Returns the raw byte[128] buffer, containing a null terminated string.
     *
     * <p>This is the low-level maximum performance getter for this field.
     *
     * @return the file field.
     */
    public byte[] getFileRaw() {
        return this.file.clone();
    }

    /**
     * Returns the file field (Boot File Name) as String.
     *
     * @return the file converted to a String (transparent encoding).
     */
    public String getFile() {
        return bytesToString(this.getFileRaw());
    }
    /**
     * Sets the file field (Boot File Name) as String.
     *
     * <p>The string is first converted to a byte[] array using transparent
     * encoding. If the resulting buffer size is > 128, an <tt>IllegalArgumentException</tt>
     * is thrown.
     *
     * <p>If <tt>file</tt> parameter is null, the buffer is filled with zeros.
     *
     * @param file The file field to set.
     * @throws IllegalArgumentException string too long
     */
    public void setFile(String file) {
        this.setFileRaw(stringToBytes(file));
    }

    /**
     * Sets the file field (Boot File Name) as String.
     *
     * <p>If the buffer size is > 128, an <tt>IllegalArgumentException</tt>
     * is thrown.
     *
     * <p>If <tt>file</tt> parameter is null, the buffer is filled with zeros.
     *
     * <p>This is the low-level maximum performance setter for this field.
     *
     * @param file The file field to set.
     * @throws IllegalArgumentException string too long
     */
    public void setFileRaw(byte[] file) {
        if (file != null) {
            if (file.length > this.file.length) {
                throw new IllegalArgumentException("File is too long:" + file.length + " max is:" + this.file.length);
            }
            Arrays.fill(this.file, (byte) 0);
            System.arraycopy(file, 0, this.file, 0, file.length);
        } else {
            Arrays.fill(this.file, (byte) 0);
        }
    }

    /**
     * Returns the flags field.
     *
     * @return the flags field.
     */
    public short getFlags() {
        return this.flags;
    }

    /**
     * Sets the flags field.
     *
     * @param flags The flags field to set.
     */
    public void setFlags(short flags) {
        this.flags = flags;
    }

    /**
     * Returns the giaddr field (Relay agent IP address).
     *
     * @return the giaddr field converted to <tt>InetAddress</tt> object.
     */
    public InetAddress getGiaddr() {
        try {
            return InetAddress.getByAddress(this.getGiaddrRaw());
        } catch (UnknownHostException e) {
            logger.log(Level.SEVERE, "Unexpected UnknownHostException", e);
            return null;    // normaly impossible
        }
    }

    /**
     * Returns the giaddr field (Relay agent IP address).
     *
     * <p>This is the low-level maximum performance getter for this field.
     *
     * @return Returns the giaddr as raw byte[4].
     */
    public byte[] getGiaddrRaw() {
        return this.giaddr.clone();
    }

    /**
     * Sets the giaddr field (Relay agent IP address).
     *
     * <p>Ths <tt>giaddr</tt> field must be of <tt>Inet4Address</tt> class or
     * an <tt>IllegalArgumentException</tt> is thrown.
     *
     * @param giaddr The giaddr to set.
     */
    public void setGiaddr(InetAddress giaddr) {
        if (!(giaddr instanceof Inet4Address)) {
            throw new IllegalArgumentException("Inet4Address required");
        }
        this.setGiaddrRaw(giaddr.getAddress());
    }

    /**
     * Sets the giaddr field (Relay agent IP address).
     *
     * @param giaddr The giaddr to set.
     * @throws UnknownHostException
     */
    public void setGiaddr(String giaddr) throws UnknownHostException {
        this.setGiaddr(InetAddress.getByName(giaddr));
    }

    /**
     * Sets the giaddr field (Relay agent IP address).
     *
     * <p><tt>giaddr</tt> must be a 4 bytes array, or an <tt>IllegalArgumentException</tt>
     * is thrown.
     *
     * <p>This is the low-level maximum performance setter for this field.
     * The array is internally copied so any further modification to <tt>ciaddr</tt>
     * parameter has no side effect.
     *
     * @param giaddr The giaddr to set.
     */
    public void setGiaddrRaw(byte[] giaddr) {
        if (giaddr.length != 4) {
            throw new IllegalArgumentException("4-byte array required");
        }
        System.arraycopy(giaddr, 0, this.giaddr, 0, 4);
    }

    /**
     * Returns the hlen field (Hardware address length).
     *
     * <p>Typical value is 6 for ethernet - 6 bytes MAC address.
     *
     * @return the hlen field.
     */
    public byte getHlen() {
        return this.hlen;
    }

    /**
     * Sets the hlen field (Hardware address length).
     *
     * <p>Typical value is 6 for ethernet - 6 bytes MAC address.
     *
     * <p>hlen value should be between 0 and 16, but no control is done here.
     *
     * @param hlen The hlen to set.
     */
    public void setHlen(byte hlen) {
        this.hlen = hlen;
    }

    /**
     * Returns the hops field.
     *
     * @return the hops field.
     */
    public byte getHops() {
        return this.hops;
    }

    /**
     * Sets the hops field.
     *
     * @param hops The hops to set.
     */
    public void setHops(byte hops) {
        this.hops = hops;
    }

    /**
     * Returns the htype field (Hardware address length).
     *
     * <p>Predefined values are:
     * <pre>
     * HTYPE_ETHER (1)
     * HTYPE_IEEE802 (6)
     * HTYPE_FDDI (8)
     * </pre>
     *
     * <p>Typical value is <tt>HTYPE_ETHER</tt>.
     *
     * @return the htype field.
     */
    public byte getHtype() {
        return this.htype;
    }

    /**
     * Sets the htype field (Hardware address length).
     *
     * <p>Predefined values are:
     * <pre>
     * HTYPE_ETHER (1)
     * HTYPE_IEEE802 (6)
     * HTYPE_FDDI (8)
     * </pre>
     *
     * <p>Typical value is <tt>HTYPE_ETHER</tt>.
     *
     * @param htype The htype to set.
     */
    public void setHtype(byte htype) {
        this.htype = htype;
    }

    /**
     * Returns whether the packet is DHCP or BOOTP.
     *
     * <p>It indicates the presence of the DHCP Magic Cookie at the end
     * of the BOOTP portion.
     *
     * <p>Default is <tt>true</tt> for a brand-new object.
     *
     * @return Returns the isDhcp.
     */
    public boolean isDhcp() {
        return this.isDhcp;
    }

    /**
     * Sets the isDhcp flag.
     *
     * <p>Indicates whether to generate a DHCP or a BOOTP packet. If <tt>true</tt>
     * the DHCP Magic Cookie is added after the BOOTP portion and before the
     * DHCP Options.
     *
     * <p>If <tt>isDhcp</tt> if false, all DHCP options are ignored when calling
     * <tt>serialize()</tt>.
     *
     * <p>Default value is <tt>true</tt>.
     *
     * @param isDhcp The isDhcp to set.
     */
    public void setDhcp(boolean isDhcp) {
        this.isDhcp = isDhcp;
    }

    /**
     * Returns the op field (Message op code).
     *
     * <p>Predefined values are:
     * <pre>
     * BOOTREQUEST (1)
     * BOOTREPLY (2)
     * </pre>
     *
     * @return the op field.
     */
    public byte getOp() {
        return this.op;
    }

    /**
     * Sets the op field (Message op code).
     *
     * <p>Predefined values are:
     * <pre>
     * BOOTREQUEST (1)
     * BOOTREPLY (2)
     * </pre>
     *
     * <p>Default value is <tt>BOOTREPLY</tt>, suitable for server replies.
     *
     * @param op The op to set.
     */
    public void setOp(byte op) {
        this.op = op;
    }

    /**
     * Returns the padding portion of the packet.
     *
     * <p>This byte array follows the DHCP Options.
     * Normally, its content is irrelevant.
     *
     * @return Returns the padding.
     */
    public byte[] getPadding() {
        return this.padding.clone();
    }

    /**
     * Sets the padding buffer.
     *
     * <p>This byte array follows the DHCP Options.
     * Normally, its content is irrelevant.
     *
     * <p>If <tt>paddig</tt> is null, it is set to an empty buffer.
     *
     * <p>Padding is automatically added at the end of the datagram when calling
     * <tt>serialize()</tt> to match DHCP minimal packet size.
     *
     * @param padding The padding to set.
     */
    public void setPadding(byte[] padding) {
        this.padding = ((padding == null) ? new byte[0] : padding.clone());
    }

    /**
     * Sets the padding buffer with <tt>length</tt> zero bytes.
     *
     * <p>This is a short cut for <tt>setPadding(new byte[length])</tt>.
     *
     * @param length size of the padding buffer
     */
    public void setPaddingWithZeroes(int length) {
        if (length < 0) {
            length = 0;
        }
        if (length > _DHCP_MAX_MTU) {
            throw new IllegalArgumentException("length is > " + _DHCP_MAX_MTU);
        }
        this.setPadding(new byte[length]);
    }

    /**
     * Returns the secs field (seconds elapsed).
     *
     * @return the secs field.
     */
    public short getSecs() {
        return this.secs;
    }

    /**
     * Sets the secs field (seconds elapsed).
     *
     * @param secs The secs to set.
     */
    public void setSecs(short secs) {
        this.secs = secs;
    }

    /**
     * Returns the siaddr field (IP address of next server).
     *
     * @return the siaddr field converted to <tt>InetAddress</tt> object.
     */
    public InetAddress getSiaddr() {
        try {
            return InetAddress.getByAddress(this.getSiaddrRaw());
        } catch (UnknownHostException e) {
            logger.log(Level.SEVERE, "Unexpected UnknownHostException", e);
            return null;    // normaly impossible
        }
    }

    /**
     * Returns the siaddr field (IP address of next server).
     *
     * <p>This is the low-level maximum performance getter for this field.
     *
     * @return Returns the siaddr as raw byte[4].
     */
    public byte[] getSiaddrRaw() {
        return this.siaddr.clone();
    }

    /**
     * Sets the siaddr field (IP address of next server).
     *
     * <p>Ths <tt>siaddr</tt> field must be of <tt>Inet4Address</tt> class or
     * an <tt>IllegalArgumentException</tt> is thrown.
     *
     * @param siaddr The siaddr to set.
     */
    public void setSiaddr(InetAddress siaddr) {
        if (!(siaddr instanceof Inet4Address)) {
            throw new IllegalArgumentException("Inet4Address required");
        }
        this.setSiaddrRaw(siaddr.getAddress());
    }

    /**
     * Sets the siaddr field (IP address of next server).
     *
     * @param siaddr The siaddr to set.
     * @throws UnknownHostException
     */
    public void setSiaddr(String siaddr) throws UnknownHostException {
        this.setSiaddr(InetAddress.getByName(siaddr));
    }

    /**
     * Sets the siaddr field (IP address of next server).
     *
     * <p><tt>siaddr</tt> must be a 4 bytes array, or an <tt>IllegalArgumentException</tt>
     * is thrown.
     *
     * <p>This is the low-level maximum performance setter for this field.
     * The array is internally copied so any further modification to <tt>ciaddr</tt>
     * parameter has no side effect.
     *
     * @param siaddr The siaddr to set.
     */
    public void setSiaddrRaw(byte[] siaddr) {
        if (siaddr.length != 4) {
            throw new IllegalArgumentException("4-byte array required");
        }
        System.arraycopy(siaddr, 0, this.siaddr, 0, 4);
    }

    /**
     * Returns the sname field (Optional server host name).
     *
     * <p>Returns the raw byte[64] buffer, containing a null terminated string.
     *
     * <p>This is the low-level maximum performance getter for this field.
     *
     * @return the sname field.
     */
    public byte[] getSnameRaw() {
        return this.sname.clone();
    }

    /**
     * Returns the sname field (Optional server host name) as String.
     *
     * @return the sname converted to a String (transparent encoding).
     */
    public String getSname() {
        return bytesToString(this.getSnameRaw());
    }

    /**
     * Sets the sname field (Optional server host name) as String.
     *
     * <p>The string is first converted to a byte[] array using transparent
     * encoding. If the resulting buffer size is > 64, an <tt>IllegalArgumentException</tt>
     * is thrown.
     *
     * <p>If <tt>sname</tt> parameter is null, the buffer is filled with zeros.
     *
     * @param sname The sname field to set.
     * @throws IllegalArgumentException string too long
     */
    public void setSname(String sname) {
        this.setSnameRaw(stringToBytes(sname));
    }

    /**
     * Sets the sname field (Optional server host name) as String.
     *
     * <p>If the buffer size is > 64, an <tt>IllegalArgumentException</tt>
     * is thrown.
     *
     * <p>If <tt>sname</tt> parameter is null, the buffer is filled with zeros.
     *
     * <p>This is the low-level maximum performance setter for this field.
     *
     * @param sname The sname field to set.
     * @throws IllegalArgumentException string too long
     */
    public void setSnameRaw(byte[] sname) {
        if (sname != null) {
            if (sname.length > this.sname.length) {
                throw new IllegalArgumentException("Sname is too long:" + sname.length + " max is:" + this.sname.length);
            }
            Arrays.fill(this.sname, (byte) 0);
            System.arraycopy(sname, 0, this.sname, 0, sname.length);
        } else {
            Arrays.fill(this.sname, (byte) 0);
        }
    }

    /**
     * Returns the xid field (Transaction ID).
     *
     * @return Returns the xid.
     */
    public int getXid() {
        return this.xid;
    }

    /**
     * Sets the xid field (Transaction ID).
     *
     * <p>This field is random generated by the client, and used by the client and
     * server to associate requests and responses for the same transaction.
     *
     * @param xid The xid to set.
     */
    public void setXid(int xid) {
        this.xid = xid;
    }

    /**
     * Returns the yiaddr field ('your' IP address).
     *
     * @return the yiaddr field converted to <tt>InetAddress</tt> object.
     */
    public InetAddress getYiaddr() {
        try {
            return InetAddress.getByAddress(this.getYiaddrRaw());
        } catch (UnknownHostException e) {
            logger.log(Level.SEVERE, "Unexpected UnknownHostException", e);
            return null;    // normaly impossible
        }
    }

    /**
     * Returns the yiaddr field ('your' IP address).
     *
     * <p>This is the low-level maximum performance getter for this field.
     *
     * @return Returns the yiaddr as raw byte[4].
     */
    public byte[] getYiaddrRaw() {
        return this.yiaddr.clone();
    }

    /**
     * Sets the yiaddr field ('your' IP address).
     *
     * <p>Ths <tt>yiaddr</tt> field must be of <tt>Inet4Address</tt> class or
     * an <tt>IllegalArgumentException</tt> is thrown.
     *
     * @param yiaddr The yiaddr to set.
     */
    public void setYiaddr(InetAddress yiaddr) {
        if (!(yiaddr instanceof Inet4Address)) {
            throw new IllegalArgumentException("Inet4Address required");
        }
        this.setYiaddrRaw(yiaddr.getAddress());
    }

    /**
     * Sets the yiaddr field ('your' IP address).
     *
     * @param yiaddr The yiaddr to set.
     * @throws UnknownHostException
     */
    public void setYiaddr(String yiaddr) throws UnknownHostException {
        this.setYiaddr(InetAddress.getByName(yiaddr));
    }

    /**
     * Sets the yiaddr field ('your' IP address).
     *
     * <p><tt>yiaddr</tt> must be a 4 bytes array, or an <tt>IllegalArgumentException</tt>
     * is thrown.
     *
     * <p>This is the low-level maximum performance setter for this field.
     * The array is internally copied so any further modification to <tt>ciaddr</tt>
     * parameter has no side effect.
     *
     * @param yiaddr The yiaddr to set.
     */
    public void setYiaddrRaw(byte[] yiaddr) {
        if (yiaddr.length != 4) {
            throw new IllegalArgumentException("4-byte array required");
        }
        System.arraycopy(yiaddr, 0, this.yiaddr, 0, 4);
    }
    /**
     * Return the DHCP Option Type.
     *
     * <p>This is a short-cut for <tt>getOptionAsByte(DHO_DHCP_MESSAGE_TYPE)</tt>.
     *
     * @return option type, of <tt>null</tt> if not present.
     */
    public Byte getDHCPMessageType() {
        return this.getOptionAsByte(DHO_DHCP_MESSAGE_TYPE);
    }

    /**
     * Sets the DHCP Option Type.
     *
     * <p>This is a short-cur for <tt>setOptionAsByte(DHO_DHCP_MESSAGE_TYPE, optionType);</tt>.
     *
     * @param optionType
     */
    public void setDHCPMessageType(byte optionType) {
        this.setOptionAsByte(DHO_DHCP_MESSAGE_TYPE, optionType);
    }

    /**
     * Indicates that the DHCP packet has been truncated and did not finished
     * with a 0xFF option. This parameter is set only when parsing packets in
     * non-strict mode (which is not the default behaviour).
     *
     * <p>This field is read-only and can be <tt>true</tt> only with objects created
     * by parsing a Datagram - getPacket() methods.
     *
     * <p>This field is cleared if the object is cloned.
     *
     * @return the truncated field.
     */
    public boolean isTruncated() {
        return this.truncated;
    }

    /**
     * Wrapper function for getValueAsNum() in DHCPOption. Returns a numerical option: int, short or byte.
     *
     * @param code DHCP option code
     * @return Integer object or <tt>null</tt>
     */
    public Integer getOptionAsNum(byte code) {
      DHCPOption opt = this.getOption(code);
      return (opt != null) ? opt.getValueAsNum() : null;
    }
   
    /**
     * Returns a DHCP Option as Byte format.
     *
     * This method is only allowed for the following option codes:
     * <pre>
     * DHO_IP_FORWARDING(19)
     * DHO_NON_LOCAL_SOURCE_ROUTING(20)
     * DHO_DEFAULT_IP_TTL(23)
     * DHO_ALL_SUBNETS_LOCAL(27)
     * DHO_PERFORM_MASK_DISCOVERY(29)
     * DHO_MASK_SUPPLIER(30)
     * DHO_ROUTER_DISCOVERY(31)
     * DHO_TRAILER_ENCAPSULATION(34)
     * DHO_IEEE802_3_ENCAPSULATION(36)
     * DHO_DEFAULT_TCP_TTL(37)
     * DHO_TCP_KEEPALIVE_GARBAGE(39)
     * DHO_NETBIOS_NODE_TYPE(46)
     * DHO_DHCP_OPTION_OVERLOAD(52)
     * DHO_DHCP_MESSAGE_TYPE(53)
     * DHO_AUTO_CONFIGURE(116)
     * </pre>
     *
     * @param code the option code.
     * @return the option value, <tt>null</tt> if option is not present.
     * @throws IllegalArgumentException the option code is not in the list above.
     * @throws DHCPBadPacketException the option value in packet is of wrong size.
     */
    public Byte getOptionAsByte(byte code) throws IllegalArgumentException {
        DHCPOption opt = this.getOption(code);
        return (opt == null) ? null : opt.getValueAsByte();
    }

    /**
     * Returns a DHCP Option as Short format.
     *
     * <p>This method is only allowed for the following option codes:
     * <pre>
     * DHO_BOOT_SIZE(13)
     * DHO_MAX_DGRAM_REASSEMBLY(22)
     * DHO_INTERFACE_MTU(26)
     * DHO_DHCP_MAX_MESSAGE_SIZE(57)
     * </pre>
     *
     * @param code the option code.
     * @return the option value, <tt>null</tt> if option is not present.
     * @throws IllegalArgumentException the option code is not in the list above.
     * @throws DHCPBadPacketException the option value in packet is of wrong size.
     */
    public Short getOptionAsShort(byte code) throws IllegalArgumentException {
        DHCPOption opt = this.getOption(code);
        return (opt == null) ? null : opt.getValueAsShort();
    }

    /**
     * Returns a DHCP Option as Integer format.
     *
     * <p>This method is only allowed for the following option codes:
     * <pre>
     * DHO_TIME_OFFSET(2)
     * DHO_PATH_MTU_AGING_TIMEOUT(24)
     * DHO_ARP_CACHE_TIMEOUT(35)
     * DHO_TCP_KEEPALIVE_INTERVAL(38)
     * DHO_DHCP_LEASE_TIME(51)
     * DHO_DHCP_RENEWAL_TIME(58)
     * DHO_DHCP_REBINDING_TIME(59)
     * </pre>
     *
     * @param code the option code.
     * @return the option value, <tt>null</tt> if option is not present.
     * @throws IllegalArgumentException the option code is not in the list above.
     * @throws DHCPBadPacketException the option value in packet is of wrong size.
     */
    public Integer getOptionAsInteger(byte code) throws IllegalArgumentException {
        DHCPOption opt = this.getOption(code);
        return (opt == null) ? null : opt.getValueAsInt();
    }

    /**
     * Returns a DHCP Option as InetAddress format.
     *
     * <p>This method is only allowed for the following option codes:
     * <pre>
     * DHO_SUBNET_MASK(1)
     * DHO_SWAP_SERVER(16)
     * DHO_BROADCAST_ADDRESS(28)
     * DHO_ROUTER_SOLICITATION_ADDRESS(32)
     * DHO_DHCP_REQUESTED_ADDRESS(50)
     * DHO_DHCP_SERVER_IDENTIFIER(54)
     * DHO_SUBNET_SELECTION(118)
     * </pre>
     *
     * @param code the option code.
     * @return the option value, <tt>null</tt> if option is not present.
     * @throws IllegalArgumentException the option code is not in the list above.
     * @throws DHCPBadPacketException the option value in packet is of wrong size.
     */
    public InetAddress getOptionAsInetAddr(byte code) throws IllegalArgumentException {
        DHCPOption opt = this.getOption(code);
        return (opt == null) ? null : opt.getValueAsInetAddr();
    }

    /**
     * Returns a DHCP Option as String format.
     *
     * <p>This method is only allowed for the following option codes:
     * <pre>
     * DHO_HOST_NAME(12)
     * DHO_MERIT_DUMP(14)
     * DHO_DOMAIN_NAME(15)
     * DHO_ROOT_PATH(17)
     * DHO_EXTENSIONS_PATH(18)
     * DHO_NETBIOS_SCOPE(47)
     * DHO_DHCP_MESSAGE(56)
     * DHO_VENDOR_CLASS_IDENTIFIER(60)
     * DHO_NWIP_DOMAIN_NAME(62)
     * DHO_NIS_DOMAIN(64)
     * DHO_NIS_SERVER(65)
     * DHO_TFTP_SERVER(66)
     * DHO_BOOTFILE(67)
     * DHO_NDS_TREE_NAME(86)
     * DHO_USER_AUTHENTICATION_PROTOCOL(98)
     * </pre>
     *
     * @param code the option code.
     * @return the option value, <tt>null</tt> if option is not present.
     * @throws IllegalArgumentException the option code is not in the list above.
     */
    public String getOptionAsString(byte code) throws IllegalArgumentException {
        DHCPOption opt = this.getOption(code);
        return (opt == null) ? null : opt.getValueAsString();
    }

    /**
     * Returns a DHCP Option as Short array format.
     *
     * <p>This method is only allowed for the following option codes:
     * <pre>
     * DHO_PATH_MTU_PLATEAU_TABLE(25)
     * DHO_NAME_SERVICE_SEARCH(117)
     * </pre>
     *
     * @param code the option code.
     * @return the option value array, <tt>null</tt> if option is not present.
     * @throws IllegalArgumentException the option code is not in the list above.
     * @throws DHCPBadPacketException the option value in packet is of wrong size.
     */
    public short[] getOptionAsShorts(byte code) throws IllegalArgumentException {
        DHCPOption opt = this.getOption(code);
        return (opt == null) ? null : opt.getValueAsShorts();
    }

    /**
     * Returns a DHCP Option as InetAddress array format.
     *
     * <p>This method is only allowed for the following option codes:
     * <pre>
     * DHO_ROUTERS(3)
     * DHO_TIME_SERVERS(4)
     * DHO_NAME_SERVERS(5)
     * DHO_DOMAIN_NAME_SERVERS(6)
     * DHO_LOG_SERVERS(7)
     * DHO_COOKIE_SERVERS(8)
     * DHO_LPR_SERVERS(9)
     * DHO_IMPRESS_SERVERS(10)
     * DHO_RESOURCE_LOCATION_SERVERS(11)
     * DHO_POLICY_FILTER(21)
     * DHO_STATIC_ROUTES(33)
     * DHO_NIS_SERVERS(41)
     * DHO_NTP_SERVERS(42)
     * DHO_NETBIOS_NAME_SERVERS(44)
     * DHO_NETBIOS_DD_SERVER(45)
     * DHO_FONT_SERVERS(48)
     * DHO_X_DISPLAY_MANAGER(49)
     * DHO_MOBILE_IP_HOME_AGENT(68)
     * DHO_SMTP_SERVER(69)
     * DHO_POP3_SERVER(70)
     * DHO_NNTP_SERVER(71)
     * DHO_WWW_SERVER(72)
     * DHO_FINGER_SERVER(73)
     * DHO_IRC_SERVER(74)
     * DHO_STREETTALK_SERVER(75)
     * DHO_STDA_SERVER(76)
     * DHO_NDS_SERVERS(85)
     * </pre>
     *
     * @param code the option code.
     * @return the option value array, <tt>null</tt> if option is not present.
     * @throws IllegalArgumentException the option code is not in the list above.
     * @throws DHCPBadPacketException the option value in packet is of wrong size.
     */
    public InetAddress[] getOptionAsInetAddrs(byte code) throws IllegalArgumentException {
        DHCPOption opt = this.getOption(code);
        return (opt == null) ? null : opt.getValueAsInetAddrs();
    }

    /**
     * Returns a DHCP Option as Byte array format.
     *
     * <p>This method is only allowed for the following option codes:
     * <pre>
     * DHO_DHCP_PARAMETER_REQUEST_LIST(55)
     * </pre>
     *
     * <p>Note: this mehtod is similar to getOptionRaw, only with option type checking.
     *
     * @param code the option code.
     * @return the option value array, <tt>null</tt> if option is not present.
     * @throws IllegalArgumentException the option code is not in the list above.
     */
    public byte[] getOptionAsBytes(byte code) throws IllegalArgumentException {
        DHCPOption opt = this.getOption(code);
        return (opt == null) ? null : opt.getValueAsBytes();
    }

    /**
     * Sets a DHCP Option as Byte format.
     *
     * <p>See <tt>DHCPOption</tt> for allowed option codes.
     *
     * @param code the option code.
     * @param val the value
     * @throws IllegalArgumentException the option code is not in the list above.
     */
    public void setOptionAsByte(byte code, byte val) {
        this.setOption(DHCPOption.newOptionAsByte(code, val));
    }

    /**
     * Sets a DHCP Option as Short format.
     *
     * <p>See <tt>DHCPOption</tt> for allowed option codes.
     *
     * @param code the option code.
     * @param val the value
     * @throws IllegalArgumentException the option code is not in the list above.
     */
    public void setOptionAsShort(byte code, short val) {
        this.setOption(DHCPOption.newOptionAsShort(code, val));
    }

    /**
     * Sets a DHCP Option as Integer format.
     *
     * <p>See <tt>DHCPOption</tt> for allowed option codes.
     *
     * @param code the option code.
     * @param val the value
     * @throws IllegalArgumentException the option code is not in the list above.
     */
    public void setOptionAsInt(byte code, int val) {
        this.setOption(DHCPOption.newOptionAsInt(code, val));
    }

    /**
     * Sets a DHCP Option as InetAddress format.
     *
     * <p>See <tt>DHCPOption</tt> for allowed option codes.
     *
     * @param code the option code.
     * @param val the value
     * @throws IllegalArgumentException the option code is not in the list above.
     */
    public void setOptionAsInetAddress(byte code, InetAddress val) {
        this.setOption(DHCPOption.newOptionAsInetAddress(code, val));
    }

    /**
     * Sets a DHCP Option as InetAddress format.
     *
     * <p>See <tt>DHCPOption</tt> for allowed option codes.
     *
     * @param code the option code in String format.
     * @param val the value
     * @throws UnknownHostException cannot find the address
     * @throws IllegalArgumentException the option code is not in the list above.
     */
    public void setOptionAsInetAddress(byte code, String val) throws UnknownHostException {
        this.setOption(DHCPOption.newOptionAsInetAddress(code, InetAddress.getByName(val)));
    }

    /**
     * Sets a DHCP Option as InetAddress array format.
     *
     * <p>See <tt>DHCPOption</tt> for allowed option codes.
     *
     * @param code the option code.
     * @param val the value array
     * @throws IllegalArgumentException the option code is not in the list above.
     */
    public void setOptionAsInetAddresses(byte code, InetAddress[] val) {
        this.setOption(DHCPOption.newOptionAsInetAddresses(code, val));
    }

    /**
     * Sets a DHCP Option as String format.
     *
     * <p>See <tt>DHCPOption</tt> for allowed option codes.
     *
     * @param code the option code.
     * @param val the value
     * @throws IllegalArgumentException the option code is not in the list above.
     */
    public void setOptionAsString(byte code, String val) {
        this.setOption(DHCPOption.newOptionAsString(code, val));
    }

    /**
     * Returns the option as raw byte[] buffer.
     *
     * <p>This is the low-level maximum performance getter for options.
     * No byte[] copy is completed to increase performance.
     *
     * @param code option code
     * @return Returns the option as raw <tt>byte[]</tt>, or <tt>null</tt> if
     * the option is not present.
     */
    public byte[] getOptionRaw(byte code) {
        final DHCPOption opt = this.getOption(code);
        return ((opt == null) ? null : opt.getValueFast());
    }

    /**
     * Returns the option as DHCPOption object.
     *
     * <p>This is the low-level maximum performance getter for options.
     * This method is used by every option getter in this object.
     *
     * @param code option code
     * @return Returns the option as <tt>DHCPOption</tt>, or <tt>null</tt> if
     * the option is not present.
     */
    public DHCPOption getOption(byte code) {
        DHCPOption opt = this.options.get(code);
        // Sanity checks
        if (opt == null) {
            return null;
        }
        assert(opt.getCode() == code);
        assert(opt.getValueFast() != null);
        return opt;
    }

    /**
     * Tests whether an option code is present in the packet.
     *
     * @param code DHCP option code
     * @return true if option is present
     */
    public boolean containsOption(byte code) {
        return this.options.containsKey(code);
    }

    /**
     * Return an ordered list/collection of all options.
     *
     * <p>The Collection is read-only.
     *
     * @return collection of <tt>DHCPOption</tt>.
     */
    public Collection<DHCPOption> getOptionsCollection() {
        return Collections.unmodifiableCollection(this.options.values());    // read only
    }

    /**
     * Return an array of all DHCP options.
     *
     * @return the options array
     */
    public DHCPOption[] getOptionsArray() {
        return this.options.values().toArray(new DHCPOption[this.options.size()]);
    }

    /**
     * Sets the option specified for the option.
     *
     * <p>If <tt>buf</tt> is <tt>null</tt>, the option is cleared.
     *
     * <p>Options are sorted in creation order. Previous values are replaced.
     *
     * <p>This is the low-level maximum performance setter for options.
     *
     * @param    code opt    option code, use <tt>DHO_*</tt> for predefined values.
     * @param    buf    raw buffer value (cloned). If null, the option is removed.
     */
    public void setOptionRaw(byte code, byte[] buf) {
        if (buf == null) {        // clear parameter
            this.removeOption(code);
        } else {
            this.setOption(new DHCPOption(code, buf));    // exception here if code=0 or code=-1
        }
    }
    /**
     * Sets the option specified for the option.
     *
     * <p>If <tt>buf</tt> is <tt>null</tt>, the option is cleared.
     *
     * <p>Options are sorted in creation order. Previous values are replaced, but their
     * previous position is retained.
     *
     * <p>This is the low-level maximum performance setter for options.
     * This method is called by all setter methods in this class.
     *
     * @param    opt    option code, use <tt>DHO_*</tt> for predefined values.
     */
    public void setOption(DHCPOption opt) {
        if (opt != null) {
            if (opt.getValueFast() == null) {
                this.removeOption(opt.getCode());
            } else {
                this.options.put(opt.getCode(), opt);
            }
        }
    }

    /**
     * Sets an array of options. Calles repeatedly setOption on each element of the array.
     *
     * @param    opts    array of options.
     */
    public void setOptions(DHCPOption[] opts) {
        if (opts != null) {
            for (DHCPOption opt : opts) {
                this.setOption(opt);
            }
        }
    }

    /**
     * Sets a Collection of options. Calles repeatedly setOption on each element of the List.
     *
     * @param    opts    List of options.
     */
    public void setOptions(Collection<DHCPOption> opts) {
        if (opts != null) {
            for (DHCPOption opt : opts) {
                this.setOption(opt);
            }
        }
    }

    /**
     * Remove this option from the options list.
     *
     * @param opt the option code to remove.
     */
    public void removeOption(byte opt) {
        this.options.remove(opt);
    }

    /**
     * Remove all options.
     */
    public void removeAllOptions() {
        this.options.clear();
    }

    /**
     * Returns the IP address of the machine to which this datagram is being sent
     * or from which the datagram was received.
     *
     * @return the IP address of the machine to which this datagram is being sent
     * or from which the datagram was received. <tt>null</tt> if no address.
     */
    public InetAddress getAddress() {
        return this.address;
    }

    /**
     * Sets the IP address of the machine to which this datagram is being sent.
     *
     * @param address the <tt>InetAddress</tt>.
     * @throws IllegalArgumentException address is not of <tt>Inet4Address</tt> class.
     */
    public void setAddress(InetAddress address) {
      if (address == null) {
        this.address = null;
      } else if (!(address instanceof Inet4Address)) {
            throw new IllegalArgumentException("only IPv4 addresses accepted");
        } else {
          this.address = address;
        }
    }

    /**
     * Returns the port number on the remote host to which this datagram is being sent
     * or from which the datagram was received.
     *
     * @return the port number on the remote host to which this datagram is being sent
     * or from which the datagram was received.
     */
    public int getPort() {
        return this.port;
    }

    /**
     * Sets the port number on the remote host to which this datagram is being sent.
     *
     * @param port the port number.
     */
    public void setPort(int port) {
        this.port = port;
    }
   
    /**
     * Syntactic sugar for getAddress/getPort.
     *
     * @return address + port.
     */
    public InetSocketAddress getAddrPort() {
      return new InetSocketAddress(address, port);
    }
   
    /**
     * Syntactic sugar for setAddress/setPort.
     *
     * @param addrPort address and port, if <tt>null</t> address is set to null and port to 0
     */
    public void setAddrPort(InetSocketAddress addrPort) {
      if (addrPort == null) {
        setAddress(null);
        setPort(0);
      } else {
        setAddress(addrPort.getAddress());
        setPort(addrPort.getPort());
      }
    }

    // ========================================================================
    // utility functions

    /**
     * Converts a null terminated byte[] string to a String object,
     * with a transparent conversion.
     *
     * Faster version than String.getBytes()
     */
    static String bytesToString(byte[] buf) {
        if (buf == null) { return ""; }
        return bytesToString(buf, 0, buf.length);
    }

    static String bytesToString(byte[] buf, int src, int len) {
        if (buf == null) { return ""; }
        if (src < 0) {
            len += src;    // reduce length
            src = 0;
        }
        if (len <= 0) { return ""; }
        if (src >= buf.length) { return ""; }
        if (src + len > buf.length) { len = buf.length - src; }
        // string should be null terminated or whole buffer
        // first find the real lentgh
        for (int i=src; i<src+len; i++) {
            if (buf[i] == 0) {
                len = i - src;
                break;
            }
        }

        char[] chars = new char[len];

        for (int i = src; i < src + len; i++) {
            chars[i - src] = (char) buf[i];
        }
        return new String(chars);
    }

    /**
     * Converts byte to hex string (2 chars) (uppercase)
     */
    private static final char[] hex = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };

    static void appendHex(StringBuilder sbuf, byte b) {
        int i = (b & 0xFF);
        sbuf.append(hex[(i & 0xF0) >> 4])
            .append(hex[i & 0x0F]);
    }

    /**
     * Converts a byte[] to a sequence of hex chars (uppercase), limited to <tt>len</tt> bytes
     * and appends them to a string buffer
     */
    static void appendHex(StringBuilder sbuf, final byte[] buf, int src, int len) {
        if (buf == null) { return; }
        if (src < 0) {
            len += src;    // reduce length
            src = 0;
        }
        if (len <= 0 || src >= buf.length) { return; }
        if (src + len > buf.length) { len = buf.length - src; }

        for (int i = src; i < src + len; i++) {
            appendHex(sbuf, buf[i]);
        }
    }

    /**
     * Convert plain byte[] to hex string (uppercase)
     */
    static void appendHex(StringBuilder sbuf, final byte[] buf) {
        appendHex(sbuf, buf, 0, buf.length);
    }

    /**
     * Convert bytes to hex string.
     *
     * @param buf
     * @return hex string (lowercase) or "" if buf is <tt>null</tt>
     */
    static String bytes2Hex(byte[] buf) {
      if (buf == null) {
        return "";
      }
      StringBuilder sb = new StringBuilder(buf.length * 2);
      appendHex(sb, buf);
      return sb.toString();
    }
   
    /**
     * Convert hes String to byte[]
     */
    static byte[] hex2Bytes(String s) {
        if ((s.length() & 1) != 0) {
            throw new IllegalArgumentException("String length must be even: " + s.length());
        }

        byte[] buf = new byte[s.length() / 2];

        for (int index = 0; index < buf.length; index++) {
            final int stringIndex = index << 1;
            buf[index] = (byte) Integer.parseInt(s.substring(stringIndex, stringIndex + 2), 16);
        }
        return buf;
    }

    /**
     * Convert integer to hex chars (uppercase) and appends them to a string builder
     */
    private static void appendHex(StringBuilder sbuf, int i) {
        appendHex(sbuf, (byte) ((i & 0xff000000) >>> 24));
        appendHex(sbuf, (byte) ((i & 0x00ff0000) >>> 16));
        appendHex(sbuf, (byte) ((i & 0x0000ff00) >>>  8));
        appendHex(sbuf, (byte) ((i & 0x000000ff)       ));
    }

    public static byte[] stringToBytes(String str) {
        if (str == null) { return null; }

        char[] chars = str.toCharArray();
        int    len   = chars.length;
        byte[] buf   = new byte[len];

        for (int i = 0; i < len; i++) {
            buf[i] = (byte) chars[i];
        }
        return buf;
    }

    /**
     * Even faster version than {@link #getHostAddress} when the address is not
     * the only piece of information put in the string.
     *
     * @param sbuf the string builder
     * @param addr the Internet address
     */
    public static void appendHostAddress(StringBuilder sbuf, InetAddress addr) {
        if (addr == null) {
            throw new IllegalArgumentException("addr must not be null");
        }
        if (!(addr instanceof Inet4Address)) {
            throw new IllegalArgumentException("addr must be an instance of Inet4Address");
        }

        byte[] src = addr.getAddress();

        sbuf.append(src[0] & 0xFF)
            .append('.')
            .append(src[1] & 0xFF)
            .append('.')
            .append(src[2] & 0xFF)
            .append('.')
            .append(src[3] & 0xFF);
    }

    /**
     * Faster version than <tt>InetAddress.getHostAddress()</tt>.
     *
     * @return String representation of address.
     */
    public static String getHostAddress(InetAddress addr) {
        StringBuilder sbuf = new StringBuilder(15);
        appendHostAddress(sbuf, addr);
        return sbuf.toString();
    }
}
TOP

Related Classes of org.dhcp4java.DHCPPacket

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.