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     @SmallTest
    477     public void testTruncatedOfferPackets() throws Exception {
    478         final byte[] packet = HexDump.hexStringToByteArray(
    479             // IP header.
    480             "450001518d0600004011144dc0a82b01c0a82bf7" +
    481             // UDP header.
    482             "00430044013d9ac7" +
    483             // BOOTP header.
    484             "02010600dfc23d1f0002000000000000c0a82bf7c0a82b0100000000" +
    485             // MAC address.
    486             "30766ff2a90c00000000000000000000" +
    487             // Server name.
    488             "0000000000000000000000000000000000000000000000000000000000000000" +
    489             "0000000000000000000000000000000000000000000000000000000000000000" +
    490             // File.
    491             "0000000000000000000000000000000000000000000000000000000000000000" +
    492             "0000000000000000000000000000000000000000000000000000000000000000" +
    493             "0000000000000000000000000000000000000000000000000000000000000000" +
    494             "0000000000000000000000000000000000000000000000000000000000000000" +
    495             // Options
    496             "638253633501023604c0a82b01330400000e103a04000007083b0400000c4e0104ffffff00" +
    497             "1c04c0a82bff0304c0a82b010604c0a82b012b0f414e44524f49445f4d455445524544ff");
    498 
    499         for (int len = 0; len < packet.length; len++) {
    500             try {
    501                 DhcpPacket.decodeFullPacket(packet, len, ENCAP_L3);
    502             } catch (ParseException e) {
    503                 if (e.errorCode == DhcpErrorEvent.PARSING_ERROR) {
    504                     fail(String.format("bad truncated packet of length %d", len));
    505                 }
    506             }
    507         }
    508     }
    509 
    510     @SmallTest
    511     public void testRandomPackets() throws Exception {
    512         final int maxRandomPacketSize = 512;
    513         final Random r = new Random();
    514         for (int i = 0; i < 10000; i++) {
    515             byte[] packet = new byte[r.nextInt(maxRandomPacketSize + 1)];
    516             r.nextBytes(packet);
    517             try {
    518                 DhcpPacket.decodeFullPacket(packet, packet.length, ENCAP_L3);
    519             } catch (ParseException e) {
    520                 if (e.errorCode == DhcpErrorEvent.PARSING_ERROR) {
    521                     fail("bad packet: " + HexDump.toHexString(packet));
    522                 }
    523             }
    524         }
    525     }
    526 
    527     private byte[] mtuBytes(int mtu) {
    528         // 0x1a02: option 26, length 2. 0xff: no more options.
    529         if (mtu > Short.MAX_VALUE - Short.MIN_VALUE) {
    530             throw new IllegalArgumentException(
    531                 String.format("Invalid MTU %d, must be 16-bit unsigned", mtu));
    532         }
    533         String hexString = String.format("1a02%04xff", mtu);
    534         return HexDump.hexStringToByteArray(hexString);
    535     }
    536 
    537     private void checkMtu(ByteBuffer packet, int expectedMtu, byte[] mtuBytes) throws Exception {
    538         if (mtuBytes != null) {
    539             packet.position(packet.capacity() - mtuBytes.length);
    540             packet.put(mtuBytes);
    541             packet.clear();
    542         }
    543         DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L3);
    544         assertTrue(offerPacket instanceof DhcpOfferPacket);  // Implicitly checks it's non-null.
    545         DhcpResults dhcpResults = offerPacket.toDhcpResults();
    546         assertDhcpResults("192.168.159.247/20", "192.168.159.254", "8.8.8.8,8.8.4.4",
    547                 null, "192.168.144.3", null, 7200, false, expectedMtu, dhcpResults);
    548     }
    549 
    550     @SmallTest
    551     public void testMtu() throws Exception {
    552         final ByteBuffer packet = ByteBuffer.wrap(HexDump.hexStringToByteArray(
    553             // IP header.
    554             "451001480000000080118849c0a89003c0a89ff7" +
    555             // UDP header.
    556             "004300440134dcfa" +
    557             // BOOTP header.
    558             "02010600c997a63b0000000000000000c0a89ff70000000000000000" +
    559             // MAC address.
    560             "30766ff2a90c00000000000000000000" +
    561             // Server name.
    562             "0000000000000000000000000000000000000000000000000000000000000000" +
    563             "0000000000000000000000000000000000000000000000000000000000000000" +
    564             // File.
    565             "0000000000000000000000000000000000000000000000000000000000000000" +
    566             "0000000000000000000000000000000000000000000000000000000000000000" +
    567             "0000000000000000000000000000000000000000000000000000000000000000" +
    568             "0000000000000000000000000000000000000000000000000000000000000000" +
    569             // Options
    570             "638253633501023604c0a89003330400001c200104fffff0000304c0a89ffe06080808080808080404" +
    571             "3a0400000e103b040000189cff00000000"));
    572 
    573         checkMtu(packet, 0, null);
    574         checkMtu(packet, 0, mtuBytes(1501));
    575         checkMtu(packet, 1500, mtuBytes(1500));
    576         checkMtu(packet, 1499, mtuBytes(1499));
    577         checkMtu(packet, 1280, mtuBytes(1280));
    578         checkMtu(packet, 0, mtuBytes(1279));
    579         checkMtu(packet, 0, mtuBytes(576));
    580         checkMtu(packet, 0, mtuBytes(68));
    581         checkMtu(packet, 0, mtuBytes(Short.MIN_VALUE));
    582         checkMtu(packet, 0, mtuBytes(Short.MAX_VALUE + 3));
    583         checkMtu(packet, 0, mtuBytes(-1));
    584     }
    585 
    586     @SmallTest
    587     public void testBadHwaddrLength() throws Exception {
    588         final ByteBuffer packet = ByteBuffer.wrap(HexDump.hexStringToByteArray(
    589             // IP header.
    590             "450001518d0600004011144dc0a82b01c0a82bf7" +
    591             // UDP header.
    592             "00430044013d9ac7" +
    593             // BOOTP header.
    594             "02010600dfc23d1f0002000000000000c0a82bf7c0a82b0100000000" +
    595             // MAC address.
    596             "30766ff2a90c00000000000000000000" +
    597             // Server name.
    598             "0000000000000000000000000000000000000000000000000000000000000000" +
    599             "0000000000000000000000000000000000000000000000000000000000000000" +
    600             // File.
    601             "0000000000000000000000000000000000000000000000000000000000000000" +
    602             "0000000000000000000000000000000000000000000000000000000000000000" +
    603             "0000000000000000000000000000000000000000000000000000000000000000" +
    604             "0000000000000000000000000000000000000000000000000000000000000000" +
    605             // Options
    606             "638253633501023604c0a82b01330400000e103a04000007083b0400000c4e0104ffffff00" +
    607             "1c04c0a82bff0304c0a82b010604c0a82b012b0f414e44524f49445f4d455445524544ff"));
    608         String expectedClientMac = "30766FF2A90C";
    609 
    610         final int hwAddrLenOffset = 20 + 8 + 2;
    611         assertEquals(6, packet.get(hwAddrLenOffset));
    612 
    613         // Expect the expected.
    614         DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L3);
    615         assertNotNull(offerPacket);
    616         assertEquals(6, offerPacket.getClientMac().length);
    617         assertEquals(expectedClientMac, HexDump.toHexString(offerPacket.getClientMac()));
    618 
    619         // Reduce the hardware address length and verify that it shortens the client MAC.
    620         packet.flip();
    621         packet.put(hwAddrLenOffset, (byte) 5);
    622         offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L3);
    623         assertNotNull(offerPacket);
    624         assertEquals(5, offerPacket.getClientMac().length);
    625         assertEquals(expectedClientMac.substring(0, 10),
    626                 HexDump.toHexString(offerPacket.getClientMac()));
    627 
    628         packet.flip();
    629         packet.put(hwAddrLenOffset, (byte) 3);
    630         offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L3);
    631         assertNotNull(offerPacket);
    632         assertEquals(3, offerPacket.getClientMac().length);
    633         assertEquals(expectedClientMac.substring(0, 6),
    634                 HexDump.toHexString(offerPacket.getClientMac()));
    635 
    636         // Set the the hardware address length to 0xff and verify that we a) don't treat it as -1
    637         // and crash, and b) hardcode it to 6.
    638         packet.flip();
    639         packet.put(hwAddrLenOffset, (byte) -1);
    640         offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L3);
    641         assertNotNull(offerPacket);
    642         assertEquals(6, offerPacket.getClientMac().length);
    643         assertEquals(expectedClientMac, HexDump.toHexString(offerPacket.getClientMac()));
    644 
    645         // Set the the hardware address length to a positive invalid value (> 16) and verify that we
    646         // hardcode it to 6.
    647         packet.flip();
    648         packet.put(hwAddrLenOffset, (byte) 17);
    649         offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L3);
    650         assertNotNull(offerPacket);
    651         assertEquals(6, offerPacket.getClientMac().length);
    652         assertEquals(expectedClientMac, HexDump.toHexString(offerPacket.getClientMac()));
    653     }
    654 
    655     @SmallTest
    656     public void testPadAndOverloadedOptionsOffer() throws Exception {
    657         // A packet observed in the real world that is interesting for two reasons:
    658         //
    659         // 1. It uses pad bytes, which we previously didn't support correctly.
    660         // 2. It uses DHCP option overloading, which we don't currently support (but it doesn't
    661         //    store any information in the overloaded fields).
    662         //
    663         // For now, we just check that it parses correctly.
    664         final ByteBuffer packet = ByteBuffer.wrap(HexDump.hexStringToByteArray(
    665             // Ethernet header.
    666             "b4cef6000000e80462236e300800" +
    667             // IP header.
    668             "4500014c00000000ff11741701010101ac119876" +
    669             // UDP header. TODO: fix invalid checksum (due to MAC address obfuscation).
    670             "004300440138ae5a" +
    671             // BOOTP header.
    672             "020106000fa0059f0000000000000000ac1198760000000000000000" +
    673             // MAC address.
    674             "b4cef600000000000000000000000000" +
    675             // Server name.
    676             "ff00000000000000000000000000000000000000000000000000000000000000" +
    677             "0000000000000000000000000000000000000000000000000000000000000000" +
    678             // File.
    679             "ff00000000000000000000000000000000000000000000000000000000000000" +
    680             "0000000000000000000000000000000000000000000000000000000000000000" +
    681             "0000000000000000000000000000000000000000000000000000000000000000" +
    682             "0000000000000000000000000000000000000000000000000000000000000000" +
    683             // Options
    684             "638253633501023604010101010104ffff000033040000a8c03401030304ac1101010604ac110101" +
    685             "0000000000000000000000000000000000000000000000ff000000"));
    686 
    687         DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L2);
    688         assertTrue(offerPacket instanceof DhcpOfferPacket);
    689         DhcpResults dhcpResults = offerPacket.toDhcpResults();
    690         assertDhcpResults("172.17.152.118/16", "172.17.1.1", "172.17.1.1",
    691                 null, "1.1.1.1", null, 43200, false, 0, dhcpResults);
    692     }
    693 
    694     @SmallTest
    695     public void testBug2111() throws Exception {
    696         final ByteBuffer packet = ByteBuffer.wrap(HexDump.hexStringToByteArray(
    697             // IP header.
    698             "4500014c00000000ff119beac3eaf3880a3f5d04" +
    699             // UDP header. TODO: fix invalid checksum (due to MAC address obfuscation).
    700             "0043004401387464" +
    701             // BOOTP header.
    702             "0201060002554812000a0000000000000a3f5d040000000000000000" +
    703             // MAC address.
    704             "00904c00000000000000000000000000" +
    705             // Server name.
    706             "0000000000000000000000000000000000000000000000000000000000000000" +
    707             "0000000000000000000000000000000000000000000000000000000000000000" +
    708             // File.
    709             "0000000000000000000000000000000000000000000000000000000000000000" +
    710             "0000000000000000000000000000000000000000000000000000000000000000" +
    711             "0000000000000000000000000000000000000000000000000000000000000000" +
    712             "0000000000000000000000000000000000000000000000000000000000000000" +
    713             // Options.
    714             "638253633501023604c00002fe33040000bfc60104fffff00003040a3f50010608c0000201c0000202" +
    715             "0f0f646f6d61696e3132332e636f2e756b0000000000ff00000000"));
    716 
    717         DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L3);
    718         assertTrue(offerPacket instanceof DhcpOfferPacket);
    719         DhcpResults dhcpResults = offerPacket.toDhcpResults();
    720         assertDhcpResults("10.63.93.4/20", "10.63.80.1", "192.0.2.1,192.0.2.2",
    721                 "domain123.co.uk", "192.0.2.254", null, 49094, false, 0, dhcpResults);
    722     }
    723 
    724     @SmallTest
    725     public void testBug2136() throws Exception {
    726         final ByteBuffer packet = ByteBuffer.wrap(HexDump.hexStringToByteArray(
    727             // Ethernet header.
    728             "bcf5ac000000d0c7890000000800" +
    729             // IP header.
    730             "4500014c00000000ff119beac3eaf3880a3f5d04" +
    731             // UDP header. TODO: fix invalid checksum (due to MAC address obfuscation).
    732             "0043004401387574" +
    733             // BOOTP header.
    734             "0201060163339a3000050000000000000a209ecd0000000000000000" +
    735             // MAC address.
    736             "bcf5ac00000000000000000000000000" +
    737             // Server name.
    738             "0000000000000000000000000000000000000000000000000000000000000000" +
    739             "0000000000000000000000000000000000000000000000000000000000000000" +
    740             // File.
    741             "0000000000000000000000000000000000000000000000000000000000000000" +
    742             "0000000000000000000000000000000000000000000000000000000000000000" +
    743             "0000000000000000000000000000000000000000000000000000000000000000" +
    744             "0000000000000000000000000000000000000000000000000000000000000000" +
    745             // Options.
    746             "6382536335010236040a20ff80330400001c200104fffff00003040a20900106089458413494584135" +
    747             "0f0b6c616e63732e61632e756b000000000000000000ff00000000"));
    748 
    749         DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L2);
    750         assertTrue(offerPacket instanceof DhcpOfferPacket);
    751         assertEquals("BCF5AC000000", HexDump.toHexString(offerPacket.getClientMac()));
    752         DhcpResults dhcpResults = offerPacket.toDhcpResults();
    753         assertDhcpResults("10.32.158.205/20", "10.32.144.1", "148.88.65.52,148.88.65.53",
    754                 "lancs.ac.uk", "10.32.255.128", null, 7200, false, 0, dhcpResults);
    755     }
    756 
    757     @SmallTest
    758     public void testUdpServerAnySourcePort() throws Exception {
    759         final ByteBuffer packet = ByteBuffer.wrap(HexDump.hexStringToByteArray(
    760             // Ethernet header.
    761             "9cd917000000001c2e0000000800" +
    762             // IP header.
    763             "45a00148000040003d115087d18194fb0a0f7af2" +
    764             // UDP header. TODO: fix invalid checksum (due to MAC address obfuscation).
    765             // NOTE: The server source port is not the canonical port 67.
    766             "C29F004401341268" +
    767             // BOOTP header.
    768             "02010600d628ba8200000000000000000a0f7af2000000000a0fc818" +
    769             // MAC address.
    770             "9cd91700000000000000000000000000" +
    771             // Server name.
    772             "0000000000000000000000000000000000000000000000000000000000000000" +
    773             "0000000000000000000000000000000000000000000000000000000000000000" +
    774             // File.
    775             "0000000000000000000000000000000000000000000000000000000000000000" +
    776             "0000000000000000000000000000000000000000000000000000000000000000" +
    777             "0000000000000000000000000000000000000000000000000000000000000000" +
    778             "0000000000000000000000000000000000000000000000000000000000000000" +
    779             // Options.
    780             "6382536335010236040a0169fc3304000151800104ffff000003040a0fc817060cd1818003d1819403" +
    781             "d18180060f0777766d2e6564751c040a0fffffff000000"));
    782 
    783         DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L2);
    784         assertTrue(offerPacket instanceof DhcpOfferPacket);
    785         assertEquals("9CD917000000", HexDump.toHexString(offerPacket.getClientMac()));
    786         DhcpResults dhcpResults = offerPacket.toDhcpResults();
    787         assertDhcpResults("10.15.122.242/16", "10.15.200.23",
    788                 "209.129.128.3,209.129.148.3,209.129.128.6",
    789                 "wvm.edu", "10.1.105.252", null, 86400, false, 0, dhcpResults);
    790     }
    791 
    792     @SmallTest
    793     public void testUdpInvalidDstPort() throws Exception {
    794         final ByteBuffer packet = ByteBuffer.wrap(HexDump.hexStringToByteArray(
    795             // Ethernet header.
    796             "9cd917000000001c2e0000000800" +
    797             // IP header.
    798             "45a00148000040003d115087d18194fb0a0f7af2" +
    799             // UDP header. TODO: fix invalid checksum (due to MAC address obfuscation).
    800             // NOTE: The destination port is a non-DHCP port.
    801             "0043aaaa01341268" +
    802             // BOOTP header.
    803             "02010600d628ba8200000000000000000a0f7af2000000000a0fc818" +
    804             // MAC address.
    805             "9cd91700000000000000000000000000" +
    806             // Server name.
    807             "0000000000000000000000000000000000000000000000000000000000000000" +
    808             "0000000000000000000000000000000000000000000000000000000000000000" +
    809             // File.
    810             "0000000000000000000000000000000000000000000000000000000000000000" +
    811             "0000000000000000000000000000000000000000000000000000000000000000" +
    812             "0000000000000000000000000000000000000000000000000000000000000000" +
    813             "0000000000000000000000000000000000000000000000000000000000000000" +
    814             // Options.
    815             "6382536335010236040a0169fc3304000151800104ffff000003040a0fc817060cd1818003d1819403" +
    816             "d18180060f0777766d2e6564751c040a0fffffff000000"));
    817 
    818         try {
    819             DhcpPacket.decodeFullPacket(packet, ENCAP_L2);
    820             fail("Packet with invalid dst port did not throw ParseException");
    821         } catch (ParseException expected) {}
    822     }
    823 
    824     @SmallTest
    825     public void testMultipleRouters() throws Exception {
    826         final ByteBuffer packet = ByteBuffer.wrap(HexDump.hexStringToByteArray(
    827             // Ethernet header.
    828             "fc3d93000000" + "081735000000" + "0800" +
    829             // IP header.
    830             "45000148c2370000ff117ac2c0a8bd02ffffffff" +
    831             // UDP header. TODO: fix invalid checksum (due to MAC address obfuscation).
    832             "0043004401343beb" +
    833             // BOOTP header.
    834             "0201060027f518e20000800000000000c0a8bd310000000000000000" +
    835             // MAC address.
    836             "fc3d9300000000000000000000000000" +
    837             // Server name.
    838             "0000000000000000000000000000000000000000000000000000000000000000" +
    839             "0000000000000000000000000000000000000000000000000000000000000000" +
    840             // File.
    841             "0000000000000000000000000000000000000000000000000000000000000000" +
    842             "0000000000000000000000000000000000000000000000000000000000000000" +
    843             "0000000000000000000000000000000000000000000000000000000000000000" +
    844             "0000000000000000000000000000000000000000000000000000000000000000" +
    845             // Options.
    846             "638253633501023604c0abbd023304000070803a04000038403b04000062700104ffffff00" +
    847             "0308c0a8bd01ffffff0006080808080808080404ff000000000000"));
    848 
    849         DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L2);
    850         assertTrue(offerPacket instanceof DhcpOfferPacket);
    851         assertEquals("FC3D93000000", HexDump.toHexString(offerPacket.getClientMac()));
    852         DhcpResults dhcpResults = offerPacket.toDhcpResults();
    853         assertDhcpResults("192.168.189.49/24", "192.168.189.1", "8.8.8.8,8.8.4.4",
    854                 null, "192.171.189.2", null, 28800, false, 0, dhcpResults);
    855     }
    856 
    857     @SmallTest
    858     public void testDiscoverPacket() throws Exception {
    859         short secs = 7;
    860         int transactionId = 0xdeadbeef;
    861         byte[] hwaddr = {
    862                 (byte) 0xda, (byte) 0x01, (byte) 0x19, (byte) 0x5b, (byte) 0xb1, (byte) 0x7a
    863         };
    864 
    865         ByteBuffer packet = DhcpPacket.buildDiscoverPacket(
    866                 DhcpPacket.ENCAP_L2, transactionId, secs, hwaddr,
    867                 false /* do unicast */, DhcpClient.REQUESTED_PARAMS);
    868 
    869         byte[] headers = new byte[] {
    870             // Ethernet header.
    871             (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
    872             (byte) 0xda, (byte) 0x01, (byte) 0x19, (byte) 0x5b, (byte) 0xb1, (byte) 0x7a,
    873             (byte) 0x08, (byte) 0x00,
    874             // IP header.
    875             (byte) 0x45, (byte) 0x10, (byte) 0x01, (byte) 0x56,
    876             (byte) 0x00, (byte) 0x00, (byte) 0x40, (byte) 0x00,
    877             (byte) 0x40, (byte) 0x11, (byte) 0x39, (byte) 0x88,
    878             (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
    879             (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
    880             // UDP header.
    881             (byte) 0x00, (byte) 0x44, (byte) 0x00, (byte) 0x43,
    882             (byte) 0x01, (byte) 0x42, (byte) 0x6a, (byte) 0x4a,
    883             // BOOTP.
    884             (byte) 0x01, (byte) 0x01, (byte) 0x06, (byte) 0x00,
    885             (byte) 0xde, (byte) 0xad, (byte) 0xbe, (byte) 0xef,
    886             (byte) 0x00, (byte) 0x07, (byte) 0x00, (byte) 0x00,
    887             (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
    888             (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
    889             (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
    890             (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
    891             (byte) 0xda, (byte) 0x01, (byte) 0x19, (byte) 0x5b,
    892             (byte) 0xb1, (byte) 0x7a
    893         };
    894         byte[] options = new byte[] {
    895             // Magic cookie 0x63825363.
    896             (byte) 0x63, (byte) 0x82, (byte) 0x53, (byte) 0x63,
    897             // Message type DISCOVER.
    898             (byte) 0x35, (byte) 0x01, (byte) 0x01,
    899             // Client identifier Ethernet, da:01:19:5b:b1:7a.
    900             (byte) 0x3d, (byte) 0x07,
    901                     (byte) 0x01,
    902                     (byte) 0xda, (byte) 0x01, (byte) 0x19, (byte) 0x5b, (byte) 0xb1, (byte) 0x7a,
    903             // Max message size 1500.
    904             (byte) 0x39, (byte) 0x02, (byte) 0x05, (byte) 0xdc,
    905             // Version "android-dhcp-???".
    906             (byte) 0x3c, (byte) 0x10,
    907                     'a', 'n', 'd', 'r', 'o', 'i', 'd', '-', 'd', 'h', 'c', 'p', '-', '?', '?', '?',
    908             // Hostname "android-01234567890abcde"
    909             (byte) 0x0c, (byte) 0x18,
    910                     'a', 'n', 'd', 'r', 'o', 'i', 'd', '-',
    911                     '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', 'a', 'b', 'c', 'd', 'e',
    912             // Requested parameter list.
    913             (byte) 0x37, (byte) 0x0a,
    914                 DHCP_SUBNET_MASK,
    915                 DHCP_ROUTER,
    916                 DHCP_DNS_SERVER,
    917                 DHCP_DOMAIN_NAME,
    918                 DHCP_MTU,
    919                 DHCP_BROADCAST_ADDRESS,
    920                 DHCP_LEASE_TIME,
    921                 DHCP_RENEWAL_TIME,
    922                 DHCP_REBINDING_TIME,
    923                 DHCP_VENDOR_INFO,
    924             // End options.
    925             (byte) 0xff,
    926             // Our packets are always of even length. TODO: find out why and possibly fix it.
    927             (byte) 0x00
    928         };
    929         byte[] expected = new byte[DhcpPacket.MIN_PACKET_LENGTH_L2 + options.length];
    930         assertTrue((expected.length & 1) == 0);
    931         System.arraycopy(headers, 0, expected, 0, headers.length);
    932         System.arraycopy(options, 0, expected, DhcpPacket.MIN_PACKET_LENGTH_L2, options.length);
    933 
    934         byte[] actual = new byte[packet.limit()];
    935         packet.get(actual);
    936         String msg =
    937                 "Expected:\n  " + Arrays.toString(expected) +
    938                 "\nActual:\n  " + Arrays.toString(actual);
    939         assertTrue(msg, Arrays.equals(expected, actual));
    940     }
    941 }
    942