Mark ab/7061308 as merged in stage.

Bug: 180401296
Merged-In: I449496e24ddbb70e848a97b9f26bda5859a9a9db
Change-Id: I2c4ae09cc96c294cb190e9fb0ae100b14dc87c21
diff --git a/staticlibs/Android.bp b/staticlibs/Android.bp
index c7a4bd5..a3bfbce 100644
--- a/staticlibs/Android.bp
+++ b/staticlibs/Android.bp
@@ -27,6 +27,10 @@
 // included in the bootclasspath, they could incorrectly be included in the SDK documentation even
 // though they are not in the current.txt files.
 
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
 java_library {
   name: "net-utils-device-common",
   srcs: [
@@ -73,6 +77,7 @@
   host_supported: true,
   visibility: [
       "//frameworks/libs/net/common/tests:__subpackages__",
+      "//frameworks/libs/net/client-libs/tests:__subpackages__",
   ],
   static_libs: [
       "kotlin-test"
@@ -149,7 +154,6 @@
     name: "net-utils-services-common-srcs",
     srcs: [
         "device/android/net/NetworkFactory.java",
-        "device/com/android/net/module/util/CollectionUtils.java",
     ],
     visibility: [
         "//frameworks/base/services/net",
@@ -163,6 +167,7 @@
         ":framework-annotations",
     ],
     sdk_version: "system_current",
+    min_sdk_version: "30",
     visibility: [
         "//frameworks/base/services/net",
     ],
diff --git a/staticlibs/device/android/net/NetworkFactory.java b/staticlibs/device/android/net/NetworkFactory.java
index 55edd29..bcc6089 100644
--- a/staticlibs/device/android/net/NetworkFactory.java
+++ b/staticlibs/device/android/net/NetworkFactory.java
@@ -244,6 +244,12 @@
         evalRequests();
     }
 
+    /** @deprecated None of the implementors use the score, remove this method */
+    @Deprecated
+    public boolean acceptRequest(NetworkRequest request, int score) {
+        return acceptRequest(request);
+    }
+
     /**
      * Overridable function to provide complex filtering.
      * Called for every request every time a new NetworkRequest is seen
@@ -263,7 +269,7 @@
      *
      * @return {@code true} to accept the request.
      */
-    public boolean acceptRequest(NetworkRequest request, int score) {
+    public boolean acceptRequest(NetworkRequest request) {
         return true;
     }
 
@@ -357,8 +363,14 @@
     protected void startNetwork() { }
     protected void stopNetwork() { }
 
-    // override to do fancier stuff
+    /** @deprecated none of the implementors use the score : migrate them */
+    @Deprecated
     protected void needNetworkFor(NetworkRequest networkRequest, int score) {
+        needNetworkFor(networkRequest);
+    }
+
+    // override to do fancier stuff
+    protected void needNetworkFor(NetworkRequest networkRequest) {
         if (++mRefCount == 1) startNetwork();
     }
 
diff --git a/staticlibs/device/com/android/net/module/util/HexDump.java b/staticlibs/device/com/android/net/module/util/HexDump.java
new file mode 100644
index 0000000..6d36487
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/HexDump.java
@@ -0,0 +1,236 @@
+/*
+ * 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 android.annotation.Nullable;
+
+/**
+ * Hex utility functions.
+ */
+public class HexDump {
+    private static final char[] HEX_DIGITS = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+            'A', 'B', 'C', 'D', 'E', 'F' };
+    private static final char[] HEX_LOWER_CASE_DIGITS = { '0', '1', '2', '3', '4', '5', '6', '7',
+            '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
+
+    /**
+     * Dump the hex string corresponding to the specified byte array.
+     *
+     * @param array byte array to be dumped.
+     */
+    public static String dumpHexString(@Nullable byte[] array) {
+        if (array == null) return "(null)";
+        return dumpHexString(array, 0, array.length);
+    }
+
+    /**
+     * Dump the hex string corresponding to the specified byte array.
+     *
+     * @param array byte array to be dumped.
+     * @param offset the offset in array where dump should start.
+     * @param length the length of bytes to be dumped.
+     */
+    public static String dumpHexString(@Nullable byte[] array, int offset, int length) {
+        if (array == null) return "(null)";
+        StringBuilder result = new StringBuilder();
+
+        byte[] line = new byte[16];
+        int lineIndex = 0;
+
+        result.append("\n0x");
+        result.append(toHexString(offset));
+
+        for (int i = offset; i < offset + length; i++) {
+            if (lineIndex == 16) {
+                result.append(" ");
+
+                for (int j = 0; j < 16; j++) {
+                    if (line[j] > ' ' && line[j] < '~') {
+                        result.append(new String(line, j, 1));
+                    } else {
+                        result.append(".");
+                    }
+                }
+
+                result.append("\n0x");
+                result.append(toHexString(i));
+                lineIndex = 0;
+            }
+
+            byte b = array[i];
+            result.append(" ");
+            result.append(HEX_DIGITS[(b >>> 4) & 0x0F]);
+            result.append(HEX_DIGITS[b & 0x0F]);
+
+            line[lineIndex++] = b;
+        }
+
+        if (lineIndex != 16) {
+            int count = (16 - lineIndex) * 3;
+            count++;
+            for (int i = 0; i < count; i++) {
+                result.append(" ");
+            }
+
+            for (int i = 0; i < lineIndex; i++) {
+                if (line[i] > ' ' && line[i] < '~') {
+                    result.append(new String(line, i, 1));
+                } else {
+                    result.append(".");
+                }
+            }
+        }
+
+        return result.toString();
+    }
+
+    /**
+     * Convert a byte to an uppercase hex string.
+     *
+     * @param b the byte to be converted.
+     */
+    public static String toHexString(byte b) {
+        return toHexString(toByteArray(b));
+    }
+
+    /**
+     * Convert a byte array to an uppercase hex string.
+     *
+     * @param array the byte array to be converted.
+     */
+    public static String toHexString(byte[] array) {
+        return toHexString(array, 0, array.length, true);
+    }
+
+    /**
+     * Convert a byte array to a hex string.
+     *
+     * @param array the byte array to be converted.
+     * @param upperCase whether the converted hex string should be uppercase or not.
+     */
+    public static String toHexString(byte[] array, boolean upperCase) {
+        return toHexString(array, 0, array.length, upperCase);
+    }
+
+    /**
+     * Convert a byte array to hex string.
+     *
+     * @param array the byte array to be converted.
+     * @param offset the offset in array where conversion should start.
+     * @param length the length of bytes to be converted.
+     */
+    public static String toHexString(byte[] array, int offset, int length) {
+        return toHexString(array, offset, length, true);
+    }
+
+    /**
+     * Convert a byte array to hex string.
+     *
+     * @param array the byte array to be converted.
+     * @param offset the offset in array where conversion should start.
+     * @param length the length of bytes to be converted.
+     * @param upperCase whether the converted hex string should be uppercase or not.
+     */
+    public static String toHexString(byte[] array, int offset, int length, boolean upperCase) {
+        char[] digits = upperCase ? HEX_DIGITS : HEX_LOWER_CASE_DIGITS;
+        char[] buf = new char[length * 2];
+
+        int bufIndex = 0;
+        for (int i = offset; i < offset + length; i++) {
+            byte b = array[i];
+            buf[bufIndex++] = digits[(b >>> 4) & 0x0F];
+            buf[bufIndex++] = digits[b & 0x0F];
+        }
+
+        return new String(buf);
+    }
+
+    /**
+     * Convert an integer to hex string.
+     *
+     * @param i the integer to be converted.
+     */
+    public static String toHexString(int i) {
+        return toHexString(toByteArray(i));
+    }
+
+    /**
+     * Convert a byte to byte array.
+     *
+     * @param b the byte to be converted.
+     */
+    public static byte[] toByteArray(byte b) {
+        byte[] array = new byte[1];
+        array[0] = b;
+        return array;
+    }
+
+    /**
+     * Convert an integer to byte array.
+     *
+     * @param i the integer to be converted.
+     */
+    public static byte[] toByteArray(int i) {
+        byte[] array = new byte[4];
+
+        array[3] = (byte) (i & 0xFF);
+        array[2] = (byte) ((i >> 8) & 0xFF);
+        array[1] = (byte) ((i >> 16) & 0xFF);
+        array[0] = (byte) ((i >> 24) & 0xFF);
+
+        return array;
+    }
+
+    private static int toByte(char c) {
+        if (c >= '0' && c <= '9') return (c - '0');
+        if (c >= 'A' && c <= 'F') return (c - 'A' + 10);
+        if (c >= 'a' && c <= 'f') return (c - 'a' + 10);
+
+        throw new RuntimeException("Invalid hex char '" + c + "'");
+    }
+
+    /**
+     * Convert a hex string to a byte array.
+     *
+     * @param hexString the string to be converted.
+     */
+    public static byte[] hexStringToByteArray(String hexString) {
+        int length = hexString.length();
+        byte[] buffer = new byte[length / 2];
+
+        for (int i = 0; i < length; i += 2) {
+            buffer[i / 2] =
+                    (byte) ((toByte(hexString.charAt(i)) << 4) | toByte(hexString.charAt(i + 1)));
+        }
+
+        return buffer;
+    }
+
+    /**
+     * Convert a byte to hex string and append it to StringBuilder.
+     *
+     * @param sb StringBuilder instance.
+     * @param b the byte to be converted.
+     * @param upperCase whether the converted hex string should be uppercase or not.
+     */
+    public static StringBuilder appendByteAsHex(StringBuilder sb, byte b, boolean upperCase) {
+        char[] digits = upperCase ? HEX_DIGITS : HEX_LOWER_CASE_DIGITS;
+        sb.append(digits[(b >> 4) & 0xf]);
+        sb.append(digits[b & 0xf]);
+        return sb;
+    }
+}
diff --git a/staticlibs/device/com/android/net/module/util/Struct.java b/staticlibs/device/com/android/net/module/util/Struct.java
index 6d8d301..f11a5ac 100644
--- a/staticlibs/device/com/android/net/module/util/Struct.java
+++ b/staticlibs/device/com/android/net/module/util/Struct.java
@@ -27,9 +27,15 @@
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Modifier;
 import java.math.BigInteger;
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
 import java.nio.BufferUnderflowException;
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
+import java.util.Arrays;
+import java.util.Objects;
 import java.util.concurrent.ConcurrentHashMap;
 
 /**
@@ -97,21 +103,23 @@
  */
 public class Struct {
     public enum Type {
-        U8,        // unsigned byte,  size = 1 byte
-        U16,       // unsigned short, size = 2 bytes
-        U32,       // unsigned int,   size = 4 bytes
-        U63,       // unsigned long(MSB: 0), size = 8 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
-        UBE16,     // unsigned short in network order, size = 2 bytes
-        UBE32,     // unsigned int in network order,   size = 4 bytes
-        UBE63,     // unsigned long(MSB: 0) in network order, size = 8 bytes
-        UBE64,     // unsigned long in network order,  size = 8 bytes
-        ByteArray, // byte array with predefined length
-        EUI48,     // a 48-bits long MAC address
+        U8,          // unsigned byte,  size = 1 byte
+        U16,         // unsigned short, size = 2 bytes
+        U32,         // unsigned int,   size = 4 bytes
+        U63,         // unsigned long(MSB: 0), size = 8 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
+        UBE16,       // unsigned short in network order, size = 2 bytes
+        UBE32,       // unsigned int in network order,   size = 4 bytes
+        UBE63,       // unsigned long(MSB: 0) in network order, size = 8 bytes
+        UBE64,       // unsigned long in network order,  size = 8 bytes
+        ByteArray,   // byte array with predefined length
+        EUI48,       // IEEE Extended Unique Identifier, a 48-bits long MAC address in network order
+        Ipv4Address, // IPv4 address in network order
+        Ipv6Address, // IPv6 address in network order
     }
 
     /**
@@ -184,6 +192,12 @@
             case EUI48:
                 if (fieldType == MacAddress.class) return;
                 break;
+            case Ipv4Address:
+                if (fieldType == Inet4Address.class) return;
+                break;
+            case Ipv6Address:
+                if (fieldType == Inet6Address.class) return;
+                break;
             default:
                 throw new IllegalArgumentException("Unknown type" + annotation.type());
         }
@@ -221,6 +235,12 @@
             case EUI48:
                 length = 6;
                 break;
+            case Ipv4Address:
+                length = 4;
+                break;
+            case Ipv6Address:
+                length = 16;
+                break;
             default:
                 throw new IllegalArgumentException("Unknown type" + annotation.type());
         }
@@ -386,6 +406,17 @@
                 buf.get(macAddress);
                 value = MacAddress.fromBytes(macAddress);
                 break;
+            case Ipv4Address:
+            case Ipv6Address:
+                final boolean isIpv6 = (fieldInfo.annotation.type() == Type.Ipv6Address);
+                final byte[] address = new byte[isIpv6 ? 16 : 4];
+                buf.get(address);
+                try {
+                    value = InetAddress.getByAddress(address);
+                } catch (UnknownHostException e) {
+                    throw new IllegalArgumentException("illegal length of IP address", e);
+                }
+                break;
             default:
                 throw new IllegalArgumentException("Unknown type:" + fieldInfo.annotation.type());
         }
@@ -459,6 +490,11 @@
                 final byte[] macAddress = ((MacAddress) value).toByteArray();
                 output.put(macAddress);
                 break;
+            case Ipv4Address:
+            case Ipv6Address:
+                final byte[] address = ((InetAddress) value).getAddress();
+                output.put(address);
+                break;
             default:
                 throw new IllegalArgumentException("Unknown type:" + fieldInfo.annotation.type());
         }
@@ -602,4 +638,83 @@
         writeToByteBufferInternal(buffer, fieldInfos);
         return output;
     }
+
+    /** Convert the parsed Struct subclass object to byte array with native order. */
+    public final byte[] writeToBytes() {
+        return writeToBytes(ByteOrder.nativeOrder());
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == null || this.getClass() != obj.getClass()) return false;
+
+        final FieldInfo[] fieldInfos = getClassFieldInfo(this.getClass());
+        for (int i = 0; i < fieldInfos.length; i++) {
+            try {
+                final Object value = fieldInfos[i].field.get(this);
+                final Object otherValue = fieldInfos[i].field.get(obj);
+
+                // Use Objects#deepEquals because the equals method on arrays does not check the
+                // contents of the array. The only difference between Objects#deepEquals and
+                // Objects#equals is that the former will call Arrays#deepEquals when comparing
+                // arrays. In turn, the only difference between Arrays#deepEquals is that it
+                // supports nested arrays. Struct does not currently support these, and if it did,
+                // Objects#deepEquals might be more correct.
+                if (!Objects.deepEquals(value, otherValue)) return false;
+            } catch (IllegalAccessException e) {
+                throw new IllegalStateException("Cannot access field: " + fieldInfos[i].field, e);
+            }
+        }
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        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);
+            }
+            // 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.
+            if (fieldInfos[i].field.getType() == byte[].class) {
+                values[i] = Arrays.hashCode((byte[]) value);
+            } else {
+                values[i] = value;
+            }
+        }
+        return Objects.hash(values);
+    }
+
+    @Override
+    public String toString() {
+        final StringBuilder sb = new StringBuilder();
+        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);
+            }
+            if (value == null) {
+                sb.append("null");
+            } else if (fieldInfos[i].annotation.type() == Type.ByteArray) {
+                sb.append("0x").append(HexDump.toHexString((byte[]) value));
+            } else if (fieldInfos[i].annotation.type() == Type.Ipv4Address
+                    || fieldInfos[i].annotation.type() == Type.Ipv6Address) {
+                sb.append(((InetAddress) value).getHostAddress());
+            } else {
+                sb.append(value.toString());
+            }
+            if (i != fieldInfos.length - 1) sb.append(", ");
+        }
+        return sb.toString();
+    }
 }
