Merge "DHCP core classes, defining data, behavior, and state transitions for the DHCP protocol.  The DHCP client and DHCP server classes are not included in this commit: still working out some integration issues with ethernet & wifi services"
diff --git a/core/java/android/net/dhcp/DhcpAckPacket.java b/core/java/android/net/dhcp/DhcpAckPacket.java
new file mode 100644
index 0000000..900a0e6
--- /dev/null
+++ b/core/java/android/net/dhcp/DhcpAckPacket.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.dhcp;
+
+import java.net.InetAddress;
+import java.net.Inet4Address;
+import java.nio.ByteBuffer;
+import java.util.List;
+
+/**
+ * This class implements the DHCP-ACK packet.
+ */
+class DhcpAckPacket extends DhcpPacket {
+
+    /**
+     * The address of the server which sent this packet.
+     */
+    private final InetAddress mSrcIp;
+
+    DhcpAckPacket(int transId, boolean broadcast, InetAddress serverAddress,
+                  InetAddress clientIp, byte[] clientMac) {
+        super(transId, Inet4Address.ANY, clientIp, Inet4Address.ANY,
+            Inet4Address.ANY, clientMac, broadcast);
+        mBroadcast = broadcast;
+        mSrcIp = serverAddress;
+    }
+
+    public String toString() {
+        String s = super.toString();
+        String dnsServers = " DNS servers: ";
+
+        for (InetAddress dnsServer: mDnsServers) {
+            dnsServers += dnsServer.toString() + " ";
+        }
+
+        return s + " ACK: your new IP " + mYourIp +
+                ", netmask " + mSubnetMask +
+                ", gateway " + mGateway + dnsServers +
+                ", lease time " + mLeaseTime;
+    }
+
+    /**
+     * Fills in a packet with the requested ACK parameters.
+     */
+    public ByteBuffer buildPacket(int encap, short destUdp, short srcUdp) {
+        ByteBuffer result = ByteBuffer.allocate(MAX_LENGTH);
+        InetAddress destIp = mBroadcast ? Inet4Address.ALL : mYourIp;
+        InetAddress srcIp = mBroadcast ? Inet4Address.ANY : mSrcIp;
+
+        fillInPacket(encap, destIp, srcIp, destUdp, srcUdp, result,
+            DHCP_BOOTREPLY, mBroadcast);
+        result.flip();
+        return result;
+    }
+
+    /**
+     * Adds the optional parameters to the client-generated ACK packet.
+     */
+    void finishPacket(ByteBuffer buffer) {
+        addTlv(buffer, DHCP_MESSAGE_TYPE, DHCP_MESSAGE_TYPE_ACK);
+        addTlv(buffer, DHCP_SERVER_IDENTIFIER, mServerIdentifier);
+        addTlv(buffer, DHCP_LEASE_TIME, mLeaseTime);
+
+        // the client should renew at 1/2 the lease-expiry interval
+        if (mLeaseTime != null) {
+            addTlv(buffer, DHCP_RENEWAL_TIME,
+                Integer.valueOf(mLeaseTime.intValue() / 2));
+        }
+
+        addTlv(buffer, DHCP_SUBNET_MASK, mSubnetMask);
+        addTlv(buffer, DHCP_ROUTER, mGateway);
+        addTlv(buffer, DHCP_DOMAIN_NAME, mDomainName);
+        addTlv(buffer, DHCP_BROADCAST_ADDRESS, mBroadcastAddress);
+        addTlv(buffer, DHCP_DNS_SERVER, mDnsServers);
+        addTlvEnd(buffer);
+    }
+
+    /**
+     * Un-boxes an Integer, returning 0 if a null reference is supplied.
+     */
+    private static final int getInt(Integer v) {
+        if (v == null) {
+            return 0;
+        } else {
+            return v.intValue();
+        }
+    }
+
+    /**
+     * Notifies the specified state machine of the ACK packet parameters.
+     */
+    public void doNextOp(DhcpStateMachine machine) {
+        machine.onAckReceived(mYourIp, mSubnetMask, mGateway, mDnsServers,
+            mServerIdentifier, getInt(mLeaseTime));
+    }
+}
diff --git a/core/java/android/net/dhcp/DhcpDeclinePacket.java b/core/java/android/net/dhcp/DhcpDeclinePacket.java
new file mode 100644
index 0000000..7646eb4
--- /dev/null
+++ b/core/java/android/net/dhcp/DhcpDeclinePacket.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.dhcp;
+
+import java.net.InetAddress;
+import java.nio.ByteBuffer;
+
+/**
+ * This class implements the DHCP-DECLINE packet.
+ */
+class DhcpDeclinePacket extends DhcpPacket {
+    /**
+     * Generates a DECLINE packet with the specified parameters.
+     */
+    DhcpDeclinePacket(int transId, InetAddress clientIp, InetAddress yourIp,
+                      InetAddress nextIp, InetAddress relayIp,
+                      byte[] clientMac) {
+        super(transId, clientIp, yourIp, nextIp, relayIp, clientMac, false);
+    }
+
+    public String toString() {
+        String s = super.toString();
+        return s + " DECLINE";
+    }
+
+    /**
+     * Fills in a packet with the requested DECLINE attributes.
+     */
+    public ByteBuffer buildPacket(int encap, short destUdp, short srcUdp) {
+        ByteBuffer result = ByteBuffer.allocate(MAX_LENGTH);
+
+        fillInPacket(encap, mClientIp, mYourIp, destUdp, srcUdp, result,
+            DHCP_BOOTREQUEST, false);
+        result.flip();
+        return result;
+    }
+
+    /**
+     * Adds optional parameters to the DECLINE packet.
+     */
+    void finishPacket(ByteBuffer buffer) {
+        // None needed
+    }
+
+    /**
+     * Informs the state machine of the arrival of a DECLINE packet.
+     */
+    public void doNextOp(DhcpStateMachine machine) {
+        machine.onDeclineReceived(mClientMac, mRequestedIp);
+    }
+}
diff --git a/core/java/android/net/dhcp/DhcpDiscoverPacket.java b/core/java/android/net/dhcp/DhcpDiscoverPacket.java
new file mode 100644
index 0000000..0e2d39b
--- /dev/null
+++ b/core/java/android/net/dhcp/DhcpDiscoverPacket.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.dhcp;
+
+import java.net.InetAddress;
+import java.net.Inet4Address;
+import java.nio.ByteBuffer;
+
+/**
+ * This class implements the DHCP-DISCOVER packet.
+ */
+class DhcpDiscoverPacket extends DhcpPacket {
+    /**
+     * Generates a DISCOVER packet with the specified parameters.
+     */
+    DhcpDiscoverPacket(int transId, byte[] clientMac, boolean broadcast) {
+        super(transId, Inet4Address.ANY, Inet4Address.ANY, Inet4Address.ANY,
+            Inet4Address.ANY, clientMac, broadcast);
+    }
+
+    public String toString() {
+        String s = super.toString();
+        return s + " DISCOVER " +
+                (mBroadcast ? "broadcast " : "unicast ");
+    }
+
+    /**
+     * Fills in a packet with the requested DISCOVER parameters.
+     */
+    public ByteBuffer buildPacket(int encap, short destUdp, short srcUdp) {
+        ByteBuffer result = ByteBuffer.allocate(MAX_LENGTH);
+        InetAddress destIp = Inet4Address.ALL;
+
+        fillInPacket(encap, Inet4Address.ALL, Inet4Address.ANY, destUdp, srcUdp,
+            result, DHCP_BOOTREQUEST, true);
+        result.flip();
+        return result;
+    }
+
+    /**
+     * Adds optional parameters to a DISCOVER packet.
+     */
+    void finishPacket(ByteBuffer buffer) {
+        addTlv(buffer, DHCP_MESSAGE_TYPE, DHCP_MESSAGE_TYPE_DISCOVER);
+        addTlv(buffer, DHCP_PARAMETER_LIST, mRequestedParams);
+        addTlvEnd(buffer);
+    }
+
+    /**
+     * Informs the state machine of the arrival of a DISCOVER packet.
+     */
+    public void doNextOp(DhcpStateMachine machine) {
+        // currently omitted: host name
+        machine.onDiscoverReceived(mBroadcast, mTransId, mClientMac,
+            mRequestedParams);
+    }
+}
diff --git a/core/java/android/net/dhcp/DhcpInformPacket.java b/core/java/android/net/dhcp/DhcpInformPacket.java
new file mode 100644
index 0000000..da73216
--- /dev/null
+++ b/core/java/android/net/dhcp/DhcpInformPacket.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.dhcp;
+
+import java.net.InetAddress;
+import java.nio.ByteBuffer;
+
+/**
+ * This class implements the (unused) DHCP-INFORM packet.
+ */
+class DhcpInformPacket extends DhcpPacket {
+    /**
+     * Generates an INFORM packet with the specified parameters.
+     */
+    DhcpInformPacket(int transId, InetAddress clientIp, InetAddress yourIp,
+                     InetAddress nextIp, InetAddress relayIp,
+                     byte[] clientMac) {
+        super(transId, clientIp, yourIp, nextIp, relayIp, clientMac, false);
+    }
+
+    public String toString() {
+        String s = super.toString();
+        return s + " INFORM";
+    }
+
+    /**
+     * Builds an INFORM packet.
+     */
+    public ByteBuffer buildPacket(int encap, short destUdp, short srcUdp) {
+        ByteBuffer result = ByteBuffer.allocate(MAX_LENGTH);
+
+        fillInPacket(encap, mClientIp, mYourIp, destUdp, srcUdp, result,
+            DHCP_BOOTREQUEST, false);
+        result.flip();
+        return result;
+    }
+
+    /**
+     * Adds additional parameters to the INFORM packet.
+     */
+    void finishPacket(ByteBuffer buffer) {
+        byte[] clientId = new byte[7];
+
+        clientId[0] = CLIENT_ID_ETHER;
+        System.arraycopy(mClientMac, 0, clientId, 1, 6);
+
+        addTlv(buffer, DHCP_MESSAGE_TYPE, DHCP_MESSAGE_TYPE_REQUEST);
+        addTlv(buffer, DHCP_PARAMETER_LIST, mRequestedParams);
+        addTlvEnd(buffer);
+    }
+
+    /**
+     * Informs the state machine of the arrival of an INFORM packet.  Not
+     * used currently.
+     */
+    public void doNextOp(DhcpStateMachine machine) {
+        InetAddress clientRequest =
+            mRequestedIp == null ? mClientIp : mRequestedIp;
+        machine.onInformReceived(mTransId, mClientMac, clientRequest,
+            mRequestedParams);
+    }
+}
diff --git a/core/java/android/net/dhcp/DhcpNakPacket.java b/core/java/android/net/dhcp/DhcpNakPacket.java
new file mode 100644
index 0000000..1f340ad
--- /dev/null
+++ b/core/java/android/net/dhcp/DhcpNakPacket.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.dhcp;
+
+import java.net.InetAddress;
+import java.net.Inet4Address;
+import java.nio.ByteBuffer;
+
+/**
+ * This class implements the DHCP-NAK packet.
+ */
+class DhcpNakPacket extends DhcpPacket {
+    /**
+     * Generates a NAK packet with the specified parameters.
+     */
+    DhcpNakPacket(int transId, InetAddress clientIp, InetAddress yourIp,
+                  InetAddress nextIp, InetAddress relayIp,
+                  byte[] clientMac) {
+        super(transId, Inet4Address.ANY, Inet4Address.ANY, nextIp, relayIp,
+            clientMac, false);
+    }
+
+    public String toString() {
+        String s = super.toString();
+        return s + " NAK, reason " + (mMessage == null ? "(none)" : mMessage);
+    }
+
+    /**
+     * Fills in a packet with the requested NAK attributes.
+     */
+    public ByteBuffer buildPacket(int encap, short destUdp, short srcUdp) {
+        ByteBuffer result = ByteBuffer.allocate(MAX_LENGTH);
+        InetAddress destIp = mClientIp;
+        InetAddress srcIp = mYourIp;
+
+        fillInPacket(encap, destIp, srcIp, destUdp, srcUdp, result,
+            DHCP_BOOTREPLY, mBroadcast);
+        result.flip();
+        return result;
+    }
+
+    /**
+     * Adds the optional parameters to the client-generated NAK packet.
+     */
+    void finishPacket(ByteBuffer buffer) {
+        addTlv(buffer, DHCP_MESSAGE_TYPE, DHCP_MESSAGE_TYPE_NAK);
+        addTlv(buffer, DHCP_SERVER_IDENTIFIER, mServerIdentifier);
+        addTlv(buffer, DHCP_MESSAGE, mMessage);
+        addTlvEnd(buffer);
+    }
+
+    /**
+     * Notifies the specified state machine of the newly-arrived NAK packet.
+     */
+    public void doNextOp(DhcpStateMachine machine) {
+        machine.onNakReceived();
+    }
+}
diff --git a/core/java/android/net/dhcp/DhcpOfferPacket.java b/core/java/android/net/dhcp/DhcpOfferPacket.java
new file mode 100644
index 0000000..3d79f4d
--- /dev/null
+++ b/core/java/android/net/dhcp/DhcpOfferPacket.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.dhcp;
+
+import java.net.InetAddress;
+import java.net.Inet4Address;
+import java.nio.ByteBuffer;
+import java.util.List;
+
+/**
+ * This class implements the DHCP-OFFER packet.
+ */
+class DhcpOfferPacket extends DhcpPacket {
+    /**
+     * The IP address of the server which sent this packet.
+     */
+    private final InetAddress mSrcIp;
+
+    /**
+     * Generates a OFFER packet with the specified parameters.
+     */
+    DhcpOfferPacket(int transId, boolean broadcast, InetAddress serverAddress,
+                    InetAddress clientIp, byte[] clientMac) {
+        super(transId, Inet4Address.ANY, clientIp, Inet4Address.ANY,
+            Inet4Address.ANY, clientMac, broadcast);
+        mSrcIp = serverAddress;
+    }
+
+    public String toString() {
+        String s = super.toString();
+        String dnsServers = ", DNS servers: ";
+
+        if (mDnsServers != null) {
+            for (InetAddress dnsServer: mDnsServers) {
+                dnsServers += dnsServer + " ";
+            }
+        }
+
+        return s + " OFFER, ip " + mYourIp + ", mask " + mSubnetMask +
+                dnsServers + ", gateway " + mGateway +
+                " lease time " + mLeaseTime + ", domain " + mDomainName;
+    }
+
+    /**
+     * Fills in a packet with the specified OFFER attributes.
+     */
+    public ByteBuffer buildPacket(int encap, short destUdp, short srcUdp) {
+        ByteBuffer result = ByteBuffer.allocate(MAX_LENGTH);
+        InetAddress destIp = mBroadcast ? Inet4Address.ALL : mYourIp;
+        InetAddress srcIp = mBroadcast ? Inet4Address.ANY : mSrcIp;
+
+        fillInPacket(encap, destIp, srcIp, destUdp, srcUdp, result,
+            DHCP_BOOTREPLY, mBroadcast);
+        result.flip();
+        return result;
+    }
+
+    /**
+     * Adds the optional parameters to the server-generated OFFER packet.
+     */
+    void finishPacket(ByteBuffer buffer) {
+        addTlv(buffer, DHCP_MESSAGE_TYPE, DHCP_MESSAGE_TYPE_OFFER);
+        addTlv(buffer, DHCP_SERVER_IDENTIFIER, mServerIdentifier);
+        addTlv(buffer, DHCP_LEASE_TIME, mLeaseTime);
+
+        // the client should renew at 1/2 the lease-expiry interval
+        if (mLeaseTime != null) {
+            addTlv(buffer, DHCP_RENEWAL_TIME,
+                Integer.valueOf(mLeaseTime.intValue() / 2));
+        }
+
+        addTlv(buffer, DHCP_SUBNET_MASK, mSubnetMask);
+        addTlv(buffer, DHCP_ROUTER, mGateway);
+        addTlv(buffer, DHCP_DOMAIN_NAME, mDomainName);
+        addTlv(buffer, DHCP_BROADCAST_ADDRESS, mBroadcastAddress);
+        addTlv(buffer, DHCP_DNS_SERVER, mDnsServers);
+        addTlvEnd(buffer);
+    }
+
+    /**
+     * Notifies the state machine of the OFFER packet parameters.
+     */
+    public void doNextOp(DhcpStateMachine machine) {
+        machine.onOfferReceived(mBroadcast, mTransId, mClientMac, mYourIp,
+            mServerIdentifier);
+    }
+}
diff --git a/core/java/android/net/dhcp/DhcpPacket.java b/core/java/android/net/dhcp/DhcpPacket.java
new file mode 100644
index 0000000..e009f70
--- /dev/null
+++ b/core/java/android/net/dhcp/DhcpPacket.java
@@ -0,0 +1,899 @@
+package android.net.dhcp;
+
+import android.util.Log;
+
+import java.io.UnsupportedEncodingException;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.charset.Charsets;
+import java.nio.ShortBuffer;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Defines basic data and operations needed to build and use packets for the
+ * DHCP protocol.  Subclasses create the specific packets used at each
+ * stage of the negotiation.
+ */
+abstract class DhcpPacket {
+    protected static final String TAG = "DhcpPacket";
+
+    /**
+     * Packet encapsulations.
+     */
+    public static final int ENCAP_L2 = 0;    // EthernetII header included
+    public static final int ENCAP_L3 = 1;    // IP/UDP header included
+    public static final int ENCAP_BOOTP = 2; // BOOTP contents only
+
+    /**
+     * IP layer definitions.
+     */
+    private static final byte IP_TYPE_UDP = (byte) 0x11;
+
+    /**
+     * IP: Version 4, Header Length 20 bytes
+     */
+    private static final byte IP_VERSION_HEADER_LEN = (byte) 0x45;
+
+    /**
+     * IP: Flags 0, Fragment Offset 0, Don't Fragment
+     */
+    private static final short IP_FLAGS_OFFSET = (short) 0x4000;
+
+    /**
+     * IP: TOS
+     */
+    private static final byte IP_TOS_LOWDELAY = (byte) 0x10;
+
+    /**
+     * IP: TTL -- use default 64 from RFC1340
+     */
+    private static final byte IP_TTL = (byte) 0x40;
+
+    /**
+     * The client DHCP port.
+     */
+    static final short DHCP_CLIENT = (short) 68;
+
+    /**
+     * The server DHCP port.
+     */
+    static final short DHCP_SERVER = (short) 67;
+
+    /**
+     * The message op code indicating a request from a client.
+     */
+    protected static final byte DHCP_BOOTREQUEST = (byte) 1;
+
+    /**
+     * The message op code indicating a response from the server.
+     */
+    protected static final byte DHCP_BOOTREPLY = (byte) 2;
+
+    /**
+     * The code type used to identify an Ethernet MAC address in the
+     * Client-ID field.
+     */
+    protected static final byte CLIENT_ID_ETHER = (byte) 1;
+
+    /**
+     * The maximum length of a packet that can be constructed.
+     */
+    protected static final int MAX_LENGTH = 1500;
+
+    /**
+     * DHCP Optional Type: DHCP Subnet Mask
+     */
+    protected static final byte DHCP_SUBNET_MASK = 1;
+    protected InetAddress mSubnetMask;
+
+    /**
+     * DHCP Optional Type: DHCP Router
+     */
+    protected static final byte DHCP_ROUTER = 3;
+    protected InetAddress mGateway;
+
+    /**
+     * DHCP Optional Type: DHCP DNS Server
+     */
+    protected static final byte DHCP_DNS_SERVER = 6;
+    protected List<InetAddress> mDnsServers;
+
+    /**
+     * DHCP Optional Type: DHCP Host Name
+     */
+    protected static final byte DHCP_HOST_NAME = 12;
+    protected String mHostName;
+
+    /**
+     * DHCP Optional Type: DHCP DOMAIN NAME
+     */
+    protected static final byte DHCP_DOMAIN_NAME = 15;
+    protected String mDomainName;
+
+    /**
+     * DHCP Optional Type: DHCP BROADCAST ADDRESS
+     */
+    protected static final byte DHCP_BROADCAST_ADDRESS = 28;
+    protected InetAddress mBroadcastAddress;
+
+    /**
+     * DHCP Optional Type: DHCP Requested IP Address
+     */
+    protected static final byte DHCP_REQUESTED_IP = 50;
+    protected InetAddress mRequestedIp;
+
+    /**
+     * DHCP Optional Type: DHCP Lease Time
+     */
+    protected static final byte DHCP_LEASE_TIME = 51;
+    protected Integer mLeaseTime;
+
+    /**
+     * DHCP Optional Type: DHCP Message Type
+     */
+    protected static final byte DHCP_MESSAGE_TYPE = 53;
+    // the actual type values
+    protected static final byte DHCP_MESSAGE_TYPE_DISCOVER = 1;
+    protected static final byte DHCP_MESSAGE_TYPE_OFFER = 2;
+    protected static final byte DHCP_MESSAGE_TYPE_REQUEST = 3;
+    protected static final byte DHCP_MESSAGE_TYPE_DECLINE = 4;
+    protected static final byte DHCP_MESSAGE_TYPE_ACK = 5;
+    protected static final byte DHCP_MESSAGE_TYPE_NAK = 6;
+    protected static final byte DHCP_MESSAGE_TYPE_INFORM = 8;
+
+    /**
+     * DHCP Optional Type: DHCP Server Identifier
+     */
+    protected static final byte DHCP_SERVER_IDENTIFIER = 54;
+    protected InetAddress mServerIdentifier;
+
+    /**
+     * DHCP Optional Type: DHCP Parameter List
+     */
+    protected static final byte DHCP_PARAMETER_LIST = 55;
+    protected byte[] mRequestedParams;
+
+    /**
+     * DHCP Optional Type: DHCP MESSAGE
+     */
+    protected static final byte DHCP_MESSAGE = 56;
+    protected String mMessage;
+
+    /**
+     * DHCP Optional Type: DHCP Renewal Time Value
+     */
+    protected static final byte DHCP_RENEWAL_TIME = 58;
+
+    /**
+     * DHCP Optional Type: Vendor Class Identifier
+     */
+    protected static final byte DHCP_VENDOR_CLASS_ID = 60;
+
+    /**
+     * DHCP Optional Type: DHCP Client Identifier
+     */
+    protected static final byte DHCP_CLIENT_IDENTIFIER = 61;
+
+    /**
+     * The transaction identifier used in this particular DHCP negotiation
+     */
+    protected final int mTransId;
+
+    /**
+     * The IP address of the client host.  This address is typically
+     * proposed by the client (from an earlier DHCP negotiation) or
+     * supplied by the server.
+     */
+    protected final InetAddress mClientIp;
+    protected final InetAddress mYourIp;
+    private final InetAddress mNextIp;
+    private final InetAddress mRelayIp;
+
+    /**
+     * Does the client request a broadcast response?
+     */
+    protected boolean mBroadcast;
+
+    /**
+     * The six-octet MAC of the client.
+     */
+    protected final byte[] mClientMac;
+
+    /**
+     * Asks the packet object to signal the next operation in the DHCP
+     * protocol.  The available actions are methods defined in the
+     * DhcpStateMachine interface.
+     */
+    public abstract void doNextOp(DhcpStateMachine stateMachine);
+
+    /**
+     * Asks the packet object to create a ByteBuffer serialization of
+     * the packet for transmission.
+     */
+    public abstract ByteBuffer buildPacket(int encap, short destUdp,
+        short srcUdp);
+
+    /**
+     * Allows the concrete class to fill in packet-type-specific details,
+     * typically optional parameters at the end of the packet.
+     */
+    abstract void finishPacket(ByteBuffer buffer);
+
+    protected DhcpPacket(int transId, InetAddress clientIp, InetAddress yourIp,
+                         InetAddress nextIp, InetAddress relayIp,
+                         byte[] clientMac, boolean broadcast) {
+        mTransId = transId;
+        mClientIp = clientIp;
+        mYourIp = yourIp;
+        mNextIp = nextIp;
+        mRelayIp = relayIp;
+        mClientMac = clientMac;
+        mBroadcast = broadcast;
+    }
+
+    /**
+     * Returns the transaction ID.
+     */
+    public int getTransactionId() {
+        return mTransId;
+    }
+
+    /**
+     * Creates a new L3 packet (including IP header) containing the
+     * DHCP udp packet.  This method relies upon the delegated method
+     * finishPacket() to insert the per-packet contents.
+     */
+    protected void fillInPacket(int encap, InetAddress destIp,
+        InetAddress srcIp, short destUdp, short srcUdp, ByteBuffer buf,
+        byte requestCode, boolean broadcast) {
+        byte[] destIpArray = destIp.getAddress();
+        byte[] srcIpArray = srcIp.getAddress();
+        int ipLengthOffset = 0;
+        int ipChecksumOffset = 0;
+        int endIpHeader = 0;
+        int udpHeaderOffset = 0;
+        int udpLengthOffset = 0;
+        int udpChecksumOffset = 0;
+
+        buf.clear();
+        buf.order(ByteOrder.BIG_ENDIAN);
+
+        // if a full IP packet needs to be generated, put the IP & UDP
+        // headers in place, and pre-populate with artificial values
+        // needed to seed the IP checksum.
+        if (encap == ENCAP_L3) {
+            // fake IP header, used in the IP-header checksum
+            buf.put(IP_VERSION_HEADER_LEN);
+            buf.put(IP_TOS_LOWDELAY);    // tos: IPTOS_LOWDELAY
+            ipLengthOffset = buf.position();
+            buf.putShort((short)0);  // length
+            buf.putShort((short)0);  // id
+            buf.putShort(IP_FLAGS_OFFSET); // ip offset: don't fragment
+            buf.put(IP_TTL);    // TTL: use default 64 from RFC1340
+            buf.put(IP_TYPE_UDP);
+            ipChecksumOffset = buf.position();
+            buf.putShort((short) 0); // checksum
+
+            buf.put(srcIpArray);
+            buf.put(destIpArray);
+            endIpHeader = buf.position();
+
+            // UDP header
+            udpHeaderOffset = buf.position();
+            buf.putShort(srcUdp);
+            buf.putShort(destUdp);
+            udpLengthOffset = buf.position();
+            buf.putShort((short) 0); // length
+            udpChecksumOffset = buf.position();
+            buf.putShort((short) 0); // UDP checksum -- initially zero
+        }
+
+        // DHCP payload
+        buf.put(requestCode);
+        buf.put((byte) 1); // Hardware Type: Ethernet
+        buf.put((byte) mClientMac.length); // Hardware Address Length
+        buf.put((byte) 0); // Hop Count
+        buf.putInt(mTransId);  // Transaction ID
+        buf.putShort((short) 0); // Elapsed Seconds
+
+        if (broadcast) {
+            buf.putShort((short) 0x8000); // Flags
+        } else {
+            buf.putShort((short) 0x0000); // Flags
+        }
+
+        buf.put(mClientIp.getAddress());
+        buf.put(mYourIp.getAddress());
+        buf.put(mNextIp.getAddress());
+        buf.put(mRelayIp.getAddress());
+        buf.put(mClientMac);
+        buf.position(buf.position() +
+                     (16 - mClientMac.length) // pad addr to 16 bytes
+                     + 64     // empty server host name (64 bytes)
+                     + 128);  // empty boot file name (128 bytes)
+        buf.putInt(0x63825363); // magic number
+        finishPacket(buf);
+
+        // round up to an even number of octets
+        if ((buf.position() & 1) == 1) {
+            buf.put((byte) 0);
+        }
+
+        // If an IP packet is being built, the IP & UDP checksums must be
+        // computed.
+        if (encap == ENCAP_L3) {
+            // fix UDP header: insert length
+            short udpLen = (short)(buf.position() - udpHeaderOffset);
+            buf.putShort(udpLengthOffset, udpLen);
+            // fix UDP header: checksum
+            // checksum for UDP at udpChecksumOffset
+            int udpSeed = 0;
+
+            // apply IPv4 pseudo-header.  Read IP address src and destination
+            // values from the IP header and accumulate checksum.
+            udpSeed += intAbs(buf.getShort(ipChecksumOffset + 2));
+            udpSeed += intAbs(buf.getShort(ipChecksumOffset + 4));
+            udpSeed += intAbs(buf.getShort(ipChecksumOffset + 6));
+            udpSeed += intAbs(buf.getShort(ipChecksumOffset + 8));
+
+            // accumulate extra data for the pseudo-header
+            udpSeed += IP_TYPE_UDP;
+            udpSeed += udpLen;
+            // and compute UDP checksum
+            buf.putShort(udpChecksumOffset, (short) checksum(buf, udpSeed,
+                                                             udpHeaderOffset,
+                                                             buf.position()));
+            // fix IP header: insert length
+            buf.putShort(ipLengthOffset, (short)buf.position());
+            // fixup IP-header checksum
+            buf.putShort(ipChecksumOffset,
+                         (short) checksum(buf, 0, 0, endIpHeader));
+        }
+    }
+
+    /**
+     * Converts a signed short value to an unsigned int value.  Needed
+     * because Java does not have unsigned types.
+     */
+    private int intAbs(short v) {
+        if (v < 0) {
+            int r = v + 65536;
+            return r;
+        } else {
+            return(v);
+        }
+    }
+
+    /**
+     * Performs an IP checksum (used in IP header and across UDP
+     * payload) on the specified portion of a ByteBuffer.  The seed
+     * allows the checksum to commence with a specified value.
+     */
+    private int checksum(ByteBuffer buf, int seed, int start, int end) {
+        int sum = seed;
+        int bufPosition = buf.position();
+
+        // set position of original ByteBuffer, so that the ShortBuffer
+        // will be correctly initialized
+        buf.position(start);
+        ShortBuffer shortBuf = buf.asShortBuffer();
+
+        // re-set ByteBuffer position
+        buf.position(bufPosition);
+
+        short[] shortArray = new short[(end - start) / 2];
+        shortBuf.get(shortArray);
+
+        for (short s : shortArray) {
+            sum += intAbs(s);
+        }
+
+        start += shortArray.length * 2;
+
+        // see if a singleton byte remains
+        if (end != start) {
+            short b = buf.get(start);
+
+            // make it unsigned
+            if (b < 0) {
+                b += 256;
+            }
+
+            sum += b * 256;
+        }
+
+        sum = ((sum >> 16) & 0xFFFF) + (sum & 0xFFFF);
+        sum = ((sum + ((sum >> 16) & 0xFFFF)) & 0xFFFF);
+        int negated = ~sum;
+        return intAbs((short) negated);
+    }
+
+    /**
+     * Adds an optional parameter containing a single byte value.
+     */
+    protected void addTlv(ByteBuffer buf, byte type, byte value) {
+        buf.put(type);
+        buf.put((byte) 1);
+        buf.put(value);
+    }
+
+    /**
+     * Adds an optional parameter containing an array of bytes.
+     */
+    protected void addTlv(ByteBuffer buf, byte type, byte[] payload) {
+        if (payload != null) {
+            buf.put(type);
+            buf.put((byte) payload.length);
+            buf.put(payload);
+        }
+    }
+
+    /**
+     * Adds an optional parameter containing an IP address.
+     */
+    protected void addTlv(ByteBuffer buf, byte type, InetAddress addr) {
+        if (addr != null) {
+            addTlv(buf, type, addr.getAddress());
+        }
+    }
+
+    /**
+     * Adds an optional parameter containing a list of IP addresses.
+     */
+    protected void addTlv(ByteBuffer buf, byte type, List<InetAddress> addrs) {
+        if (addrs != null && addrs.size() > 0) {
+            buf.put(type);
+            buf.put((byte)(4 * addrs.size()));
+
+            for (InetAddress addr : addrs) {
+                buf.put(addr.getAddress());
+            }
+        }
+    }
+
+    /**
+     * Adds an optional parameter containing a simple integer
+     */
+    protected void addTlv(ByteBuffer buf, byte type, Integer value) {
+        if (value != null) {
+            buf.put(type);
+            buf.put((byte) 4);
+            buf.putInt(value.intValue());
+        }
+    }
+
+    /**
+     * Adds an optional parameter containing and ASCII string.
+     */
+    protected void addTlv(ByteBuffer buf, byte type, String str) {
+        if (str != null) {
+            buf.put(type);
+            buf.put((byte) str.length());
+
+            for (int i = 0; i < str.length(); i++) {
+                buf.put((byte) str.charAt(i));
+            }
+        }
+    }
+
+    /**
+     * Adds the special end-of-optional-parameters indicator.
+     */
+    protected void addTlvEnd(ByteBuffer buf) {
+        buf.put((byte) 0xFF);
+    }
+
+    /**
+     * Converts a MAC from an array of octets to an ASCII string.
+     */
+    public static String macToString(byte[] mac) {
+        String macAddr = "";
+
+        for (int i = 0; i < mac.length; i++) {
+            String hexString = "0" + Integer.toHexString(mac[i]);
+
+            // substring operation grabs the last 2 digits: this
+            // allows signed bytes to be converted correctly.
+            macAddr += hexString.substring(hexString.length() - 2);
+
+            if (i != (mac.length - 1)) {
+                macAddr += ":";
+            }
+        }
+
+        return macAddr;
+    }
+
+    public String toString() {
+        String macAddr = macToString(mClientMac);
+
+        return macAddr;
+    }
+
+    /**
+     * Reads a four-octet value from a ByteBuffer and construct
+     * an IPv4 address from that value.
+     */
+    private static InetAddress readIpAddress(ByteBuffer packet) {
+        InetAddress result = null;
+        byte[] ipAddr = new byte[4];
+        packet.get(ipAddr);
+
+        try {
+            result = InetAddress.getByAddress(ipAddr);
+        } catch (UnknownHostException ex) {
+            // ipAddr is numeric, so this should not be
+            // triggered.  However, if it is, just nullify
+            result = null;
+        }
+
+        return result;
+    }
+
+    /**
+     * Reads a string of specified length from the buffer.
+     */
+    private static String readAsciiString(ByteBuffer buf, int byteCount) {
+        byte[] bytes = new byte[byteCount];
+        buf.get(bytes);
+        return new String(bytes, 0, bytes.length, Charsets.US_ASCII);
+    }
+
+    /**
+     * Creates a concrete DhcpPacket from the supplied ByteBuffer.  The
+     * buffer may have an L2 encapsulation (which is the full EthernetII
+     * format starting with the source-address MAC) or an L3 encapsulation
+     * (which starts with the IP header).
+     * <br>
+     * A subset of the optional parameters are parsed and are stored
+     * in object fields.
+     */
+    public static DhcpPacket decodeFullPacket(ByteBuffer packet, int pktType)
+    {
+        // bootp parameters
+        int transactionId;
+        InetAddress clientIp;
+        InetAddress yourIp;
+        InetAddress nextIp;
+        InetAddress relayIp;
+        byte[] clientMac;
+        List<InetAddress> dnsServers = null;
+        InetAddress gateway = null; // aka router
+        Integer leaseTime = null;
+        InetAddress serverIdentifier = null;
+        InetAddress netMask = null;
+        String message = null;
+        String vendorId = null;
+        byte[] expectedParams = null;
+        String hostName = null;
+        String domainName = null;
+        InetAddress ipSrc = null;
+        InetAddress ipDst = null;
+        InetAddress bcAddr = null;
+        InetAddress requestedIp = null;
+
+        // dhcp options
+        byte dhcpType = (byte) 0xFF;
+
+        packet.order(ByteOrder.BIG_ENDIAN);
+
+        // check to see if we need to parse L2, IP, and UDP encaps
+        if (pktType == ENCAP_L2) {
+            // System.out.println("buffer len " + packet.limit());
+            byte[] l2dst = new byte[6];
+            byte[] l2src = new byte[6];
+
+            packet.get(l2dst);
+            packet.get(l2src);
+
+            short l2type = packet.getShort();
+
+            if (l2type != 0x0800)
+                return null;
+        }
+
+        if ((pktType == ENCAP_L2) || (pktType == ENCAP_L3)) {
+            // assume l2type is 0x0800, i.e. IP
+            byte ipType = packet.get();
+            // System.out.println("ipType is " + ipType);
+            byte ipDiffServicesField = packet.get();
+            short ipTotalLength = packet.getShort();
+            short ipIdentification = packet.getShort();
+            byte ipFlags = packet.get();
+            byte ipFragOffset = packet.get();
+            byte ipTTL = packet.get();
+            byte ipProto = packet.get();
+            short ipChksm = packet.getShort();
+
+            ipSrc = readIpAddress(packet);
+            ipDst = readIpAddress(packet);
+
+            if (ipProto != IP_TYPE_UDP) // UDP
+                return null;
+
+            // assume UDP
+            short udpSrcPort = packet.getShort();
+            short udpDstPort = packet.getShort();
+            short udpLen = packet.getShort();
+            short udpChkSum = packet.getShort();
+
+            if ((udpSrcPort != DHCP_SERVER) && (udpSrcPort != DHCP_CLIENT))
+                return null;
+        }
+
+        // assume bootp
+        byte type = packet.get();
+        byte hwType = packet.get();
+        byte addrLen = packet.get();
+        byte hops = packet.get();
+        transactionId = packet.getInt();
+        short elapsed = packet.getShort();
+        short bootpFlags = packet.getShort();
+        boolean broadcast = (bootpFlags & 0x8000) != 0;
+        byte[] ipv4addr = new byte[4];
+
+        try {
+            packet.get(ipv4addr);
+            clientIp = InetAddress.getByAddress(ipv4addr);
+            packet.get(ipv4addr);
+            yourIp = InetAddress.getByAddress(ipv4addr);
+            packet.get(ipv4addr);
+            nextIp = InetAddress.getByAddress(ipv4addr);
+            packet.get(ipv4addr);
+            relayIp = InetAddress.getByAddress(ipv4addr);
+        } catch (UnknownHostException ex) {
+            return null;
+        }
+
+        clientMac = new byte[addrLen];
+        packet.get(clientMac);
+
+        // skip over address padding (16 octets allocated)
+        packet.position(packet.position() + (16 - addrLen)
+                        + 64    // skip server host name (64 chars)
+                        + 128); // skip boot file name (128 chars)
+
+        int dhcpMagicCookie = packet.getInt();
+
+        if (dhcpMagicCookie !=  0x63825363)
+            return null;
+
+        // parse options
+        boolean notFinishedOptions = true;
+
+        while ((packet.position() < packet.limit()) && notFinishedOptions) {
+            byte optionType = packet.get();
+
+            if (optionType == (byte) 0xFF) {
+                notFinishedOptions = false;
+            } else {
+                byte optionLen = packet.get();
+                int expectedLen = 0;
+
+                switch(optionType) {
+                    case DHCP_SUBNET_MASK:
+                        netMask = readIpAddress(packet);
+                        expectedLen = 4;
+                        break;
+                    case DHCP_ROUTER:
+                        gateway = readIpAddress(packet);
+                        expectedLen = 4;
+                        break;
+                    case DHCP_DNS_SERVER:
+                        dnsServers = new ArrayList<InetAddress>();
+                        expectedLen = 0;
+
+                        for (expectedLen = 0; expectedLen < optionLen;
+                             expectedLen += 4) {
+                            dnsServers.add(readIpAddress(packet));
+                        }
+                        break;
+                    case DHCP_HOST_NAME:
+                        expectedLen = optionLen;
+                        hostName = readAsciiString(packet, optionLen);
+                        break;
+                    case DHCP_DOMAIN_NAME:
+                        expectedLen = optionLen;
+                        domainName = readAsciiString(packet, optionLen);
+                        break;
+                    case DHCP_BROADCAST_ADDRESS:
+                        bcAddr = readIpAddress(packet);
+                        expectedLen = 4;
+                        break;
+                    case DHCP_REQUESTED_IP:
+                        requestedIp = readIpAddress(packet);
+                        expectedLen = 4;
+                        break;
+                    case DHCP_LEASE_TIME:
+                        leaseTime = Integer.valueOf(packet.getInt());
+                        expectedLen = 4;
+                        break;
+                    case DHCP_MESSAGE_TYPE:
+                        dhcpType = packet.get();
+                        expectedLen = 1;
+                        break;
+                    case DHCP_SERVER_IDENTIFIER:
+                        serverIdentifier = readIpAddress(packet);
+                        expectedLen = 4;
+                        break;
+                    case DHCP_PARAMETER_LIST:
+                        expectedParams = new byte[optionLen];
+                        packet.get(expectedParams);
+                        expectedLen = optionLen;
+                        break;
+                    case DHCP_MESSAGE:
+                        expectedLen = optionLen;
+                        message = readAsciiString(packet, optionLen);
+                        break;
+                    case DHCP_VENDOR_CLASS_ID:
+                        expectedLen = optionLen;
+                        vendorId = readAsciiString(packet, optionLen);
+                        break;
+                    case DHCP_CLIENT_IDENTIFIER: { // Client identifier
+                        byte[] id = new byte[optionLen];
+                        packet.get(id);
+                        expectedLen = optionLen;
+                    } break;
+                    default:
+                        // ignore any other parameters
+                        for (int i = 0; i < optionLen; i++) {
+                            expectedLen++;
+                            byte throwaway = packet.get();
+                        }
+                }
+
+                if (expectedLen != optionLen) {
+                    return null;
+                }
+            }
+        }
+
+        DhcpPacket newPacket;
+
+        switch(dhcpType) {
+            case -1: return null;
+            case DHCP_MESSAGE_TYPE_DISCOVER:
+                newPacket = new DhcpDiscoverPacket(
+                    transactionId, clientMac, broadcast);
+                break;
+            case DHCP_MESSAGE_TYPE_OFFER:
+                newPacket = new DhcpOfferPacket(
+                    transactionId, broadcast, ipSrc, yourIp, clientMac);
+                break;
+            case DHCP_MESSAGE_TYPE_REQUEST:
+                newPacket = new DhcpRequestPacket(
+                    transactionId, clientIp, clientMac, broadcast);
+                break;
+            case DHCP_MESSAGE_TYPE_DECLINE:
+                newPacket = new DhcpDeclinePacket(
+                    transactionId, clientIp, yourIp, nextIp, relayIp,
+                    clientMac);
+                break;
+            case DHCP_MESSAGE_TYPE_ACK:
+                newPacket = new DhcpAckPacket(
+                    transactionId, broadcast, ipSrc, yourIp, clientMac);
+                break;
+            case DHCP_MESSAGE_TYPE_NAK:
+                newPacket = new DhcpNakPacket(
+                    transactionId, clientIp, yourIp, nextIp, relayIp,
+                    clientMac);
+                break;
+            case DHCP_MESSAGE_TYPE_INFORM:
+                newPacket = new DhcpInformPacket(
+                    transactionId, clientIp, yourIp, nextIp, relayIp,
+                    clientMac);
+                break;
+            default:
+                System.out.println("Unimplemented type: " + dhcpType);
+                return null;
+        }
+
+        newPacket.mBroadcastAddress = bcAddr;
+        newPacket.mDnsServers = dnsServers;
+        newPacket.mDomainName = domainName;
+        newPacket.mGateway = gateway;
+        newPacket.mHostName = hostName;
+        newPacket.mLeaseTime = leaseTime;
+        newPacket.mMessage = message;
+        newPacket.mRequestedIp = requestedIp;
+        newPacket.mRequestedParams = expectedParams;
+        newPacket.mServerIdentifier = serverIdentifier;
+        newPacket.mSubnetMask = netMask;
+        return newPacket;
+    }
+
+    /**
+     * Parse a packet from an array of bytes.
+     */
+    public static DhcpPacket decodeFullPacket(byte[] packet, int pktType)
+    {
+        ByteBuffer buffer = ByteBuffer.wrap(packet).order(ByteOrder.BIG_ENDIAN);
+        return decodeFullPacket(buffer, pktType);
+    }
+
+    /**
+     * Builds a DHCP-DISCOVER packet from the required specified
+     * parameters.
+     */
+    public static ByteBuffer buildDiscoverPacket(int encap, int transactionId,
+        byte[] clientMac, boolean broadcast, byte[] expectedParams) {
+        DhcpPacket pkt = new DhcpDiscoverPacket(
+            transactionId, clientMac, broadcast);
+        pkt.mRequestedParams = expectedParams;
+        return pkt.buildPacket(encap, DHCP_SERVER, DHCP_CLIENT);
+    }
+
+    /**
+     * Builds a DHCP-OFFER packet from the required specified
+     * parameters.
+     */
+    public static ByteBuffer buildOfferPacket(int encap, int transactionId,
+        boolean broadcast, InetAddress serverIpAddr, InetAddress clientIpAddr,
+        byte[] mac, Integer timeout, InetAddress netMask, InetAddress bcAddr,
+        InetAddress gateway, List<InetAddress> dnsServers,
+        InetAddress dhcpServerIdentifier, String domainName) {
+        DhcpPacket pkt = new DhcpOfferPacket(
+            transactionId, broadcast, serverIpAddr, clientIpAddr, mac);
+        pkt.mGateway = gateway;
+        pkt.mDnsServers = dnsServers;
+        pkt.mLeaseTime = timeout;
+        pkt.mDomainName = domainName;
+        pkt.mServerIdentifier = dhcpServerIdentifier;
+        pkt.mSubnetMask = netMask;
+        pkt.mBroadcastAddress = bcAddr;
+        return pkt.buildPacket(encap, DHCP_CLIENT, DHCP_SERVER);
+    }
+
+    /**
+     * Builds a DHCP-ACK packet from the required specified parameters.
+     */
+    public static ByteBuffer buildAckPacket(int encap, int transactionId,
+        boolean broadcast, InetAddress serverIpAddr, InetAddress clientIpAddr,
+        byte[] mac, Integer timeout, InetAddress netMask, InetAddress bcAddr,
+        InetAddress gateway, List<InetAddress> dnsServers,
+        InetAddress dhcpServerIdentifier, String domainName) {
+        DhcpPacket pkt = new DhcpAckPacket(
+            transactionId, broadcast, serverIpAddr, clientIpAddr, mac);
+        pkt.mGateway = gateway;
+        pkt.mDnsServers = dnsServers;
+        pkt.mLeaseTime = timeout;
+        pkt.mDomainName = domainName;
+        pkt.mSubnetMask = netMask;
+        pkt.mServerIdentifier = dhcpServerIdentifier;
+        pkt.mBroadcastAddress = bcAddr;
+        return pkt.buildPacket(encap, DHCP_CLIENT, DHCP_SERVER);
+    }
+
+    /**
+     * Builds a DHCP-NAK packet from the required specified parameters.
+     */
+    public static ByteBuffer buildNakPacket(int encap, int transactionId,
+        InetAddress serverIpAddr, InetAddress clientIpAddr, byte[] mac) {
+        DhcpPacket pkt = new DhcpNakPacket(transactionId, clientIpAddr,
+            serverIpAddr, serverIpAddr, serverIpAddr, mac);
+        pkt.mMessage = "requested address not available";
+        pkt.mRequestedIp = clientIpAddr;
+        return pkt.buildPacket(encap, DHCP_CLIENT, DHCP_SERVER);
+    }
+
+    /**
+     * Builds a DHCP-REQUEST packet from the required specified parameters.
+     */
+    public static ByteBuffer buildRequestPacket(int encap,
+        int transactionId, InetAddress clientIp, boolean broadcast,
+        byte[] clientMac, InetAddress requestedIpAddress,
+        InetAddress serverIdentifier, byte[] requestedParams, String hostName) {
+        DhcpPacket pkt = new DhcpRequestPacket(transactionId, clientIp,
+            clientMac, broadcast);
+        pkt.mRequestedIp = requestedIpAddress;
+        pkt.mServerIdentifier = serverIdentifier;
+        pkt.mHostName = hostName;
+        pkt.mRequestedParams = requestedParams;
+        ByteBuffer result = pkt.buildPacket(encap, DHCP_SERVER, DHCP_CLIENT);
+        return result;
+    }
+}
diff --git a/core/java/android/net/dhcp/DhcpRequestPacket.java b/core/java/android/net/dhcp/DhcpRequestPacket.java
new file mode 100644
index 0000000..cf32957
--- /dev/null
+++ b/core/java/android/net/dhcp/DhcpRequestPacket.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.dhcp;
+
+import android.util.Log;
+
+import java.net.InetAddress;
+import java.net.Inet4Address;
+import java.nio.ByteBuffer;
+
+/**
+ * This class implements the DHCP-REQUEST packet.
+ */
+class DhcpRequestPacket extends DhcpPacket {
+    /**
+     * Generates a REQUEST packet with the specified parameters.
+     */
+    DhcpRequestPacket(int transId, InetAddress clientIp, byte[] clientMac,
+                      boolean broadcast) {
+        super(transId, clientIp, Inet4Address.ANY, Inet4Address.ANY,
+          Inet4Address.ANY, clientMac, broadcast);
+    }
+
+    public String toString() {
+        String s = super.toString();
+        return s + " REQUEST, desired IP " + mRequestedIp + " from host '"
+            + mHostName + "', param list length "
+            + (mRequestedParams == null ? 0 : mRequestedParams.length);
+    }
+
+    /**
+     * Fills in a packet with the requested REQUEST attributes.
+     */
+    public ByteBuffer buildPacket(int encap, short destUdp, short srcUdp) {
+        ByteBuffer result = ByteBuffer.allocate(MAX_LENGTH);
+
+        fillInPacket(encap, Inet4Address.ALL, Inet4Address.ANY, destUdp, srcUdp,
+            result, DHCP_BOOTREQUEST, mBroadcast);
+        result.flip();
+        return result;
+    }
+
+    /**
+     * Adds the optional parameters to the client-generated REQUEST packet.
+     */
+    void finishPacket(ByteBuffer buffer) {
+        byte[] clientId = new byte[7];
+
+        // assemble client identifier
+        clientId[0] = CLIENT_ID_ETHER;
+        System.arraycopy(mClientMac, 0, clientId, 1, 6);
+
+        addTlv(buffer, DHCP_MESSAGE_TYPE, DHCP_MESSAGE_TYPE_REQUEST);
+        addTlv(buffer, DHCP_PARAMETER_LIST, mRequestedParams);
+        addTlv(buffer, DHCP_REQUESTED_IP, mRequestedIp);
+        addTlv(buffer, DHCP_SERVER_IDENTIFIER, mServerIdentifier);
+        addTlv(buffer, DHCP_CLIENT_IDENTIFIER, clientId);
+        addTlvEnd(buffer);
+    }
+
+    /**
+     * Notifies the specified state machine of the REQUEST packet parameters.
+     */
+    public void doNextOp(DhcpStateMachine machine) {
+        InetAddress clientRequest =
+            mRequestedIp == null ? mClientIp : mRequestedIp;
+        Log.v(TAG, "requested IP is " + mRequestedIp + " and client IP is " +
+            mClientIp);
+        machine.onRequestReceived(mBroadcast, mTransId, mClientMac,
+            clientRequest, mRequestedParams, mHostName);
+    }
+}
diff --git a/core/java/android/net/dhcp/DhcpStateMachine.java b/core/java/android/net/dhcp/DhcpStateMachine.java
new file mode 100644
index 0000000..b6c384d
--- /dev/null
+++ b/core/java/android/net/dhcp/DhcpStateMachine.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.dhcp;
+
+import java.net.InetAddress;
+import java.nio.ByteBuffer;
+import java.util.List;
+
+/**
+ * This class defines the "next steps" which occur after a given DHCP
+ * packet has been received.
+ */
+interface DhcpStateMachine {
+    /**
+     * Signals that an offer packet has been received with the specified
+     * parameters.
+     */
+    public void onOfferReceived(boolean broadcast, int transactionId,
+        byte[] myMac, InetAddress offeredIpAddress,
+        InetAddress serverIpAddress);
+
+    /**
+     * Signals that a NAK packet has been received.
+     */
+    public void onNakReceived();
+
+    /**
+     * Signals that the final ACK has been received from the server.
+     */
+    public void onAckReceived(InetAddress myIpAddress, InetAddress myNetMask,
+        InetAddress myGateway, List<InetAddress> myDnsServers,
+        InetAddress myDhcpServer, int leaseTime);
+
+    /**
+     * Signals that a client's DISCOVER packet has been received with the
+     * specified parameters.
+     */
+    public void onDiscoverReceived(boolean broadcast, int transactionId,
+        byte[] clientMac, byte[] requestedParameterList);
+
+    /**
+     * Signals that a client's REQUEST packet has been received with the
+     * specified parameters.
+     */
+    public void onRequestReceived(boolean broadcast, int transactionId,
+        byte[] clientMac, InetAddress requestedIp, byte[] requestedParams,
+        String clientHostName);
+
+    /**
+     * Signals that a client's INFORM packet has been received with the
+     * specified parameters.
+     */
+    public void onInformReceived(int transactionId, byte[] clientMac,
+        InetAddress preassignedIp, byte[] requestedParams);
+
+    /**
+     * Signals that a client's DECLINE packet has been received with the
+     * specified parameters.
+     */
+    public void onDeclineReceived(byte[] clientMac, InetAddress declinedIp);
+}