Snap for 9948759 from 45c3085f1a3d8d188586382d5e221d14cb38784c to mainline-media-swcodec-release

Change-Id: I686d5e249362b04699ae6ce8c8acae550cd50531
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index aa24b56..9fe8b1d 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -43,10 +43,6 @@
     <uses-permission android:name="android.permission.READ_DEVICE_CONFIG" />
     <!-- Signature permission defined in NetworkStackStub -->
     <uses-permission android:name="android.permission.MAINLINE_NETWORK_STACK" />
-    <!-- Used by creating test network -->
-    <uses-permission android:name="android.permission.MANAGE_TEST_NETWORKS" />
-    <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
-    <uses-permission android:name="android.permission.WRITE_SETTINGS" />
     <application
         android:extractNativeLibs="false"
         android:persistent="true"
diff --git a/src/android/net/dhcp6/Dhcp6AdvertisePacket.java b/src/android/net/dhcp6/Dhcp6AdvertisePacket.java
new file mode 100644
index 0000000..3eb5503
--- /dev/null
+++ b/src/android/net/dhcp6/Dhcp6AdvertisePacket.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.dhcp6;
+
+import static com.android.net.module.util.NetworkStackConstants.DHCP_MAX_LENGTH;
+
+import androidx.annotation.NonNull;
+
+import java.nio.ByteBuffer;
+
+/**
+ * DHCPv6 ADVERTISE packet class, a server sends an Advertise message to indicate that it's
+ * available for DHCP service, in response to a Solicit message received from a client.
+ *
+ * https://tools.ietf.org/html/rfc8415#page-24
+ */
+public class Dhcp6AdvertisePacket extends Dhcp6Packet {
+    /**
+     * Generates an advertise packet with the specified parameters.
+     */
+    Dhcp6AdvertisePacket(int transId, @NonNull final byte[] clientDuid,
+            @NonNull final byte[] serverDuid, final byte[] iapd) {
+        super(transId, (short) 0 /* secs */, clientDuid, serverDuid, iapd);
+    }
+
+    /**
+     * Build a DHCPv6 Advertise message with the specific parameters.
+     */
+    public ByteBuffer buildPacket() {
+        final ByteBuffer packet = ByteBuffer.allocate(DHCP_MAX_LENGTH);
+        final int msgTypeAndTransId = (DHCP6_MESSAGE_TYPE_ADVERTISE << 24) | (mTransId & 0x0FFF);
+        packet.putInt(msgTypeAndTransId);
+
+        addTlv(packet, DHCP6_CLIENT_IDENTIFIER, mClientDuid);
+        addTlv(packet, DHCP6_SERVER_IDENTIFIER, mServerDuid);
+        addTlv(packet, DHCP6_IA_PD, mIaPd);
+
+        packet.flip();
+        return packet;
+    }
+}
diff --git a/src/android/net/dhcp6/Dhcp6Packet.java b/src/android/net/dhcp6/Dhcp6Packet.java
index 1a1a5f3..98c214b 100644
--- a/src/android/net/dhcp6/Dhcp6Packet.java
+++ b/src/android/net/dhcp6/Dhcp6Packet.java
@@ -19,8 +19,15 @@
 import static com.android.net.module.util.NetworkStackConstants.DHCP_MAX_OPTION_LEN;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.VisibleForTesting;
 