diff --git a/staticlibs/device/com/android/net/module/util/CollectionUtils.java b/staticlibs/framework/com/android/net/module/util/CollectionUtils.java
similarity index 79%
rename from staticlibs/device/com/android/net/module/util/CollectionUtils.java
rename to staticlibs/framework/com/android/net/module/util/CollectionUtils.java
index 74f738d..cb1e3e7 100644
--- a/staticlibs/device/com/android/net/module/util/CollectionUtils.java
+++ b/staticlibs/framework/com/android/net/module/util/CollectionUtils.java
@@ -66,6 +66,28 @@
     }
 
     /**
+     * @return True if all elements satisfy the predicate, false otherwise.
+     *   Note that means this always returns true for empty collections.
+     */
+    public static <T> boolean all(@NonNull Collection<T> elem, @NonNull Predicate<T> predicate) {
+        for (final T e : elem) {
+            if (!predicate.test(e)) return false;
+        }
+        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) {
+        for (final T e : elem) {
+            if (predicate.test(e)) return true;
+        }
+        return false;
+    }
+
+    /**
      * @return True if there exists at least one element in the sparse array for which
      * condition {@code predicate}
      */
diff --git a/staticlibs/framework/com/android/net/module/util/NetUtils.java b/staticlibs/framework/com/android/net/module/util/NetUtils.java
index 4331b65..f08257a 100644
--- a/staticlibs/framework/com/android/net/module/util/NetUtils.java
+++ b/staticlibs/framework/com/android/net/module/util/NetUtils.java
@@ -23,6 +23,7 @@
 import java.net.Inet4Address;
 import java.net.Inet6Address;
 import java.net.InetAddress;
