Mark ab/6881855 as merged

Bug: 172690556
Change-Id: I387ce9a1a766a23c1cc7ea298d7e6936a9a3b0f6
diff --git a/staticlibs/Android.bp b/staticlibs/Android.bp
index e5cc8b1..a1a998e 100644
--- a/staticlibs/Android.bp
+++ b/staticlibs/Android.bp
@@ -45,10 +45,11 @@
   ],
   visibility: [
         "//frameworks/base/packages/Tethering",
+        "//packages/modules/Connectivity/Tethering",
         "//frameworks/opt/net/ike",
         "//frameworks/opt/net/wifi/service",
         "//frameworks/opt/net/telephony",
-        "//packages/modules/NetworkStack",
+        "//packages/modules/NetworkStack:__subpackages__",
         "//packages/modules/CaptivePortalLogin",
         "//frameworks/libs/net/common/tests:__subpackages__",
   ],
@@ -123,7 +124,10 @@
     jarjar_rules: "jarjar-rules-shared.txt",
     visibility: [
         "//cts/tests/tests/net",
+        "//packages/modules/Connectivity/tests/cts/net",
         "//frameworks/base/packages/Tethering",
+        "//packages/modules/Connectivity/Tethering",
+        "//frameworks/base/tests:__subpackages__",
         "//frameworks/opt/net/ike",
         "//frameworks/opt/telephony",
         "//frameworks/base/wifi:__subpackages__",
@@ -133,10 +137,20 @@
     ]
 }
 
