Merge "Rename StringNetworkSpecifier to Ethernet"
diff --git a/staticlibs/Android.bp b/staticlibs/Android.bp
index a3bfbce..03296e7 100644
--- a/staticlibs/Android.bp
+++ b/staticlibs/Android.bp
@@ -60,6 +60,9 @@
         "//packages/modules/CaptivePortalLogin",
         "//frameworks/libs/net/common/tests:__subpackages__",
   ],
+  static_libs: [
+      "net-utils-framework-common",
+  ],
   libs: [
       "androidx.annotation_annotation",
   ],
@@ -146,6 +149,7 @@
         "//packages/modules/NetworkStack:__subpackages__",
         "//packages/modules/CaptivePortalLogin",
         "//frameworks/libs/net/common/tests:__subpackages__",
+        "//frameworks/libs/net/common/device",
         "//packages/modules/Wifi/framework/tests:__subpackages__",
     ]
 }
diff --git a/staticlibs/device/com/android/net/module/util/Ipv6Utils.java b/staticlibs/device/com/android/net/module/util/Ipv6Utils.java
new file mode 100644
index 0000000..73c2431
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/Ipv6Utils.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2021 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 com.android.net.module.util;
+
+import static android.system.OsConstants.IPPROTO_ICMPV6;
+
+import static com.android.net.module.util.IpUtils.icmpv6Checksum;
+import static com.android.net.module.util.NetworkStackConstants.ETHER_TYPE_IPV6;
+import static com.android.net.module.util.NetworkStackConstants.ICMPV6_NEIGHBOR_ADVERTISEMENT;
+import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ROUTER_ADVERTISEMENT;
+import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ROUTER_SOLICITATION;
+
+import android.net.MacAddress;
+
+import com.android.net.module.util.structs.EthernetHeader;
+import com.android.net.module.util.structs.Icmpv6Header;
+import com.android.net.module.util.structs.Ipv6Header;
+import com.android.net.module.util.structs.NaHeader;
+import com.android.net.module.util.structs.RaHeader;
+import com.android.net.module.util.structs.RsHeader;
+
+import java.net.Inet6Address;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+/**
+ * Utilities for IPv6 packets.
+ */
+public class Ipv6Utils {
+    /**
+     * Build a generic ICMPv6 packet(e.g., packet used in the neighbor discovery protocol).
+     */
+    public static ByteBuffer buildIcmpv6Packet(final MacAddress srcMac, final MacAddress dstMac,
+            final Inet6Address srcIp, final Inet6Address dstIp, short type, short code,
+            final ByteBuffer... options) {
+        final int etherHeaderLen = Struct.getSize(EthernetHeader.class);
+        final int ipv6HeaderLen = Struct.getSize(Ipv6Header.class);
+        final int icmpv6HeaderLen = Struct.getSize(Icmpv6Header.class);
+        int payloadLen = 0;
+        for (ByteBuffer option: options) {
+            payloadLen += option.limit();
+        }
+        final ByteBuffer packet = ByteBuffer.allocate(etherHeaderLen + ipv6HeaderLen
+                + icmpv6HeaderLen + payloadLen);
+        final EthernetHeader ethHeader =
+                new EthernetHeader(dstMac, srcMac, (short) ETHER_TYPE_IPV6);
+        final Ipv6Header ipv6Header =
+                new Ipv6Header((int) 0x60000000 /* version, traffic class, flowlabel */,
+                icmpv6HeaderLen + payloadLen /* payload length */,
+                (byte) IPPROTO_ICMPV6 /* next header */, (byte) 0xff /* hop limit */, srcIp, dstIp);
+        final Icmpv6Header icmpv6Header = new Icmpv6Header(type, code, (short) 0 /* checksum */);
+
+        ethHeader.writeToByteBuffer(packet);
+        ipv6Header.writeToByteBuffer(packet);
+        icmpv6Header.writeToByteBuffer(packet);
+        for (ByteBuffer option : options) {
+            packet.put(option);
+            // in case option might be reused by caller, restore the position and
+            // limit of bytebuffer.
+            option.clear();
+        }
+        packet.flip();
+
+        // Populate the ICMPv6 checksum field.
+        packet.putShort(etherHeaderLen + ipv6HeaderLen + 2, icmpv6Checksum(packet,
+                etherHeaderLen /* ipOffset */,
+                (int) (etherHeaderLen + ipv6HeaderLen) /* transportOffset */,
+                (short) (icmpv6HeaderLen + payloadLen) /* transportLen */));
+        return packet;
+    }
+
+    /**
+     * Build the ICMPv6 packet payload including payload header and specific options.
+     */
+    private static ByteBuffer[] buildIcmpv6Payload(final ByteBuffer payloadHeader,
+            final ByteBuffer... options) {
+        final ByteBuffer[] payload = new ByteBuffer[options.length + 1];
+        payload[0] = payloadHeader;
+        System.arraycopy(options, 0, payload, 1, options.length);
+        return payload;
+    }
+
+    /**
+     * Build an ICMPv6 Router Advertisement packet from the required specified parameters.
+     */
+    public static ByteBuffer buildRaPacket(final MacAddress srcMac, final MacAddress dstMac,
+            final Inet6Address srcIp, final Inet6Address dstIp, final byte flags,
+            final int lifetime, final long reachableTime, final long retransTimer,
+            final ByteBuffer... options) {
+        final RaHeader raHeader = new RaHeader((byte) 0 /* hopLimit, unspecified */,
+                flags, lifetime, reachableTime, retransTimer);
+        final ByteBuffer[] payload = buildIcmpv6Payload(
+                ByteBuffer.wrap(raHeader.writeToBytes(ByteOrder.BIG_ENDIAN)), options);
+        return buildIcmpv6Packet(srcMac, dstMac, srcIp, dstIp,
+                (byte) ICMPV6_ROUTER_ADVERTISEMENT /* type */, (byte) 0 /* code */, payload);
+    }
+
+    /**
+     * Build an ICMPv6 Neighbor Advertisement packet from the required specified parameters.
+     */
+    public static ByteBuffer buildNaPacket(final MacAddress srcMac, final MacAddress dstMac,
+            final Inet6Address srcIp, final Inet6Address dstIp, final int flags,
+            final Inet6Address target, final ByteBuffer... options) {
+        final NaHeader naHeader = new NaHeader(flags, target);
+        final ByteBuffer[] payload = buildIcmpv6Payload(
+                ByteBuffer.wrap(naHeader.writeToBytes(ByteOrder.BIG_ENDIAN)), options);
+        return buildIcmpv6Packet(srcMac, dstMac, srcIp, dstIp,
+                (byte) ICMPV6_NEIGHBOR_ADVERTISEMENT /* type */, (byte) 0 /* code */, payload);
+    }
+
+    /**
+     * Build an ICMPv6 Router Solicitation packet from the required specified parameters.
+     */
+    public static ByteBuffer buildRsPacket(final MacAddress srcMac, final MacAddress dstMac,
+            final Inet6Address srcIp, final Inet6Address dstIp, final ByteBuffer... options) {
+        final RsHeader rsHeader = new RsHeader((int) 0 /* reserved */);
+        final ByteBuffer[] payload = buildIcmpv6Payload(
+                ByteBuffer.wrap(rsHeader.writeToBytes(ByteOrder.BIG_ENDIAN)), options);
+        return buildIcmpv6Packet(srcMac, dstMac, srcIp, dstIp,
+                (byte) ICMPV6_ROUTER_SOLICITATION /* type */, (byte) 0 /* code */, payload);
+    }
+}
diff --git a/staticlibs/device/com/android/net/module/util/Struct.java b/staticlibs/device/com/android/net/module/util/Struct.java
index f11a5ac..d95c303 100644
--- a/staticlibs/device/com/android/net/module/util/Struct.java
+++ b/staticlibs/device/com/android/net/module/util/Struct.java
@@ -17,6 +17,7 @@
 package com.android.net.module.util;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.net.MacAddress;
 
 import java.lang.annotation.ElementType;