+import java.net.UnknownHostException;
 import java.util.Collection;
 
 /**
@@ -67,4 +68,44 @@
         }
         return bestRoute;
     }
+
+    /**
+     * Get InetAddress masked with prefixLength.  Will never return null.
+     * @param address the IP address to mask with
+     * @param prefixLength the prefixLength used to mask the IP
+     */
+    public static InetAddress getNetworkPart(InetAddress address, int prefixLength) {
+        byte[] array = address.getAddress();
+        maskRawAddress(array, prefixLength);
+
+        InetAddress netPart = null;
+        try {
+            netPart = InetAddress.getByAddress(array);
+        } catch (UnknownHostException e) {
+            throw new RuntimeException("getNetworkPart error - " + e.toString());
+        }
+        return netPart;
+    }
+
+    /**
+     *  Masks a raw IP address byte array with the specified prefix length.
+     */
+    public static void maskRawAddress(byte[] array, int prefixLength) {
+        if (prefixLength < 0 || prefixLength > array.length * 8) {
+            throw new RuntimeException("IP address with " + array.length
+                    + " bytes has invalid prefix length " + prefixLength);
+        }
+
+        int offset = prefixLength / 8;
+        int remainder = prefixLength % 8;
+        byte mask = (byte) (0xFF << (8 - remainder));
+
+        if (offset < array.length) array[offset] = (byte) (array[offset] & mask);
+
+        offset++;
+
+        for (; offset < array.length; offset++) {
+            array[offset] = 0;
+        }
+    }
 }
diff --git a/staticlibs/device/com/android/net/module/util/NetworkCapabilitiesUtils.java b/staticlibs/framework/com/android/net/module/util/NetworkCapabilitiesUtils.java
similarity index 98%
rename from staticlibs/device/com/android/net/module/util/NetworkCapabilitiesUtils.java
rename to staticlibs/framework/com/android/net/module/util/NetworkCapabilitiesUtils.java
index dd5a481..5a0200f 100644
--- a/staticlibs/device/com/android/net/module/util/NetworkCapabilitiesUtils.java
+++ b/staticlibs/framework/com/android/net/module/util/NetworkCapabilitiesUtils.java
@@ -23,7 +23,8 @@
 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
 import static android.net.NetworkCapabilities.TRANSPORT_WIFI_AWARE;
 
-import androidx.annotation.NonNull;
+import android.annotation.NonNull;
+
 
 /**
  * Utilities to examine {@link android.net.NetworkCapabilities}.
diff --git a/staticlibs/framework/com/android/net/module/util/NetworkIdentityUtils.java b/staticlibs/framework/com/android/net/module/util/NetworkIdentityUtils.java
new file mode 100644
index 0000000..94e6017
--- /dev/null
+++ b/staticlibs/framework/com/android/net/module/util/NetworkIdentityUtils.java
@@ -0,0 +1,51 @@
+/*
+ * 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 android.annotation.NonNull;
+import android.annotation.Nullable;
+
+/**
+ * Utilities to examine {@link android.net.NetworkIdentity}.
+ */
+public class NetworkIdentityUtils {
+    /**
+     * Scrub given IMSI on production builds.
+     */
+    @NonNull
+    public static String scrubSubscriberId(@Nullable String subscriberId) {
+        if (subscriberId != null) {
+            // TODO: parse this as MCC+MNC instead of hard-coding
+            return subscriberId.substring(0, Math.min(6, subscriberId.length())) + "...";
+        } else {
+            return "null";
+        }
+    }
+
+    /**
+     * Scrub given IMSI on production builds.
+     */
+    @Nullable
+    public static String[] scrubSubscriberIds(@Nullable String[] subscriberIds) {
+        if (subscriberIds == null) return null;
+        final String[] res = new String[subscriberIds.length];
+        for (int i = 0; i < res.length; i++) {
+            res[i] = scrubSubscriberId(subscriberIds[i]);
+        }
+        return res;
+    }
+}
diff --git a/staticlibs/framework/com/android/net/module/util/NetworkStackConstants.java b/staticlibs/framework/com/android/net/module/util/NetworkStackConstants.java
index a227505..5f62186 100644
--- a/staticlibs/framework/com/android/net/module/util/NetworkStackConstants.java
+++ b/staticlibs/framework/com/android/net/module/util/NetworkStackConstants.java
@@ -16,7 +16,10 @@
 
 package com.android.net.module.util;
 
+import android.net.InetAddresses;
+
 import java.net.Inet4Address;
+import java.net.Inet6Address;
 import java.net.InetAddress;
 import java.net.UnknownHostException;
 
@@ -51,8 +54,6 @@
             (byte) 0xff, (byte) 0xff, (byte) 0xff,
     };
 
-    public static final int DEFAULT_LINK_MTU = 1500;
-
     /**
      * ARP constants.
      *
@@ -98,7 +99,11 @@
             (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff);
     public static final Inet4Address IPV4_ADDR_ANY = makeInet4Address(
             (byte) 0, (byte) 0, (byte) 0, (byte) 0);
-
+    public static final Inet6Address IPV6_ADDR_ANY = makeInet6Address(new byte[]{
+            (byte) 0, (byte) 0, (byte) 0, (byte) 0,
+            (byte) 0, (byte) 0, (byte) 0, (byte) 0,
+            (byte) 0, (byte) 0, (byte) 0, (byte) 0,
+            (byte) 0, (byte) 0, (byte) 0, (byte) 0 });
     /**
      * IPv6 constants.
      *
@@ -112,6 +117,12 @@
     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;
+    public static final Inet6Address IPV6_ADDR_ALL_NODES_MULTICAST =
+            (Inet6Address) InetAddresses.parseNumericAddress("ff02::1");
+    public static final Inet6Address IPV6_ADDR_ALL_ROUTERS_MULTICAST =
+            (Inet6Address) InetAddresses.parseNumericAddress("ff02::2");
+    public static final Inet6Address IPV6_ADDR_ALL_HOSTS_MULTICAST =
+            (Inet6Address) InetAddresses.parseNumericAddress("ff02::3");
 
     /**
      * ICMPv6 constants.
@@ -182,6 +193,16 @@
         }
     }
 
+    /**
+     * Make an Inet6Address from 16 bytes in network byte order.
+     */
+    private static Inet6Address makeInet6Address(byte[] bytes) {
+        try {
+            return (Inet6Address) InetAddress.getByAddress(bytes);
+        } catch (UnknownHostException e) {
+            throw new IllegalArgumentException("addr must be 16 bytes: this should never happen");
+        }
+    }
     private NetworkStackConstants() {
         throw new UnsupportedOperationException("This class is not to be instantiated");
     }