+filegroup {
+    name: "net-utils-services-common-srcs",
+    srcs: [
+        "device/android/net/NetworkFactory.java",
+    ],
+    visibility: [
+        "//frameworks/base/services/net",
+    ],
+}
+
 java_library {
     name: "net-utils-services-common",
     srcs: [
-        "device/android/net/NetworkFactory.java",
+        ":net-utils-services-common-srcs",
         ":framework-annotations",
     ],
     sdk_version: "system_current",
diff --git a/staticlibs/device/android/net/util/Struct.java b/staticlibs/device/android/net/util/Struct.java
new file mode 100644
index 0000000..92e05aa
--- /dev/null
+++ b/staticlibs/device/android/net/util/Struct.java
@@ -0,0 +1,377 @@
+/*
+ * Copyright (C) 2020 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.util;
+
+import android.annotation.NonNull;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Modifier;
+import java.math.BigInteger;
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+/**
+ * Define a generic class that helps to parse the structured message.
+ *
+ * Example usage:
+ *
+ *    // C-style NduserOption message header definition in the kernel:
+ *    struct nduseroptmsg {
+ *        unsigned char nduseropt_family;
+ *        unsigned char nduseropt_pad1;
+ *        unsigned short nduseropt_opts_len;
+ *        int nduseropt_ifindex;
+ *        __u8 nduseropt_icmp_type;
+ *        __u8 nduseropt_icmp_code;
+ *        unsigned short nduseropt_pad2;
+ *        unsigned int nduseropt_pad3;
+ *    }
+ *
+ *    - Declare a subclass with explicit constructor or not which extends from this class to parse
+ *      NduserOption header from raw bytes array.
+ *
+ *    - Option w/ explicit constructor:
+ *      static class NduserOptHeaderMessage extends Struct {
+ *          @Field(order = 0, type = Type.U8, padding = 1)
+ *          final short family;
+ *          @Field(order = 1, type = Type.U16)
+ *          final int len;
+ *          @Field(order = 2, type = Type.S32)
+ *          final int ifindex;
+ *          @Field(order = 3, type = Type.U8)
+ *          final short type;
+ *          @Field(order = 4, type = Type.U8, padding = 6)
+ *          final short code;
+ *
+ *          NduserOptHeaderMessage(final short family, final int len, final int ifindex,
+ *                  final short type, final short code) {
+ *              this.family = family;
+ *              this.len = len;
+ *              this.ifindex = ifindex;
+ *              this.type = type;
+ *              this.code = code;
+ *          }
+ *      }
+ *
+ *      - Option w/o explicit constructor:
+ *        static class NduserOptHeaderMessage extends Struct {
+ *            @Field(order = 0, type = Type.U8, padding = 1)
+ *            short family;
+ *            @Field(order = 1, type = Type.U16)
+ *            int len;
+ *            @Field(order = 2, type = Type.S32)
+ *            int ifindex;
+ *            @Field(order = 3, type = Type.U8)
+ *            short type;
+ *            @Field(order = 4, type = Type.U8, padding = 6)
+ *            short code;
+ *        }
+ *
+ *    - Parse the target message and refer the members.
+ *      final ByteBuffer buf = ByteBuffer.wrap(RAW_BYTES_ARRAY);
+ *      buf.order(ByteOrder.nativeOrder());
+ *      final NduserOptHeaderMessage nduserHdrMsg = Struct.parse(NduserOptHeaderMessage.class, buf);
+ *      assertEquals(10, nduserHdrMsg.family);
+ */
+public class Struct {
+    public enum Type {
+        U8,        // unsigned byte,  size = 1 byte
+        U16,       // unsigned short, size = 2 bytes
+        U32,       // unsigned int,   size = 4 bytes
+        U64,       // unsigned long,  size = 8 bytes
+        S8,        // signed byte,    size = 1 byte
+        S16,       // signed short,   size = 2 bytes
+        S32,       // signed int,     size = 4 bytes
+        S64,       // signed long,    size = 8 bytes
+        BE16,      // unsigned short in network order, size = 2 bytes
+        BE32,      // unsigned int in network order,   size = 4 bytes
+        BE64,      // unsigned long in network order,  size = 8 bytes
+        ByteArray, // byte array with predefined length
+    }
+
+    /**
+     * Indicate that the field marked with this annotation will automatically be managed by this
+     * class (e.g., will be parsed by #parse).
+     *
+     * order:     The placeholder associated with each field, consecutive order starting from zero.
+     * type:      The primitive data type listed in above Type enumeration.
+     * padding:   Padding bytes appear after the field for alignment.
+     * arraysize: The length of byte array.
+     *
+     * Annotation associated with field MUST have order and type properties at least, padding
+     * and arraysize properties depend on the specific usage, if these properties are absence,
+     * then default value 0 will be applied.
+     */
+    @Retention(RetentionPolicy.RUNTIME)
+    @Target(ElementType.FIELD)
+    public @interface Field {
+        int order();
+        Type type();
+        int padding() default 0;
+        int arraysize() default 0;
+    }
+
+    private static class FieldInfo {
+        @NonNull
+        public final Field annotation;
+        @NonNull
+        public final java.lang.reflect.Field field;
+
+        FieldInfo(final Field annotation, final java.lang.reflect.Field field) {
+            this.annotation = annotation;
+            this.field = field;
+        }
+    }
+
+    private static void checkAnnotationType(final Type type, final Class fieldType) {
+        switch (type) {
+            case U8:
+            case S16:
+                if (fieldType == Short.TYPE) return;
+                break;
+            case U16:
+            case S32:
+            case BE16:
+                if (fieldType == Integer.TYPE) return;
+                break;
+            case U32:
+            case S64:
+            case BE32:
+                if (fieldType == Long.TYPE) return;
+                break;
+            case U64:
+            case BE64:
+                if (fieldType == BigInteger.class || fieldType == Long.TYPE) return;
+                break;
+            case S8:
+                if (fieldType == Byte.TYPE) return;
+                break;
+            case ByteArray:
+                if (fieldType == byte[].class) return;
+                break;
+            default:
+                throw new IllegalArgumentException("Unknown type" + type);
+        }
+        throw new IllegalArgumentException("Invalid primitive data type: " + fieldType
+                + "for annotation type: " + type);
+    }
+
+    private static boolean isStructSubclass(final Class clazz) {
+        return clazz != null && Struct.class.isAssignableFrom(clazz) && Struct.class != clazz;
+    }
+
+    private static int getAnnotationFieldCount(final Class clazz) {
+        int count = 0;
+        for (java.lang.reflect.Field field : clazz.getDeclaredFields()) {
+            if (field.isAnnotationPresent(Field.class)) count++;
+        }
+        return count;
+    }
+
+    private static boolean matchModifier(final FieldInfo[] fields, boolean immutable) {
+        for (FieldInfo fi : fields) {
+            if (Modifier.isFinal(fi.field.getModifiers()) != immutable) return false;
+        }
+        return true;
+    }
+
+    private static boolean hasBothMutableAndImmutableFields(final FieldInfo[] fields) {
+        return !matchModifier(fields, true /* immutable */)
+                && !matchModifier(fields, false /* mutable */);
+    }
+
+    private static boolean matchConstructor(final Constructor cons, final FieldInfo[] fields) {
+        final Class[] paramTypes = cons.getParameterTypes();
+        if (paramTypes.length != fields.length) return false;
+        for (int i = 0; i < paramTypes.length; i++) {
+            if (!paramTypes[i].equals(fields[i].field.getType())) return false;
+        }
+        return true;
+    }
+
+    private static BigInteger readBigInteger(final ByteBuffer buf) {
+        // The magnitude argument of BigInteger constructor is a byte array in big-endian order.
+        // If ByteBuffer is read in little-endian, reverse the order of the bytes is required;
+        // if ByteBuffer is read in big-endian, then just keep it as-is.
+        final byte[] input = new byte[8];
+        for (int i = 0; i < 8; i++) {
+            input[(buf.order() == ByteOrder.LITTLE_ENDIAN ? input.length - 1 - i : i)] = buf.get();
+        }
+        return new BigInteger(1, input);
+    }
+
+    private static Object getFieldValue(final ByteBuffer buf, final FieldInfo fieldInfo)
+            throws BufferUnderflowException {
+        final Object value;
+        checkAnnotationType(fieldInfo.annotation.type(), fieldInfo.field.getType());
+        switch (fieldInfo.annotation.type()) {
+            case U8:
+                value = (short) (buf.get() & 0xFF);
+                break;
+            case U16:
+                value = (int) (buf.getShort() & 0xFFFF);
+                break;
+            case U32:
+                value = (long) (buf.getInt() & 0xFFFFFFFFL);
+                break;
+            case U64:
+                if (fieldInfo.field.getType() == BigInteger.class) {
+                    value = readBigInteger(buf);
+                } else {
+                    value = buf.getLong();
+                }
+                break;
+            case S8:
+                value = buf.get();
+                break;
+            case S16:
+                value = buf.getShort();
+                break;
+            case S32:
+                value = buf.getInt();
+                break;
+            case S64:
+                value = buf.getLong();
+                break;
+            case BE16:
+                if (buf.order() == ByteOrder.LITTLE_ENDIAN) {
+                    value = (int) (Short.reverseBytes(buf.getShort()) & 0xFFFF);
+                } else {
+                    value = (int) (buf.getShort() & 0xFFFF);
+                }
+                break;
+            case BE32:
+                if (buf.order() == ByteOrder.LITTLE_ENDIAN) {
+                    value = (long) (Integer.reverseBytes(buf.getInt()) & 0xFFFFFFFFL);
+                } else {
+                    value = (long) (buf.getInt() & 0xFFFFFFFFL);
+                }
+                break;
+            case BE64:
+                if (fieldInfo.field.getType() == BigInteger.class) {
+                    value = readBigInteger(buf);
+                } else {
+                    if (buf.order() == ByteOrder.LITTLE_ENDIAN) {
+                        value = Long.reverseBytes(buf.getLong());
+                    } else {
+                        value = buf.getLong();
+                    }
+                }
+                break;
+            case ByteArray:
+                final byte[] array = new byte[fieldInfo.annotation.arraysize()];
+                buf.get(array);
+                value = array;
+                break;
+            default:
+                throw new IllegalArgumentException("Unknown type:" + fieldInfo.annotation.type());
+        }
+
+        // Skip the padding data for alignment if any.
+        if (fieldInfo.annotation.padding() > 0) {
+            buf.position(buf.position() + fieldInfo.annotation.padding());
+        }
+        return value;
+    }
+
+    private static FieldInfo[] getClassFieldInfo(final Class clazz) {
+        if (!isStructSubclass(clazz)) {
+            throw new IllegalArgumentException(clazz.getName() + " is not a subclass of "
+                    + Struct.class.getName());
+        }
+
+        final FieldInfo[] annotationFields = new FieldInfo[getAnnotationFieldCount(clazz)];
+
+        // Since array returned from Class#getDeclaredFields doesn't guarantee the actual order
+        // of field appeared in the class, that is a problem when parsing raw data read from
+        // ByteBuffer. Store the fields appeared by the order() defined in the Field annotation.
+        for (java.lang.reflect.Field field : clazz.getDeclaredFields()) {
+            if (Modifier.isStatic(field.getModifiers())) continue;
+
+            final Field annotation = field.getAnnotation(Field.class);
+            if (annotation == null) {
+                throw new IllegalArgumentException("Field " + field.getName()
+                        + " is missing the " + Field.class.getSimpleName()
+                        + " annotation");
+            }
+            if (annotation.order() < 0 || annotation.order() >= annotationFields.length) {
+                throw new IllegalArgumentException("Annotation order: " + annotation.order()
+                        + " is negative or non-consecutive");
+            }
+            if (annotationFields[annotation.order()] != null) {
+                throw new IllegalArgumentException("Duplicated annotation order: "
+                        + annotation.order());
+            }
+            annotationFields[annotation.order()] = new FieldInfo(annotation, field);
+        }
+        return annotationFields;
+    }
+
+    /**
+     * Parse raw data from ByteBuffer according to the pre-defined annotation rule and return
+     * the type-variable object which is subclass of Struct class.
+     *
+     * TODO:
+     * 1. Support subclass inheritance.
+     * 2. Introduce annotation processor to enforce the subclass naming schema.
+     */
+    public static <T> T parse(final Class<T> clazz, final ByteBuffer buf) {
+        try {
+            final FieldInfo[] foundFields = getClassFieldInfo(clazz);
+            if (hasBothMutableAndImmutableFields(foundFields)) {
+                throw new IllegalArgumentException("Class has both immutable and mutable fields");
+            }
+
+            Constructor<?> constructor = null;
+            Constructor<?> defaultConstructor = null;
+            final Constructor<?>[] constructors = clazz.getDeclaredConstructors();
+            for (Constructor cons : constructors) {
+                if (matchConstructor(cons, foundFields)) constructor = cons;
+                if (cons.getParameterTypes().length == 0) defaultConstructor = cons;
+            }
+
+            if (constructor == null && defaultConstructor == null) {
+                throw new IllegalArgumentException("Fail to find available constructor");
+            }
+            if (constructor != null) {
+                final Object[] args = new Object[foundFields.length];
+                for (int i = 0; i < args.length; i++) {
+                    args[i] = getFieldValue(buf, foundFields[i]);
+                }
+                return (T) constructor.newInstance(args);
+            }
+
+            final Object instance = defaultConstructor.newInstance();
+            for (FieldInfo fi : foundFields) {
+                fi.field.set(instance, getFieldValue(buf, fi));
+            }
+            return (T) instance;
+        } catch (IllegalAccessException
+                | InvocationTargetException
+                | InstantiationException e) {
+            throw new IllegalArgumentException("Fail to create a instance from constructor", e);
+        } catch (BufferUnderflowException e) {
+            throw new IllegalArgumentException("Fail to read raw data from ByteBuffer", e);
+        }
+    }
+}
diff --git a/staticlibs/device/com/android/net/module/util/NetworkStackConstants.java b/staticlibs/device/com/android/net/module/util/NetworkStackConstants.java
new file mode 100644
index 0000000..1088477
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/NetworkStackConstants.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2020 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 java.net.Inet4Address;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+
+/**
+ * Network constants used by the network stack.
+ */
+public final class NetworkStackConstants {
+
+    /**
+     * Ethernet constants.
+     *
+     * See also:
+     *     - https://tools.ietf.org/html/rfc894
+     *     - https://tools.ietf.org/html/rfc2464
+     *     - https://tools.ietf.org/html/rfc7042
+     *     - http://www.iana.org/assignments/ethernet-numbers/ethernet-numbers.xhtml
+     *     - http://www.iana.org/assignments/ieee-802-numbers/ieee-802-numbers.xhtml
+     */
+    public static final int ETHER_DST_ADDR_OFFSET = 0;
+    public static final int ETHER_SRC_ADDR_OFFSET = 6;
+    public static final int ETHER_ADDR_LEN = 6;
+    public static final int ETHER_TYPE_OFFSET = 12;
+    public static final int ETHER_TYPE_LENGTH = 2;
+    public static final int ETHER_TYPE_ARP  = 0x0806;
+    public static final int ETHER_TYPE_IPV4 = 0x0800;
+    public static final int ETHER_TYPE_IPV6 = 0x86dd;
+    public static final int ETHER_HEADER_LEN = 14;
+    public static final int ETHER_MTU = 1500;
+
+    /**
+     * ARP constants.
+     *
+     * See also:
+     *     - https://tools.ietf.org/html/rfc826
+     *     - http://www.iana.org/assignments/arp-parameters/arp-parameters.xhtml
+     */
+    public static final int ARP_PAYLOAD_LEN = 28;  // For Ethernet+IPv4.
+    public static final int ARP_ETHER_IPV4_LEN = ARP_PAYLOAD_LEN + ETHER_HEADER_LEN;
+    public static final int ARP_REQUEST = 1;
+    public static final int ARP_REPLY   = 2;
+    public static final int ARP_HWTYPE_RESERVED_LO = 0;
+    public static final int ARP_HWTYPE_ETHER       = 1;
+    public static final int ARP_HWTYPE_RESERVED_HI = 0xffff;
+
+    /**
+     * IPv4 Address Conflict Detection constants.
+     *
+     * See also:
+     *     - https://tools.ietf.org/html/rfc5227
+     */
+    public static final int IPV4_CONFLICT_PROBE_NUM = 3;
+    public static final int IPV4_CONFLICT_ANNOUNCE_NUM = 2;
+
+    /**
+     * IPv4 constants.
+     *
+     * See also:
+     *     - https://tools.ietf.org/html/rfc791
+     */
+    public static final int IPV4_ADDR_BITS = 32;
+    public static final int IPV4_MIN_MTU = 68;
+    public static final int IPV4_MAX_MTU = 65_535;
+    public static final int IPV4_HEADER_MIN_LEN = 20;
+    public static final int IPV4_IHL_MASK = 0xf;
+    public static final int IPV4_FLAGS_OFFSET = 6;
+    public static final int IPV4_FRAGMENT_MASK = 0x1fff;
+    public static final int IPV4_PROTOCOL_OFFSET = 9;
+    public static final int IPV4_SRC_ADDR_OFFSET = 12;
+    public static final int IPV4_DST_ADDR_OFFSET = 16;
+    public static final int IPV4_ADDR_LEN = 4;
+    public static final Inet4Address IPV4_ADDR_ALL = makeInet4Address(
+            (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff);
+    public static final Inet4Address IPV4_ADDR_ANY = makeInet4Address(
+            (byte) 0, (byte) 0, (byte) 0, (byte) 0);
+
+    /**
+     * IPv6 constants.
+     *
+     * See also:
+     *     - https://tools.ietf.org/html/rfc2460
+     */
+    public static final int IPV6_ADDR_LEN = 16;
+    public static final int IPV6_HEADER_LEN = 40;
+    public static final int IPV6_LEN_OFFSET = 4;
+    public static final int IPV6_PROTOCOL_OFFSET = 6;
+    public static final int IPV6_SRC_ADDR_OFFSET = 8;
+    public static final int IPV6_DST_ADDR_OFFSET = 24;
+    public static final int IPV6_MIN_MTU = 1280;
+
+    /**
+     * ICMPv6 constants.
+     *
+     * See also:
+     *     - https://tools.ietf.org/html/rfc4443
+     *     - https://tools.ietf.org/html/rfc4861
+     */
+    public static final int ICMPV6_HEADER_MIN_LEN = 4;
+    public static final int ICMPV6_CHECKSUM_OFFSET = 2;
+    public static final int ICMPV6_ECHO_REPLY_TYPE = 129;
+    public static final int ICMPV6_ECHO_REQUEST_TYPE = 128;
+    public static final int ICMPV6_ROUTER_SOLICITATION    = 133;
+    public static final int ICMPV6_ROUTER_ADVERTISEMENT   = 134;
+    public static final int ICMPV6_NEIGHBOR_SOLICITATION  = 135;
+    public static final int ICMPV6_NEIGHBOR_ADVERTISEMENT = 136;
+    public static final int ICMPV6_ND_OPTION_MIN_LENGTH = 8;
+    public static final int ICMPV6_ND_OPTION_LENGTH_SCALING_FACTOR = 8;
+    public static final int ICMPV6_ND_OPTION_SLLA  = 1;
+    public static final int ICMPV6_ND_OPTION_TLLA  = 2;
+    public static final int ICMPV6_ND_OPTION_PIO   = 3;
+    public static final int ICMPV6_ND_OPTION_MTU   = 5;
+    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;
+
+    /**
+     * UDP constants.
+     *
+     * See also:
+     *     - https://tools.ietf.org/html/rfc768
+     */
+    public static final int UDP_HEADER_LEN = 8;
+
+
+    /**
+     * DHCP constants.
+     *
+     * See also:
+     *     - https://tools.ietf.org/html/rfc2131
+     */
+    public static final int INFINITE_LEASE = 0xffffffff;
+    public static final int DHCP4_CLIENT_PORT = 68;
+
+    /**
+     * IEEE802.11 standard constants.
+     *
+     * See also:
+     *     - https://ieeexplore.ieee.org/document/7786995
+     */
+    public static final int VENDOR_SPECIFIC_IE_ID = 0xdd;
+
+    // TODO: Move to Inet4AddressUtils
+    // See aosp/1455936: NetworkStackConstants can't depend on it as it causes jarjar-related issues
+    // for users of both the net-utils-device-common and net-utils-framework-common libraries.
+    // Jarjar rule management needs to be simplified for that: b/170445871
+
+    /**
+     * Make an Inet4Address from 4 bytes in network byte order.
+     */
+    private static Inet4Address makeInet4Address(byte b1, byte b2, byte b3, byte b4) {
+        try {
+            return (Inet4Address) InetAddress.getByAddress(new byte[] { b1, b2, b3, b4 });
+        } catch (UnknownHostException e) {
+            throw new IllegalArgumentException("addr must be 4 bytes: this should never happen");
+        }
+    }
+
+    private NetworkStackConstants() {
+        throw new UnsupportedOperationException("This class is not to be instantiated");
+    }
+}
diff --git a/staticlibs/devicetests/com/android/testutils/TapPacketReaderRule.kt b/staticlibs/devicetests/com/android/testutils/TapPacketReaderRule.kt
index 3319fbe..701666c 100644
--- a/staticlibs/devicetests/com/android/testutils/TapPacketReaderRule.kt
+++ b/staticlibs/devicetests/com/android/testutils/TapPacketReaderRule.kt
@@ -19,6 +19,7 @@
 import android.Manifest.permission.MANAGE_TEST_NETWORKS
 import android.net.TestNetworkInterface
 import android.net.TestNetworkManager
+import android.os.Handler
 import android.os.HandlerThread
 import androidx.test.platform.app.InstrumentationRegistry
 import org.junit.rules.TestRule
@@ -31,52 +32,123 @@
 
 /**
  * A [TestRule] that sets up a [TapPacketReader] on a [TestNetworkInterface] for use in the test.
+ *
+ * @param maxPacketSize Maximum size of packets read in the [TapPacketReader] buffer.
+ * @param autoStart Whether to initialize the interface and start the reader automatically for every
+ *                  test. If false, each test must either call start() and stop(), or be annotated
+ *                  with TapPacketReaderTest before using the reader or interface.
  */
 class TapPacketReaderRule @JvmOverloads constructor(
-    private val maxPacketSize: Int = 1500
+    private val maxPacketSize: Int = 1500,
+    private val autoStart: Boolean = true
 ) : TestRule {
     // Use lateinit as the below members can't be initialized in the rule constructor (the
     // InstrumentationRegistry may not be ready), but from the point of view of test cases using
-    // this rule, the members are always initialized (in setup/test/teardown): tests cases should be
-    // able use them directly.
-    // lateinit also allows getting good exceptions detailing what went wrong in the unlikely event
-    // that the members are referenced before they could be initialized.
+    // this rule with autoStart = true, the members are always initialized (in setup/test/teardown):
+    // tests cases should be able use them directly.
+    // lateinit also allows getting good exceptions detailing what went wrong if the members are
+    // referenced before they could be initialized (typically if autoStart is false and the test
+    // does not call start or use @TapPacketReaderTest).
     lateinit var iface: TestNetworkInterface
     lateinit var reader: TapPacketReader
 
-    // The reader runs on its own handlerThread created locally, but this is not an actual
-    // requirement: any handler could be used for this rule. If using a specific handler is needed,
-    // a method could be added to start the TapPacketReader manually on a given handler.
-    private val handlerThread = HandlerThread(TapPacketReaderRule::class.java.simpleName)
+    @Volatile
+    private var readerRunning = false
 
-    override fun apply(base: Statement, description: Description): Statement {
-        return TapReaderStatement(base)
+    /**
+     * Indicates that the [TapPacketReaderRule] should initialize its [TestNetworkInterface] and
+     * start the [TapPacketReader] before the test, and tear them down afterwards.
+     *
+     * For use when [TapPacketReaderRule] is created with autoStart = false.
+     */
+    annotation class TapPacketReaderTest
+
+    /**
+     * Initialize the tap interface and start the [TapPacketReader].
+     *
+     * Tests using this method must also call [stop] before exiting.
+     * @param handler Handler to run the reader on. Callers are responsible for safely terminating
+     *                the handler when the test ends. If null, a handler thread managed by the
+     *                rule will be used.
+     */
+    @JvmOverloads
+    fun start(handler: Handler? = null) {
+        if (this::iface.isInitialized) {
+            fail("${TapPacketReaderRule::class.java.simpleName} was already started")
+        }
+
+        val ctx = InstrumentationRegistry.getInstrumentation().context
+        iface = runAsShell(MANAGE_TEST_NETWORKS) {
+            val tnm = ctx.getSystemService(TestNetworkManager::class.java)
+                    ?: fail("Could not obtain the TestNetworkManager")
+            tnm.createTapInterface()
+        }
+        val usedHandler = handler ?: HandlerThread(
+                TapPacketReaderRule::class.java.simpleName).apply { start() }.threadHandler
+        reader = TapPacketReader(usedHandler, iface.fileDescriptor.fileDescriptor, maxPacketSize)
+        reader.startAsyncForTest()
+        readerRunning = true
     }
 
-    private inner class TapReaderStatement(private val base: Statement) : Statement() {
-        override fun evaluate() {
-            val ctx: android.content.Context = InstrumentationRegistry.getInstrumentation().context
-            iface = runAsShell(MANAGE_TEST_NETWORKS) {
-                val tnm = ctx.getSystemService(TestNetworkManager::class.java)
-                        ?: fail("Could not obtain the TestNetworkManager")
-                tnm.createTapInterface()
-            }
+    /**
+     * Stop the [TapPacketReader].
+     *
+     * Tests calling [start] must call this method before exiting. If a handler was specified in
+     * [start], all messages on that handler must also be processed after calling this method and
+     * before exiting.
+     *
+     * If [start] was not called, calling this method is a no-op.
+     */
+    fun stop() {
+        // The reader may not be initialized if the test case did not use the rule, even though
+        // other test cases in the same class may be using it (so test classes may call stop in
+        // tearDown even if start is not called for all test cases).
+        if (!this::reader.isInitialized) return
+        reader.handler.post {
+            reader.stop()
+            readerRunning = false
+        }
+    }
 
-            handlerThread.start()
-            reader = TapPacketReader(handlerThread.threadHandler,
-                    iface.fileDescriptor.fileDescriptor, maxPacketSize)
-            reader.startAsyncForTest()
+    override fun apply(base: Statement, description: Description): Statement {
+        return TapReaderStatement(base, description)
+    }
+
+    private inner class TapReaderStatement(
+        private val base: Statement,
+        private val description: Description
+    ) : Statement() {
+        override fun evaluate() {
+            val shouldStart = autoStart ||
+                    description.getAnnotation(TapPacketReaderTest::class.java) != null
+            if (shouldStart) {
+                start()
+            }
 
             try {
                 base.evaluate()
             } finally {
-                handlerThread.threadHandler.post(reader::stop)
-                handlerThread.quitSafely()
-                handlerThread.join(HANDLER_TIMEOUT_MS)
-                assertFalse(handlerThread.isAlive,
-                        "HandlerThread did not exit within $HANDLER_TIMEOUT_MS ms")
-                iface.fileDescriptor.close()
+                if (shouldStart) {
+                    stop()
+                    reader.handler.looper.apply {
+                        quitSafely()
+                        thread.join(HANDLER_TIMEOUT_MS)
+                        assertFalse(thread.isAlive,
+                                "HandlerThread did not exit within $HANDLER_TIMEOUT_MS ms")
+                    }
+                }
+
+                if (this@TapPacketReaderRule::iface.isInitialized) {
+                    iface.fileDescriptor.close()
+                }
             }
+
+            assertFalse(readerRunning,
+                    "stop() was not called, or the provided handler did not process the stop " +
+                    "message before the test ended. If not using autostart, make sure to call " +
+                    "stop() after the test. If a handler is specified in start(), make sure all " +
+                    "messages are processed after calling stop(), before quitting (for example " +
+                    "by using HandlerThread#quitSafely and HandlerThread#join).")
         }
     }
 }
\ No newline at end of file
diff --git a/staticlibs/framework/com/android/net/module/util/IpUtils.java b/staticlibs/framework/com/android/net/module/util/IpUtils.java
new file mode 100644
index 0000000..dbc2285
--- /dev/null
+++ b/staticlibs/framework/com/android/net/module/util/IpUtils.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2015 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 android.system.OsConstants.IPPROTO_TCP;
+import static android.system.OsConstants.IPPROTO_UDP;
+
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.nio.ByteBuffer;
+import java.nio.ShortBuffer;
+
+/**
+ * @hide
+ */
+public class IpUtils {
+    /**
+     * Converts a signed short value to an unsigned int value.  Needed
+     * because Java does not have unsigned types.
+     */
+    private static int intAbs(short v) {
+        return v & 0xFFFF;
+    }
+
+    /**
+     * Performs an IP checksum (used in IP header and across UDP
+     * payload) on the specified portion of a ByteBuffer.  The seed
+     * allows the checksum to commence with a specified value.
+     */
+    private static int checksum(ByteBuffer buf, int seed, int start, int end) {
+        int sum = seed;
+        final int bufPosition = buf.position();
+
+        // set position of original ByteBuffer, so that the ShortBuffer
+        // will be correctly initialized
+        buf.position(start);
+        ShortBuffer shortBuf = buf.asShortBuffer();
+
+        // re-set ByteBuffer position
+        buf.position(bufPosition);
+
+        final int numShorts = (end - start) / 2;
+        for (int i = 0; i < numShorts; i++) {
+            sum += intAbs(shortBuf.get(i));
+        }
+        start += numShorts * 2;
+
+        // see if a singleton byte remains
+        if (end != start) {
+            short b = buf.get(start);
+
+            // make it unsigned
+            if (b < 0) {
+                b += 256;
+            }
+
+            sum += b * 256;
+        }
+
+        sum = ((sum >> 16) & 0xFFFF) + (sum & 0xFFFF);
+        sum = ((sum + ((sum >> 16) & 0xFFFF)) & 0xFFFF);
+        int negated = ~sum;
+        return intAbs((short) negated);
+    }
+
+    private static int pseudoChecksumIPv4(
+            ByteBuffer buf, int headerOffset, int protocol, int transportLen) {
+        int partial = protocol + transportLen;
+        partial += intAbs(buf.getShort(headerOffset + 12));
+        partial += intAbs(buf.getShort(headerOffset + 14));
+        partial += intAbs(buf.getShort(headerOffset + 16));
+        partial += intAbs(buf.getShort(headerOffset + 18));
+        return partial;
+    }
+
+    private static int pseudoChecksumIPv6(
+            ByteBuffer buf, int headerOffset, int protocol, int transportLen) {
+        int partial = protocol + transportLen;
+        for (int offset = 8; offset < 40; offset += 2) {
+            partial += intAbs(buf.getShort(headerOffset + offset));
+        }
+        return partial;
+    }
+
+    private static byte ipversion(ByteBuffer buf, int headerOffset) {
+        return (byte) ((buf.get(headerOffset) & (byte) 0xf0) >> 4);
+   }
+
+    public static short ipChecksum(ByteBuffer buf, int headerOffset) {
+        byte ihl = (byte) (buf.get(headerOffset) & 0x0f);
+        return (short) checksum(buf, 0, headerOffset, headerOffset + ihl * 4);
+    }
+
+    private static short transportChecksum(ByteBuffer buf, int protocol,
+            int ipOffset, int transportOffset, int transportLen) {
+        if (transportLen < 0) {
+            throw new IllegalArgumentException("Transport length < 0: " + transportLen);
+        }
+        int sum;
+        byte ver = ipversion(buf, ipOffset);
+        if (ver == 4) {
+            sum = pseudoChecksumIPv4(buf, ipOffset, protocol, transportLen);
+        } else if (ver == 6) {
+            sum = pseudoChecksumIPv6(buf, ipOffset, protocol, transportLen);
+        } else {
+            throw new UnsupportedOperationException("Checksum must be IPv4 or IPv6");
+        }
+
+        sum = checksum(buf, sum, transportOffset, transportOffset + transportLen);
+        if (protocol == IPPROTO_UDP && sum == 0) {
+            sum = (short) 0xffff;
+        }
+        return (short) sum;
+    }
+
+    /**
+     * Calculate the UDP checksum for an UDP packet.
+     */
+    public static short udpChecksum(ByteBuffer buf, int ipOffset, int transportOffset) {
+        int transportLen = intAbs(buf.getShort(transportOffset + 4));
+        return transportChecksum(buf, IPPROTO_UDP, ipOffset, transportOffset, transportLen);
+    }
+
+    /**
+     * Calculate the TCP checksum for a TCP packet.
+     */
+    public static short tcpChecksum(ByteBuffer buf, int ipOffset, int transportOffset,
+            int transportLen) {
+        return transportChecksum(buf, IPPROTO_TCP, ipOffset, transportOffset, transportLen);
+    }
+
+    /**
+     * Calculate the ICMPv6 checksum for an ICMPv6 packet.
+     */
+    public static short icmpv6Checksum(ByteBuffer buf, int ipOffset, int transportOffset,
+            int transportLen) {
+        return transportChecksum(buf, IPPROTO_ICMPV6, ipOffset, transportOffset, transportLen);
+    }
+
+    public static String addressAndPortToString(InetAddress address, int port) {
+        return String.format(
+                (address instanceof Inet6Address) ? "[%s]:%d" : "%s:%d",
+                address.getHostAddress(), port);
+    }
+
+    public static boolean isValidUdpOrTcpPort(int port) {
+        return port > 0 && port < 65536;
+    }
+}
diff --git a/staticlibs/tests/unit/Android.bp b/staticlibs/tests/unit/Android.bp
index f18ffcf..c20b87d 100644
--- a/staticlibs/tests/unit/Android.bp
+++ b/staticlibs/tests/unit/Android.bp
@@ -6,7 +6,6 @@
     name: "NetworkStaticLibTestsLib",
     srcs: ["src/**/*.java","src/**/*.kt"],
     min_sdk_version: "29",
-    jarjar_rules: "jarjar-rules.txt",
     static_libs: [
         "net-utils-framework-common",
         "androidx.test.rules",
@@ -17,8 +16,10 @@
         "android.test.runner",
         "android.test.base",
     ],
+    jarjar_rules: "jarjar-rules-sharedlib.txt",
     visibility: [
         "//frameworks/base/packages/Tethering/tests/integration",
+        "//packages/modules/Connectivity/Tethering/tests/integration",
         "//packages/modules/NetworkStack/tests/integration",
     ]
 }
@@ -29,6 +30,7 @@
     static_libs: [
         "NetworkStaticLibTestsLib",
     ],
+    jarjar_rules: "jarjar-rules.txt",
     test_suites: ["device-tests"],
 }
 
diff --git a/staticlibs/tests/unit/jarjar-rules-sharedlib.txt b/staticlibs/tests/unit/jarjar-rules-sharedlib.txt
new file mode 100644
index 0000000..fceccfb
--- /dev/null
+++ b/staticlibs/tests/unit/jarjar-rules-sharedlib.txt
@@ -0,0 +1,6 @@
+# TODO: move the classes to the target package in java
+rule android.net.util.IpRange* com.android.net.module.util.IpRange@1
+rule android.net.util.MacAddressUtils* com.android.net.module.util.MacAddressUtils@1
+rule android.net.util.LinkPropertiesUtils* com.android.net.module.util.LinkPropertiesUtils@1
+rule android.net.util.NetUtils* com.android.net.module.util.NetUtils@1
+rule android.net.util.nsd.** com.android.net.module.util.nsd.@1
diff --git a/staticlibs/tests/unit/jarjar-rules.txt b/staticlibs/tests/unit/jarjar-rules.txt
index fceccfb..e032ae5 100644
--- a/staticlibs/tests/unit/jarjar-rules.txt
+++ b/staticlibs/tests/unit/jarjar-rules.txt
@@ -1,6 +1 @@
-# TODO: move the classes to the target package in java
-rule android.net.util.IpRange* com.android.net.module.util.IpRange@1
-rule android.net.util.MacAddressUtils* com.android.net.module.util.MacAddressUtils@1
-rule android.net.util.LinkPropertiesUtils* com.android.net.module.util.LinkPropertiesUtils@1
-rule android.net.util.NetUtils* com.android.net.module.util.NetUtils@1
-rule android.net.util.nsd.** com.android.net.module.util.nsd.@1
+rule com.android.net.module.util.** com.android.net.moduletests.util.@1
diff --git a/staticlibs/tests/unit/src/android/net/util/LinkPropertiesUtilsTest.java b/staticlibs/tests/unit/src/android/net/util/LinkPropertiesUtilsTest.java
index 6ffcc1d..2142090 100644
--- a/staticlibs/tests/unit/src/android/net/util/LinkPropertiesUtilsTest.java
+++ b/staticlibs/tests/unit/src/android/net/util/LinkPropertiesUtilsTest.java
@@ -28,6 +28,7 @@
 import android.net.RouteInfo;
 import android.net.util.LinkPropertiesUtils.CompareOrUpdateResult;
 import android.net.util.LinkPropertiesUtils.CompareResult;
+import android.util.ArraySet;
 
 import androidx.test.runner.AndroidJUnit4;
 
@@ -166,6 +167,58 @@
         assertFalse(LinkPropertiesUtils.isIdenticalHttpProxy(target, source));
     }
 
+    private <T> void compareResult(List<T> oldItems, List<T> newItems, List<T> expectRemoved,
+            List<T> expectAdded) {
+        CompareResult<T> result = new CompareResult<>(oldItems, newItems);
+        assertEquals(new ArraySet<>(expectAdded), new ArraySet<>(result.added));
+        assertEquals(new ArraySet<>(expectRemoved), (new ArraySet<>(result.removed)));
+    }
+
+    @Test
+    public void testCompareResult() {
+        // Either adding or removing items
+        compareResult(Arrays.asList(1, 2, 3, 4), Arrays.asList(1),
+                Arrays.asList(2, 3, 4), new ArrayList<>());
+        compareResult(Arrays.asList(1, 2), Arrays.asList(3, 2, 1, 4),
+                new ArrayList<>(), Arrays.asList(3, 4));
+
+
+        // adding and removing items at the same time
+        compareResult(Arrays.asList(1, 2, 3, 4), Arrays.asList(2, 3, 4, 5),
+                Arrays.asList(1), Arrays.asList(5));
+        compareResult(Arrays.asList(1, 2, 3), Arrays.asList(4, 5, 6),
+                Arrays.asList(1, 2, 3), Arrays.asList(4, 5, 6));
+
+        // null cases
+        compareResult(Arrays.asList(1, 2, 3), null, Arrays.asList(1, 2, 3), new ArrayList<>());
+        compareResult(null, Arrays.asList(3, 2, 1), new ArrayList<>(), Arrays.asList(1, 2, 3));
+        compareResult(null, null, new ArrayList<>(), new ArrayList<>());
+
+        // Some more tests with strings
+        final ArrayList<String> list1 = new ArrayList<>();
+        list1.add("string1");
+
+        final ArrayList<String> list2 = new ArrayList<>(list1);
+        final CompareResult<String> cr1 = new CompareResult<>(list1, list2);
+        assertTrue(cr1.added.isEmpty());
+        assertTrue(cr1.removed.isEmpty());
+
+        list2.add("string2");
+        final CompareResult<String> cr2 = new CompareResult<>(list1, list2);
+        assertEquals(Arrays.asList("string2"), cr2.added);
+        assertTrue(cr2.removed.isEmpty());
+
+        list2.remove("string1");
+        final CompareResult<String> cr3 = new CompareResult<>(list1, list2);
+        assertEquals(Arrays.asList("string2"), cr3.added);
+        assertEquals(Arrays.asList("string1"), cr3.removed);
+
+        list1.add("string2");
+        final CompareResult<String> cr4 = new CompareResult<>(list1, list2);
+        assertTrue(cr4.added.isEmpty());
+        assertEquals(Arrays.asList("string1"), cr3.removed);
+    }
+
     @Test
     public void testCompareAddresses() {
         final LinkProperties source = createTestObject();
diff --git a/staticlibs/tests/unit/src/android/net/util/StructTest.java b/staticlibs/tests/unit/src/android/net/util/StructTest.java
new file mode 100644
index 0000000..3451739
--- /dev/null
+++ b/staticlibs/tests/unit/src/android/net/util/StructTest.java
@@ -0,0 +1,487 @@
+/*
+ * Copyright (C) 2020 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.util;
+
+import static com.android.testutils.MiscAsserts.assertThrows;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.net.IpPrefix;
+import android.net.util.Struct.Field;
+import android.net.util.Struct.Type;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.util.HexDump;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.math.BigInteger;
+import java.net.InetAddress;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.Arrays;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class StructTest {
+
+    // IPv6, 0 bytes of options, ifindex 15715755: 0x00efcdab, type 134 (RA), code 0, padding.
+    private static final String HDR_EMPTY = "0a00" + "0000" + "abcdef00" + "8600000000000000";
+
+    // BE16: 0xfeff, BE32: 0xfeffffff, BE64: 0xfffffffffffffffe, 0x7effffffffffffff
+    private static final String NETWORK_ORDER_MSG = "feff" + "feffffff" + "fffffffffffffffe"
+            + "7effffffffffffff";
+
+    // S8: 0x7f, S16: 0x7fff, S32: 0x7fffffff, S64: 0x7fffffffffffffff
+    private static final String SIGNED_DATA = "7f" + "ff7f" + "ffffff7f" + "ffffffffffffff7f";
+
+    // nS8: 0x81, nS16: 0x8001, nS32: 0x80000001, nS64: 800000000000000001
+    private static final String SIGNED_NEGATIVE_DATA = "81" + "0180" + "01000080"
+            + "0100000000000080";
+
+    // U8: 0xff, U16: 0xffff, U32: 0xffffffff, U64: 0xffffffffffffffff, U63: 0x7fffffffffffffff,
+    // U63: 0xffffffffffffffff(-1L)
+    private static final String UNSIGNED_DATA = "ff" + "ffff" + "ffffffff" + "ffffffffffffffff"
+            + "ffffffffffffff7f" + "ffffffffffffffff";
+
+    // PREF64 option, 2001:db8:3:4:5:6::/96, lifetime: 10064
+    private static final String OPT_PREF64 = "2750" + "20010db80003000400050006";
+
+    private <T> T doParsingMessageTest(final String hexString, Class<T> clazz) {
+        final ByteBuffer buf = toByteBuffer(hexString);
+        buf.order(ByteOrder.LITTLE_ENDIAN);
+        return Struct.parse(clazz, buf);
+    }
+
+    static class HeaderMsgWithConstructor extends Struct {
+        static int sType;
+        static int sLength;
+
+        @Field(order = 0, type = Type.U8, padding = 1)
+        final short mFamily;
+        @Field(order = 1, type = Type.U16)
+        final int mLen;
+        @Field(order = 2, type = Type.S32)
+        final int mIfindex;
+        @Field(order = 3, type = Type.U8)
+        final short mIcmpType;
+        @Field(order = 4, type = Type.U8, padding = 6)
+        final short mIcmpCode;
+
+        HeaderMsgWithConstructor(final short family, final int len, final int ifindex,
+                final short type, final short code) {
+            mFamily = family;
+            mLen = len;
+            mIfindex = ifindex;
+            mIcmpType = type;
+            mIcmpCode = code;
+        }
+    }
+
+    @Test
+    public void testClassWithExplicitConstructor() {
+        final HeaderMsgWithConstructor msg = doParsingMessageTest(HDR_EMPTY,
+                HeaderMsgWithConstructor.class);
+        assertEquals(10, msg.mFamily);
+        assertEquals(0, msg.mLen);
+        assertEquals(15715755, msg.mIfindex);
+        assertEquals(134, msg.mIcmpType);
+        assertEquals(0, msg.mIcmpCode);
+    }
+
+    static class HeaderMsgWithoutConstructor extends Struct {
+        static int sType;
+        static int sLength;
+
+        @Field(order = 0, type = Type.U8, padding = 1)
+        short mFamily;
+        @Field(order = 1, type = Type.U16)
+        int mLen;
+        @Field(order = 2, type = Type.S32)
+        int mIfindex;
+        @Field(order = 3, type = Type.U8)
+        short mIcmpType;
+        @Field(order = 4, type = Type.U8, padding = 6)
+        short mIcmpCode;
+    }
+
+    @Test
+    public void testClassWithDefaultConstrcutor() {
+        final HeaderMsgWithoutConstructor msg = doParsingMessageTest(HDR_EMPTY,
+                HeaderMsgWithoutConstructor.class);
+        assertEquals(10, msg.mFamily);
+        assertEquals(0, msg.mLen);
+        assertEquals(15715755, msg.mIfindex);
+        assertEquals(134, msg.mIcmpType);
+        assertEquals(0, msg.mIcmpCode);
+    }
+
+    static class HeaderMessage {
+        @Field(order = 0, type = Type.U8, padding = 1)
+        short mFamily;
+        @Field(order = 1, type = Type.U16)
+        int mLen;
+        @Field(order = 2, type = Type.S32)
+        int mIfindex;
+        @Field(order = 3, type = Type.U8)
+        short mIcmpType;
+        @Field(order = 4, type = Type.U8, padding = 6)
+        short mIcmpCode;
+    }
+
+    @Test
+    public void testInvalidClass_NotSubClass() {
+        final ByteBuffer buf = toByteBuffer(HDR_EMPTY);
+        assertThrows(IllegalArgumentException.class, () -> Struct.parse(HeaderMessage.class, buf));
+    }
+
+    static class HeaderMessageMissingAnnotation extends Struct {
+        @Field(order = 0, type = Type.U8, padding = 1)
+        short mFamily;
+        @Field(order = 1, type = Type.U16)
+        int mLen;
+        int mIfindex;
+        @Field(order = 2, type = Type.U8)
+        short mIcmpType;
+        @Field(order = 3, type = Type.U8, padding = 6)
+        short mIcmpCode;
+    }
+
+    @Test
+    public void testInvalidClass_MissingAnnotationField() {
+        final ByteBuffer buf = toByteBuffer(HDR_EMPTY);
+        assertThrows(IllegalArgumentException.class,
+                () -> Struct.parse(HeaderMessageMissingAnnotation.class, buf));
+    }
+
+    static class NetworkOrderMessage extends Struct {
+        @Field(order = 0, type = Type.BE16)
+        final int mBE16;
+        @Field(order = 1, type = Type.BE32)
+        final long mBE32;
+        @Field(order = 2, type = Type.BE64)
+        final BigInteger mBE64;
+        @Field(order = 3, type = Type.BE64)
+        final long mBE63;
+
+        NetworkOrderMessage(final int be16, final long be32, final BigInteger be64,
+                final long be63) {
+            mBE16 = be16;
+            mBE32 = be32;
+            mBE64 = be64;
+            mBE63 = be63;
+        }
+    }
+
+    @Test
+    public void testNetworkOrder() {
+        final NetworkOrderMessage msg = doParsingMessageTest(NETWORK_ORDER_MSG,
+                NetworkOrderMessage.class);
+        assertEquals(65279, msg.mBE16);
+        assertEquals(4278190079L, msg.mBE32);
+        assertEquals(new BigInteger("18374686479671623679"), msg.mBE64);
+        assertEquals(9151314442816847871L, msg.mBE63);
+    }
+
+    static class UnsignedDataMessage extends Struct {
+        @Field(order = 0, type = Type.U8)
+        final short mU8;
+        @Field(order = 1, type = Type.U16)
+        final int mU16;
+        @Field(order = 2, type = Type.U32)
+        final long mU32;
+        @Field(order = 3, type = Type.U64)
+        final BigInteger mU64; // represent U64 with BigInteger.
+        @Field(order = 4, type = Type.U64)
+        final long mU63;  // represent U63 with long primitive.
+        @Field(order = 5, type = Type.U64)
+        final long mLU64; // represent U64 with long primitive.
+
+        UnsignedDataMessage(final short u8, final int u16, final long u32, final BigInteger u64,
+                final long u63, final long lu64) {
+            mU8 = u8;
+            mU16 = u16;
+            mU32 = u32;
+            mU64 = u64;
+            mU63 = u63;
+            mLU64 = lu64;
+        }
+    }
+
+    @Test
+    public void testUnsignedData() {
+        final UnsignedDataMessage msg = doParsingMessageTest(UNSIGNED_DATA,
+                UnsignedDataMessage.class);
+        assertEquals(255, msg.mU8);
+        assertEquals(65535, msg.mU16);
+        assertEquals(4294967295L, msg.mU32);
+        assertEquals(new BigInteger("18446744073709551615"), msg.mU64);
+        assertEquals(9223372036854775807L, msg.mU63);
+        assertEquals(-1L, msg.mLU64);
+    }
+
+    static class SignedDataMessage extends Struct {
+        @Field(order = 0, type = Type.S8)
+        final byte mS8;
+        @Field(order = 1, type = Type.S16)
+        final short mS16;
+        @Field(order = 2, type = Type.S32)
+        final int mS32;
+        @Field(order = 3, type = Type.S64)
+        final long mS64;
+
+        SignedDataMessage(final byte s8, final short s16, final int s32, final long s64) {
+            mS8 = s8;
+            mS16 = s16;
+            mS32 = s32;
+            mS64 = s64;
+        }
+    }
+
+    @Test
+    public void testSignedPositiveData() {
+        final SignedDataMessage msg = doParsingMessageTest(SIGNED_DATA, SignedDataMessage.class);
+        assertEquals(127, msg.mS8);
+        assertEquals(32767, msg.mS16);
+        assertEquals(2147483647, msg.mS32);
+        assertEquals(9223372036854775807L, msg.mS64);
+    }
+
+    @Test
+    public void testSignedNegativeData() {
+        final SignedDataMessage msg = doParsingMessageTest(SIGNED_NEGATIVE_DATA,
+                SignedDataMessage.class);
+        assertEquals(-127, msg.mS8);
+        assertEquals(-32767, msg.mS16);
+        assertEquals(-2147483647, msg.mS32);
+        assertEquals(-9223372036854775807L, msg.mS64);
+    }
+
+    static class HeaderMessageWithDuplicateOrder extends Struct {
+        @Field(order = 0, type = Type.U8, padding = 1)
+        short mFamily;
+        @Field(order = 1, type = Type.U16)
+        int mLen;
+        @Field(order = 2, type = Type.S32)
+        int mIfindex;
+        @Field(order = 2, type = Type.U8)
+        short mIcmpType;
+        @Field(order = 3, type = Type.U8, padding = 6)
+        short mIcmpCode;
+    }
+
+    @Test
+    public void testInvalidClass_DuplicateFieldOrder() {
+        final ByteBuffer buf = toByteBuffer(HDR_EMPTY);
+        assertThrows(IllegalArgumentException.class,
+                () -> Struct.parse(HeaderMessageWithDuplicateOrder.class, buf));
+    }
+
+    static class HeaderMessageWithNegativeOrder extends Struct {
+        @Field(order = 0, type = Type.U8, padding = 1)
+        short mFamily;
+        @Field(order = 1, type = Type.U16)
+        int mLen;
+        @Field(order = 2, type = Type.S32)
+        int mIfindex;
+        @Field(order = 3, type = Type.U8)
+        short mIcmpType;
+        @Field(order = -4, type = Type.U8, padding = 6)
+        short mIcmpCode;
+    }
+
+    @Test
+    public void testInvalidClass_NegativeFieldOrder() {
+        final ByteBuffer buf = toByteBuffer(HDR_EMPTY);
+        assertThrows(IllegalArgumentException.class,
+                () -> Struct.parse(HeaderMessageWithNegativeOrder.class, buf));
+    }
+
+    static class HeaderMessageOutOfIndexBounds extends Struct {
+        @Field(order = 0, type = Type.U8, padding = 1)
+        short mFamily;
+        @Field(order = 1, type = Type.U16)
+        int mLen;
+        @Field(order = 2, type = Type.S32)
+        int mIfindex;
+        @Field(order = 3, type = Type.U8)
+        short mIcmpType;
+        @Field(order = 5, type = Type.U8, padding = 6)
+        short mIcmpCode;
+    }
+
+    @Test
+    public void testInvalidClass_OutOfIndexBounds() {
+        final ByteBuffer buf = toByteBuffer(HDR_EMPTY);
+        assertThrows(IllegalArgumentException.class,
+                () -> Struct.parse(HeaderMessageOutOfIndexBounds.class, buf));
+    }
+
+    static class HeaderMessageMismatchedPrimitiveType extends Struct {
+        @Field(order = 0, type = Type.U8, padding = 1)
+        short mFamily;
+        @Field(order = 1, type = Type.U16)
+        short mLen; // should be integer
+        @Field(order = 2, type = Type.S32)
+        int mIfindex;
+        @Field(order = 3, type = Type.U8)
+        short mIcmpType;
+        @Field(order = 4, type = Type.U8, padding = 6)
+        short mIcmpCode;
+    }
+
+    @Test
+    public void testInvalidClass_MismatchedPrimitiveDataType() {
+        final ByteBuffer buf = toByteBuffer(HDR_EMPTY);
+        assertThrows(IllegalArgumentException.class,
+                () -> Struct.parse(HeaderMessageMismatchedPrimitiveType.class, buf));
+    }
+
+    static class PrefixMessage extends Struct {
+        @Field(order = 0, type = Type.BE16)
+        final int mLifetime;
+        @Field(order = 1, type = Type.ByteArray, arraysize = 12)
+        final byte[] mPrefix;
+
+        PrefixMessage(final int lifetime, final byte[] prefix) {
+            mLifetime = lifetime;
+            mPrefix = prefix;
+        }
+    }
+
+    @Test
+    public void testPrefixArrayField() throws Exception {
+        final ByteBuffer buf = toByteBuffer(OPT_PREF64);
+        buf.order(ByteOrder.LITTLE_ENDIAN);
+
+        // The original PREF64 option message has just 12 bytes for prefix byte array
+        // (Highest 96 bits of the Prefix), copyOf pads the 128-bits IPv6 address with
+        // prefix and 4-bytes zeros.
+        final PrefixMessage msg = Struct.parse(PrefixMessage.class, buf);
+        final InetAddress addr = InetAddress.getByAddress(Arrays.copyOf(msg.mPrefix, 16));
+        final IpPrefix prefix = new IpPrefix(addr, 96);
+        assertEquals(10064, msg.mLifetime);
+        assertTrue(prefix.equals(new IpPrefix("2001:db8:3:4:5:6::/96")));
+    }
+
+    static class HeaderMessageWithMutableField extends Struct {
+        @Field(order = 0, type = Type.U8, padding = 1)
+        final short mFamily;
+        @Field(order = 1, type = Type.U16)
+        final int mLen;
+        @Field(order = 2, type = Type.S32)
+        int mIfindex;
+        @Field(order = 3, type = Type.U8)
+        short mIcmpType;
+        @Field(order = 4, type = Type.U8, padding = 6)
+        final short mIcmpCode;
+
+        HeaderMessageWithMutableField(final short family, final int len, final short code) {
+            mFamily = family;
+            mLen = len;
+            mIcmpCode = code;
+        }
+    }
+
+    @Test
+    public void testMixMutableAndImmutableFields() {
+        final ByteBuffer buf = toByteBuffer(HDR_EMPTY);
+        assertThrows(IllegalArgumentException.class,
+                () -> Struct.parse(HeaderMessageWithMutableField.class, buf));
+    }
+
+    static class HeaderMsgWithStaticConstant extends Struct {
+        private static final String TAG = "HeaderMessage";
+        private static final int FIELD_COUNT = 5;
+
+        @Field(order = 0, type = Type.U8, padding = 1)
+        final short mFamily;
+        @Field(order = 1, type = Type.U16)
+        final int mLen;
+        @Field(order = 2, type = Type.S32)
+        final int mIfindex;
+        @Field(order = 3, type = Type.U8)
+        final short mIcmpType;
+        @Field(order = 4, type = Type.U8, padding = 6)
+        final short mIcmpCode;
+
+        HeaderMsgWithStaticConstant(final short family, final int len, final int ifindex,
+                final short type, final short code) {
+            mFamily = family;
+            mLen = len;
+            mIfindex = ifindex;
+            mIcmpType = type;
+            mIcmpCode = code;
+        }
+    }
+
+    @Test
+    public void testStaticConstantField() {
+        final HeaderMsgWithStaticConstant msg = doParsingMessageTest(HDR_EMPTY,
+                HeaderMsgWithStaticConstant.class);
+        assertEquals(10, msg.mFamily);
+        assertEquals(0, msg.mLen);
+        assertEquals(15715755, msg.mIfindex);
+        assertEquals(134, msg.mIcmpType);
+        assertEquals(0, msg.mIcmpCode);
+    }
+
+    static class MismatchedConstructor extends Struct {
+        @Field(order = 0, type = Type.U16) final int mInt1;
+        @Field(order = 1, type = Type.U16) final int mInt2;
+        MismatchedConstructor(String int1, String int2) {
+            mInt1 = Integer.valueOf(int1);
+            mInt2 = Integer.valueOf(int2);
+        }
+    }
+
+    @Test
+    public void testMisMatchedConstructor() {
+        final ByteBuffer buf = toByteBuffer("1234" + "5678");
+        assertThrows(IllegalArgumentException.class,
+                () -> Struct.parse(MismatchedConstructor.class, buf));
+    }
+
+    static class ClassWithTwoConstructors extends Struct {
+        @Field(order = 0, type = Type.U16) final int mInt1;
+        @Field(order = 1, type = Type.U16) final int mInt2;
+        ClassWithTwoConstructors(String int1, String int2) {
+            mInt1 = Integer.valueOf(int1);
+            mInt2 = Integer.valueOf(int2);
+        }
+        ClassWithTwoConstructors(int int1, int int2) {
+            mInt1 = int1;
+            mInt2 = int2;
+        }
+    }
+
+    @Test
+    public void testClassWithTwoConstructors() {
+        final ClassWithTwoConstructors msg = doParsingMessageTest("1234" + "5678",
+                ClassWithTwoConstructors.class);
+        assertEquals(13330 /* 0x3412 */, msg.mInt1);
+        assertEquals(30806 /* 0x7856 */, msg.mInt2);
+    }
+
+    private ByteBuffer toByteBuffer(final String hexString) {
+        return ByteBuffer.wrap(HexDump.hexStringToByteArray(hexString));
+    }
+}
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/IpUtilsTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/IpUtilsTest.java
new file mode 100644
index 0000000..3468309
--- /dev/null
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/IpUtilsTest.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2020 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 org.junit.Assert.assertEquals;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.nio.ByteBuffer;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class IpUtilsTest {
+
+    private static final int IPV4_HEADER_LENGTH = 20;
+    private static final int IPV6_HEADER_LENGTH = 40;
+    private static final int TCP_HEADER_LENGTH = 20;
+    private static final int UDP_HEADER_LENGTH = 8;
+    private static final int IP_CHECKSUM_OFFSET = 10;
+    private static final int TCP_CHECKSUM_OFFSET = 16;
+    private static final int UDP_CHECKSUM_OFFSET = 6;
+
+    private int getUnsignedByte(ByteBuffer buf, int offset) {
+        return buf.get(offset) & 0xff;
+    }
+
+    private int getChecksum(ByteBuffer buf, int offset) {
+        return getUnsignedByte(buf, offset) * 256 + getUnsignedByte(buf, offset + 1);
+    }
+
+    private void assertChecksumEquals(int expected, short actual) {
+        assertEquals(Integer.toHexString(expected), Integer.toHexString(actual & 0xffff));
+    }
+
+    // Generate test packets using Python code like this::
+    //
+    // from scapy import all as scapy
+    //
+    // def JavaPacketDefinition(bytes):
+    //   out = "        ByteBuffer packet = ByteBuffer.wrap(new byte[] {\n            "
+    //   for i in xrange(len(bytes)):
+    //     out += "(byte) 0x%02x" % ord(bytes[i])
+    //     if i < len(bytes) - 1:
+    //       if i % 4 == 3:
+    //         out += ",\n            "
+    //       else:
+    //         out += ", "
+    //   out += "\n        });"
+    //   return out
+    //
+    // packet = (scapy.IPv6(src="2001:db8::1", dst="2001:db8::2") /
+    //           scapy.UDP(sport=12345, dport=7) /
+    //           "hello")
+    // print JavaPacketDefinition(str(packet))
+
+    @Test
+    public void testIpv6TcpChecksum() throws Exception {
+        // packet = (scapy.IPv6(src="2001:db8::1", dst="2001:db8::2", tc=0x80) /
+        //           scapy.TCP(sport=12345, dport=7,
+        //                     seq=1692871236, ack=128376451, flags=16,
+        //                     window=32768) /
+        //           "hello, world")
+        ByteBuffer packet = ByteBuffer.wrap(new byte[] {
+            (byte) 0x68, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x20, (byte) 0x06, (byte) 0x40,
+            (byte) 0x20, (byte) 0x01, (byte) 0x0d, (byte) 0xb8,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x01,
+            (byte) 0x20, (byte) 0x01, (byte) 0x0d, (byte) 0xb8,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x02,
+            (byte) 0x30, (byte) 0x39, (byte) 0x00, (byte) 0x07,
+            (byte) 0x64, (byte) 0xe7, (byte) 0x2a, (byte) 0x44,
+            (byte) 0x07, (byte) 0xa6, (byte) 0xde, (byte) 0x83,
+            (byte) 0x50, (byte) 0x10, (byte) 0x80, (byte) 0x00,
+            (byte) 0xee, (byte) 0x71, (byte) 0x00, (byte) 0x00,
+            (byte) 0x68, (byte) 0x65, (byte) 0x6c, (byte) 0x6c,
+            (byte) 0x6f, (byte) 0x2c, (byte) 0x20, (byte) 0x77,
+            (byte) 0x6f, (byte) 0x72, (byte) 0x6c, (byte) 0x64
+        });
+
+        // Check that a valid packet has checksum 0.
+        int transportLen = packet.limit() - IPV6_HEADER_LENGTH;
+        assertEquals(0, IpUtils.tcpChecksum(packet, 0, IPV6_HEADER_LENGTH, transportLen));
+
+        // Check that we can calculate the checksum from scratch.
+        int sumOffset = IPV6_HEADER_LENGTH + TCP_CHECKSUM_OFFSET;
+        int sum = getUnsignedByte(packet, sumOffset) * 256 + getUnsignedByte(packet, sumOffset + 1);
+        assertEquals(0xee71, sum);
+
+        packet.put(sumOffset, (byte) 0);
+        packet.put(sumOffset + 1, (byte) 0);
+        assertChecksumEquals(sum, IpUtils.tcpChecksum(packet, 0, IPV6_HEADER_LENGTH, transportLen));
+
+        // Check that writing the checksum back into the packet results in a valid packet.
+        packet.putShort(
+            sumOffset,
+            IpUtils.tcpChecksum(packet, 0, IPV6_HEADER_LENGTH, transportLen));
+        assertEquals(0, IpUtils.tcpChecksum(packet, 0, IPV6_HEADER_LENGTH, transportLen));
+    }
+
+    @Test
+    public void testIpv4UdpChecksum() {
+        // packet = (scapy.IP(src="192.0.2.1", dst="192.0.2.2", tos=0x40) /
+        //           scapy.UDP(sport=32012, dport=4500) /
+        //           "\xff")
+        ByteBuffer packet = ByteBuffer.wrap(new byte[] {
+            (byte) 0x45, (byte) 0x40, (byte) 0x00, (byte) 0x1d,
+            (byte) 0x00, (byte) 0x01, (byte) 0x00, (byte) 0x00,
+            (byte) 0x40, (byte) 0x11, (byte) 0xf6, (byte) 0x8b,
+            (byte) 0xc0, (byte) 0x00, (byte) 0x02, (byte) 0x01,
+            (byte) 0xc0, (byte) 0x00, (byte) 0x02, (byte) 0x02,
+            (byte) 0x7d, (byte) 0x0c, (byte) 0x11, (byte) 0x94,
+            (byte) 0x00, (byte) 0x09, (byte) 0xee, (byte) 0x36,
+            (byte) 0xff
+        });
+
+        // Check that a valid packet has IP checksum 0 and UDP checksum 0xffff (0 is not a valid
+        // UDP checksum, so the udpChecksum rewrites 0 to 0xffff).
+        assertEquals(0, IpUtils.ipChecksum(packet, 0));
+        assertEquals((short) 0xffff, IpUtils.udpChecksum(packet, 0, IPV4_HEADER_LENGTH));
+
+        // Check that we can calculate the checksums from scratch.
+        final int ipSumOffset = IP_CHECKSUM_OFFSET;
+        final int ipSum = getChecksum(packet, ipSumOffset);
+        assertEquals(0xf68b, ipSum);
+
+        packet.put(ipSumOffset, (byte) 0);
+        packet.put(ipSumOffset + 1, (byte) 0);
+        assertChecksumEquals(ipSum, IpUtils.ipChecksum(packet, 0));
+
+        final int udpSumOffset = IPV4_HEADER_LENGTH + UDP_CHECKSUM_OFFSET;
+        final int udpSum = getChecksum(packet, udpSumOffset);
+        assertEquals(0xee36, udpSum);
+
+        packet.put(udpSumOffset, (byte) 0);
+        packet.put(udpSumOffset + 1, (byte) 0);
+        assertChecksumEquals(udpSum, IpUtils.udpChecksum(packet, 0, IPV4_HEADER_LENGTH));
+
+        // Check that writing the checksums back into the packet results in a valid packet.
+        packet.putShort(ipSumOffset, IpUtils.ipChecksum(packet, 0));
+        packet.putShort(udpSumOffset, IpUtils.udpChecksum(packet, 0, IPV4_HEADER_LENGTH));
+        assertEquals(0, IpUtils.ipChecksum(packet, 0));
+        assertEquals((short) 0xffff, IpUtils.udpChecksum(packet, 0, IPV4_HEADER_LENGTH));
+    }
+}