@@ -428,6 +429,15 @@
         return value;
     }
 
+    @Nullable
+    private Object getFieldValue(@NonNull java.lang.reflect.Field field) {
+        try {
+            return field.get(this);
+        } catch (IllegalAccessException e) {
+            throw new IllegalStateException("Cannot access field: " + field, e);
+        }
+    }
+
     private static void putFieldValue(final ByteBuffer output, final FieldInfo fieldInfo,
             final Object value) throws BufferUnderflowException {
         switch (fieldInfo.annotation.type()) {
@@ -484,6 +494,7 @@
                 output.put(bigIntegerToU64Bytes((BigInteger) value, output.order(), Type.UBE64));
                 break;
             case ByteArray:
+                checkByteArraySize((byte[]) value, fieldInfo);
                 output.put((byte[]) value);
                 break;
             case EUI48:
@@ -595,12 +606,23 @@
         return size;
     }
 
+    // Check whether the actual size of byte array matches the array size declared in
+    // annotation. For other annotation types, the actual length of field could be always
+    // deduced from annotation correctly.
+    private static void checkByteArraySize(@Nullable final byte[] array,
+            @NonNull final FieldInfo fieldInfo) {
+        Objects.requireNonNull(array, "null byte array for field " + fieldInfo.field.getName());
+        int annotationArraySize = fieldInfo.annotation.arraysize();
+        if (array.length == annotationArraySize) return;
+        throw new IllegalStateException("byte array actual length: "
+                + array.length + " doesn't match the declared array size: " + annotationArraySize);
+    }
+
     private void writeToByteBufferInternal(final ByteBuffer output, final FieldInfo[] fieldInfos) {
         for (FieldInfo fi : fieldInfos) {
+            final Object value = getFieldValue(fi.field);
             try {
-                putFieldValue(output, fi, fi.field.get(this));
-            } catch (IllegalAccessException e) {
-                throw new IllegalArgumentException("Fail to get the field value from instance", e);
+                putFieldValue(output, fi, value);
             } catch (BufferUnderflowException e) {
                 throw new IllegalArgumentException("Fail to fill raw data to ByteBuffer", e);
             }
@@ -673,12 +695,7 @@
         final FieldInfo[] fieldInfos = getClassFieldInfo(this.getClass());
         final Object[] values = new Object[fieldInfos.length];
         for (int i = 0; i < fieldInfos.length; i++) {
-            final Object value;
-            try {
-                value = fieldInfos[i].field.get(this);
-            } catch (IllegalAccessException e) {
-                throw new IllegalStateException("Cannot access field: " + fieldInfos[i].field, e);
-            }
+            final Object value = getFieldValue(fieldInfos[i].field);
             // For byte array field, put the hash code generated based on the array content into
             // the Object array instead of the reference to byte array, which might change and cause
             // to get a different hash code even with the exact same elements.
@@ -697,12 +714,7 @@
         final FieldInfo[] fieldInfos = getClassFieldInfo(this.getClass());
         for (int i = 0; i < fieldInfos.length; i++) {
             sb.append(fieldInfos[i].field.getName()).append(": ");
-            final Object value;
-            try {
-                value = fieldInfos[i].field.get(this);
-            } catch (IllegalAccessException e) {
-                throw new IllegalStateException("Cannot access field: " + fieldInfos[i].field, e);
-            }
+            final Object value = getFieldValue(fieldInfos[i].field);
             if (value == null) {
                 sb.append("null");
             } else if (fieldInfos[i].annotation.type() == Type.ByteArray) {
diff --git a/staticlibs/device/com/android/net/module/util/structs/EthernetHeader.java b/staticlibs/device/com/android/net/module/util/structs/EthernetHeader.java
new file mode 100644
index 0000000..92ef8a7
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/structs/EthernetHeader.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2021 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 com.android.net.module.util.structs;
+
+import android.net.MacAddress;
+
+import com.android.net.module.util.Struct;
+import com.android.net.module.util.Struct.Field;
+import com.android.net.module.util.Struct.Type;
+
+/**
+ * L2 ethernet header as per IEEE 802.3. Does not include a 802.1Q tag.
+ *
+ * 0                   1
+ * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |          Destination          |
+ * +-                             -+
+ * |            Ethernet           |
+ * +-                             -+
+ * |            Address            |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |             Source            |
+ * +-                             -+
+ * |            Ethernet           |
+ * +-                             -+
+ * |            Address            |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |            EtherType          |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ */
+public class EthernetHeader extends Struct {
+    @Field(order = 0, type = Type.EUI48)
+    public final MacAddress dstMac;
+    @Field(order = 1, type = Type.EUI48)
+    public final MacAddress srcMac;
+    @Field(order = 2, type = Type.U16)
+    public final int etherType;
+
+    public EthernetHeader(final MacAddress dstMac, final MacAddress srcMac,
+            final int etherType) {
+        this.dstMac = dstMac;
+        this.srcMac = srcMac;
+        this.etherType = etherType;
+    }
+}
diff --git a/staticlibs/device/com/android/net/module/util/structs/Icmpv6Header.java b/staticlibs/device/com/android/net/module/util/structs/Icmpv6Header.java
new file mode 100644
index 0000000..c82ae02
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/structs/Icmpv6Header.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2021 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 com.android.net.module.util.structs;
+
+import com.android.net.module.util.Struct;
+import com.android.net.module.util.Struct.Field;
+import com.android.net.module.util.Struct.Type;
+
+/**
+ * ICMPv6 header as per https://tools.ietf.org/html/rfc4443.
+ *
+ * 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
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |     Type      |     Code      |          Checksum             |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ */
+public class Icmpv6Header extends Struct {
+    @Field(order = 0, type = Type.U8)
+    public short type;
+    @Field(order = 1, type = Type.U8)
+    public short code;
+    @Field(order = 2, type = Type.S16)
+    public short checksum;
+
+    public Icmpv6Header(final short type, final short code, final short checksum) {
+        this.type = type;
+        this.code = code;
+        this.checksum = checksum;
+    }
+}
diff --git a/staticlibs/device/com/android/net/module/util/structs/Ipv6Header.java b/staticlibs/device/com/android/net/module/util/structs/Ipv6Header.java
new file mode 100644
index 0000000..a14e064
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/structs/Ipv6Header.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2021 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 com.android.net.module.util.structs;
+
+import com.android.net.module.util.Struct;
+import com.android.net.module.util.Struct.Field;
+import com.android.net.module.util.Struct.Type;
+
+import java.net.Inet6Address;
+
+/**
+ * L3 IPv6 header as per https://tools.ietf.org/html/rfc8200.
+ *
+ * 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
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |Version| Traffic Class |           Flow Label                  |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |         Payload Length        |  Next Header  |   Hop Limit   |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |                                                               |
+ * +                                                               +
+ * |                                                               |
+ * +                         Source Address                        +
+ * |                                                               |
+ * +                                                               +
+ * |                                                               |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |                                                               |
+ * +                                                               +
+ * |                                                               |
+ * +                      Destination Address                      +
+ * |                                                               |
+ * +                                                               +
+ * |                                                               |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ */
+public class Ipv6Header extends Struct {
+    @Field(order = 0, type = Type.S32)
+    public int vtf;
+    @Field(order = 1, type = Type.U16)
+    public int payloadLength;
+    @Field(order = 2, type = Type.S8)
+    public byte nextHeader;
+    @Field(order = 3, type = Type.U8)
+    public short hopLimit;
+    @Field(order = 4, type = Type.Ipv6Address)
+    public Inet6Address srcIp;
+    @Field(order = 5, type = Type.Ipv6Address)
+    public Inet6Address dstIp;
+
+    public Ipv6Header(final int vtf, final int payloadLength, final byte nextHeader,
+            final short hopLimit, final Inet6Address srcIp, final Inet6Address dstIp) {
+        this.vtf = vtf;
+        this.payloadLength = payloadLength;
+        this.nextHeader = nextHeader;
+        this.hopLimit = hopLimit;
+        this.srcIp = srcIp;
+        this.dstIp = dstIp;
+    }
+}
diff --git a/staticlibs/device/com/android/net/module/util/structs/LlaOption.java b/staticlibs/device/com/android/net/module/util/structs/LlaOption.java
new file mode 100644
index 0000000..fbaccab
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/structs/LlaOption.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2021 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 com.android.net.module.util.structs;
+
+import android.net.MacAddress;
+
+import com.android.net.module.util.Struct;
+import com.android.net.module.util.Struct.Field;
+import com.android.net.module.util.Struct.Type;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+/**
+ * ICMPv6 source/target link-layer address option, as per https://tools.ietf.org/html/rfc4861.
+ *
+ * 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
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |     Type      |    Length     |    Link-Layer Address ...
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ */
+public class LlaOption extends Struct {
+    @Field(order = 0, type = Type.S8)
+    public final byte type;
+    @Field(order = 1, type = Type.S8)
+    public final byte length; // Length in 8-byte units
+    @Field(order = 2, type = Type.EUI48)
+    // Link layer address length and format varies on different link layers, which is not
+    // guaranteed to be a 6-byte MAC address. However, Struct only supports 6-byte MAC
+    // addresses type(EUI-48) for now.
+    public final MacAddress linkLayerAddress;
+
+    LlaOption(final byte type, final byte length, final MacAddress linkLayerAddress) {
+        this.type = type;
+        this.length = length;
+        this.linkLayerAddress = linkLayerAddress;
+    }
+
+    /**
+     * Build a target link-layer address option from the required specified parameters.
+     */
+    public static ByteBuffer build(final byte type, final MacAddress linkLayerAddress) {
+        final LlaOption option = new LlaOption(type, (byte) 1 /* option len */, linkLayerAddress);
+        return ByteBuffer.wrap(option.writeToBytes(ByteOrder.BIG_ENDIAN));
+    }
+}
diff --git a/staticlibs/device/com/android/net/module/util/structs/MtuOption.java b/staticlibs/device/com/android/net/module/util/structs/MtuOption.java
new file mode 100644
index 0000000..34bc21c
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/structs/MtuOption.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2021 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 com.android.net.module.util.structs;
+
+import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ND_OPTION_MTU;
+
+import com.android.net.module.util.Struct;
+import com.android.net.module.util.Struct.Field;
+import com.android.net.module.util.Struct.Type;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+/**
+ * ICMPv6 MTU option, as per https://tools.ietf.org/html/rfc4861.
+ *
+ * 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
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |     Type      |    Length     |           Reserved            |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |                              MTU                              |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ */
+public class MtuOption extends Struct {
+    @Field(order = 0, type = Type.S8)
+    public final byte type;
+    @Field(order = 1, type = Type.S8)
+    public final byte length; // Length in 8-byte units
+    @Field(order = 2, type = Type.S16)
+    public final short reserved;
+    @Field(order = 3, type = Type.U32)
+    public final long mtu;
+
+    MtuOption(final byte type, final byte length, final short reserved,
+            final long mtu) {
+        this.type = type;
+        this.length = length;
+        this.reserved = reserved;
+        this.mtu = mtu;
+    }
+
+    /**
+     * Build a MTU option from the required specified parameters.
+     */
+    public static ByteBuffer build(final long mtu) {
+        final MtuOption option = new MtuOption((byte) ICMPV6_ND_OPTION_MTU,
+                (byte) 1 /* option len */, (short) 0 /* reserved */, mtu);
+        return ByteBuffer.wrap(option.writeToBytes(ByteOrder.BIG_ENDIAN));
+    }
+}
diff --git a/staticlibs/device/com/android/net/module/util/structs/NaHeader.java b/staticlibs/device/com/android/net/module/util/structs/NaHeader.java
new file mode 100644
index 0000000..571d67b
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/structs/NaHeader.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2021 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 com.android.net.module.util.structs;
+
+import com.android.net.module.util.Struct;
+import com.android.net.module.util.Struct.Field;
+import com.android.net.module.util.Struct.Type;
+
+import java.net.Inet6Address;
+
+/**
+ * ICMPv6 Neighbor Advertisement header, follow {@link Icmpv6Header}, as per
+ * https://tools.ietf.org/html/rfc4861. This does not contain any option.
+ *
+ * 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
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |     Type      |     Code      |          Checksum             |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |R|S|O|                     Reserved                            |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |                                                               |
+ * +                                                               +
+ * |                                                               |
+ * +                       Target Address                          +
+ * |                                                               |
+ * +                                                               +
+ * |                                                               |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |   Options ...
+ * +-+-+-+-+-+-+-+-+-+-+-+-
+ */
+public class NaHeader extends Struct {
+    @Field(order = 0, type = Type.S32)
+    public int flags; // Router flag, Solicited flag, Override flag and 29 Reserved bits.
+    @Field(order = 1, type = Type.Ipv6Address)
+    public Inet6Address target;
+
+    public NaHeader(final int flags, final Inet6Address target) {
+        this.flags = flags;
+        this.target = target;
+    }
+}
diff --git a/staticlibs/device/com/android/net/module/util/structs/PrefixInformationOption.java b/staticlibs/device/com/android/net/module/util/structs/PrefixInformationOption.java
new file mode 100644
index 0000000..1bdee29
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/structs/PrefixInformationOption.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2021 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 com.android.net.module.util.structs;
+
+import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ND_OPTION_PIO;
+
+import android.annotation.NonNull;
+import android.net.IpPrefix;
+
+import com.android.net.module.util.Struct;
+import com.android.net.module.util.Struct.Field;
+import com.android.net.module.util.Struct.Type;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+/**
+ * ICMPv6 prefix information option, as per https://tools.ietf.org/html/rfc4861.
+ *
+ * 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
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |     Type      |    Length     | Prefix Length |L|A| Reserved1 |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |                         Valid Lifetime                        |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |                       Preferred Lifetime                      |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |                           Reserved2                           |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |                                                               |
+ * +                                                               +
+ * |                                                               |
+ * +                            Prefix                             +
+ * |                                                               |
+ * +                                                               +
+ * |                                                               |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ */
+public class PrefixInformationOption extends Struct {
+    @Field(order = 0, type = Type.S8)
+    public final byte type;
+    @Field(order = 1, type = Type.S8)
+    public final byte length; // Length in 8-byte units
+    @Field(order = 2, type = Type.S8)
+    public final byte prefixLen;
+    @Field(order = 3, type = Type.S8)
+    // On-link flag, Autonomous address configuration flag, 6-reserved bits
+    public final byte flags;
+    @Field(order = 4, type = Type.U32)
+    public final long validLifetime;
+    @Field(order = 5, type = Type.U32)
+    public final long preferredLifetime;
+    @Field(order = 6, type = Type.S32)
+    public final int reserved;
+    @Field(order = 7, type = Type.ByteArray, arraysize = 16)
+    public final byte[] prefix;
+
+    PrefixInformationOption(final byte type, final byte length, final byte prefixLen,
+            final byte flags, final long validLifetime, final long preferredLifetime,
+            final int reserved, @NonNull final byte[] prefix) {
+        this.type = type;
+        this.length = length;
+        this.prefixLen = prefixLen;
+        this.flags = flags;
+        this.validLifetime = validLifetime;
+        this.preferredLifetime = preferredLifetime;
+        this.reserved = reserved;
+        this.prefix = prefix;
+    }
+
+    /**
+     * Build a Prefix Information option from the required specified parameters.
+     */
+    public static ByteBuffer build(final IpPrefix prefix, final byte flags,
+            final long validLifetime, final long preferredLifetime) {
+        final PrefixInformationOption option = new PrefixInformationOption(
+                (byte) ICMPV6_ND_OPTION_PIO, (byte) 4 /* option len */,
+                (byte) prefix.getPrefixLength(), flags, validLifetime, preferredLifetime,
+                (int) 0, prefix.getRawAddress());
+        return ByteBuffer.wrap(option.writeToBytes(ByteOrder.BIG_ENDIAN));
+    }
+}
diff --git a/staticlibs/device/com/android/net/module/util/structs/RaHeader.java b/staticlibs/device/com/android/net/module/util/structs/RaHeader.java
new file mode 100644
index 0000000..31a5cb7
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/structs/RaHeader.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2021 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 com.android.net.module.util.structs;
+
+import com.android.net.module.util.Struct;
+import com.android.net.module.util.Struct.Field;
+import com.android.net.module.util.Struct.Type;
+
+/**
+ * ICMPv6 Router Advertisement header, follow [Icmpv6Header], as per
+ * https://tools.ietf.org/html/rfc4861. This does not contain any option.
+ *
+ * 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
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |     Type      |     Code      |          Checksum             |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Cur Hop Limit |M|O|  Reserved |       Router Lifetime         |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |                         Reachable Time                        |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |                          Retrans Timer                        |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |   Options ...
+ * +-+-+-+-+-+-+-+-+-+-+-+-
+ */
+public class RaHeader extends Struct {
+    @Field(order = 0, type = Type.S8)
+    public final byte hopLimit;
+    // "Managed address configuration", "Other configuration" bits, and 6 reserved bits
+    @Field(order = 1, type = Type.S8)
+    public final byte flags;
+    @Field(order = 2, type = Type.U16)
+    public final int lifetime;
+    @Field(order = 3, type = Type.U32)
+    public final long reachableTime;
+    @Field(order = 4, type = Type.U32)
+    public final long retransTimer;
+
+    public RaHeader(final byte hopLimit, final byte flags, final int lifetime,
+            final long reachableTime, final long retransTimer) {
+        this.hopLimit = hopLimit;
+        this.flags = flags;
+        this.lifetime = lifetime;
+        this.reachableTime = reachableTime;
+        this.retransTimer = retransTimer;
+    }
+}
diff --git a/staticlibs/device/com/android/net/module/util/structs/RdnssOption.java b/staticlibs/device/com/android/net/module/util/structs/RdnssOption.java
new file mode 100644
index 0000000..b7c2b0c
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/structs/RdnssOption.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2021 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 com.android.net.module.util.structs;
+
+import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ND_OPTION_RDNSS;
+
+import android.net.InetAddresses;
+
+import com.android.net.module.util.Struct;
+import com.android.net.module.util.Struct.Field;
+import com.android.net.module.util.Struct.Type;
+
+import java.net.Inet6Address;
+import java.nio.ByteBuffer;
+
+/**
+ * IPv6 RA recursive DNS server option, as per https://tools.ietf.org/html/rfc8106.
+ * This should be followed by a series of DNSv6 server addresses.
+ *
+ * 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
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |     Type      |     Length    |           Reserved            |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |                           Lifetime                            |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |                                                               |
+ * :            Addresses of IPv6 Recursive DNS Servers            :
+ * |                                                               |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ */
+public class RdnssOption extends Struct {
+    @Field(order = 0, type = Type.S8)
+    public final byte type;
+    @Field(order = 1, type = Type.S8)
+    public final byte length; // Length in 8-byte units
+    @Field(order = 2, type = Type.S16)
+    public final short reserved;
+    @Field(order = 3, type = Type.U32)
+    public final long lifetime;
+
+    RdnssOption(final byte type, final byte length, final short reserved, final long lifetime) {
+        this.type = type;
+        this.length = length;
+        this.reserved = reserved;
+        this.lifetime = lifetime;
+    }
+
+    /**
+     * Build a RDNSS option from the required specified Inet6Address parameters.
+     */
+    public static ByteBuffer build(final long lifetime, final Inet6Address... servers) {
+        final byte length = (byte) (1 + 2 * servers.length);
+        final RdnssOption option = new RdnssOption((byte) ICMPV6_ND_OPTION_RDNSS,
+                length, (short) 0, lifetime);
+        final ByteBuffer buffer = ByteBuffer.allocate(length * 8);
+        option.writeToByteBuffer(buffer);
+        for (Inet6Address server : servers) {
+            buffer.put(server.getAddress());
+        }
+        buffer.flip();
+        return buffer;
+    }
+
+    /**
+     * Build a RDNSS option from the required specified String parameters.
+     *
+     * @throws IllegalArgumentException if {@code servers} does not contain only numeric addresses.
+     */
+    public static ByteBuffer build(final long lifetime, final String... servers) {
+        final Inet6Address[] serverArray = new Inet6Address[servers.length];
+        for (int i = 0; i < servers.length; i++) {
+            serverArray[i] = (Inet6Address) InetAddresses.parseNumericAddress(servers[i]);
+        }
+        return build(lifetime, serverArray);
+    }
+}
diff --git a/staticlibs/device/com/android/net/module/util/structs/RsHeader.java b/staticlibs/device/com/android/net/module/util/structs/RsHeader.java
new file mode 100644
index 0000000..0b51ff2
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/structs/RsHeader.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2021 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 com.android.net.module.util.structs;
+
+import com.android.net.module.util.Struct;
+import com.android.net.module.util.Struct.Field;
+import com.android.net.module.util.Struct.Type;
+
+/**
+ * ICMPv6 Router Solicitation header, follow [Icmpv6Header], as per
+ * https://tools.ietf.org/html/rfc4861. This does not contain any option.
+ *
+ * 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
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |     Type      |     Code      |          Checksum             |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |                            Reserved                           |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |   Options ...
+ * +-+-+-+-+-+-+-+-+-+-+-+-
+ */
+public class RsHeader extends Struct {
+    @Field(order = 0, type = Type.S32)
+    public final int reserved;
+
+    public RsHeader(final int reserved) {
+        this.reserved = reserved;
+    }
+}
diff --git a/staticlibs/framework/com/android/net/module/util/CollectionUtils.java b/staticlibs/framework/com/android/net/module/util/CollectionUtils.java
index e5bb58d..2223443 100644
--- a/staticlibs/framework/com/android/net/module/util/CollectionUtils.java
+++ b/staticlibs/framework/com/android/net/module/util/CollectionUtils.java
@@ -38,6 +38,13 @@
     }
 
     /**
+     * @return True if the collection is null or 0-length.
+     */
+    public static <T> boolean isEmpty(@Nullable Collection<T> collection) {
+        return collection == null || collection.isEmpty();
+    }
+
+    /**
      * Returns an int array from the given Integer list.
      */
     @NonNull
@@ -76,15 +83,26 @@
         return true;
 
     }
+
     /**
      * @return True if any element satisfies the predicate, false otherwise.
      *   Note that means this always returns false for empty collections.
      */
     public static <T> boolean any(@NonNull Collection<T> elem, @NonNull Predicate<T> predicate) {
+        return indexOf(elem, predicate) >= 0;
+    }
+
+    /**
+     * @return The index of the first element that matches the predicate, or -1 if none.
+     */
+    @Nullable
+    public static <T> int indexOf(@NonNull Collection<T> elem, @NonNull Predicate<T> predicate) {
+        int idx = 0;
         for (final T e : elem) {
-            if (predicate.test(e)) return true;
+            if (predicate.test(e)) return idx;
+            idx++;
         }
-        return false;
+        return -1;
     }
 
     /**
diff --git a/staticlibs/framework/com/android/net/module/util/NetworkCapabilitiesUtils.java b/staticlibs/framework/com/android/net/module/util/NetworkCapabilitiesUtils.java
index 5a0200f..3de78c6 100644
--- a/staticlibs/framework/com/android/net/module/util/NetworkCapabilitiesUtils.java
+++ b/staticlibs/framework/com/android/net/module/util/NetworkCapabilitiesUtils.java
@@ -78,4 +78,31 @@
         }
         return transports[0];
     }
+
+    /**
+     * Unpacks long value into an array of bits.
+     */
+    public static int[] unpackBits(long val) {
+        int size = Long.bitCount(val);
+        int[] result = new int[size];
+        int index = 0;
+        int bitPos = 0;
+        while (val != 0) {
+            if ((val & 1) == 1) result[index++] = bitPos;
+            val = val >>> 1;
+            bitPos++;
+        }
+        return result;
+    }
+
+    /**
+     * Packs array of bits into a long value.
+     */
+    public static long packBits(int[] bits) {
+        long packed = 0;
+        for (int b : bits) {
+            packed |= (1L << b);
+        }
+        return packed;
+    }
 }
diff --git a/staticlibs/framework/com/android/net/module/util/NetworkStackConstants.java b/staticlibs/framework/com/android/net/module/util/NetworkStackConstants.java
index 5f62186..499297c 100644
--- a/staticlibs/framework/com/android/net/module/util/NetworkStackConstants.java
+++ b/staticlibs/framework/com/android/net/module/util/NetworkStackConstants.java
@@ -148,9 +148,18 @@
     public static final int ICMPV6_ND_OPTION_RDNSS = 25;
     public static final int ICMPV6_ND_OPTION_PREF64 = 38;
 
-
     public static final int ICMPV6_RA_HEADER_LEN = 16;
 
+    public static final int NEIGHBOR_ADVERTISEMENT_FLAG_ROUTER    = 1 << 31;
+    public static final int NEIGHBOR_ADVERTISEMENT_FLAG_SOLICITED = 1 << 30;
+    public static final int NEIGHBOR_ADVERTISEMENT_FLAG_OVERRIDE  = 1 << 29;
+
+    public static final byte ROUTER_ADVERTISEMENT_FLAG_MANAGED_ADDRESS = (byte) (1 << 7);
+    public static final byte ROUTER_ADVERTISEMENT_FLAG_OTHER = (byte) (1 << 6);
+
+    public static final byte PIO_FLAG_ON_LINK = (byte) (1 << 7);
+    public static final byte PIO_FLAG_AUTONOMOUS = (byte) (1 << 6);
+
     /**
      * UDP constants.
      *
diff --git a/staticlibs/hostdevice/com/android/testutils/MiscAsserts.kt b/staticlibs/hostdevice/com/android/testutils/MiscAsserts.kt
index 09126d7..91fb1cd 100644
--- a/staticlibs/hostdevice/com/android/testutils/MiscAsserts.kt
+++ b/staticlibs/hostdevice/com/android/testutils/MiscAsserts.kt
@@ -32,10 +32,18 @@
     assertEquals(0, len, "Expected empty array, but length was $len")
 }
 
+fun <T> assertEmpty(ts: List<T>) = ts.size.let { len ->
+    assertEquals(0, len, "Expected empty list, but length was $len")
+}
+
 fun <T> assertLength(expected: Int, got: Array<T>) = got.size.let { len ->
     assertEquals(expected, len, "Expected array of length $expected, but was $len for $got")
 }
 
+fun <T> assertLength(expected: Int, got: List<T>) = got.size.let { len ->
+    assertEquals(expected, len, "Expected list of length $expected, but was $len for $got")
+}
+
 // Bridge method to help write this in Java. If you're writing Kotlin, consider using
 // kotlin.test.assertFailsWith instead, as that method is reified and inlined.
 fun <T : Exception> assertThrows(expected: Class<T>, block: ThrowingRunnable): T {
@@ -103,3 +111,12 @@
         !Modifier.isStatic(it.modifiers) && !Modifier.isTransient(it.modifiers)
     }.size)
 }
+
+fun assertSameElements(expected: List<String?>, actual: List<String?>) {
+    val expectedSet: HashSet<String?> = HashSet<String?>(expected)
+    assertEquals(expectedSet.size.toLong(), expected.size.toLong(),
+            "expected list contains duplicates")
+    val actualSet: HashSet<String?> = HashSet<String?>(actual)
+    assertEquals(actualSet.size.toLong(), actual.size.toLong(), "actual list contains duplicates")
+    assertEquals(expectedSet, actualSet)
+}
diff --git a/staticlibs/tests/unit/src/android/net/util/LinkPropertiesUtilsTest.java b/staticlibs/tests/unit/src/android/net/util/LinkPropertiesUtilsTest.java
index e47c864..45493bd 100644
--- a/staticlibs/tests/unit/src/android/net/util/LinkPropertiesUtilsTest.java
+++ b/staticlibs/tests/unit/src/android/net/util/LinkPropertiesUtilsTest.java
@@ -16,6 +16,8 @@
 
 package android.net.util;
 
+import static com.android.testutils.MiscAsserts.assertSameElements;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
@@ -40,7 +42,6 @@
 import java.net.InetAddress;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.HashSet;
 import java.util.List;
 import java.util.function.Function;
 
@@ -247,14 +248,6 @@
         assertEquals(linkAddr2, results.added.get(0));
     }
 
-    private void assertSameElements(List<String> expected, List<String> actual) {
-        HashSet<String> expectedSet = new HashSet(expected);
-        assertEquals("expected list contains duplicates", expectedSet.size(), expected.size());
-        HashSet<String> actualSet = new HashSet(actual);
-        assertEquals("actual list contains duplicates", actualSet.size(), actual.size());
-        assertEquals(expectedSet, actualSet);
-    }
-
     private void assertCompareOrUpdateResult(CompareOrUpdateResult result,
             List<String> expectedAdded, List<String> expectedRemoved,
             List<String> expectedUpdated) {
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/CollectionUtilsTest.kt b/staticlibs/tests/unit/src/com/android/net/module/util/CollectionUtilsTest.kt
index 0007742..0886426 100644
--- a/staticlibs/tests/unit/src/com/android/net/module/util/CollectionUtilsTest.kt
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/CollectionUtilsTest.kt
@@ -20,6 +20,7 @@
 import androidx.test.runner.AndroidJUnit4
 import org.junit.Test
 import org.junit.runner.RunWith
+import kotlin.test.assertEquals
 import kotlin.test.assertFalse
 import kotlin.test.assertTrue
 
@@ -40,6 +41,15 @@
     }
 
     @Test
+    fun testIndexOf() {
+        assertEquals(4, CollectionUtils.indexOf(listOf("A", "B", "C", "D", "E")) { it == "E" })
+        assertEquals(0, CollectionUtils.indexOf(listOf("A", "B", "C", "D", "E")) { it == "A" })
+        assertEquals(1, CollectionUtils.indexOf(listOf("AA", "BBB", "CCCC")) { it.length >= 3 })
+        assertEquals(1, CollectionUtils.indexOf(listOf("AA", null, "CCCC")) { it == null })
+        assertEquals(1, CollectionUtils.indexOf(listOf(null, "CCCC")) { it != null })
+    }
+
+    @Test
     fun testAll() {
         assertFalse(CollectionUtils.all(listOf("A", "B", "C", "D", "E")) { it != "E" })
         assertTrue(CollectionUtils.all(listOf("A", "B", "C", "D", "E")) { it != "F" })
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/NetworkCapabilitiesUtilsTest.kt b/staticlibs/tests/unit/src/com/android/net/module/util/NetworkCapabilitiesUtilsTest.kt
index e94d132..df2f459 100644
--- a/staticlibs/tests/unit/src/com/android/net/module/util/NetworkCapabilitiesUtilsTest.kt
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/NetworkCapabilitiesUtilsTest.kt
@@ -26,11 +26,14 @@
 import androidx.test.filters.SmallTest
 import androidx.test.runner.AndroidJUnit4
 import com.android.net.module.util.NetworkCapabilitiesUtils.getDisplayTransport
+import com.android.net.module.util.NetworkCapabilitiesUtils.packBits
+import com.android.net.module.util.NetworkCapabilitiesUtils.unpackBits
 import org.junit.Test
 import org.junit.runner.RunWith
 import java.lang.IllegalArgumentException
 import kotlin.test.assertEquals
 import kotlin.test.assertFailsWith
+import kotlin.test.assertTrue
 
 @RunWith(AndroidJUnit4::class)
 @SmallTest
@@ -58,4 +61,21 @@
             getDisplayTransport(intArrayOf())
         }
     }
-}
\ No newline at end of file
+
+    @Test
+    fun testBitPackingTestCase() {
+        runBitPackingTestCase(0, intArrayOf())
+        runBitPackingTestCase(1, intArrayOf(0))
+        runBitPackingTestCase(3, intArrayOf(0, 1))
+        runBitPackingTestCase(4, intArrayOf(2))
+        runBitPackingTestCase(63, intArrayOf(0, 1, 2, 3, 4, 5))
+        runBitPackingTestCase(Long.MAX_VALUE.inv(), intArrayOf(63))
+        runBitPackingTestCase(Long.MAX_VALUE.inv() + 1, intArrayOf(0, 63))
+        runBitPackingTestCase(Long.MAX_VALUE.inv() + 2, intArrayOf(1, 63))
+    }
+
+    fun runBitPackingTestCase(packedBits: Long, bits: IntArray) {
+        assertEquals(packedBits, packBits(bits))
+        assertTrue(bits contentEquals unpackBits(packedBits))
+    }
+}
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/StructTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/StructTest.java
index b172e21..df74398 100644
--- a/staticlibs/tests/unit/src/com/android/net/module/util/StructTest.java
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/StructTest.java
@@ -1027,4 +1027,48 @@
         assertTrue(msg.equals(msg1));
         assertEquals(msg.hashCode(), msg1.hashCode());
     }
+
+    static class InvalidByteArray extends Struct {
+        @Field(order = 0, type = Type.ByteArray, arraysize = 12) public byte[] bytes;
+    }
+
+    @Test
+    public void testStructClass_WrongByteArraySize() {
+        final InvalidByteArray msg = doParsingMessageTest("20010db80003000400050006",
+                InvalidByteArray.class, ByteOrder.BIG_ENDIAN);
+
+        // Actual byte array size doesn't match the size declared in the annotation.
+        msg.bytes = new byte[16];
+        assertThrows(IllegalStateException.class, () -> msg.writeToBytes());
+
+        final ByteBuffer output = ByteBuffer.allocate(Struct.getSize(InvalidByteArray.class));
+        output.order(ByteOrder.LITTLE_ENDIAN);
+        assertThrows(IllegalStateException.class, () -> msg.writeToByteBuffer(output));
+    }
+
+    @Test
+    public void testStructClass_NullByteArray() {
+        final InvalidByteArray msg = doParsingMessageTest("20010db80003000400050006",
+                InvalidByteArray.class, ByteOrder.BIG_ENDIAN);
+
+        msg.bytes = null;
+        assertThrows(NullPointerException.class, () -> msg.writeToBytes());
+
+        final ByteBuffer output = ByteBuffer.allocate(Struct.getSize(InvalidByteArray.class));
+        output.order(ByteOrder.LITTLE_ENDIAN);
+        assertThrows(NullPointerException.class, () -> msg.writeToByteBuffer(output));
+    }
+
+    @Test
+    public void testStructClass_ParsingByteArrayAfterInitialization() {
+        InvalidByteArray msg = new InvalidByteArray();
+        msg.bytes = new byte[]{(byte) 0x00, (byte) 0x01, (byte) 0x02, (byte) 0x03};
+
+        // Although bytes member has been initialized with the length different with
+        // annotation size, parsing from ByteBuffer will get bytes member have the
+        // reference to byte array with correct size.
+        msg = doParsingMessageTest("20010db80003000400050006", InvalidByteArray.class,
+                ByteOrder.BIG_ENDIAN);
+        assertArrayEquals(TEST_PREFIX64, msg.bytes);
+    }
 }