diff --git a/staticlibs/framework/com/android/net/module/util/PermissionUtils.java b/staticlibs/framework/com/android/net/module/util/PermissionUtils.java
new file mode 100644
index 0000000..ce8a745
--- /dev/null
+++ b/staticlibs/framework/com/android/net/module/util/PermissionUtils.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 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.content.pm.PackageManager.PERMISSION_GRANTED;
+
+import android.annotation.NonNull;
+import android.content.Context;
+
+/**
+ * Collection of permission utilities.
+ * @hide
+ */
+public final class PermissionUtils {
+    /**
+     * Return true if the context has one of given permission.
+     */
+    public static boolean checkAnyPermissionOf(@NonNull Context context,
+            @NonNull String... permissions) {
+        for (String permission : permissions) {
+            if (context.checkCallingOrSelfPermission(permission) == PERMISSION_GRANTED) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Enforce permission check on the context that should have one of given permission.
+     */
+    public static void enforceAnyPermissionOf(@NonNull Context context,
+            @NonNull String... permissions) {
+        if (!checkAnyPermissionOf(context, permissions)) {
+            throw new SecurityException("Requires one of the following permissions: "
+                    + String.join(", ", permissions) + ".");
+        }
+    }
+}
diff --git a/staticlibs/framework/com/android/net/module/util/ProxyUtils.java b/staticlibs/framework/com/android/net/module/util/ProxyUtils.java
index a7b8393..fdd7dca 100644
--- a/staticlibs/framework/com/android/net/module/util/ProxyUtils.java
+++ b/staticlibs/framework/com/android/net/module/util/ProxyUtils.java
@@ -22,6 +22,8 @@
 import java.util.Collections;
 import java.util.List;
 import java.util.Locale;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 /**
  * Collection of network common utilities.
@@ -30,6 +32,28 @@
  */
 public final class ProxyUtils {
 
+    public static final int PROXY_VALID             = 0;
+    public static final int PROXY_HOSTNAME_EMPTY    = 1;
+    public static final int PROXY_HOSTNAME_INVALID  = 2;
+    public static final int PROXY_PORT_EMPTY        = 3;
+    public static final int PROXY_PORT_INVALID      = 4;
+    public static final int PROXY_EXCLLIST_INVALID  = 5;
+
+    // Hostname / IP REGEX validation
+    // Matches blank input, ips, and domain names
+    private static final String NAME_IP_REGEX =
+            "[a-zA-Z0-9]+(\\-[a-zA-Z0-9]+)*(\\.[a-zA-Z0-9]+(\\-[a-zA-Z0-9]+)*)*";
+    private static final Pattern HOSTNAME_PATTERN;
+    private static final String HOSTNAME_REGEXP = "^$|^" + NAME_IP_REGEX + "$";
+    private static final Pattern EXCLLIST_PATTERN;
+    private static final String EXCL_REGEX =
+            "[a-zA-Z0-9*]+(\\-[a-zA-Z0-9*]+)*(\\.[a-zA-Z0-9*]+(\\-[a-zA-Z0-9*]+)*)*";
+    private static final String EXCLLIST_REGEXP = "^$|^" + EXCL_REGEX + "(," + EXCL_REGEX + ")*$";
+    static {
+        HOSTNAME_PATTERN = Pattern.compile(HOSTNAME_REGEXP);
+        EXCLLIST_PATTERN = Pattern.compile(EXCLLIST_REGEXP);
+    }
+
     /** Converts exclusion list from String to List. */
     public static List<String> exclusionStringAsList(String exclusionList) {
         if (exclusionList == null) {
@@ -45,4 +69,30 @@
         }
         return TextUtils.join(",", exclusionList);
     }
+
+    /**
+     * Validate syntax of hostname, port and exclusion list entries
+     */
+    public static int validate(String hostname, String port, String exclList) {
+        Matcher match = HOSTNAME_PATTERN.matcher(hostname);
+        Matcher listMatch = EXCLLIST_PATTERN.matcher(exclList);
+
+        if (!match.matches()) return PROXY_HOSTNAME_INVALID;
+
+        if (!listMatch.matches()) return PROXY_EXCLLIST_INVALID;
+
+        if (hostname.length() > 0 && port.length() == 0) return PROXY_PORT_EMPTY;
+
+        if (port.length() > 0) {
+            if (hostname.length() == 0) return PROXY_HOSTNAME_EMPTY;
+            int portVal = -1;
+            try {
+                portVal = Integer.parseInt(port);
+            } catch (NumberFormatException ex) {
+                return PROXY_PORT_INVALID;
+            }
+            if (portVal <= 0 || portVal > 0xFFFF) return PROXY_PORT_INVALID;
+        }
+        return PROXY_VALID;
+    }
 }
diff --git a/staticlibs/native/OWNERS b/staticlibs/native/OWNERS
new file mode 100644
index 0000000..7655338
--- /dev/null
+++ b/staticlibs/native/OWNERS
@@ -0,0 +1 @@
+maze@google.com
diff --git a/staticlibs/native/bpf_syscall_wrappers/Android.bp b/staticlibs/native/bpf_syscall_wrappers/Android.bp
new file mode 100644
index 0000000..fa90655
--- /dev/null
+++ b/staticlibs/native/bpf_syscall_wrappers/Android.bp
@@ -0,0 +1,39 @@
+// 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 {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_library_headers {
+    name: "bpf_syscall_wrappers",
+    vendor_available: false,
+    host_supported: false,
+    native_bridge_supported: true,
+    export_include_dirs: ["include"],
+    cflags: [
+        "-Wall",
+        "-Werror",
+    ],
+    sdk_version: "30",
+    min_sdk_version: "30",
+    apex_available: [
+        "//apex_available:platform",
+        "com.android.tethering",
+    ],
+    visibility: [
+        "//packages/modules/Connectivity/Tethering",
+        "//system/bpf/libbpf_android",
+    ],
+}
diff --git a/staticlibs/native/bpf_syscall_wrappers/include/BpfSyscallWrappers.h b/staticlibs/native/bpf_syscall_wrappers/include/BpfSyscallWrappers.h
new file mode 100644
index 0000000..72eebf3
--- /dev/null
+++ b/staticlibs/native/bpf_syscall_wrappers/include/BpfSyscallWrappers.h
@@ -0,0 +1,149 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <linux/bpf.h>
+#include <linux/unistd.h>
+
+#ifdef BPF_FD_JUST_USE_INT
+  #define BPF_FD_TYPE int
+  #define BPF_FD_TO_U32(x) static_cast<__u32>(x)
+#else
+  #include <android-base/unique_fd.h>
+  #define BPF_FD_TYPE base::unique_fd&
+  #define BPF_FD_TO_U32(x) static_cast<__u32>((x).get())
+#endif
+
+#define ptr_to_u64(x) ((uint64_t)(uintptr_t)(x))
+
+namespace android {
+namespace bpf {
+
+/* Note: bpf_attr is a union which might have a much larger size then the anonymous struct portion
+ * of it that we are using.  The kernel's bpf() system call will perform a strict check to ensure
+ * all unused portions are zero.  It will fail with E2BIG if we don't fully zero bpf_attr.
+ */
+
+inline int bpf(int cmd, const bpf_attr& attr) {
+    return syscall(__NR_bpf, cmd, &attr, sizeof(attr));
+}
+
+inline int createMap(bpf_map_type map_type, uint32_t key_size, uint32_t value_size,
+                     uint32_t max_entries, uint32_t map_flags) {
+    return bpf(BPF_MAP_CREATE, {
+                                       .map_type = map_type,
+                                       .key_size = key_size,
+                                       .value_size = value_size,
+                                       .max_entries = max_entries,
+                                       .map_flags = map_flags,
+                               });
+}
+
+inline int writeToMapEntry(const BPF_FD_TYPE map_fd, const void* key, const void* value,
+                           uint64_t flags) {
+    return bpf(BPF_MAP_UPDATE_ELEM, {
+                                            .map_fd = BPF_FD_TO_U32(map_fd),
+                                            .key = ptr_to_u64(key),
+                                            .value = ptr_to_u64(value),
+                                            .flags = flags,
+                                    });
+}
+
+inline int findMapEntry(const BPF_FD_TYPE map_fd, const void* key, void* value) {
+    return bpf(BPF_MAP_LOOKUP_ELEM, {
+                                            .map_fd = BPF_FD_TO_U32(map_fd),
+                                            .key = ptr_to_u64(key),
+                                            .value = ptr_to_u64(value),
+                                    });
+}
+
+inline int deleteMapEntry(const BPF_FD_TYPE map_fd, const void* key) {
+    return bpf(BPF_MAP_DELETE_ELEM, {
+                                            .map_fd = BPF_FD_TO_U32(map_fd),
+                                            .key = ptr_to_u64(key),
+                                    });
+}
+
+inline int getNextMapKey(const BPF_FD_TYPE map_fd, const void* key, void* next_key) {
+    return bpf(BPF_MAP_GET_NEXT_KEY, {
+                                             .map_fd = BPF_FD_TO_U32(map_fd),
+                                             .key = ptr_to_u64(key),
+                                             .next_key = ptr_to_u64(next_key),
+                                     });
+}
+
+inline int getFirstMapKey(const BPF_FD_TYPE map_fd, void* firstKey) {
+    return getNextMapKey(map_fd, NULL, firstKey);
+}
+
+inline int bpfFdPin(const BPF_FD_TYPE map_fd, const char* pathname) {
+    return bpf(BPF_OBJ_PIN, {
+                                    .pathname = ptr_to_u64(pathname),
+                                    .bpf_fd = BPF_FD_TO_U32(map_fd),
+                            });
+}
+
+inline int bpfFdGet(const char* pathname, uint32_t flag) {
+    return bpf(BPF_OBJ_GET, {
+                                    .pathname = ptr_to_u64(pathname),
+                                    .file_flags = flag,
+                            });
+}
+
+inline int mapRetrieve(const char* pathname, uint32_t flag) {
+    return bpfFdGet(pathname, flag);
+}
+
+inline int mapRetrieveRW(const char* pathname) {
+    return mapRetrieve(pathname, 0);
+}
+
+inline int mapRetrieveRO(const char* pathname) {
+    return mapRetrieve(pathname, BPF_F_RDONLY);
+}
+
+inline int mapRetrieveWO(const char* pathname) {
+    return mapRetrieve(pathname, BPF_F_WRONLY);
+}
+
+inline int retrieveProgram(const char* pathname) {
+    return bpfFdGet(pathname, BPF_F_RDONLY);
+}
+
+inline int attachProgram(bpf_attach_type type, const BPF_FD_TYPE prog_fd,
+                         const BPF_FD_TYPE cg_fd) {
+    return bpf(BPF_PROG_ATTACH, {
+                                        .target_fd = BPF_FD_TO_U32(cg_fd),
+                                        .attach_bpf_fd = BPF_FD_TO_U32(prog_fd),
+                                        .attach_type = type,
+                                });
+}
+
+inline int detachProgram(bpf_attach_type type, const BPF_FD_TYPE cg_fd) {
+    return bpf(BPF_PROG_DETACH, {
+                                        .target_fd = BPF_FD_TO_U32(cg_fd),
+                                        .attach_type = type,
+                                });
+}
+
+}  // namespace bpf
+}  // namespace android
+
+#undef ptr_to_u64
+#undef BPF_FD_TO_U32
+#undef BPF_FD_TYPE
+#undef BPF_FD_JUST_USE_INT
diff --git a/staticlibs/native/netjniutils/Android.bp b/staticlibs/native/netjniutils/Android.bp
index 8417c52..d8e6a04 100644
--- a/staticlibs/native/netjniutils/Android.bp
+++ b/staticlibs/native/netjniutils/Android.bp
@@ -12,6 +12,10 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
 cc_library_static {
     name: "libnetjniutils",
     srcs: ["netjniutils.cpp"],
diff --git a/staticlibs/tests/unit/Android.bp b/staticlibs/tests/unit/Android.bp
index c00202e..4ce4b0e 100644
--- a/staticlibs/tests/unit/Android.bp
+++ b/staticlibs/tests/unit/Android.bp
@@ -2,6 +2,10 @@
 // Build NetworkStaticLibTests package
 //########################################################################
 
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
 android_library {
     name: "NetworkStaticLibTestsLib",
     srcs: ["src/**/*.java","src/**/*.kt"],
@@ -38,4 +42,3 @@
     jarjar_rules: "jarjar-rules.txt",
     test_suites: ["device-tests"],
 }
-
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
new file mode 100644
index 0000000..0007742
--- /dev/null
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/CollectionUtilsTest.kt
@@ -0,0 +1,54 @@
+/*
+ * 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 androidx.test.filters.SmallTest
+import androidx.test.runner.AndroidJUnit4
+import org.junit.Test
+import org.junit.runner.RunWith
+import kotlin.test.assertFalse
+import kotlin.test.assertTrue
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class CollectionUtilsTest {
+    @Test
+    fun testAny() {
+        assertTrue(CollectionUtils.any(listOf("A", "B", "C", "D", "E")) { it == "E" })
+        assertFalse(CollectionUtils.any(listOf("A", "B", "C", "D", "E")) { it == "F" })
+        assertTrue(CollectionUtils.any(listOf("AA", "BBB")) { it.length >= 3 })
+        assertFalse(CollectionUtils.any(listOf("A", "BB", "CCC")) { it.length >= 4 })
+        assertFalse(CollectionUtils.any(listOf("A", "BB", "CCC")) { it.length < 0 })
+        assertFalse(CollectionUtils.any(listOf<String>()) { true })
+        assertFalse(CollectionUtils.any(listOf<String>()) { false })
+        assertTrue(CollectionUtils.any(listOf("A")) { true })
+        assertFalse(CollectionUtils.any(listOf("A")) { false })
+    }
+
+    @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" })
+        assertFalse(CollectionUtils.all(listOf("A", "BB", "CCC")) { it.length > 2 })
+        assertTrue(CollectionUtils.all(listOf("A", "BB", "CCC")) { it.length >= 1 })
+        assertTrue(CollectionUtils.all(listOf("A", "BB", "CCC")) { it.length < 4 })
+        assertTrue(CollectionUtils.all(listOf<String>()) { true })
+        assertTrue(CollectionUtils.all(listOf<String>()) { false })
+        assertTrue(CollectionUtils.all(listOf(1)) { true })
+        assertFalse(CollectionUtils.all(listOf(1)) { false })
+    }
+}
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/HexDumpTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/HexDumpTest.java
new file mode 100644
index 0000000..5a15585
--- /dev/null
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/HexDumpTest.java
@@ -0,0 +1,72 @@
+/*
+ * 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 org.junit.Assert.assertArrayEquals;
+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;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class HexDumpTest {
+    @Test
+    public void testBytesToHexString() {
+        assertEquals("abcdef", HexDump.toHexString(
+                new byte[]{(byte) 0xab, (byte) 0xcd, (byte) 0xef}, false));
+        assertEquals("ABCDEF", HexDump.toHexString(
+                new byte[]{(byte) 0xab, (byte) 0xcd, (byte) 0xef}, true));
+    }
+
+    @Test
+    public void testNullArray() {
+        assertEquals("(null)", HexDump.dumpHexString(null));
+    }
+
+    @Test
+    public void testHexStringToByteArray() {
+        assertArrayEquals(new byte[]{(byte) 0xab, (byte) 0xcd, (byte) 0xef},
+                HexDump.hexStringToByteArray("abcdef"));
+        assertArrayEquals(new byte[]{(byte) 0xAB, (byte) 0xCD, (byte) 0xEF},
+                HexDump.hexStringToByteArray("ABCDEF"));
+    }
+
+    @Test
+    public void testIntegerToByteArray() {
+        assertArrayEquals(new byte[]{(byte) 0xff, (byte) 0x00, (byte) 0x00, (byte) 0x04},
+                HexDump.toByteArray((int) 0xff000004));
+    }
+
+    @Test
+    public void testByteToByteArray() {
+        assertArrayEquals(new byte[]{(byte) 0x7f}, HexDump.toByteArray((byte) 0x7f));
+    }
+
+    @Test
+    public void testIntegerToHexString() {
+        assertEquals("FF000004", HexDump.toHexString((int) 0xff000004));
+    }
+
+    @Test
+    public void testByteToHexString() {
+        assertEquals("7F", HexDump.toHexString((byte) 0x7f));
+    }
+}
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/NetworkIdentityUtilsTest.kt b/staticlibs/tests/unit/src/com/android/net/module/util/NetworkIdentityUtilsTest.kt
new file mode 100644
index 0000000..2904e12
--- /dev/null
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/NetworkIdentityUtilsTest.kt
@@ -0,0 +1,49 @@
+/*
+ * 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 androidx.test.filters.SmallTest
+import androidx.test.runner.AndroidJUnit4
+import com.android.net.module.util.NetworkIdentityUtils.scrubSubscriberId
+import com.android.net.module.util.NetworkIdentityUtils.scrubSubscriberIds
+import com.android.testutils.assertContainsStringsExactly
+import org.junit.Test
+import org.junit.runner.RunWith
+import kotlin.test.assertEquals
+import kotlin.test.assertNull
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class NetworkIdentityUtilsTest {
+    @Test
+    fun testScrubSubscriberId() {
+        assertEquals("123456...", scrubSubscriberId("1234567890123"))
+        assertEquals("123456...", scrubSubscriberId("1234567"))
+        assertEquals("123...", scrubSubscriberId("123"))
+        assertEquals("...", scrubSubscriberId(""))
+        assertEquals("null", scrubSubscriberId(null))
+    }
+
+    @Test
+    fun testScrubSubscriberIds() {
+        assertContainsStringsExactly(scrubSubscriberIds(arrayOf("1234567", "", null))!!,
+                "123456...", "...", "null")
+        assertContainsStringsExactly(scrubSubscriberIds(arrayOf("12345"))!!, "12345...")
+        assertContainsStringsExactly(scrubSubscriberIds(arrayOf())!!)
+        assertNull(scrubSubscriberIds(null))
+    }
+}
\ No newline at end of file
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 47b34b9..b172e21 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
@@ -20,8 +20,11 @@
 
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertTrue;
 
+import android.net.InetAddresses;
 import android.net.IpPrefix;
 import android.net.MacAddress;
 
@@ -36,6 +39,8 @@
 import org.junit.runner.RunWith;
 
 import java.math.BigInteger;
+import java.net.Inet4Address;
+import java.net.Inet6Address;
 import java.net.InetAddress;
 import java.nio.BufferOverflowException;
 import java.nio.ByteBuffer;
@@ -67,6 +72,15 @@
 
     // PREF64 option, 2001:db8:3:4:5:6::/96, lifetime: 10064
     private static final String OPT_PREF64 = "2750" + "20010db80003000400050006";
+    private static final byte[] TEST_PREFIX64 = new byte[]{
+            (byte) 0x20, (byte) 0x01, (byte) 0x0d, (byte) 0xb8, (byte) 0x00, (byte) 0x03,
+            (byte) 0x00, (byte) 0x04, (byte) 0x00, (byte) 0x05, (byte) 0x00, (byte) 0x06,
+    };
+
+    private static final Inet4Address TEST_IPV4_ADDRESS =
+            (Inet4Address) InetAddresses.parseNumericAddress("192.168.100.1");
+    private static final Inet6Address TEST_IPV6_ADDRESS =
+            (Inet6Address) InetAddresses.parseNumericAddress("2001:db8:3:4:5:6:7:8");
 
     private <T> T doParsingMessageTest(final String hexString, final Class<T> clazz,
             final ByteOrder order) {
@@ -697,4 +711,320 @@
         assertThrows(IllegalArgumentException.class,
                 () -> Struct.parse(BadMacAddressType.class, toByteBuffer("ffffffffffff")));
     }
+
+    @Test
+    public void testStructToByteArrayRoundTrip() {
+        final SignedDataMessage littleEndianMsg = doParsingMessageTest(SIGNED_DATA,
+                SignedDataMessage.class, ByteOrder.LITTLE_ENDIAN);
+        assertArrayEquals(toByteBuffer(SIGNED_DATA).array(),
+                littleEndianMsg.writeToBytes(ByteOrder.LITTLE_ENDIAN));
+
+        final SignedDataMessage bigEndianMsg = doParsingMessageTest(SIGNED_DATA,
+                SignedDataMessage.class, ByteOrder.BIG_ENDIAN);
+        assertArrayEquals(toByteBuffer(SIGNED_DATA).array(),
+                bigEndianMsg.writeToBytes(ByteOrder.BIG_ENDIAN));
+
+        final SignedDataMessage nativeOrderMsg = ByteOrder.nativeOrder().equals(
+                ByteOrder.LITTLE_ENDIAN) ? littleEndianMsg : bigEndianMsg;
+        assertArrayEquals(toByteBuffer(SIGNED_DATA).array(),
+                nativeOrderMsg.writeToBytes());
+    }
+
+    @Test
+    public void testStructToByteArray() {
+        final SignedDataMessage msg = new SignedDataMessage((byte) -5, (short) 42, (int) 0xff000004,
+                (long) 0xff000004ff000005L);
+        final String leHexString = "fb" + "2a00" + "040000ff" + "050000ff040000ff";
+        final String beHexString = "fb" + "002a" + "ff000004" + "ff000004ff000005";
+        final String hexString = ByteOrder.nativeOrder().equals(ByteOrder.LITTLE_ENDIAN)
+                ? leHexString : beHexString;
+        assertArrayEquals(toByteBuffer(hexString).array(), msg.writeToBytes());
+    }
+
+    static class IpAddressMessage extends Struct {
+        @Field(order = 0, type = Type.Ipv4Address) public final Inet4Address ipv4Address;
+        @Field(order = 1, type = Type.Ipv6Address) public final Inet6Address ipv6Address;
+
+        IpAddressMessage(final Inet4Address ipv4Address, final Inet6Address ipv6Address) {
+            this.ipv4Address = ipv4Address;
+            this.ipv6Address = ipv6Address;
+        }
+    }
+
+    @Test
+    public void testIpAddressType() {
+        final IpAddressMessage msg = doParsingMessageTest("c0a86401"
+                + "20010db8000300040005000600070008", IpAddressMessage.class, ByteOrder.BIG_ENDIAN);
+
+        assertEquals(TEST_IPV4_ADDRESS, msg.ipv4Address);
+        assertEquals(TEST_IPV6_ADDRESS, msg.ipv6Address);
+
+        assertEquals(20, Struct.getSize(IpAddressMessage.class));
+        assertArrayEquals(toByteBuffer("c0a86401" + "20010db8000300040005000600070008").array(),
+                msg.writeToBytes(ByteOrder.BIG_ENDIAN));
+    }
+
+    static class WrongIpAddressType extends Struct {
+        @Field(order = 0, type = Type.Ipv4Address) public byte[] ipv4Address;
+        @Field(order = 1, type = Type.Ipv6Address) public byte[] ipv6Address;
+    }
+
+    @Test
+    public void testIncorrectType_IpAddressWithByteArray() {
+        assertThrows(IllegalArgumentException.class,
+                () -> Struct.parse(WrongIpAddressType.class,
+                                   toByteBuffer("c0a86401" + "20010db8000300040005000600070008")));
+    }
+
+    static class FullTypeMessage extends Struct {
+        @Field(order = 0, type = Type.U8) public final short u8;
+        @Field(order = 1, type = Type.U16) public final int u16;
+        @Field(order = 2, type = Type.U32) public final long u32;
+        @Field(order = 3, type = Type.U63) public final long u63;
+        @Field(order = 4, type = Type.U64) public final BigInteger u64;
+        @Field(order = 5, type = Type.S8) public final byte s8;
+        @Field(order = 6, type = Type.S16) public final short s16;
+        @Field(order = 7, type = Type.S32) public final int s32;
+        @Field(order = 8, type = Type.S64) public final long s64;
+        @Field(order = 9, type = Type.UBE16) public final int ube16;
+        @Field(order = 10, type = Type.UBE32) public final long ube32;
+        @Field(order = 11, type = Type.UBE63) public final long ube63;
+        @Field(order = 12, type = Type.UBE64) public final BigInteger ube64;
+        @Field(order = 13, type = Type.ByteArray, arraysize = 12) public final byte[] bytes;
+        @Field(order = 14, type = Type.EUI48) public final MacAddress eui48;
+        @Field(order = 15, type = Type.Ipv4Address) public final Inet4Address ipv4Address;
+        @Field(order = 16, type = Type.Ipv6Address) public final Inet6Address ipv6Address;
+
+        FullTypeMessage(final short u8, final int u16, final long u32, final long u63,
+                final BigInteger u64, final byte s8, final short s16, final int s32, final long s64,
+                final int ube16, final long ube32, final long ube63, final BigInteger ube64,
+                final byte[] bytes, final MacAddress eui48, final Inet4Address ipv4Address,
+                final Inet6Address ipv6Address) {
+            this.u8 = u8;
+            this.u16 = u16;
+            this.u32 = u32;
+            this.u63 = u63;
+            this.u64 = u64;
+            this.s8 = s8;
+            this.s16 = s16;
+            this.s32 = s32;
+            this.s64 = s64;
+            this.ube16 = ube16;
+            this.ube32 = ube32;
+            this.ube63 = ube63;
+            this.ube64 = ube64;
+            this.bytes = bytes;
+            this.eui48 = eui48;
+            this.ipv4Address = ipv4Address;
+            this.ipv6Address = ipv6Address;
+        }
+    }
+
+    private static final String FULL_TYPE_DATA = "ff" + "ffff" + "ffffffff" + "7fffffffffffffff"
+            + "ffffffffffffffff" + "7f" + "7fff" + "7fffffff" + "7fffffffffffffff" + "7fff"
+            + "7fffffff" + "7fffffffffffffff" + "ffffffffffffffff" + "20010db80003000400050006"
+            + "001122334455" + "c0a86401" + "20010db8000300040005000600070008";
+    private static final String FULL_TYPE_DATA_DIFF_MAC = "ff" + "ffff" + "ffffffff"
+            + "7fffffffffffffff" + "ffffffffffffffff" + "7f" + "7fff" + "7fffffff"
+            + "7fffffffffffffff" + "7fff" + "7fffffff" + "7fffffffffffffff" + "ffffffffffffffff"
+            + "20010db80003000400050006" + "112233445566"
+            + "c0a86401" + "20010db8000300040005000600070008";
+    private static final String FULL_TYPE_DATA_DIFF_LONG = "ff" + "ffff" + "ffffffff"
+            + "7ffffffffffffffe" + "ffffffffffffffff" + "7f" + "7fff" + "7fffffff"
+            + "7fffffffffffffff" + "7fff" + "7fffffff" + "7fffffffffffffff" + "ffffffffffffffff"
+            + "20010db80003000400050006" + "001122334455"
+            + "c0a86401" + "20010db8000300040005000600070008";
+    private static final String FULL_TYPE_DATA_DIFF_INTEGER = "ff" + "ffff" + "ffffffff"
+            + "7fffffffffffffff" + "ffffffffffffffff" + "7f" + "7fff" + "7fffffff"
+            + "7fffffffffffffff" + "7fff" + "ffffff7f" + "7fffffffffffffff" + "ffffffffffffffff"
+            + "20010db80003000400050006" + "001122334455"
+            + "c0a86401" + "20010db8000300040005000600070008";
+    private static final String FULL_TYPE_DATA_DIFF_IPV4 = "ff" + "ffff" + "ffffffff"
+            + "7fffffffffffffff" + "ffffffffffffffff" + "7f" + "7fff" + "7fffffff"
+            + "7fffffffffffffff" + "7fff" + "ffffff7f" + "7fffffffffffffff" + "ffffffffffffffff"
+            + "20010db80003000400050006" + "001122334455"
+            + "c0a81010" + "20010db8000300040005000600070008";
+    private static final String FULL_TYPE_DATA_DIFF_IPV6 = "ff" + "ffff" + "ffffffff"
+            + "7fffffffffffffff" + "ffffffffffffffff" + "7f" + "7fff" + "7fffffff"
+            + "7fffffffffffffff" + "7fff" + "ffffff7f" + "7fffffffffffffff" + "ffffffffffffffff"
+            + "20010db80003000400050006" + "001122334455"
+            + "c0a86401" + "20010db800030004000500060007000a";
+    @Test
+    public void testStructClass_equals() {
+        final FullTypeMessage msg = doParsingMessageTest(FULL_TYPE_DATA, FullTypeMessage.class,
+                ByteOrder.BIG_ENDIAN);
+
+        assertEquals(255, msg.u8);
+        assertEquals(65535, msg.u16);
+        assertEquals(4294967295L, msg.u32);
+        assertEquals(9223372036854775807L, msg.u63);
+        assertEquals(new BigInteger("18446744073709551615"), msg.u64);
+        assertEquals(127, msg.s8);
+        assertEquals(32767, msg.s16);
+        assertEquals(2147483647, msg.s32);
+        assertEquals(9223372036854775807L, msg.s64);
+        assertEquals(32767, msg.ube16);
+        assertEquals(2147483647, msg.ube32);
+        assertEquals(9223372036854775807L, msg.ube63);
+        assertEquals(new BigInteger("18446744073709551615"), msg.ube64);
+        assertArrayEquals(TEST_PREFIX64, msg.bytes);
+        assertEquals(MacAddress.fromString("00:11:22:33:44:55"), msg.eui48);
+        assertEquals(TEST_IPV4_ADDRESS, msg.ipv4Address);
+        assertEquals(TEST_IPV6_ADDRESS, msg.ipv6Address);
+
+        assertEquals(98, msg.getSize(FullTypeMessage.class));
+        assertArrayEquals(toByteBuffer(FULL_TYPE_DATA).array(),
+                msg.writeToBytes(ByteOrder.BIG_ENDIAN));
+
+        final FullTypeMessage msg1 = new FullTypeMessage((short) 0xff, (int) 0xffff,
+                (long) 0xffffffffL, (long) 0x7fffffffffffffffL,
+                new BigInteger("18446744073709551615"), (byte) 0x7f, (short) 0x7fff,
+                (int) 0x7fffffff, (long) 0x7fffffffffffffffL, (int) 0x7fff, (long) 0x7fffffffL,
+                (long) 0x7fffffffffffffffL, new BigInteger("18446744073709551615"), TEST_PREFIX64,
+                MacAddress.fromString("00:11:22:33:44:55"), TEST_IPV4_ADDRESS, TEST_IPV6_ADDRESS);
+        assertTrue(msg.equals(msg1));
+    }
+
+    static class FullTypeMessageWithDupType extends Struct {
+        @Field(order = 0, type = Type.U8) public final short u8;
+        @Field(order = 1, type = Type.U16) public final int u16;
+        @Field(order = 2, type = Type.U32) public final long u32;
+        @Field(order = 3, type = Type.S64) public final long u63; // old: U63, new: S64
+        @Field(order = 4, type = Type.UBE64) public final BigInteger u64; // old: U64, new: UBE64
+        @Field(order = 5, type = Type.S8) public final byte s8;
+        @Field(order = 6, type = Type.S16) public final short s16;
+        @Field(order = 7, type = Type.S32) public final int s32;
+        @Field(order = 8, type = Type.S64) public final long s64;
+        @Field(order = 9, type = Type.U16) public final int ube16; // old:UBE16, new: U16
+        @Field(order = 10, type = Type.UBE32) public final long ube32;
+        @Field(order = 11, type = Type.UBE63) public final long ube63;
+        @Field(order = 12, type = Type.UBE64) public final BigInteger ube64;
+        @Field(order = 13, type = Type.ByteArray, arraysize = 12) public final byte[] bytes;
+        @Field(order = 14, type = Type.EUI48) public final MacAddress eui48;
+        @Field(order = 15, type = Type.Ipv4Address) public final Inet4Address ipv4Address;
+        @Field(order = 16, type = Type.Ipv6Address) public final Inet6Address ipv6Address;
+
+        FullTypeMessageWithDupType(final short u8, final int u16, final long u32, final long u63,
+                final BigInteger u64, final byte s8, final short s16, final int s32, final long s64,
+                final int ube16, final long ube32, final long ube63, final BigInteger ube64,
+                final byte[] bytes, final MacAddress eui48, final Inet4Address ipv4Address,
+                final Inet6Address ipv6Address) {
+            this.u8 = u8;
+            this.u16 = u16;
+            this.u32 = u32;
+            this.u63 = u63;
+            this.u64 = u64;
+            this.s8 = s8;
+            this.s16 = s16;
+            this.s32 = s32;
+            this.s64 = s64;
+            this.ube16 = ube16;
+            this.ube32 = ube32;
+            this.ube63 = ube63;
+            this.ube64 = ube64;
+            this.bytes = bytes;
+            this.eui48 = eui48;
+            this.ipv4Address = ipv4Address;
+            this.ipv6Address = ipv6Address;
+        }
+    }
+
+    @Test
+    public void testStructClass_notEqualWithDifferentClass() {
+        final FullTypeMessage msg = doParsingMessageTest(FULL_TYPE_DATA, FullTypeMessage.class,
+                ByteOrder.BIG_ENDIAN);
+        final FullTypeMessageWithDupType msg1 = doParsingMessageTest(FULL_TYPE_DATA,
+                FullTypeMessageWithDupType.class, ByteOrder.BIG_ENDIAN);
+
+        assertFalse(msg.equals(msg1));
+    }
+
+    @Test
+    public void testStructClass_notEqualWithDifferentValue() {
+        final FullTypeMessage msg = doParsingMessageTest(FULL_TYPE_DATA, FullTypeMessage.class,
+                ByteOrder.BIG_ENDIAN);
+
+        // With different MAC address.
+        final FullTypeMessage msg1 = doParsingMessageTest(FULL_TYPE_DATA_DIFF_MAC,
+                FullTypeMessage.class, ByteOrder.BIG_ENDIAN);
+        assertNotEquals(msg.eui48, msg1.eui48);
+        assertFalse(msg.equals(msg1));
+
+        // With different byte array.
+        final FullTypeMessage msg2 = doParsingMessageTest(FULL_TYPE_DATA, FullTypeMessage.class,
+                ByteOrder.BIG_ENDIAN);
+        msg2.bytes[5] = (byte) 42; // change one byte in the array.
+        assertFalse(msg.equals(msg2));
+
+        // With different Long primitive.
+        final FullTypeMessage msg3 = doParsingMessageTest(FULL_TYPE_DATA_DIFF_LONG,
+                FullTypeMessage.class, ByteOrder.BIG_ENDIAN);
+        assertNotEquals(msg.u63, msg3.u63);
+        assertFalse(msg.equals(msg3));
+
+        // With different Integer primitive.
+        final FullTypeMessage msg4 = doParsingMessageTest(FULL_TYPE_DATA_DIFF_INTEGER,
+                FullTypeMessage.class, ByteOrder.BIG_ENDIAN);
+        assertNotEquals(msg.ube32, msg4.ube32);
+        assertFalse(msg.equals(msg4));
+
+        // With different IPv4 address.
+        final FullTypeMessage msg5 = doParsingMessageTest(FULL_TYPE_DATA_DIFF_IPV4,
+                FullTypeMessage.class, ByteOrder.BIG_ENDIAN);
+        assertNotEquals(msg.ipv4Address, msg5.ipv4Address);
+        assertFalse(msg.equals(msg5));
+
+        // With different IPv6 address.
+        final FullTypeMessage msg6 = doParsingMessageTest(FULL_TYPE_DATA_DIFF_IPV6,
+                FullTypeMessage.class, ByteOrder.BIG_ENDIAN);
+        assertNotEquals(msg.ipv6Address, msg6.ipv6Address);
+        assertFalse(msg.equals(msg6));
+    }
+
+    @Test
+    public void testStructClass_toString() {
+        final String expected = "u8: 255, u16: 65535, u32: 4294967295,"
+                + " u63: 9223372036854775807, u64: 18446744073709551615, s8: 127, s16: 32767,"
+                + " s32: 2147483647, s64: 9223372036854775807, ube16: 32767, ube32: 2147483647,"
+                + " ube63: 9223372036854775807, ube64: 18446744073709551615,"
+                + " bytes: 0x20010DB80003000400050006,"
+                + " eui48: 00:11:22:33:44:55,"
+                + " ipv4Address: 192.168.100.1,"
+                + " ipv6Address: 2001:db8:3:4:5:6:7:8";
+
+        final FullTypeMessage msg = doParsingMessageTest(FULL_TYPE_DATA, FullTypeMessage.class,
+                ByteOrder.BIG_ENDIAN);
+        assertEquals(expected, msg.toString());
+    }
+
+    @Test
+    public void testStructClass_toStringWithNullMember() {
+        final String expected = "u8: 255, u16: 65535, u32: 4294967295,"
+                + " u63: 9223372036854775807, u64: null, s8: 127, s16: 32767,"
+                + " s32: 2147483647, s64: 9223372036854775807, ube16: 32767, ube32: 2147483647,"
+                + " ube63: 9223372036854775807, ube64: 18446744073709551615,"
+                + " bytes: null, eui48: null, ipv4Address: 192.168.100.1,"
+                + " ipv6Address: null";
+
+        final FullTypeMessage msg = new FullTypeMessage((short) 0xff, (int) 0xffff,
+                (long) 0xffffffffL, (long) 0x7fffffffffffffffL,
+                null /* u64 */, (byte) 0x7f, (short) 0x7fff,
+                (int) 0x7fffffff, (long) 0x7fffffffffffffffL, (int) 0x7fff, (long) 0x7fffffffL,
+                (long) 0x7fffffffffffffffL, new BigInteger("18446744073709551615"),
+                null /* bytes */, null /* eui48 */, TEST_IPV4_ADDRESS, null /* ipv6Address */);
+        assertEquals(expected, msg.toString());
+    }
+
+    @Test
+    public void testStructClass_hashcode() {
+        final FullTypeMessage msg = doParsingMessageTest(FULL_TYPE_DATA, FullTypeMessage.class,
+                ByteOrder.BIG_ENDIAN);
+        final FullTypeMessage msg1 = doParsingMessageTest(FULL_TYPE_DATA, FullTypeMessage.class,
+                ByteOrder.BIG_ENDIAN);
+
+        assertNotEquals(0, msg.hashCode());
+        assertNotEquals(0, msg1.hashCode());
+        assertTrue(msg.equals(msg1));
+        assertEquals(msg.hashCode(), msg1.hashCode());
+    }
 }