Home | History | Annotate | Download | only in dhcp
      1 /*
      2  * Copyright (C) 2015 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package android.net.dhcp;
     18 
     19 import android.net.DhcpResults;
     20 import android.net.LinkAddress;
     21 import android.net.NetworkUtils;
     22 import android.net.metrics.DhcpErrorEvent;
     23 import android.system.OsConstants;
     24 import android.test.suitebuilder.annotation.SmallTest;
     25 import com.android.internal.util.HexDump;
     26 import java.net.Inet4Address;
     27 import java.nio.ByteBuffer;
     28 import java.util.ArrayList;
     29 import java.util.Arrays;
     30 import java.util.Random;
     31 import junit.framework.TestCase;
     32 
     33 import static android.net.dhcp.DhcpPacket.*;
     34 
     35 public class DhcpPacketTest extends TestCase {
     36 
     37     private static Inet4Address SERVER_ADDR = v4Address("192.0.2.1");
     38     private static Inet4Address CLIENT_ADDR = v4Address("192.0.2.234");
     39     // Use our own empty address instead of Inet4Address.ANY or INADDR_ANY to ensure that the code
     40     // doesn't use == instead of equals when comparing addresses.
     41     private static Inet4Address ANY = (Inet4Address) v4Address("0.0.0.0");
     42 
     43     private static byte[] CLIENT_MAC = new byte[] { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05 };
     44 
     45     private static final Inet4Address v4Address(String addrString) throws IllegalArgumentException {
     46         return (Inet4Address) NetworkUtils.numericToInetAddress(addrString);
     47     }
     48 
     49     public void setUp() {
     50         DhcpPacket.testOverrideVendorId = "android-dhcp-???";
     51         DhcpPacket.testOverrideHostname = "android-01234567890abcde";
     52     }
     53 
     54     class TestDhcpPacket extends DhcpPacket {
     55         private byte mType;
     56         // TODO: Make this a map of option numbers to bytes instead.
     57         private byte[] mDomainBytes, mVendorInfoBytes, mLeaseTimeBytes, mNetmaskBytes;
     58 
     59         public TestDhcpPacket(byte type, Inet4Address clientIp, Inet4Address yourIp) {
     60             super(0xdeadbeef, (short) 0, clientIp, yourIp, INADDR_ANY, INADDR_ANY,
     61                   CLIENT_MAC, true);
     62             mType = type;
     63         }
     64 
     65         public TestDhcpPacket(byte type) {
     66             this(type, INADDR_ANY, CLIENT_ADDR);
     67         }
     68 
     69         public TestDhcpPacket setDomainBytes(byte[] domainBytes) {
     70             mDomainBytes = domainBytes;
     71             return this;
     72         }
     73 
     74         public TestDhcpPacket setVendorInfoBytes(byte[] vendorInfoBytes) {
     75             mVendorInfoBytes = vendorInfoBytes;
     76             return this;
     77         }
     78 
     79         public TestDhcpPacket setLeaseTimeBytes(byte[] leaseTimeBytes) {
     80             mLeaseTimeBytes = leaseTimeBytes;
     81             return this;
     82         }
     83 
     84         public TestDhcpPacket setNetmaskBytes(byte[] netmaskBytes) {
     85             mNetmaskBytes = netmaskBytes;
     86             return this;
     87         }
     88 
     89         public ByteBuffer buildPacket(int encap, short unusedDestUdp, short unusedSrcUdp) {
     90             ByteBuffer result = ByteBuffer.allocate(MAX_LENGTH);
     91             fillInPacket(encap, CLIENT_ADDR, SERVER_ADDR,
     92                          DHCP_CLIENT, DHCP_SERVER, result, DHCP_BOOTREPLY, false);
     93             return result;
     94         }
     95 
     96         public void finishPacket(ByteBuffer buffer) {
     97             addTlv(buffer, DHCP_MESSAGE_TYPE, mType);
     98             if (mDomainBytes != null) {
     99                 addTlv(buffer, DHCP_DOMAIN_NAME, mDomainBytes);
    100             }
    101             if (mVendorInfoBytes != null) {
    102                 addTlv(buffer, DHCP_VENDOR_INFO, mVendorInfoBytes);
    103             }
    104             if (mLeaseTimeBytes != null) {
    105                 addTlv(buffer, DHCP_LEASE_TIME, mLeaseTimeBytes);
    106             }
    107             if (mNetmaskBytes != null) {
    108                 addTlv(buffer, DHCP_SUBNET_MASK, mNetmaskBytes);
    109             }
    110             addTlvEnd(buffer);
    111         }
    112 
    113         // Convenience method.
    114         public ByteBuffer build() {
    115             // ENCAP_BOOTP packets don't contain ports, so just pass in 0.
    116             ByteBuffer pkt = buildPacket(ENCAP_BOOTP, (short) 0, (short) 0);
    117             pkt.flip();
    118             return pkt;
    119         }
    120     }
    121 
    122     private void assertDomainAndVendorInfoParses(
    123             String expectedDomain, byte[] domainBytes,
    124             String expectedVendorInfo, byte[] vendorInfoBytes) throws Exception {
    125         ByteBuffer packet = new TestDhcpPacket(DHCP_MESSAGE_TYPE_OFFER)
    126                 .setDomainBytes(domainBytes)
    127                 .setVendorInfoBytes(vendorInfoBytes)
    128                 .build();
    129         DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_BOOTP);
    130         assertEquals(expectedDomain, offerPacket.mDomainName);
    131         assertEquals(expectedVendorInfo, offerPacket.mVendorInfo);
    132     }
    133 
    134     @SmallTest
    135     public void testDomainName() throws Exception {
    136         byte[] nullByte = new byte[] { 0x00 };
    137         byte[] twoNullBytes = new byte[] { 0x00, 0x00 };
    138         byte[] nonNullDomain = new byte[] {
    139             (byte) 'g', (byte) 'o', (byte) 'o', (byte) '.', (byte) 'g', (byte) 'l'
    140         };
    141         byte[] trailingNullDomain = new byte[] {
    142             (byte) 'g', (byte) 'o', (byte) 'o', (byte) '.', (byte) 'g', (byte) 'l', 0x00
    143         };
    144         byte[] embeddedNullsDomain = new byte[] {
    145             (byte) 'g', (byte) 'o', (byte) 'o', 0x00, 0x00, (byte) 'g', (byte) 'l'
    146         };
    147         byte[] metered = "ANDROID_METERED".getBytes("US-ASCII");
    148 
    149         byte[] meteredEmbeddedNull = metered.clone();
    150         meteredEmbeddedNull[7] = (char) 0;
    151 
    152         byte[] meteredTrailingNull = metered.clone();
    153         meteredTrailingNull[meteredTrailingNull.length - 1] = (char) 0;
    154 
    155         assertDomainAndVendorInfoParses("", nullByte, "\u0000", nullByte);
    156         assertDomainAndVendorInfoParses("", twoNullBytes, "\u0000\u0000", twoNullBytes);
    157         assertDomainAndVendorInfoParses("goo.gl", nonNullDomain, "ANDROID_METERED", metered);
    158         assertDomainAndVendorInfoParses("goo", embeddedNullsDomain,
    159                                         "ANDROID\u0000METERED", meteredEmbeddedNull);
    160         assertDomainAndVendorInfoParses("goo.gl", trailingNullDomain,
    161                                         "ANDROID_METERE\u0000", meteredTrailingNull);
    162     }
    163 
    164     private void assertLeaseTimeParses(boolean expectValid, Integer rawLeaseTime,
    165             long leaseTimeMillis, byte[] leaseTimeBytes) throws Exception {
    166         TestDhcpPacket testPacket = new TestDhcpPacket(DHCP_MESSAGE_TYPE_OFFER);
    167         if (leaseTimeBytes != null) {
    168             testPacket.setLeaseTimeBytes(leaseTimeBytes);
    169         }
    170         ByteBuffer packet = testPacket.build();
    171         DhcpPacket offerPacket = null;
    172 
    173         if (!expectValid) {
    174             try {
    175                 offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_BOOTP);
    176                 fail("Invalid packet parsed successfully: " + offerPacket);
    177             } catch (ParseException expected) {
    178             }
    179             return;
    180         }
    181 
    182         offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_BOOTP);
    183         assertNotNull(offerPacket);
    184         assertEquals(rawLeaseTime, offerPacket.mLeaseTime);
    185         DhcpResults dhcpResults = offerPacket.toDhcpResults();  // Just check this doesn't crash.
    186         assertEquals(leaseTimeMillis, offerPacket.getLeaseTimeMillis());
    187     }
    188 
    189     @SmallTest
    190     public void testLeaseTime() throws Exception {
    191         byte[] noLease = null;
    192         byte[] tooShortLease = new byte[] { 0x00, 0x00 };
    193         byte[] tooLongLease = new byte[] { 0x00, 0x00, 0x00, 60, 0x01 };
    194         byte[] zeroLease = new byte[] { 0x00, 0x00, 0x00, 0x00 };
    195         byte[] tenSecondLease = new byte[] { 0x00, 0x00, 0x00, 10 };
    196         byte[] oneMinuteLease = new byte[] { 0x00, 0x00, 0x00, 60 };
    197         byte[] fiveMinuteLease = new byte[] { 0x00, 0x00, 0x01, 0x2c };
    198         byte[] oneDayLease = new byte[] { 0x00, 0x01, 0x51, (byte) 0x80 };
    199         byte[] maxIntPlusOneLease = new byte[] { (byte) 0x80, 0x00, 0x00, 0x01 };
    200         byte[] infiniteLease = new byte[] { (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff };
    201 
    202         assertLeaseTimeParses(true, null, 0, noLease);
    203         assertLeaseTimeParses(false, null, 0, tooShortLease);
    204         assertLeaseTimeParses(false, null, 0, tooLongLease);
    205         assertLeaseTimeParses(true, 0, 60 * 1000, zeroLease);
    206         assertLeaseTimeParses(true, 10, 60 * 1000, tenSecondLease);
    207         assertLeaseTimeParses(true, 60, 60 * 1000, oneMinuteLease);
    208         assertLeaseTimeParses(true, 300, 300 * 1000, fiveMinuteLease);
    209         assertLeaseTimeParses(true, 86400, 86400 * 1000, oneDayLease);
    210         assertLeaseTimeParses(true, -2147483647, 2147483649L * 1000, maxIntPlusOneLease);
    211         assertLeaseTimeParses(true, DhcpPacket.INFINITE_LEASE, 0, infiniteLease);
    212     }
    213 
    214     private void checkIpAddress(String expected, Inet4Address clientIp, Inet4Address yourIp,
    215                                 byte[] netmaskBytes) throws Exception {
    216         checkIpAddress(expected, DHCP_MESSAGE_TYPE_OFFER, clientIp, yourIp, netmaskBytes);
    217         checkIpAddress(expected, DHCP_MESSAGE_TYPE_ACK, clientIp, yourIp, netmaskBytes);
    218     }
    219 
    220     private void checkIpAddress(String expected, byte type,
    221                                 Inet4Address clientIp, Inet4Address yourIp,
    222                                 byte[] netmaskBytes) throws Exception {
    223         ByteBuffer packet = new TestDhcpPacket(type, clientIp, yourIp)
    224                 .setNetmaskBytes(netmaskBytes)
    225                 .build();
    226         DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_BOOTP);
    227         DhcpResults results = offerPacket.toDhcpResults();
    228 
    229         if (expected != null) {
    230             LinkAddress expectedAddress = new LinkAddress(expected);
    231             assertEquals(expectedAddress, results.ipAddress);
    232         } else {
    233             assertNull(results);
    234         }
    235     }
    236 
    237     @SmallTest
    238     public void testIpAddress() throws Exception {
    239         byte[] slash11Netmask = new byte[] { (byte) 0xff, (byte) 0xe0, 0x00, 0x00 };
    240         byte[] slash24Netmask = new byte[] { (byte) 0xff, (byte) 0xff, (byte) 0xff, 0x00 };
    241         byte[] invalidNetmask = new byte[] { (byte) 0xff, (byte) 0xfb, (byte) 0xff, 0x00 };
    242         Inet4Address example1 = v4Address("192.0.2.1");
    243         Inet4Address example2 = v4Address("192.0.2.43");
    244 
    245         // A packet without any addresses is not valid.
    246         checkIpAddress(null, ANY, ANY, slash24Netmask);
    247 
    248         // ClientIP is used iff YourIP is not present.
    249         checkIpAddress("192.0.2.1/24", example2, example1, slash24Netmask);
    250         checkIpAddress("192.0.2.43/11", example2, ANY, slash11Netmask);
    251         checkIpAddress("192.0.2.43/11", ANY, example2, slash11Netmask);
    252 
    253         // Invalid netmasks are ignored.
    254         checkIpAddress(null, example2, ANY, invalidNetmask);
    255 
    256         // If there is no netmask, implicit netmasks are used.
    257         checkIpAddress("192.0.2.43/24", ANY, example2, null);
    258     }
    259 
    260     private void assertDhcpResults(String ipAddress, String gateway, String dnsServersString,
    261             String domains, String serverAddress, String vendorInfo, int leaseDuration,
    262             boolean hasMeteredHint, int mtu, DhcpResults dhcpResults) throws Exception {
    263         assertEquals(new LinkAddress(ipAddress), dhcpResults.ipAddress);
    264         assertEquals(v4Address(gateway), dhcpResults.gateway);
    265 
    266         String[] dnsServerStrings = dnsServersString.split(",");
    267         ArrayList dnsServers = new ArrayList();
    268         for (String dnsServerString : dnsServerStrings) {
    269             dnsServers.add(v4Address(dnsServerString));
    270         }
    271         assertEquals(dnsServers, dhcpResults.dnsServers);
    272 
    273         assertEquals(domains, dhcpResults.domains);
    274         assertEquals(v4Address(serverAddress), dhcpResults.serverAddress);
    275         assertEquals(vendorInfo, dhcpResults.vendorInfo);
    276         assertEquals(leaseDuration, dhcpResults.leaseDuration);
    277         assertEquals(hasMeteredHint, dhcpResults.hasMeteredHint());
    278         assertEquals(mtu, dhcpResults.mtu);
    279     }
    280 
    281     @SmallTest
    282     public void testOffer1() throws Exception {
    283         // TODO: Turn all of these into golden files. This will probably require modifying
    284         // Android.mk appropriately, making this into an AndroidTestCase, and adding code to read
    285         // the golden files from the test APK's assets via mContext.getAssets().
    286         final ByteBuffer packet = ByteBuffer.wrap(HexDump.hexStringToByteArray(
    287             // IP header.
    288             "451001480000000080118849c0a89003c0a89ff7" +
    289             // UDP header.
    290             "004300440134dcfa" +
    291             // BOOTP header.
    292             "02010600c997a63b0000000000000000c0a89ff70000000000000000" +
    293             // MAC address.
    294             "30766ff2a90c00000000000000000000" +
    295             // Server name.
    296             "0000000000000000000000000000000000000000000000000000000000000000" +
    297             "0000000000000000000000000000000000000000000000000000000000000000" +
    298             // File.
    299             "0000000000000000000000000000000000000000000000000000000000000000" +
    300             "0000000000000000000000000000000000000000000000000000000000000000" +
    301             "0000000000000000000000000000000000000000000000000000000000000000" +
    302             "0000000000000000000000000000000000000000000000000000000000000000" +
    303             // Options
    304             "638253633501023604c0a89003330400001c200104fffff0000304c0a89ffe06080808080808080404" +
    305             "3a0400000e103b040000189cff00000000000000000000"));
    306 
    307         DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L3);
    308         assertTrue(offerPacket instanceof DhcpOfferPacket);  // Implicitly checks it's non-null.
    309         DhcpResults dhcpResults = offerPacket.toDhcpResults();
    310         assertDhcpResults("192.168.159.247/20", "192.168.159.254", "8.8.8.8,8.8.4.4",
    311                 null, "192.168.144.3", null, 7200, false, 0, dhcpResults);
    312     }
    313 
    314     @SmallTest
    315     public void testOffer2() throws Exception {
    316         final ByteBuffer packet = ByteBuffer.wrap(HexDump.hexStringToByteArray(
    317             // IP header.
    318             "450001518d0600004011144dc0a82b01c0a82bf7" +
    319             // UDP header.
    320             "00430044013d9ac7" +
    321             // BOOTP header.
    322             "02010600dfc23d1f0002000000000000c0a82bf7c0a82b0100000000" +
    323             // MAC address.
    324             "30766ff2a90c00000000000000000000" +
    325             // Server name.
    326             "0000000000000000000000000000000000000000000000000000000000000000" +
    327             "0000000000000000000000000000000000000000000000000000000000000000" +
    328             // File.
    329             "0000000000000000000000000000000000000000000000000000000000000000" +
    330             "0000000000000000000000000000000000000000000000000000000000000000" +
    331             "0000000000000000000000000000000000000000000000000000000000000000" +
    332             "0000000000000000000000000000000000000000000000000000000000000000" +
    333             // Options
    334             "638253633501023604c0a82b01330400000e103a04000007083b0400000c4e0104ffffff00" +
    335             "1c04c0a82bff0304c0a82b010604c0a82b012b0f414e44524f49445f4d455445524544ff"));
    336 
    337         assertEquals(337, packet.limit());
    338         DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L3);
    339         assertTrue(offerPacket instanceof DhcpOfferPacket);  // Implicitly checks it's non-null.
    340         DhcpResults dhcpResults = offerPacket.toDhcpResults();
    341         assertDhcpResults("192.168.43.247/24", "192.168.43.1", "192.168.43.1",
    342                 null, "192.168.43.1", "ANDROID_METERED", 3600, true, 0, dhcpResults);
    343         assertTrue(dhcpResults.hasMeteredHint());
    344     }
    345 
    346     @SmallTest
    347     public void testBadIpPacket() throws Exception {
    348         final byte[] packet = HexDump.hexStringToByteArray(
    349             // IP header.
    350             "450001518d0600004011144dc0a82b01c0a82bf7");
    351 
    352         try {
    353             DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, packet.length, ENCAP_L3);
    354         } catch (DhcpPacket.ParseException expected) {
    355             assertDhcpErrorCodes(DhcpErrorEvent.L3_TOO_SHORT, expected.errorCode);
    356             return;
    357         }
    358         fail("Dhcp packet parsing should have failed");
    359     }
    360 
    361     @SmallTest
    362     public void testBadDhcpPacket() throws Exception {
    363         final byte[] packet = HexDump.hexStringToByteArray(
    364             // IP header.
    365             "450001518d0600004011144dc0a82b01c0a82bf7" +
    366             // UDP header.
    367             "00430044013d9ac7" +
    368             // BOOTP header.
    369             "02010600dfc23d1f0002000000000000c0a82bf7c0a82b0100000000");
    370 
    371         try {
    372             DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, packet.length, ENCAP_L3);
    373         } catch (DhcpPacket.ParseException expected) {
    374             assertDhcpErrorCodes(DhcpErrorEvent.L3_TOO_SHORT, expected.errorCode);
    375             return;
    376         }
    377         fail("Dhcp packet parsing should have failed");
    378     }
    379 
    380     @SmallTest
    381     public void testBadTruncatedOffer() throws Exception {
    382         final byte[] packet = HexDump.hexStringToByteArray(
    383             // IP header.
    384             "450001518d0600004011144dc0a82b01c0a82bf7" +
    385             // UDP header.
    386             "00430044013d9ac7" +
    387             // BOOTP header.
    388             "02010600dfc23d1f0002000000000000c0a82bf7c0a82b0100000000" +
    389             // MAC address.
    390             "30766ff2a90c00000000000000000000" +
    391             // Server name.
    392             "0000000000000000000000000000000000000000000000000000000000000000" +
    393             "0000000000000000000000000000000000000000000000000000000000000000" +
    394             // File, missing one byte
    395             "0000000000000000000000000000000000000000000000000000000000000000" +
    396             "0000000000000000000000000000000000000000000000000000000000000000" +
    397             "0000000000000000000000000000000000000000000000000000000000000000" +
    398             "00000000000000000000000000000000000000000000000000000000000000");
    399 
    400         try {
    401             DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, packet.length, ENCAP_L3);
    402         } catch (DhcpPacket.ParseException expected) {
    403             assertDhcpErrorCodes(DhcpErrorEvent.L3_TOO_SHORT, expected.errorCode);
    404             return;
    405         }
    406         fail("Dhcp packet parsing should have failed");
    407     }
    408 
    409     @SmallTest
    410     public void testBadOfferWithoutACookie() throws Exception {
    411         final byte[] packet = HexDump.hexStringToByteArray(
    412             // IP header.
    413             "450001518d0600004011144dc0a82b01c0a82bf7" +
    414             // UDP header.
    415             "00430044013d9ac7" +
    416             // BOOTP header.
    417             "02010600dfc23d1f0002000000000000c0a82bf7c0a82b0100000000" +
    418             // MAC address.
    419             "30766ff2a90c00000000000000000000" +
    420             // Server name.
    421             "0000000000000000000000000000000000000000000000000000000000000000" +
    422             "0000000000000000000000000000000000000000000000000000000000000000" +
    423             // File.
    424             "0000000000000000000000000000000000000000000000000000000000000000" +
    425             "0000000000000000000000000000000000000000000000000000000000000000" +
    426             "0000000000000000000000000000000000000000000000000000000000000000" +
    427             "0000000000000000000000000000000000000000000000000000000000000000"
    428             // No options
    429             );
    430 
    431         try {
    432             DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, packet.length, ENCAP_L3);
    433         } catch (DhcpPacket.ParseException expected) {
    434             assertDhcpErrorCodes(DhcpErrorEvent.DHCP_NO_COOKIE, expected.errorCode);
    435             return;
    436         }
    437         fail("Dhcp packet parsing should have failed");
    438     }
    439 
    440     @SmallTest
    441     public void testOfferWithBadCookie() throws Exception {
    442         final byte[] packet = HexDump.hexStringToByteArray(
    443             // IP header.
    444             "450001518d0600004011144dc0a82b01c0a82bf7" +
    445             // UDP header.
    446             "00430044013d9ac7" +
    447             // BOOTP header.
    448             "02010600dfc23d1f0002000000000000c0a82bf7c0a82b0100000000" +
    449             // MAC address.
    450             "30766ff2a90c00000000000000000000" +
    451             // Server name.
    452             "0000000000000000000000000000000000000000000000000000000000000000" +
    453             "0000000000000000000000000000000000000000000000000000000000000000" +
    454             // File.
    455             "0000000000000000000000000000000000000000000000000000000000000000" +
    456             "0000000000000000000000000000000000000000000000000000000000000000" +
    457             "0000000000000000000000000000000000000000000000000000000000000000" +
    458             "0000000000000000000000000000000000000000000000000000000000000000" +
    459             // Bad cookie
    460             "DEADBEEF3501023604c0a82b01330400000e103a04000007083b0400000c4e0104ffffff00" +
    461             "1c04c0a82bff0304c0a82b010604c0a82b012b0f414e44524f49445f4d455445524544ff");
    462 
    463         try {
    464             DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, packet.length, ENCAP_L3);
    465         } catch (DhcpPacket.ParseException expected) {
    466             assertDhcpErrorCodes(DhcpErrorEvent.DHCP_BAD_MAGIC_COOKIE, expected.errorCode);
    467             return;
    468         }
    469         fail("Dhcp packet parsing should have failed");
    470     }
    471 
    472     private void assertDhcpErrorCodes(int expected, int got) {
    473         assertEquals(Integer.toHexString(expected), Integer.toHexString(got));
    474     }
    475 
    476     public void testTruncatedOfferPackets() throws Exception {
    477         final byte[] packet = HexDump.hexStringToByteArray(
    478             // IP header.
    479             "450001518d0600004011144dc0a82b01c0a82bf7" +
    480             // UDP header.
    481             "00430044013d9ac7" +
    482             // BOOTP header.
    483             "02010600dfc23d1f0002000000000000c0a82bf7c0a82b0100000000" +
    484             // MAC address.
    485             "30766ff2a90c00000000000000000000" +
    486             // Server name.
    487             "0000000000000000000000000000000000000000000000000000000000000000" +
    488             "0000000000000000000000000000000000000000000000000000000000000000" +
    489             // File.
    490             "0000000000000000000000000000000000000000000000000000000000000000" +
    491             "0000000000000000000000000000000000000000000000000000000000000000" +
    492             "0000000000000000000000000000000000000000000000000000000000000000" +
    493             "0000000000000000000000000000000000000000000000000000000000000000" +
    494             // Options
    495             "638253633501023604c0a82b01330400000e103a04000007083b0400000c4e0104ffffff00" +
    496             "1c04c0a82bff0304c0a82b010604c0a82b012b0f414e44524f49445f4d455445524544ff");
    497 
    498         for (int len = 0; len < packet.length; len++) {
    499             try {
    500                 DhcpPacket.decodeFullPacket(packet, len, ENCAP_L3);
    501             } catch (ParseException e) {
    502                 if (e.errorCode == DhcpErrorEvent.PARSING_ERROR) {
    503                     fail(String.format("bad truncated packet of length %d", len));
    504                 }
    505             }
    506         }
    507     }
    508 
    509     public void testRandomPackets() throws Exception {
    510         final int maxRandomPacketSize = 512;
    511         final Random r = new Random();
    512         for (int i = 0; i < 10000; i++) {
    513             byte[] packet = new byte[r.nextInt(maxRandomPacketSize + 1)];
    514             r.nextBytes(packet);
    515             try {
    516                 DhcpPacket.decodeFullPacket(packet, packet.length, ENCAP_L3);
    517             } catch (ParseException e) {
    518                 if (e.errorCode == DhcpErrorEvent.PARSING_ERROR) {
    519                     fail("bad packet: " + HexDump.toHexString(packet));
    520                 }
    521             }
    522         }
    523     }
    524 
    525     private byte[] mtuBytes(int mtu) {
    526         // 0x1a02: option 26, length 2. 0xff: no more options.
    527         if (mtu > Short.MAX_VALUE - Short.MIN_VALUE) {
    528             throw new IllegalArgumentException(
    529                 String.format("Invalid MTU %d, must be 16-bit unsigned", mtu));
    530         }
    531         String hexString = String.format("1a02%04xff", mtu);
    532         return HexDump.hexStringToByteArray(hexString);
    533     }
    534 
    535     private void checkMtu(ByteBuffer packet, int expectedMtu, byte[] mtuBytes) throws Exception {
    536         if (mtuBytes != null) {
    537             packet.position(packet.capacity() - mtuBytes.length);
    538             packet.put(mtuBytes);
    539             packet.clear();
    540         }
    541         DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L3);
    542         assertTrue(offerPacket instanceof DhcpOfferPacket);  // Implicitly checks it's non-null.
    543         DhcpResults dhcpResults = offerPacket.toDhcpResults();
    544         assertDhcpResults("192.168.159.247/20", "192.168.159.254", "8.8.8.8,8.8.4.4",
    545                 null, "192.168.144.3", null, 7200, false, expectedMtu, dhcpResults);
    546     }
    547 
    548     @SmallTest
    549     public void testMtu() throws Exception {
    550         final ByteBuffer packet = ByteBuffer.wrap(HexDump.hexStringToByteArray(
    551             // IP header.
    552             "451001480000000080118849c0a89003c0a89ff7" +
    553             // UDP header.
    554             "004300440134dcfa" +
    555             // BOOTP header.
    556             "02010600c997a63b0000000000000000c0a89ff70000000000000000" +
    557             // MAC address.
    558             "30766ff2a90c00000000000000000000" +
    559             // Server name.
    560             "0000000000000000000000000000000000000000000000000000000000000000" +
    561             "0000000000000000000000000000000000000000000000000000000000000000" +
    562             // File.
    563             "0000000000000000000000000000000000000000000000000000000000000000" +
    564             "0000000000000000000000000000000000000000000000000000000000000000" +
    565             "0000000000000000000000000000000000000000000000000000000000000000" +
    566             "0000000000000000000000000000000000000000000000000000000000000000" +
    567             // Options
    568             "638253633501023604c0a89003330400001c200104fffff0000304c0a89ffe06080808080808080404" +
    569             "3a0400000e103b040000189cff00000000"));
    570 
    571         checkMtu(packet, 0, null);
    572         checkMtu(packet, 0, mtuBytes(1501));
    573         checkMtu(packet, 1500, mtuBytes(1500));
    574         checkMtu(packet, 1499, mtuBytes(1499));
    575         checkMtu(packet, 1280, mtuBytes(1280));
    576         checkMtu(packet, 0, mtuBytes(1279));
    577         checkMtu(packet, 0, mtuBytes(576));
    578         checkMtu(packet, 0, mtuBytes(68));
    579         checkMtu(packet, 0, mtuBytes(Short.MIN_VALUE));
    580         checkMtu(packet, 0, mtuBytes(Short.MAX_VALUE + 3));
    581         checkMtu(packet, 0, mtuBytes(-1));
    582     }
    583 
    584     @SmallTest
    585     public void testBadHwaddrLength() throws Exception {
    586         final ByteBuffer packet = ByteBuffer.wrap(HexDump.hexStringToByteArray(
    587             // IP header.
    588             "450001518d0600004011144dc0a82b01c0a82bf7" +
    589             // UDP header.
    590             "00430044013d9ac7" +
    591             // BOOTP header.
    592             "02010600dfc23d1f0002000000000000c0a82bf7c0a82b0100000000" +
    593             // MAC address.
    594             "30766ff2a90c00000000000000000000" +
    595             // Server name.
    596             "0000000000000000000000000000000000000000000000000000000000000000" +
    597             "0000000000000000000000000000000000000000000000000000000000000000" +
    598             // File.
    599             "0000000000000000000000000000000000000000000000000000000000000000" +
    600             "0000000000000000000000000000000000000000000000000000000000000000" +
    601             "0000000000000000000000000000000000000000000000000000000000000000" +
    602             "0000000000000000000000000000000000000000000000000000000000000000" +
    603             // Options
    604             "638253633501023604c0a82b01330400000e103a04000007083b0400000c4e0104ffffff00" +
    605             "1c04c0a82bff0304c0a82b010604c0a82b012b0f414e44524f49445f4d455445524544ff"));
    606         String expectedClientMac = "30766FF2A90C";
    607 
    608         final int hwAddrLenOffset = 20 + 8 + 2;
    609         assertEquals(6, packet.get(hwAddrLenOffset));
    610 
    611         // Expect the expected.
    612         DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L3);
    613         assertNotNull(offerPacket);
    614         assertEquals(6, offerPacket.getClientMac().length);
    615         assertEquals(expectedClientMac, HexDump.toHexString(offerPacket.getClientMac()));
    616 
    617         // Reduce the hardware address length and verify that it shortens the client MAC.
    618         packet.flip();
    619         packet.put(hwAddrLenOffset, (byte) 5);
    620         offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L3);
    621         assertNotNull(offerPacket);
    622         assertEquals(5, offerPacket.getClientMac().length);
    623         assertEquals(expectedClientMac.substring(0, 10),
    624                 HexDump.toHexString(offerPacket.getClientMac()));
    625 
    626         packet.flip();
    627         packet.put(hwAddrLenOffset, (byte) 3);
    628         offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L3);
    629         assertNotNull(offerPacket);
    630         assertEquals(3, offerPacket.getClientMac().length);
    631         assertEquals(expectedClientMac.substring(0, 6),
    632                 HexDump.toHexString(offerPacket.getClientMac()));
    633 
    634         // Set the the hardware address length to 0xff and verify that we a) don't treat it as -1
    635         // and crash, and b) hardcode it to 6.
    636         packet.flip();
    637         packet.put(hwAddrLenOffset, (byte) -1);
    638         offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L3);
    639         assertNotNull(offerPacket);
    640         assertEquals(6, offerPacket.getClientMac().length);
    641         assertEquals(expectedClientMac, HexDump.toHexString(offerPacket.getClientMac()));
    642 
    643         // Set the the hardware address length to a positive invalid value (> 16) and verify that we
    644         // hardcode it to 6.
    645         packet.flip();
    646         packet.put(hwAddrLenOffset, (byte) 17);
    647         offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L3);
    648         assertNotNull(offerPacket);
    649         assertEquals(6, offerPacket.getClientMac().length);
    650         assertEquals(expectedClientMac, HexDump.toHexString(offerPacket.getClientMac()));
    651     }
    652 
    653     @SmallTest
    654     public void testPadAndOverloadedOptionsOffer() throws Exception {
    655         // A packet observed in the real world that is interesting for two reasons:
    656         //
    657         // 1. It uses pad bytes, which we previously didn't support correctly.
    658         // 2. It uses DHCP option overloading, which we don't currently support (but it doesn't
    659         //    store any information in the overloaded fields).
    660         //
    661         // For now, we just check that it parses correctly.
    662         final ByteBuffer packet = ByteBuffer.wrap(HexDump.hexStringToByteArray(
    663             // Ethernet header.
    664             "b4cef6000000e80462236e300800" +
    665             // IP header.
    666             "4500014c00000000ff11741701010101ac119876" +
    667             // UDP header. TODO: fix invalid checksum (due to MAC address obfuscation).
    668             "004300440138ae5a" +
    669             // BOOTP header.
    670             "020106000fa0059f0000000000000000ac1198760000000000000000" +
    671             // MAC address.
    672             "b4cef600000000000000000000000000" +
    673             // Server name.
    674             "ff00000000000000000000000000000000000000000000000000000000000000" +
    675             "0000000000000000000000000000000000000000000000000000000000000000" +
    676             // File.
    677             "ff00000000000000000000000000000000000000000000000000000000000000" +
    678             "0000000000000000000000000000000000000000000000000000000000000000" +
    679             "0000000000000000000000000000000000000000000000000000000000000000" +
    680             "0000000000000000000000000000000000000000000000000000000000000000" +
    681             // Options
    682             "638253633501023604010101010104ffff000033040000a8c03401030304ac1101010604ac110101" +
    683             "0000000000000000000000000000000000000000000000ff000000"));
    684 
    685         DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L2);
    686         assertTrue(offerPacket instanceof DhcpOfferPacket);
    687         DhcpResults dhcpResults = offerPacket.toDhcpResults();
    688         assertDhcpResults("172.17.152.118/16", "172.17.1.1", "172.17.1.1",
    689                 null, "1.1.1.1", null, 43200, false, 0, dhcpResults);
    690     }
    691 
    692     @SmallTest
    693     public void testBug2111() throws Exception {
    694         final ByteBuffer packet = ByteBuffer.wrap(HexDump.hexStringToByteArray(
    695             // IP header.
    696             "4500014c00000000ff119beac3eaf3880a3f5d04" +
    697             // UDP header. TODO: fix invalid checksum (due to MAC address obfuscation).
    698             "0043004401387464" +
    699             // BOOTP header.
    700             "0201060002554812000a0000000000000a3f5d040000000000000000" +
    701             // MAC address.
    702             "00904c00000000000000000000000000" +
    703             // Server name.
    704             "0000000000000000000000000000000000000000000000000000000000000000" +
    705             "0000000000000000000000000000000000000000000000000000000000000000" +
    706             // File.
    707             "0000000000000000000000000000000000000000000000000000000000000000" +
    708             "0000000000000000000000000000000000000000000000000000000000000000" +
    709             "0000000000000000000000000000000000000000000000000000000000000000" +
    710             "0000000000000000000000000000000000000000000000000000000000000000" +
    711             // Options.
    712             "638253633501023604c00002fe33040000bfc60104fffff00003040a3f50010608c0000201c0000202" +
    713             "0f0f646f6d61696e3132332e636f2e756b0000000000ff00000000"));
    714 
    715         DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L3);
    716         assertTrue(offerPacket instanceof DhcpOfferPacket);
    717         DhcpResults dhcpResults = offerPacket.toDhcpResults();
    718         assertDhcpResults("10.63.93.4/20", "10.63.80.1", "192.0.2.1,192.0.2.2",
    719                 "domain123.co.uk", "192.0.2.254", null, 49094, false, 0, dhcpResults);
    720     }
    721 
    722     @SmallTest
    723     public void testBug2136() throws Exception {
    724         final ByteBuffer packet = ByteBuffer.wrap(HexDump.hexStringToByteArray(
    725             // Ethernet header.
    726             "bcf5ac000000d0c7890000000800" +
    727             // IP header.
    728             "4500014c00000000ff119beac3eaf3880a3f5d04" +
    729             // UDP header. TODO: fix invalid checksum (due to MAC address obfuscation).
    730             "0043004401387574" +
    731             // BOOTP header.
    732             "0201060163339a3000050000000000000a209ecd0000000000000000" +
    733             // MAC address.
    734             "bcf5ac00000000000000000000000000" +
    735             // Server name.
    736             "0000000000000000000000000000000000000000000000000000000000000000" +
    737             "0000000000000000000000000000000000000000000000000000000000000000" +
    738             // File.
    739             "0000000000000000000000000000000000000000000000000000000000000000" +
    740             "0000000000000000000000000000000000000000000000000000000000000000" +
    741             "0000000000000000000000000000000000000000000000000000000000000000" +
    742             "0000000000000000000000000000000000000000000000000000000000000000" +
    743             // Options.
    744             "6382536335010236040a20ff80330400001c200104fffff00003040a20900106089458413494584135" +
    745             "0f0b6c616e63732e61632e756b000000000000000000ff00000000"));
    746 
    747         DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L2);
    748         assertTrue(offerPacket instanceof DhcpOfferPacket);
    749         assertEquals("BCF5AC000000", HexDump.toHexString(offerPacket.getClientMac()));
    750         DhcpResults dhcpResults = offerPacket.toDhcpResults();
    751         assertDhcpResults("10.32.158.205/20", "10.32.144.1", "148.88.65.52,148.88.65.53",
    752                 "lancs.ac.uk", "10.32.255.128", null, 7200, false, 0, dhcpResults);
    753     }
    754 
    755     @SmallTest
    756     public void testUdpServerAnySourcePort() throws Exception {
    757         final ByteBuffer packet = ByteBuffer.wrap(HexDump.hexStringToByteArray(
    758             // Ethernet header.
    759             "9cd917000000001c2e0000000800" +
    760             // IP header.
    761             "45a00148000040003d115087d18194fb0a0f7af2" +
    762             // UDP header. TODO: fix invalid checksum (due to MAC address obfuscation).
    763             // NOTE: The server source port is not the canonical port 67.
    764             "C29F004401341268" +
    765             // BOOTP header.
    766             "02010600d628ba8200000000000000000a0f7af2000000000a0fc818" +
    767             // MAC address.
    768             "9cd91700000000000000000000000000" +
    769             // Server name.
    770             "0000000000000000000000000000000000000000000000000000000000000000" +
    771             "0000000000000000000000000000000000000000000000000000000000000000" +
    772             // File.
    773             "0000000000000000000000000000000000000000000000000000000000000000" +
    774             "0000000000000000000000000000000000000000000000000000000000000000" +
    775             "0000000000000000000000000000000000000000000000000000000000000000" +
    776             "0000000000000000000000000000000000000000000000000000000000000000" +
    777             // Options.
    778             "6382536335010236040a0169fc3304000151800104ffff000003040a0fc817060cd1818003d1819403" +
    779             "d18180060f0777766d2e6564751c040a0fffffff000000"));
    780 
    781         DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L2);
    782         assertTrue(offerPacket instanceof DhcpOfferPacket);
    783         assertEquals("9CD917000000", HexDump.toHexString(offerPacket.getClientMac()));
    784         DhcpResults dhcpResults = offerPacket.toDhcpResults();
    785         assertDhcpResults("10.15.122.242/16", "10.15.200.23",
    786                 "209.129.128.3,209.129.148.3,209.129.128.6",
    787                 "wvm.edu", "10.1.105.252", null, 86400, false, 0, dhcpResults);
    788     }
    789 
    790     @SmallTest
    791     public void testUdpInvalidDstPort() throws Exception {
    792         final ByteBuffer packet = ByteBuffer.wrap(HexDump.hexStringToByteArray(
    793             // Ethernet header.
    794             "9cd917000000001c2e0000000800" +
    795             // IP header.
    796             "45a00148000040003d115087d18194fb0a0f7af2" +
    797             // UDP header. TODO: fix invalid checksum (due to MAC address obfuscation).
    798             // NOTE: The destination port is a non-DHCP port.
    799             "0043aaaa01341268" +
    800             // BOOTP header.
    801             "02010600d628ba8200000000000000000a0f7af2000000000a0fc818" +
    802             // MAC address.
    803             "9cd91700000000000000000000000000" +
    804             // Server name.
    805             "0000000000000000000000000000000000000000000000000000000000000000" +
    806             "0000000000000000000000000000000000000000000000000000000000000000" +
    807             // File.
    808             "0000000000000000000000000000000000000000000000000000000000000000" +
    809             "0000000000000000000000000000000000000000000000000000000000000000" +
    810             "0000000000000000000000000000000000000000000000000000000000000000" +
    811             "0000000000000000000000000000000000000000000000000000000000000000" +
    812             // Options.
    813             "6382536335010236040a0169fc3304000151800104ffff000003040a0fc817060cd1818003d1819403" +
    814             "d18180060f0777766d2e6564751c040a0fffffff000000"));
    815 
    816         try {
    817             DhcpPacket.decodeFullPacket(packet, ENCAP_L2);
    818             fail("Packet with invalid dst port did not throw ParseException");
    819         } catch (ParseException expected) {}
    820     }
    821 
    822     @SmallTest
    823     public void testMultipleRouters() throws Exception {
    824         final ByteBuffer packet = ByteBuffer.wrap(HexDump.hexStringToByteArray(
    825             // Ethernet header.
    826             "fc3d93000000" + "081735000000" + "0800" +
    827             // IP header.
    828             "45000148c2370000ff117ac2c0a8bd02ffffffff" +
    829             // UDP header. TODO: fix invalid checksum (due to MAC address obfuscation).
    830             "0043004401343beb" +
    831             // BOOTP header.
    832             "0201060027f518e20000800000000000c0a8bd310000000000000000" +
    833             // MAC address.
    834             "fc3d9300000000000000000000000000" +
    835             // Server name.
    836             "0000000000000000000000000000000000000000000000000000000000000000" +
    837             "0000000000000000000000000000000000000000000000000000000000000000" +
    838             // File.
    839             "0000000000000000000000000000000000000000000000000000000000000000" +
    840             "0000000000000000000000000000000000000000000000000000000000000000" +
    841             "0000000000000000000000000000000000000000000000000000000000000000" +
    842             "0000000000000000000000000000000000000000000000000000000000000000" +
    843             // Options.
    844             "638253633501023604c0abbd023304000070803a04000038403b04000062700104ffffff00" +
    845             "0308c0a8bd01ffffff0006080808080808080404ff000000000000"));
    846 
    847         DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L2);
    848         assertTrue(offerPacket instanceof DhcpOfferPacket);
    849         assertEquals("FC3D93000000", HexDump.toHexString(offerPacket.getClientMac()));
    850         DhcpResults dhcpResults = offerPacket.toDhcpResults();
    851         assertDhcpResults("192.168.189.49/24", "192.168.189.1", "8.8.8.8,8.8.4.4",
    852                 null, "192.171.189.2", null, 28800, false, 0, dhcpResults);
    853     }
    854 
    855     @SmallTest
    856     public void testDiscoverPacket() throws Exception {
    857         short secs = 7;
    858         int transactionId = 0xdeadbeef;
    859         byte[] hwaddr = {
    860                 (byte) 0xda, (byte) 0x01, (byte) 0x19, (byte) 0x5b, (byte) 0xb1, (byte) 0x7a
    861         };
    862 
    863         ByteBuffer packet = DhcpPacket.buildDiscoverPacket(
    864                 DhcpPacket.ENCAP_L2, transactionId, secs, hwaddr,
    865                 false /* do unicast */, DhcpClient.REQUESTED_PARAMS);
    866 
    867         byte[] headers = new byte[] {
    868             // Ethernet header.
    869             (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
    870             (byte) 0xda, (byte) 0x01, (byte) 0x19, (byte) 0x5b, (byte) 0xb1, (byte) 0x7a,
    871             (byte) 0x08, (byte) 0x00,
    872             // IP header.
    873             (byte) 0x45, (byte) 0x10, (byte) 0x01, (byte) 0x56,
    874             (byte) 0x00, (byte) 0x00, (byte) 0x40, (byte) 0x00,
    875             (byte) 0x40, (byte) 0x11, (byte) 0x39, (byte) 0x88,
    876             (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
    877             (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
    878             // UDP header.
    879             (byte) 0x00, (byte) 0x44, (byte) 0x00, (byte) 0x43,
    880             (byte) 0x01, (byte) 0x42, (byte) 0x6a, (byte) 0x4a,
    881             // BOOTP.
    882             (byte) 0x01, (byte) 0x01, (byte) 0x06, (byte) 0x00,
    883             (byte) 0xde, (byte) 0xad, (byte) 0xbe, (byte) 0xef,
    884             (byte) 0x00, (byte) 0x07, (byte) 0x00, (byte) 0x00,
    885             (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
    886             (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
    887             (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
    888             (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
    889             (byte) 0xda, (byte) 0x01, (byte) 0x19, (byte) 0x5b,
    890             (byte) 0xb1, (byte) 0x7a
    891         };
    892         byte[] options = new byte[] {
    893             // Magic cookie 0x63825363.
    894             (byte) 0x63, (byte) 0x82, (byte) 0x53, (byte) 0x63,
    895             // Message type DISCOVER.
    896             (byte) 0x35, (byte) 0x01, (byte) 0x01,
    897             // Client identifier Ethernet, da:01:19:5b:b1:7a.
    898             (byte) 0x3d, (byte) 0x07,
    899                     (byte) 0x01,
    900                     (byte) 0xda, (byte) 0x01, (byte) 0x19, (byte) 0x5b, (byte) 0xb1, (byte) 0x7a,
    901             // Max message size 1500.
    902             (byte) 0x39, (byte) 0x02, (byte) 0x05, (byte) 0xdc,
    903             // Version "android-dhcp-???".
    904             (byte) 0x3c, (byte) 0x10,
    905                     'a', 'n', 'd', 'r', 'o', 'i', 'd', '-', 'd', 'h', 'c', 'p', '-', '?', '?', '?',
    906             // Hostname "android-01234567890abcde"
    907             (byte) 0x0c, (byte) 0x18,
    908                     'a', 'n', 'd', 'r', 'o', 'i', 'd', '-',
    909                     '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', 'a', 'b', 'c', 'd', 'e',
    910             // Requested parameter list.
    911             (byte) 0x37, (byte) 0x0a,
    912                 DHCP_SUBNET_MASK,
    913                 DHCP_ROUTER,
    914                 DHCP_DNS_SERVER,
    915                 DHCP_DOMAIN_NAME,
    916                 DHCP_MTU,
    917                 DHCP_BROADCAST_ADDRESS,
    918                 DHCP_LEASE_TIME,
    919                 DHCP_RENEWAL_TIME,
    920                 DHCP_REBINDING_TIME,
    921                 DHCP_VENDOR_INFO,
    922             // End options.
    923             (byte) 0xff,
    924             // Our packets are always of even length. TODO: find out why and possibly fix it.
    925             (byte) 0x00
    926         };
    927         byte[] expected = new byte[DhcpPacket.MIN_PACKET_LENGTH_L2 + options.length];
    928         assertTrue((expected.length & 1) == 0);
    929         System.arraycopy(headers, 0, expected, 0, headers.length);
    930         System.arraycopy(options, 0, expected, DhcpPacket.MIN_PACKET_LENGTH_L2, options.length);
    931 
    932         byte[] actual = new byte[packet.limit()];
    933         packet.get(actual);
    934         String msg =
    935                 "Expected:\n  " + Arrays.toString(expected) +
    936                 "\nActual:\n  " + Arrays.toString(actual);
    937         assertTrue(msg, Arrays.equals(expected, actual));
    938     }
    939 }
    940