+import com.android.net.module.util.Struct;
+import com.android.net.module.util.structs.IaPrefixOption;
+
+import java.nio.BufferUnderflowException;
 import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.charset.StandardCharsets;
 
 /**
  * Defines basic data and operations needed to build and use packets for the
@@ -89,6 +96,8 @@
     public static final byte DHCP6_IA_PD = 25;
     @NonNull
     protected final byte[] mIaPd;
+    @NonNull
+    protected PrefixDelegation mPrefixDelegation;
 
     /**
      * The transaction identifier used in this particular DHCPv6 negotiation
@@ -139,6 +148,203 @@
     }
 
     /**
+     * A class to take DHCPv6 IA_PD option allocated from server.
+     * https://www.rfc-editor.org/rfc/rfc8415.html#section-21.21
+     */
+    public static class PrefixDelegation {
+        public int iaid;
+        public int t1;
+        public int t2;
+        public final IaPrefixOption ipo;
+
+        PrefixDelegation(int iaid, int t1, int t2, final IaPrefixOption ipo) {
+            this.iaid = iaid;
+            this.t1 = t1;
+            this.t2 = t2;
+            this.ipo = ipo;
+        }
+
+        @Override
+        public String toString() {
+            return "Prefix Delegation: iaid " + iaid + ", t1 " + t1 + ", t2 " + t2
+                    + ", prefix " + ipo;
+        }
+    }
+
+    /**
+     * DHCPv6 packet parsing exception.
+     */
+    public static class ParseException extends Exception {
+        ParseException(String msg) {
+            super(msg);
+        }
+    }
+
+    private static void skipOption(final ByteBuffer packet, int optionLen)
+            throws BufferUnderflowException {
+        for (int i = 0; i < optionLen; i++) {
+            packet.get();
+        }
+    }
+
+    /**
+     * Reads a string of specified length from the buffer.
+     *
+     * TODO: move to a common place which can be shared with DhcpClient.
+     */
+    private static String readAsciiString(@NonNull final ByteBuffer buf, int byteCount,
+            boolean isNullOk) {
+        final byte[] bytes = new byte[byteCount];
+        buf.get(bytes);
+        return readAsciiString(bytes, isNullOk);
+    }
+
+    private static String readAsciiString(@NonNull final byte[] payload, boolean isNullOk) {
+        final byte[] bytes = payload;
+        int length = bytes.length;
+        if (!isNullOk) {
+            // Stop at the first null byte. This is because some DHCP options (e.g., the domain
+            // name) are passed to netd via FrameworkListener, which refuses arguments containing
+            // null bytes. We don't do this by default because vendorInfo is an opaque string which
+            // could in theory contain null bytes.
+            for (length = 0; length < bytes.length; length++) {
+                if (bytes[length] == 0) {
+                    break;
+                }
+            }
+        }
+        return new String(bytes, 0, length, StandardCharsets.US_ASCII);
+    }
+
+    /**
+     * Creates a concrete Dhcp6Packet from the supplied ByteBuffer.
+     *
+     * The buffer only starts with a UDP encapsulation (i.e. DHCPv6 message). A subset of the
+     * optional parameters are parsed and are stored in object fields. Client/Server message
+     * format:
+     *
+     *  0                   1                   2                   3
+     *  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+     * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+     * |    msg-type   |               transaction-id                  |
+     * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+     * |                                                               |
+     * .                            options                            .
+     * .                 (variable number and length)                  .
+     * |                                                               |
+     * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+     */
+    @VisibleForTesting
+    static Dhcp6Packet decodePacket(@NonNull final ByteBuffer packet) throws ParseException {
+        short secs = 0;
+        byte[] iapd = null;
+        byte[] serverDuid = null;
+        byte[] clientDuid = null;
+        short statusCode = STATUS_SUCCESS;
+        String statusMsg = null;
+
+        packet.order(ByteOrder.BIG_ENDIAN);
+
+        // DHCPv6 message contents.
+        final int msgTypeAndTransId = packet.getInt();
+        final byte messageType = (byte) (msgTypeAndTransId >> 24);
+        final int transId = msgTypeAndTransId & 0x0FFF;
+
+        /**
+         * Parse DHCPv6 options, option format:
+         *
+         * 0                   1                   2                   3
+         * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+         * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+         * |          option-code          |           option-len          |
+         * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+         * |                          option-data                          |
+         * |                      (option-len octets)                      |
+         * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+         */
+        while (packet.hasRemaining()) {
+            try {
+                final short optionType = packet.getShort();
+                final int optionLen = packet.getShort() & 0xFFFF;
+                int expectedLen = 0;
+
+                switch(optionType) {
+                    case DHCP6_SERVER_IDENTIFIER:
+                        expectedLen = optionLen;
+                        final byte[] sduid = new byte[expectedLen];
+                        packet.get(sduid, 0 /* offset */, expectedLen);
+                        serverDuid = sduid;
+                        break;
+                    case DHCP6_CLIENT_IDENTIFIER:
+                        expectedLen = optionLen;
+                        final byte[] cduid = new byte[expectedLen];
+                        packet.get(cduid, 0 /* offset */, expectedLen);
+                        clientDuid = cduid;
+                        break;
+                    case DHCP6_IA_PD:
+                        expectedLen = optionLen;
+                        final byte[] bytes = new byte[expectedLen];
+                        packet.get(bytes, 0 /* offset */, expectedLen);
+                        iapd = bytes;
+                        break;
+                    case DHCP6_ELAPSED_TIME:
+                        expectedLen = 2;
+                        secs = packet.getShort();
+                        break;
+                    case DHCP6_STATUS_CODE:
+                        expectedLen = optionLen;
+                        statusCode = packet.getShort();
+                        statusMsg = readAsciiString(packet, expectedLen - 2, false /* isNullOk */);
+                        break;
+                    default:
+                        skipOption(packet, optionLen);
+                        break;
+                }
+                if (expectedLen != optionLen) {
+                    throw new ParseException(
+                            "Invalid length " + optionLen + " for option " + optionType
+                                    + ", expected " + expectedLen);
+                }
+            } catch (BufferUnderflowException e) {
+                throw new ParseException(e.getMessage());
+            }
+        }
+
+        Dhcp6Packet newPacket;
+
+        switch(messageType) {
+            case DHCP6_MESSAGE_TYPE_SOLICIT:
+                newPacket = new Dhcp6SolicitPacket(transId, secs, clientDuid, iapd);
+                break;
+            case DHCP6_MESSAGE_TYPE_ADVERTISE:
+                newPacket = new Dhcp6AdvertisePacket(transId, clientDuid, serverDuid, iapd);
+                break;
+            case DHCP6_MESSAGE_TYPE_REQUEST:
+                newPacket = new Dhcp6RequestPacket(transId, secs, clientDuid, serverDuid, iapd);
+                break;
+            case DHCP6_MESSAGE_TYPE_REPLY:
+                newPacket = new Dhcp6ReplyPacket(transId, clientDuid, serverDuid, iapd);
+                break;
+            default:
+                throw new ParseException("Unimplemented DHCP6 message type %d" + messageType);
+        }
+
+        if (iapd != null) {
+            final ByteBuffer buffer = ByteBuffer.wrap(iapd);
+            final int iaid = buffer.getInt();
+            final int t1 = buffer.getInt();
+            final int t2 = buffer.getInt();
+            final IaPrefixOption ipo = Struct.parse(IaPrefixOption.class, buffer);
+            newPacket.mPrefixDelegation = new PrefixDelegation(iaid, t1, t2, ipo);
+            newPacket.mIaId = iaid;
+        }
+        newPacket.mStatusCode = statusCode;
+        newPacket.mStatusMsg = statusMsg;
+
+        return newPacket;
+    }
+
+    /**
      * Adds an optional parameter containing an array of bytes.
      */
     protected static void addTlv(ByteBuffer buf, short type, @NonNull byte[] payload) {
@@ -163,9 +369,38 @@
     /**
      * Builds a DHCPv6 SOLICIT packet from the required specified parameters.
      */
-    public static ByteBuffer buildSolicitPacket(int transId, short secs, final byte[] iapd,
-            final byte[] duid) {
-        final Dhcp6SolicitPacket pkt = new Dhcp6SolicitPacket(transId, secs, duid, iapd);
+    public static ByteBuffer buildSolicitPacket(int transId, short secs, @NonNull final byte[] iapd,
+            @NonNull final byte[] clientDuid) {
+        final Dhcp6SolicitPacket pkt = new Dhcp6SolicitPacket(transId, secs, clientDuid, iapd);
+        return pkt.buildPacket();
+    }
+
+    /**
+     * Builds a DHCPv6 ADVERTISE packet from the required specified parameters.
+     */
+    public static ByteBuffer buildAdvertisePacket(int transId, @NonNull final byte[] iapd,
+            @NonNull final byte[] clientDuid, @NonNull final byte[] serverDuid) {
+        final Dhcp6AdvertisePacket pkt =
+                new Dhcp6AdvertisePacket(transId, clientDuid, serverDuid, iapd);
+        return pkt.buildPacket();
+    }
+
+    /**
+     * Builds a DHCPv6 REPLY packet from the required specified parameters.
+     */
+    public static ByteBuffer buildReplyPacket(int transId, @NonNull final byte[] iapd,
+            @NonNull final byte[] clientDuid, @NonNull final byte[] serverDuid) {
+        final Dhcp6ReplyPacket pkt = new Dhcp6ReplyPacket(transId, clientDuid, serverDuid, iapd);
+        return pkt.buildPacket();
+    }
+
+    /**
+     * Builds a DHCPv6 REQUEST packet from the required specified parameters.
+     */
+    public static ByteBuffer buildRequestPacket(int transId, short secs, @NonNull final byte[] iapd,
+            @NonNull final byte[] clientDuid, @NonNull final byte[] serverDuid) {
+        final Dhcp6RequestPacket pkt =
+                new Dhcp6RequestPacket(transId, secs, clientDuid, serverDuid, iapd);
         return pkt.buildPacket();
     }
 }
diff --git a/src/android/net/dhcp6/Dhcp6ReplyPacket.java b/src/android/net/dhcp6/Dhcp6ReplyPacket.java
new file mode 100644
index 0000000..4b37478
--- /dev/null
+++ b/src/android/net/dhcp6/Dhcp6ReplyPacket.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.dhcp6;
+
+import static com.android.net.module.util.NetworkStackConstants.DHCP_MAX_LENGTH;
+
+import androidx.annotation.NonNull;
+
+import java.nio.ByteBuffer;
+
+/**
+ * DHCPv6 REPLY packet class, a server sends an Reply message containing assigned leases
+ * and configuration parameters in response to a Solicit, Request, Renew or Rebind messages
+ * received from a client.
+ *
+ * https://tools.ietf.org/html/rfc8415#page-24
+ */
+public class Dhcp6ReplyPacket extends Dhcp6Packet {
+    /**
+     * Generates a reply packet with the specified parameters.
+     */
+    Dhcp6ReplyPacket(int transId, @NonNull final byte[] clientDuid,
+            @NonNull final byte[] serverDuid, final byte[] iapd) {
+        super(transId, (short) 0 /* secs */, clientDuid, serverDuid, iapd);
+    }
+
+    /**
+     * Build a DHCPv6 Reply message with the specific parameters.
+     */
+    public ByteBuffer buildPacket() {
+        final ByteBuffer packet = ByteBuffer.allocate(DHCP_MAX_LENGTH);
+        final int msgTypeAndTransId = (DHCP6_MESSAGE_TYPE_REPLY << 24) | (mTransId & 0x0FFF);
+        packet.putInt(msgTypeAndTransId);
+
+        addTlv(packet, DHCP6_CLIENT_IDENTIFIER, mClientDuid);
+        addTlv(packet, DHCP6_SERVER_IDENTIFIER, mServerDuid);
+        addTlv(packet, DHCP6_IA_PD, mIaPd);
+
+        packet.flip();
+        return packet;
+    }
+}
diff --git a/src/android/net/dhcp6/Dhcp6RequestPacket.java b/src/android/net/dhcp6/Dhcp6RequestPacket.java
new file mode 100644
index 0000000..f2f398d
--- /dev/null
+++ b/src/android/net/dhcp6/Dhcp6RequestPacket.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.dhcp6;
+
+import static com.android.net.module.util.NetworkStackConstants.DHCP_MAX_LENGTH;
+
+import androidx.annotation.NonNull;
+
+import java.nio.ByteBuffer;
+
+/**
+ * DHCPv6 REQUEST packet class, a client sends a Request message to request configuration
+ * parameters, including addresses and/or delegated prefixes from a specific server.
+ *
+ * https://tools.ietf.org/html/rfc8415#page-24
+ */
+public class Dhcp6RequestPacket extends Dhcp6Packet {
+    /**
+     * Generates a request packet with the specified parameters.
+     */
+    Dhcp6RequestPacket(int transId, short secs, @NonNull final byte[] clientDuid,
+            @NonNull final byte[] serverDuid, final byte[] iapd) {
+        super(transId, secs, clientDuid, serverDuid, iapd);
+    }
+
+    /**
+     * Build a DHCPv6 Request message with the specific parameters.
+     */
+    public ByteBuffer buildPacket() {
+        final ByteBuffer packet = ByteBuffer.allocate(DHCP_MAX_LENGTH);
+        final int msgTypeAndTransId = (DHCP6_MESSAGE_TYPE_REQUEST << 24) | (mTransId & 0x0FFF);
+        packet.putInt(msgTypeAndTransId);
+
+        addTlv(packet, DHCP6_SERVER_IDENTIFIER, getServerDuid());
+        addTlv(packet, DHCP6_CLIENT_IDENTIFIER, getClientDuid());
+        addTlv(packet, DHCP6_ELAPSED_TIME, mSecs);
+        addTlv(packet, DHCP6_IA_PD, mIaPd);
+
+        packet.flip();
+        return packet;
+    }
+}
diff --git a/src/android/net/dhcp6/Dhcp6SolicitPacket.java b/src/android/net/dhcp6/Dhcp6SolicitPacket.java
index 23de8c9..909ff2c 100644
--- a/src/android/net/dhcp6/Dhcp6SolicitPacket.java
+++ b/src/android/net/dhcp6/Dhcp6SolicitPacket.java
@@ -18,6 +18,8 @@
 
 import static com.android.net.module.util.NetworkStackConstants.DHCP_MAX_LENGTH;
 
+import androidx.annotation.NonNull;
+
 import java.nio.ByteBuffer;
 
 /**
@@ -29,7 +31,8 @@
     /**
      * Generates a solicit packet with the specified parameters.
      */
-    Dhcp6SolicitPacket(int transId, short secs, final byte[] clientDuid, final byte[] iapd) {
+    Dhcp6SolicitPacket(int transId, short secs, @NonNull final byte[] clientDuid,
+            final byte[] iapd) {
         super(transId, secs, clientDuid, null /* serverDuid */, iapd);
     }
 
diff --git a/src/android/net/ip/IpReachabilityMonitor.java b/src/android/net/ip/IpReachabilityMonitor.java
index b7cb9ce..ff6d65e 100644
--- a/src/android/net/ip/IpReachabilityMonitor.java
+++ b/src/android/net/ip/IpReachabilityMonitor.java
@@ -259,7 +259,7 @@
         mCm = context.getSystemService(ConnectivityManager.class);
         mDependencies = dependencies;
         mMulticastResolicitEnabled = dependencies.isFeatureEnabled(context,
-                IP_REACHABILITY_MCAST_RESOLICIT_VERSION, false /* defaultEnabled */);
+                IP_REACHABILITY_MCAST_RESOLICIT_VERSION, true /* defaultEnabled */);
         mIgnoreIncompleteIpv6DnsServerEnabled = dependencies.isFeatureEnabled(context,
                 IP_REACHABILITY_IGNORE_INCOMPLETE_IPV6_DNS_SERVER_VERSION,
                 false /* defaultEnabled */);
diff --git a/tests/integration/AndroidManifest.xml b/tests/integration/AndroidManifest.xml
index bfd3735..85c971a 100644
--- a/tests/integration/AndroidManifest.xml
+++ b/tests/integration/AndroidManifest.xml
@@ -23,7 +23,10 @@
 
          05-14 00:41:02.723 18330 18330 E AndroidRuntime: java.lang.IllegalStateException: Signature|privileged permissions not in privapp-permissions whitelist: {com.android.server.networkstack.integrationtests: android.permission.CONNECTIVITY_INTERNAL}
     -->
-
+    <!-- Used by creating test network -->
+    <uses-permission android:name="android.permission.MANAGE_TEST_NETWORKS" />
+    <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
+    <uses-permission android:name="android.permission.WRITE_SETTINGS" />
     <application android:debuggable="true">
         <uses-library android:name="android.test.runner" />
     </application>
diff --git a/tests/integration/common/android/net/ip/IpClientIntegrationTestCommon.java b/tests/integration/common/android/net/ip/IpClientIntegrationTestCommon.java
index f4618a6..9790ec9 100644
--- a/tests/integration/common/android/net/ip/IpClientIntegrationTestCommon.java
+++ b/tests/integration/common/android/net/ip/IpClientIntegrationTestCommon.java
@@ -2890,8 +2890,12 @@
         waitForRouterSolicitation();
         mPacketReader.sendResponse(ra);
 
+        // Wait until we see both success IPv4 and IPv6 provisioning, then there would be 4
+        // addresses in LinkProperties, they are IPv4 address, IPv6 link-local address, stable
+        // privacy address and privacy address.
         verify(mCb, timeout(TEST_TIMEOUT_MS).atLeastOnce()).onLinkPropertiesChange(argThat(x -> {
             if (!x.isIpv4Provisioned() || !x.isIpv6Provisioned()) return false;
+            if (x.getLinkAddresses().size() != 4) return false;
             lpFuture.complete(x);
             return true;
         }));
diff --git a/tests/unit/src/android/net/dhcp6/Dhcp6PacketTest.kt b/tests/unit/src/android/net/dhcp6/Dhcp6PacketTest.kt
new file mode 100644
index 0000000..264f785
--- /dev/null
+++ b/tests/unit/src/android/net/dhcp6/Dhcp6PacketTest.kt
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.dhcp6
+
+import androidx.test.filters.SmallTest
+import androidx.test.runner.AndroidJUnit4
+import com.android.net.module.util.HexDump
+import com.android.testutils.assertThrows
+import java.nio.ByteBuffer
+import kotlin.test.assertTrue
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class Dhcp6PacketTest {
+    @Test
+    fun testDecodeDhcp6SolicitPacket() {
+        val solicitHex =
+                // Solicit, Transaction ID
+                "01000F51" +
+                // client identifier option(option_len=12)
+                "0001000C0003001B024CCBFFFE5F6EA9" +
+                // elapsed time option(option_len=2)
+                "000800020000" +
+                // IA_PD option(option_len=41, including IA prefix option)
+                "00190029DE3570F50000000000000000" +
+                // IA prefix option(option_len=25)
+                "001A001900000000000000004000000000000000000000000000000000"
+        val bytes = HexDump.hexStringToByteArray(solicitHex)
+        val packet = Dhcp6Packet.decodePacket(ByteBuffer.wrap(bytes))
+        assertTrue(packet is Dhcp6SolicitPacket)
+    }
+
+    @Test
+    fun testDecodeDhcp6SolicitPacket_incorrectOptionLength() {
+        val solicitHex =
+                // Solicit, Transaction ID
+                "01000F51" +
+                // client identifier option(option_len=12)
+                "0001000C0003001B024CCBFFFE5F6EA9" +
+                // elapsed time option(wrong option_len: 4)
+                "000800040000" +
+                // IA_PD option(option_len=41, including IA prefix option)
+                "00190029DE3570F50000000000000000" +
+                // IA prefix option(option_len=25)
+                "001A001900000000000000004000000000000000000000000000000000"
+        val bytes = HexDump.hexStringToByteArray(solicitHex)
+        assertThrows(Dhcp6Packet.ParseException::class.java) {
+                Dhcp6Packet.decodePacket(ByteBuffer.wrap(bytes))
+        }
+    }
+
+    @Test
+    fun testDecodeDhcp6SolicitPacket_lastTruncatedOption() {
+        val solicitHex =
+                // Solicit, Transaction ID
+                "01000F51" +
+                // client identifier option(option_len=12)
+                "0001000C0003001B024CCBFFFE5F6EA9" +
+                // elapsed time option(option_len=2)
+                "000800020000" +
+                // IA_PD option(option_len=41, including IA prefix option)
+                "00190029DE3570F50000000000000000" +
+                // IA prefix option(option_len=25, missing one byte)
+                "001A0019000000000000000040000000000000000000000000000000"
+        val bytes = HexDump.hexStringToByteArray(solicitHex)
+        assertThrows(Dhcp6Packet.ParseException::class.java) {
+                Dhcp6Packet.decodePacket(ByteBuffer.wrap(bytes))
+        }
+    }
+
+    @Test
+    fun testDecodeDhcp6SolicitPacket_middleTruncatedOption() {
+        val solicitHex =
+                // Solicit, Transaction ID
+                "01000F51" +
+                // client identifier option(option_len=12, missing one byte)
+                "0001000C0003001B024CCBFFFE5F6E" +
+                // elapsed time option(option_len=2)
+                "000800020000" +
+                // IA_PD option(option_len=41, including IA prefix option)
+                "00190029DE3570F50000000000000000" +
+                // IA prefix option(option_len=25)
+                "001A001900000000000000004000000000000000000000000000000000"
+        val bytes = HexDump.hexStringToByteArray(solicitHex)
+        assertThrows(Dhcp6Packet.ParseException::class.java) {
+                Dhcp6Packet.decodePacket(ByteBuffer.wrap(bytes))
+        }
+    }
+}