Merge "Move LocationPermissionChecker to libs/net"
diff --git a/staticlibs/device/com/android/net/module/util/Struct.java b/staticlibs/device/com/android/net/module/util/Struct.java
index f11a5ac..d95c303 100644
--- a/staticlibs/device/com/android/net/module/util/Struct.java
+++ b/staticlibs/device/com/android/net/module/util/Struct.java
@@ -17,6 +17,7 @@
 package com.android.net.module.util;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.net.MacAddress;
 
 import java.lang.annotation.ElementType;
@@ -428,6 +429,15 @@
         return value;
     }
 
+    @Nullable
+    private Object getFieldValue(@NonNull java.lang.reflect.Field field) {
+        try {
+            return field.get(this);
+        } catch (IllegalAccessException e) {
+            throw new IllegalStateException("Cannot access field: " + field, e);
+        }
+    }
+
     private static void putFieldValue(final ByteBuffer output, final FieldInfo fieldInfo,
             final Object value) throws BufferUnderflowException {
         switch (fieldInfo.annotation.type()) {
@@ -484,6 +494,7 @@
                 output.put(bigIntegerToU64Bytes((BigInteger) value, output.order(), Type.UBE64));
                 break;
             case ByteArray:
+                checkByteArraySize((byte[]) value, fieldInfo);
                 output.put((byte[]) value);
                 break;
             case EUI48:
@@ -595,12 +606,23 @@
         return size;
     }
 
+    // Check whether the actual size of byte array matches the array size declared in
+    // annotation. For other annotation types, the actual length of field could be always
+    // deduced from annotation correctly.
+    private static void checkByteArraySize(@Nullable final byte[] array,
+            @NonNull final FieldInfo fieldInfo) {
+        Objects.requireNonNull(array, "null byte array for field " + fieldInfo.field.getName());
+        int annotationArraySize = fieldInfo.annotation.arraysize();
+        if (array.length == annotationArraySize) return;
+        throw new IllegalStateException("byte array actual length: "
+                + array.length + " doesn't match the declared array size: " + annotationArraySize);
+    }
+
     private void writeToByteBufferInternal(final ByteBuffer output, final FieldInfo[] fieldInfos) {
         for (FieldInfo fi : fieldInfos) {
+            final Object value = getFieldValue(fi.field);
             try {
-                putFieldValue(output, fi, fi.field.get(this));
-            } catch (IllegalAccessException e) {
-                throw new IllegalArgumentException("Fail to get the field value from instance", e);
+                putFieldValue(output, fi, value);
             } catch (BufferUnderflowException e) {
                 throw new IllegalArgumentException("Fail to fill raw data to ByteBuffer", e);
             }
@@ -673,12 +695,7 @@
         final FieldInfo[] fieldInfos = getClassFieldInfo(this.getClass());
         final Object[] values = new Object[fieldInfos.length];
         for (int i = 0; i < fieldInfos.length; i++) {
-            final Object value;
-            try {
-                value = fieldInfos[i].field.get(this);
-            } catch (IllegalAccessException e) {
-                throw new IllegalStateException("Cannot access field: " + fieldInfos[i].field, e);
-            }
+            final Object value = getFieldValue(fieldInfos[i].field);
             // For byte array field, put the hash code generated based on the array content into
             // the Object array instead of the reference to byte array, which might change and cause
             // to get a different hash code even with the exact same elements.
@@ -697,12 +714,7 @@
         final FieldInfo[] fieldInfos = getClassFieldInfo(this.getClass());
         for (int i = 0; i < fieldInfos.length; i++) {
             sb.append(fieldInfos[i].field.getName()).append(": ");
-            final Object value;
-            try {
-                value = fieldInfos[i].field.get(this);
-            } catch (IllegalAccessException e) {
-                throw new IllegalStateException("Cannot access field: " + fieldInfos[i].field, e);
-            }
+            final Object value = getFieldValue(fieldInfos[i].field);
             if (value == null) {
                 sb.append("null");
             } else if (fieldInfos[i].annotation.type() == Type.ByteArray) {
diff --git a/staticlibs/device/com/android/net/module/util/structs/EthernetHeader.java b/staticlibs/device/com/android/net/module/util/structs/EthernetHeader.java
index d895cfa..92ef8a7 100644
--- a/staticlibs/device/com/android/net/module/util/structs/EthernetHeader.java
+++ b/staticlibs/device/com/android/net/module/util/structs/EthernetHeader.java
@@ -40,7 +40,7 @@
  * +-                             -+
  * |            Address            |
  * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- * |1 0 0 0 0 1 1 0 1 1 0 1 1 1 0 1|
+ * |            EtherType          |
  * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  */
 public class EthernetHeader extends Struct {
diff --git a/staticlibs/device/com/android/net/module/util/structs/Ipv6Header.java b/staticlibs/device/com/android/net/module/util/structs/Ipv6Header.java
index 3ad8157..a14e064 100644
--- a/staticlibs/device/com/android/net/module/util/structs/Ipv6Header.java
+++ b/staticlibs/device/com/android/net/module/util/structs/Ipv6Header.java
@@ -53,7 +53,7 @@
     @Field(order = 0, type = Type.S32)
     public int vtf;
     @Field(order = 1, type = Type.U16)
-    public int length;
+    public int payloadLength;
     @Field(order = 2, type = Type.S8)
     public byte nextHeader;
     @Field(order = 3, type = Type.U8)
@@ -63,10 +63,10 @@
     @Field(order = 5, type = Type.Ipv6Address)
     public Inet6Address dstIp;
 
-    public Ipv6Header(final int vtf, final int length, final byte nextHeader, final short hopLimit,
-            final Inet6Address srcIp, final Inet6Address dstIp) {
+    public Ipv6Header(final int vtf, final int payloadLength, final byte nextHeader,
+            final short hopLimit, final Inet6Address srcIp, final Inet6Address dstIp) {
         this.vtf = vtf;
-        this.length = length;
+        this.payloadLength = payloadLength;
         this.nextHeader = nextHeader;
         this.hopLimit = hopLimit;
         this.srcIp = srcIp;
diff --git a/staticlibs/device/com/android/net/module/util/structs/LlaOption.java b/staticlibs/device/com/android/net/module/util/structs/LlaOption.java
index 2cdeefb..fbaccab 100644
--- a/staticlibs/device/com/android/net/module/util/structs/LlaOption.java
+++ b/staticlibs/device/com/android/net/module/util/structs/LlaOption.java
@@ -36,14 +36,16 @@
  */
 public class LlaOption extends Struct {
     @Field(order = 0, type = Type.S8)
-    // 1 for Source Link-layer Address; 2 for Target Link-layer Address.
     public final byte type;
     @Field(order = 1, type = Type.S8)
     public final byte length; // Length in 8-byte units
     @Field(order = 2, type = Type.EUI48)
+    // Link layer address length and format varies on different link layers, which is not
+    // guaranteed to be a 6-byte MAC address. However, Struct only supports 6-byte MAC
+    // addresses type(EUI-48) for now.
     public final MacAddress linkLayerAddress;
 
-    public LlaOption(final byte type, final byte length, final MacAddress linkLayerAddress) {
+    LlaOption(final byte type, final byte length, final MacAddress linkLayerAddress) {
         this.type = type;
         this.length = length;
         this.linkLayerAddress = linkLayerAddress;
@@ -54,6 +56,6 @@
      */
     public static ByteBuffer build(final byte type, final MacAddress linkLayerAddress) {
         final LlaOption option = new LlaOption(type, (byte) 1 /* option len */, linkLayerAddress);
-        return ByteBuffer.wrap(option.writeToBytes(ByteOrder.nativeOrder()));
+        return ByteBuffer.wrap(option.writeToBytes(ByteOrder.BIG_ENDIAN));
     }
 }
diff --git a/staticlibs/device/com/android/net/module/util/structs/MtuOption.java b/staticlibs/device/com/android/net/module/util/structs/MtuOption.java
index a35b576..34bc21c 100644
--- a/staticlibs/device/com/android/net/module/util/structs/MtuOption.java
+++ b/staticlibs/device/com/android/net/module/util/structs/MtuOption.java
@@ -46,7 +46,7 @@
     @Field(order = 3, type = Type.U32)
     public final long mtu;
 
-    public MtuOption(final byte type, final byte length, final short reserved,
+    MtuOption(final byte type, final byte length, final short reserved,
             final long mtu) {
         this.type = type;
         this.length = length;
@@ -60,6 +60,6 @@
     public static ByteBuffer build(final long mtu) {
         final MtuOption option = new MtuOption((byte) ICMPV6_ND_OPTION_MTU,
                 (byte) 1 /* option len */, (short) 0 /* reserved */, mtu);
-        return ByteBuffer.wrap(option.writeToBytes(ByteOrder.nativeOrder()));
+        return ByteBuffer.wrap(option.writeToBytes(ByteOrder.BIG_ENDIAN));
     }
 }
diff --git a/staticlibs/device/com/android/net/module/util/structs/NaHeader.java b/staticlibs/device/com/android/net/module/util/structs/NaHeader.java
index 8101b06..571d67b 100644
--- a/staticlibs/device/com/android/net/module/util/structs/NaHeader.java
+++ b/staticlibs/device/com/android/net/module/util/structs/NaHeader.java
@@ -23,7 +23,7 @@
 import java.net.Inet6Address;
 
 /**
- * ICMPv6 Neighbor Advertisement header, follow [Icmpv6Header], as per
+ * ICMPv6 Neighbor Advertisement header, follow {@link Icmpv6Header}, as per
  * https://tools.ietf.org/html/rfc4861. This does not contain any option.
  *
  * 0                   1                   2                   3
diff --git a/staticlibs/device/com/android/net/module/util/structs/PrefixInformationOption.java b/staticlibs/device/com/android/net/module/util/structs/PrefixInformationOption.java
index 7dc0192..1bdee29 100644
--- a/staticlibs/device/com/android/net/module/util/structs/PrefixInformationOption.java
+++ b/staticlibs/device/com/android/net/module/util/structs/PrefixInformationOption.java
@@ -70,7 +70,7 @@
     @Field(order = 7, type = Type.ByteArray, arraysize = 16)
     public final byte[] prefix;
 
-    public PrefixInformationOption(final byte type, final byte length, final byte prefixLen,
+    PrefixInformationOption(final byte type, final byte length, final byte prefixLen,
             final byte flags, final long validLifetime, final long preferredLifetime,
             final int reserved, @NonNull final byte[] prefix) {
         this.type = type;
@@ -92,6 +92,6 @@
                 (byte) ICMPV6_ND_OPTION_PIO, (byte) 4 /* option len */,
                 (byte) prefix.getPrefixLength(), flags, validLifetime, preferredLifetime,
                 (int) 0, prefix.getRawAddress());
-        return ByteBuffer.wrap(option.writeToBytes(ByteOrder.nativeOrder()));
+        return ByteBuffer.wrap(option.writeToBytes(ByteOrder.BIG_ENDIAN));
     }
 }
diff --git a/staticlibs/device/com/android/net/module/util/structs/RdnssOption.java b/staticlibs/device/com/android/net/module/util/structs/RdnssOption.java
index de55e66..b7c2b0c 100644
--- a/staticlibs/device/com/android/net/module/util/structs/RdnssOption.java
+++ b/staticlibs/device/com/android/net/module/util/structs/RdnssOption.java
@@ -53,8 +53,7 @@
     @Field(order = 3, type = Type.U32)
     public final long lifetime;
 
-    public RdnssOption(final byte type, final byte length, final short reserved,
-            final long lifetime) {
+    RdnssOption(final byte type, final byte length, final short reserved, final long lifetime) {
         this.type = type;
         this.length = length;
         this.reserved = reserved;
@@ -79,6 +78,8 @@
 
     /**
      * Build a RDNSS option from the required specified String parameters.
+     *
+     * @throws IllegalArgumentException if {@code servers} does not contain only numeric addresses.
      */
     public static ByteBuffer build(final long lifetime, final String... servers) {
         final Inet6Address[] serverArray = new Inet6Address[servers.length];
diff --git a/staticlibs/devicetests/com/android/testutils/CompatUtil.kt b/staticlibs/devicetests/com/android/testutils/CompatUtil.kt
new file mode 100644
index 0000000..4bb90a8
--- /dev/null
+++ b/staticlibs/devicetests/com/android/testutils/CompatUtil.kt
@@ -0,0 +1,53 @@
+/*
+ * 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.testutils
+
+import android.net.NetworkSpecifier
+import android.os.Build
+
+/**
+ * Test utility to create [NetworkSpecifier]s on different SDK versions.
+ */
+object CompatUtil {
+    @JvmStatic
+    fun makeTestNetworkSpecifier(ifName: String): NetworkSpecifier {
+        // Until R, there was no TestNetworkSpecifier, StringNetworkSpecifier was used instead
+        if (isDevSdkInRange(minExclusive = null, maxInclusive = Build.VERSION_CODES.R)) {
+            makeNetworkSpecifierInternal("android.net.StringNetworkSpecifier", ifName)
+        }
+        // TestNetworkSpecifier is not part of the SDK in some branches using this utility
+        // TODO: replace with a direct call to the constructor
+        return makeNetworkSpecifierInternal("android.net.TestNetworkSpecifier", ifName)
+    }
+
+    @JvmStatic
+    fun makeEthernetNetworkSpecifier(ifName: String): NetworkSpecifier {
+        // Until R, there was no EthernetNetworkSpecifier, StringNetworkSpecifier was used instead
+        if (isDevSdkInRange(minExclusive = null, maxInclusive = Build.VERSION_CODES.R)) {
+            makeNetworkSpecifierInternal("android.net.StringNetworkSpecifier", ifName)
+        }
+        // EthernetNetworkSpecifier is not part of the SDK in some branches using this utility
+        // TODO: replace with a direct call to the constructor
+        return makeNetworkSpecifierInternal("android.net.EthernetNetworkSpecifier", ifName)
+    }
+
+    private fun makeNetworkSpecifierInternal(clazz: String, specifier: String): NetworkSpecifier {
+        // StringNetworkSpecifier was removed after R (and was hidden API before that)
+        return Class.forName(clazz)
+                .getConstructor(String::class.java).newInstance(specifier) as NetworkSpecifier
+    }
+}
\ No newline at end of file
diff --git a/staticlibs/devicetests/com/android/testutils/TestNetworkTracker.kt b/staticlibs/devicetests/com/android/testutils/TestNetworkTracker.kt
index 4bd9ae8..5b97861 100644
--- a/staticlibs/devicetests/com/android/testutils/TestNetworkTracker.kt
+++ b/staticlibs/devicetests/com/android/testutils/TestNetworkTracker.kt
@@ -23,7 +23,6 @@
 import android.net.Network
 import android.net.NetworkCapabilities
 import android.net.NetworkRequest
-import android.net.StringNetworkSpecifier
 import android.net.TestNetworkInterface
 import android.net.TestNetworkManager
 import android.os.Binder
@@ -68,7 +67,7 @@
                 // Test networks do not have NOT_VPN or TRUSTED capabilities by default
                 .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
                 .removeCapability(NetworkCapabilities.NET_CAPABILITY_TRUSTED)
-                .setNetworkSpecifier(StringNetworkSpecifier(iface.interfaceName))
+                .setNetworkSpecifier(CompatUtil.makeTestNetworkSpecifier(iface.interfaceName))
                 .build()
         networkCallback = object : NetworkCallback() {
             override fun onAvailable(network: Network) {
diff --git a/staticlibs/framework/com/android/net/module/util/CollectionUtils.java b/staticlibs/framework/com/android/net/module/util/CollectionUtils.java
index 115f19d..2223443 100644
--- a/staticlibs/framework/com/android/net/module/util/CollectionUtils.java
+++ b/staticlibs/framework/com/android/net/module/util/CollectionUtils.java
@@ -38,6 +38,13 @@
     }
 
     /**
+     * @return True if the collection is null or 0-length.
+     */
+    public static <T> boolean isEmpty(@Nullable Collection<T> collection) {
+        return collection == null || collection.isEmpty();
+    }
+
+    /**
      * Returns an int array from the given Integer list.
      */
     @NonNull
diff --git a/staticlibs/framework/com/android/net/module/util/NetworkCapabilitiesUtils.java b/staticlibs/framework/com/android/net/module/util/NetworkCapabilitiesUtils.java
index 5a0200f..3de78c6 100644
--- a/staticlibs/framework/com/android/net/module/util/NetworkCapabilitiesUtils.java
+++ b/staticlibs/framework/com/android/net/module/util/NetworkCapabilitiesUtils.java
@@ -78,4 +78,31 @@
         }
         return transports[0];
     }
+
+    /**
+     * Unpacks long value into an array of bits.
+     */
+    public static int[] unpackBits(long val) {
+        int size = Long.bitCount(val);
+        int[] result = new int[size];
+        int index = 0;
+        int bitPos = 0;
+        while (val != 0) {
+            if ((val & 1) == 1) result[index++] = bitPos;
+            val = val >>> 1;
+            bitPos++;
+        }
+        return result;
+    }
+
+    /**
+     * Packs array of bits into a long value.
+     */
+    public static long packBits(int[] bits) {
+        long packed = 0;
+        for (int b : bits) {
+            packed |= (1L << b);
+        }
+        return packed;
+    }
 }
diff --git a/staticlibs/framework/com/android/net/module/util/NetworkStackConstants.java b/staticlibs/framework/com/android/net/module/util/NetworkStackConstants.java
index 5f62186..499297c 100644
--- a/staticlibs/framework/com/android/net/module/util/NetworkStackConstants.java
+++ b/staticlibs/framework/com/android/net/module/util/NetworkStackConstants.java
@@ -148,9 +148,18 @@
     public static final int ICMPV6_ND_OPTION_RDNSS = 25;
     public static final int ICMPV6_ND_OPTION_PREF64 = 38;
 
-
     public static final int ICMPV6_RA_HEADER_LEN = 16;
 
+    public static final int NEIGHBOR_ADVERTISEMENT_FLAG_ROUTER    = 1 << 31;
+    public static final int NEIGHBOR_ADVERTISEMENT_FLAG_SOLICITED = 1 << 30;
+    public static final int NEIGHBOR_ADVERTISEMENT_FLAG_OVERRIDE  = 1 << 29;
+
+    public static final byte ROUTER_ADVERTISEMENT_FLAG_MANAGED_ADDRESS = (byte) (1 << 7);
+    public static final byte ROUTER_ADVERTISEMENT_FLAG_OTHER = (byte) (1 << 6);
+
+    public static final byte PIO_FLAG_ON_LINK = (byte) (1 << 7);
+    public static final byte PIO_FLAG_AUTONOMOUS = (byte) (1 << 6);
+
     /**
      * UDP constants.
      *
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/NetworkCapabilitiesUtilsTest.kt b/staticlibs/tests/unit/src/com/android/net/module/util/NetworkCapabilitiesUtilsTest.kt
index e94d132..df2f459 100644
--- a/staticlibs/tests/unit/src/com/android/net/module/util/NetworkCapabilitiesUtilsTest.kt
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/NetworkCapabilitiesUtilsTest.kt
@@ -26,11 +26,14 @@
 import androidx.test.filters.SmallTest
 import androidx.test.runner.AndroidJUnit4
 import com.android.net.module.util.NetworkCapabilitiesUtils.getDisplayTransport
+import com.android.net.module.util.NetworkCapabilitiesUtils.packBits
+import com.android.net.module.util.NetworkCapabilitiesUtils.unpackBits
 import org.junit.Test
 import org.junit.runner.RunWith
 import java.lang.IllegalArgumentException
 import kotlin.test.assertEquals
 import kotlin.test.assertFailsWith
+import kotlin.test.assertTrue
 
 @RunWith(AndroidJUnit4::class)
 @SmallTest
@@ -58,4 +61,21 @@
             getDisplayTransport(intArrayOf())
         }
     }
-}
\ No newline at end of file
+
+    @Test
+    fun testBitPackingTestCase() {
+        runBitPackingTestCase(0, intArrayOf())
+        runBitPackingTestCase(1, intArrayOf(0))
+        runBitPackingTestCase(3, intArrayOf(0, 1))
+        runBitPackingTestCase(4, intArrayOf(2))
+        runBitPackingTestCase(63, intArrayOf(0, 1, 2, 3, 4, 5))
+        runBitPackingTestCase(Long.MAX_VALUE.inv(), intArrayOf(63))
+        runBitPackingTestCase(Long.MAX_VALUE.inv() + 1, intArrayOf(0, 63))
+        runBitPackingTestCase(Long.MAX_VALUE.inv() + 2, intArrayOf(1, 63))
+    }
+
+    fun runBitPackingTestCase(packedBits: Long, bits: IntArray) {
+        assertEquals(packedBits, packBits(bits))
+        assertTrue(bits contentEquals unpackBits(packedBits))
+    }
+}
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/StructTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/StructTest.java
index b172e21..df74398 100644
--- a/staticlibs/tests/unit/src/com/android/net/module/util/StructTest.java
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/StructTest.java
@@ -1027,4 +1027,48 @@
         assertTrue(msg.equals(msg1));
         assertEquals(msg.hashCode(), msg1.hashCode());
     }
+
+    static class InvalidByteArray extends Struct {
+        @Field(order = 0, type = Type.ByteArray, arraysize = 12) public byte[] bytes;
+    }
+
+    @Test
+    public void testStructClass_WrongByteArraySize() {
+        final InvalidByteArray msg = doParsingMessageTest("20010db80003000400050006",
+                InvalidByteArray.class, ByteOrder.BIG_ENDIAN);
+
+        // Actual byte array size doesn't match the size declared in the annotation.
+        msg.bytes = new byte[16];
+        assertThrows(IllegalStateException.class, () -> msg.writeToBytes());
+
+        final ByteBuffer output = ByteBuffer.allocate(Struct.getSize(InvalidByteArray.class));
+        output.order(ByteOrder.LITTLE_ENDIAN);
+        assertThrows(IllegalStateException.class, () -> msg.writeToByteBuffer(output));
+    }
+
+    @Test
+    public void testStructClass_NullByteArray() {
+        final InvalidByteArray msg = doParsingMessageTest("20010db80003000400050006",
+                InvalidByteArray.class, ByteOrder.BIG_ENDIAN);
+
+        msg.bytes = null;
+        assertThrows(NullPointerException.class, () -> msg.writeToBytes());
+
+        final ByteBuffer output = ByteBuffer.allocate(Struct.getSize(InvalidByteArray.class));
+        output.order(ByteOrder.LITTLE_ENDIAN);
+        assertThrows(NullPointerException.class, () -> msg.writeToByteBuffer(output));
+    }
+
+    @Test
+    public void testStructClass_ParsingByteArrayAfterInitialization() {
+        InvalidByteArray msg = new InvalidByteArray();
+        msg.bytes = new byte[]{(byte) 0x00, (byte) 0x01, (byte) 0x02, (byte) 0x03};
+
+        // Although bytes member has been initialized with the length different with
+        // annotation size, parsing from ByteBuffer will get bytes member have the
+        // reference to byte array with correct size.
+        msg = doParsingMessageTest("20010db80003000400050006", InvalidByteArray.class,
+                ByteOrder.BIG_ENDIAN);
+        assertArrayEquals(TEST_PREFIX64, msg.bytes);
+    }
 }