Merge "Use UnboundedSdkLevel for DevSdkIgnoreRule"
diff --git a/staticlibs/Android.bp b/staticlibs/Android.bp
index 2fc4142..d13f938 100644
--- a/staticlibs/Android.bp
+++ b/staticlibs/Android.bp
@@ -111,6 +111,7 @@
     name: "net-utils-device-common-bpf",
     srcs: [
         "device/com/android/net/module/util/BpfBitmap.java",
+        "device/com/android/net/module/util/BpfDump.java",
         "device/com/android/net/module/util/BpfMap.java",
         "device/com/android/net/module/util/BpfUtils.java",
         "device/com/android/net/module/util/HexDump.java",
@@ -118,7 +119,6 @@
         "device/com/android/net/module/util/JniUtil.java",
         "device/com/android/net/module/util/Struct.java",
         "device/com/android/net/module/util/TcUtils.java",
-        "device/com/android/net/module/util/bpf/*.java",
     ],
     sdk_version: "module_current",
     min_sdk_version: "29",
@@ -232,7 +232,9 @@
         ":net-utils-framework-common-srcs",
     ],
     sdk_version: "module_current",
+    min_sdk_version: "29",
     libs: [
+        "androidx.annotation_annotation",
         "framework-annotations-lib",
         "framework-connectivity.stubs.module_lib",
         "framework-connectivity-t.stubs.module_lib",
@@ -290,6 +292,7 @@
         "//packages/modules/Connectivity/service",
         "//packages/modules/Connectivity/tests:__subpackages__",
         "//packages/modules/Bluetooth/android/app",
+        "//packages/modules/Wifi/service:__subpackages__",
     ],
     lint: { strict_updatability_linting: true },
 }
diff --git a/staticlibs/device/com/android/net/module/util/BpfDump.java b/staticlibs/device/com/android/net/module/util/BpfDump.java
new file mode 100644
index 0000000..fec225c
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/BpfDump.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2022 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.util.Base64;
+
+import androidx.annotation.NonNull;
+
+/**
+ * The classes and the methods for BPF dump utilization.
+ */
+public class BpfDump {
+    // Using "," as a separator between base64 encoded key and value is safe because base64
+    // characters are [0-9a-zA-Z/=+].
+    public static final String BASE64_DELIMITER = ",";
+
+    /**
+     * Encode BPF key and value into a base64 format string which uses the delimiter ',':
+     * <base64 encoded key>,<base64 encoded value>
+     */
+    public static <K extends Struct, V extends Struct> String toBase64EncodedString(
+            @NonNull final K key, @NonNull final V value) {
+        final byte[] keyBytes = key.writeToBytes();
+        final String keyBase64Str = Base64.encodeToString(keyBytes, Base64.DEFAULT)
+                .replace("\n", "");
+        final byte[] valueBytes = value.writeToBytes();
+        final String valueBase64Str = Base64.encodeToString(valueBytes, Base64.DEFAULT)
+                .replace("\n", "");
+
+        return keyBase64Str + BASE64_DELIMITER + valueBase64Str;
+    }
+
+    // TODO: add a helper to dump bpf map content with the map name, the header line
+    // (ex: "BPF ingress map: iif nat64Prefix v6Addr -> v4Addr oif"), a lambda that
+    // knows how to dump each line, and the PrintWriter.
+}
diff --git a/staticlibs/device/com/android/net/module/util/bpf/ClatEgress4Key.java b/staticlibs/device/com/android/net/module/util/bpf/ClatEgress4Key.java
deleted file mode 100644
index 12200ec..0000000
--- a/staticlibs/device/com/android/net/module/util/bpf/ClatEgress4Key.java
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright (C) 2022 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.bpf;
-
-import com.android.net.module.util.Struct;
-import com.android.net.module.util.Struct.Field;
-import com.android.net.module.util.Struct.Type;
-
-import java.net.Inet4Address;
-
-/** Key type for clat egress IPv4 maps. */
-public class ClatEgress4Key extends Struct {
-    @Field(order = 0, type = Type.U32)
-    public final long iif; // The input interface index
-
-    @Field(order = 1, type = Type.Ipv4Address)
-    public final Inet4Address local4; // The source IPv4 address
-
-    public ClatEgress4Key(final long iif, final Inet4Address local4) {
-        this.iif = iif;
-        this.local4 = local4;
-    }
-}
diff --git a/staticlibs/device/com/android/net/module/util/bpf/ClatEgress4Value.java b/staticlibs/device/com/android/net/module/util/bpf/ClatEgress4Value.java
deleted file mode 100644
index c10cb4d..0000000
--- a/staticlibs/device/com/android/net/module/util/bpf/ClatEgress4Value.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2022 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.bpf;
-
-import com.android.net.module.util.Struct;
-import com.android.net.module.util.Struct.Field;
-import com.android.net.module.util.Struct.Type;
-
-import java.net.Inet6Address;
-
-/** Value type for clat egress IPv4 maps. */
-public class ClatEgress4Value extends Struct {
-    @Field(order = 0, type = Type.U32)
-    public final long oif; // The output interface to redirect to
-
-    @Field(order = 1, type = Type.Ipv6Address)
-    public final Inet6Address local6; // The full 128-bits of the source IPv6 address
-
-    @Field(order = 2, type = Type.Ipv6Address)
-    public final Inet6Address pfx96; // The destination /96 nat64 prefix, bottom 32 bits must be 0
-
-    @Field(order = 3, type = Type.U8, padding = 3)
-    public final short oifIsEthernet; // Whether the output interface requires ethernet header
-
-    public ClatEgress4Value(final long oif, final Inet6Address local6, final Inet6Address pfx96,
-            final short oifIsEthernet) {
-        this.oif = oif;
-        this.local6 = local6;
-        this.pfx96 = pfx96;
-        this.oifIsEthernet = oifIsEthernet;
-    }
-}
diff --git a/staticlibs/device/com/android/net/module/util/bpf/ClatIngress6Key.java b/staticlibs/device/com/android/net/module/util/bpf/ClatIngress6Key.java
deleted file mode 100644
index 1e2f4e0..0000000
--- a/staticlibs/device/com/android/net/module/util/bpf/ClatIngress6Key.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 2022 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.bpf;
-
-import com.android.net.module.util.Struct;
-import com.android.net.module.util.Struct.Field;
-import com.android.net.module.util.Struct.Type;
-
-import java.net.Inet6Address;
-
-/** Key type for clat ingress IPv6 maps. */
-public class ClatIngress6Key extends Struct {
-    @Field(order = 0, type = Type.U32)
-    public final long iif; // The input interface index
-
-    @Field(order = 1, type = Type.Ipv6Address)
-    public final Inet6Address pfx96; // The source /96 nat64 prefix, bottom 32 bits must be 0
-
-    @Field(order = 2, type = Type.Ipv6Address)
-    public final Inet6Address local6; // The full 128-bits of the destination IPv6 address
-
-    public ClatIngress6Key(final long iif, final Inet6Address pfx96, final Inet6Address local6) {
-        this.iif = iif;
-        this.pfx96 = pfx96;
-        this.local6 = local6;
-    }
-}
diff --git a/staticlibs/device/com/android/net/module/util/bpf/ClatIngress6Value.java b/staticlibs/device/com/android/net/module/util/bpf/ClatIngress6Value.java
deleted file mode 100644
index bfec44f..0000000
--- a/staticlibs/device/com/android/net/module/util/bpf/ClatIngress6Value.java
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright (C) 2022 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.bpf;
-
-import com.android.net.module.util.Struct;
-import com.android.net.module.util.Struct.Field;
-import com.android.net.module.util.Struct.Type;
-
-import java.net.Inet4Address;
-
-/** Value type for clat ingress IPv6 maps. */
-public class ClatIngress6Value extends Struct {
-    @Field(order = 0, type = Type.U32)
-    public final long oif; // The output interface to redirect to (0 means don't redirect)
-
-    @Field(order = 1, type = Type.Ipv4Address)
-    public final Inet4Address local4; // The destination IPv4 address
-
-    public ClatIngress6Value(final long oif, final Inet4Address local4) {
-        this.oif = oif;
-        this.local4 = local4;
-    }
-}
diff --git a/staticlibs/device/com/android/net/module/util/bpf/Tether4Key.java b/staticlibs/device/com/android/net/module/util/bpf/Tether4Key.java
deleted file mode 100644
index 638576f..0000000
--- a/staticlibs/device/com/android/net/module/util/bpf/Tether4Key.java
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * 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.bpf;
-
-import android.net.MacAddress;
-
-import androidx.annotation.NonNull;
-
-import com.android.net.module.util.Struct;
-import com.android.net.module.util.Struct.Field;
-import com.android.net.module.util.Struct.Type;
-
-import java.net.Inet4Address;
-import java.net.UnknownHostException;
-import java.util.Objects;
-
-/** Key type for downstream & upstream IPv4 forwarding maps. */
-public class Tether4Key extends Struct {
-    @Field(order = 0, type = Type.U32)
-    public final long iif;
-
-    @Field(order = 1, type = Type.EUI48)
-    public final MacAddress dstMac;
-
-    @Field(order = 2, type = Type.U8, padding = 1)
-    public final short l4proto;
-
-    @Field(order = 3, type = Type.ByteArray, arraysize = 4)
-    public final byte[] src4;
-
-    @Field(order = 4, type = Type.ByteArray, arraysize = 4)
-    public final byte[] dst4;
-
-    @Field(order = 5, type = Type.UBE16)
-    public final int srcPort;
-
-    @Field(order = 6, type = Type.UBE16)
-    public final int dstPort;
-
-    public Tether4Key(final long iif, @NonNull final MacAddress dstMac, final short l4proto,
-            final byte[] src4, final byte[] dst4, final int srcPort,
-            final int dstPort) {
-        Objects.requireNonNull(dstMac);
-
-        this.iif = iif;
-        this.dstMac = dstMac;
-        this.l4proto = l4proto;
-        this.src4 = src4;
-        this.dst4 = dst4;
-        this.srcPort = srcPort;
-        this.dstPort = dstPort;
-    }
-
-    @Override
-    public String toString() {
-        try {
-            return String.format(
-                    "iif: %d, dstMac: %s, l4proto: %d, src4: %s, dst4: %s, "
-                            + "srcPort: %d, dstPort: %d",
-                    iif, dstMac, l4proto,
-                    Inet4Address.getByAddress(src4), Inet4Address.getByAddress(dst4),
-                    Short.toUnsignedInt((short) srcPort), Short.toUnsignedInt((short) dstPort));
-        } catch (UnknownHostException | IllegalArgumentException e) {
-            return String.format("Invalid IP address", e);
-        }
-    }
-}
diff --git a/staticlibs/device/com/android/net/module/util/bpf/Tether4Value.java b/staticlibs/device/com/android/net/module/util/bpf/Tether4Value.java
deleted file mode 100644
index de98766..0000000
--- a/staticlibs/device/com/android/net/module/util/bpf/Tether4Value.java
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * 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.bpf;
-
-import android.net.MacAddress;
-
-import androidx.annotation.NonNull;
-
-import com.android.net.module.util.Struct;
-import com.android.net.module.util.Struct.Field;
-import com.android.net.module.util.Struct.Type;
-
-import java.net.InetAddress;
-import java.net.UnknownHostException;
-import java.util.Objects;
-
-/** Value type for downstream & upstream IPv4 forwarding maps. */
-public class Tether4Value extends Struct {
-    @Field(order = 0, type = Type.U32)
-    public final long oif;
-
-    // The ethhdr struct which is defined in uapi/linux/if_ether.h
-    @Field(order = 1, type = Type.EUI48)
-    public final MacAddress ethDstMac;
-    @Field(order = 2, type = Type.EUI48)
-    public final MacAddress ethSrcMac;
-    @Field(order = 3, type = Type.UBE16)
-    public final int ethProto;  // Packet type ID field.
-
-    @Field(order = 4, type = Type.U16)
-    public final int pmtu;
-
-    @Field(order = 5, type = Type.ByteArray, arraysize = 16)
-    public final byte[] src46;
-
-    @Field(order = 6, type = Type.ByteArray, arraysize = 16)
-    public final byte[] dst46;
-
-    @Field(order = 7, type = Type.UBE16)
-    public final int srcPort;
-
-    @Field(order = 8, type = Type.UBE16)
-    public final int dstPort;
-
-    // TODO: consider using U64.
-    @Field(order = 9, type = Type.U63)
-    public final long lastUsed;
-
-    public Tether4Value(final long oif, @NonNull final MacAddress ethDstMac,
-            @NonNull final MacAddress ethSrcMac, final int ethProto, final int pmtu,
-            final byte[] src46, final byte[] dst46, final int srcPort,
-            final int dstPort, final long lastUsed) {
-        Objects.requireNonNull(ethDstMac);
-        Objects.requireNonNull(ethSrcMac);
-
-        this.oif = oif;
-        this.ethDstMac = ethDstMac;
-        this.ethSrcMac = ethSrcMac;
-        this.ethProto = ethProto;
-        this.pmtu = pmtu;
-        this.src46 = src46;
-        this.dst46 = dst46;
-        this.srcPort = srcPort;
-        this.dstPort = dstPort;
-        this.lastUsed = lastUsed;
-    }
-
-    @Override
-    public String toString() {
-        try {
-            return String.format(
-                    "oif: %d, ethDstMac: %s, ethSrcMac: %s, ethProto: %d, pmtu: %d, "
-                            + "src46: %s, dst46: %s, srcPort: %d, dstPort: %d, "
-                            + "lastUsed: %d",
-                    oif, ethDstMac, ethSrcMac, ethProto, pmtu,
-                    InetAddress.getByAddress(src46), InetAddress.getByAddress(dst46),
-                    Short.toUnsignedInt((short) srcPort), Short.toUnsignedInt((short) dstPort),
-                    lastUsed);
-        } catch (UnknownHostException | IllegalArgumentException e) {
-            return String.format("Invalid IP address", e);
-        }
-    }
-}
diff --git a/staticlibs/device/com/android/net/module/util/bpf/TetherStatsKey.java b/staticlibs/device/com/android/net/module/util/bpf/TetherStatsKey.java
deleted file mode 100644
index c6d595b..0000000
--- a/staticlibs/device/com/android/net/module/util/bpf/TetherStatsKey.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * 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.bpf;
-
-import com.android.net.module.util.Struct;
-import com.android.net.module.util.Struct.Field;
-import com.android.net.module.util.Struct.Type;
-
-/** The key of BpfMap which is used for tethering stats. */
-public class TetherStatsKey extends Struct {
-    @Field(order = 0, type = Type.U32)
-    public final long ifindex;  // upstream interface index
-
-    public TetherStatsKey(final long ifindex) {
-        this.ifindex = ifindex;
-    }
-
-    // TODO: remove equals, hashCode and toString once aosp/1536721 is merged.
-    @Override
-    public boolean equals(Object obj) {
-        if (this == obj) return true;
-
-        if (!(obj instanceof TetherStatsKey)) return false;
-
-        final TetherStatsKey that = (TetherStatsKey) obj;
-
-        return ifindex == that.ifindex;
-    }
-
-    @Override
-    public int hashCode() {
-        return Long.hashCode(ifindex);
-    }
-
-    @Override
-    public String toString() {
-        return String.format("ifindex: %d", ifindex);
-    }
-}
diff --git a/staticlibs/device/com/android/net/module/util/bpf/TetherStatsValue.java b/staticlibs/device/com/android/net/module/util/bpf/TetherStatsValue.java
deleted file mode 100644
index 028d217..0000000
--- a/staticlibs/device/com/android/net/module/util/bpf/TetherStatsValue.java
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * 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.bpf;
-
-import com.android.net.module.util.Struct;
-import com.android.net.module.util.Struct.Field;
-import com.android.net.module.util.Struct.Type;
-
-/** The key of BpfMap which is used for tethering stats. */
-public class TetherStatsValue extends Struct {
-    // Use the signed long variable to store the uint64 stats from stats BPF map.
-    // U63 is enough for each data element even at 5Gbps for ~468 years.
-    // 2^63 / (5 * 1000 * 1000 * 1000) * 8 / 86400 / 365 = 468.
-    @Field(order = 0, type = Type.U63)
-    public final long rxPackets;
-    @Field(order = 1, type = Type.U63)
-    public final long rxBytes;
-    @Field(order = 2, type = Type.U63)
-    public final long rxErrors;
-    @Field(order = 3, type = Type.U63)
-    public final long txPackets;
-    @Field(order = 4, type = Type.U63)
-    public final long txBytes;
-    @Field(order = 5, type = Type.U63)
-    public final long txErrors;
-
-    public TetherStatsValue(final long rxPackets, final long rxBytes, final long rxErrors,
-            final long txPackets, final long txBytes, final long txErrors) {
-        this.rxPackets = rxPackets;
-        this.rxBytes = rxBytes;
-        this.rxErrors = rxErrors;
-        this.txPackets = txPackets;
-        this.txBytes = txBytes;
-        this.txErrors = txErrors;
-    }
-
-    // TODO: remove equals, hashCode and toString once aosp/1536721 is merged.
-    @Override
-    public boolean equals(Object obj) {
-        if (this == obj) return true;
-
-        if (!(obj instanceof TetherStatsValue)) return false;
-
-        final TetherStatsValue that = (TetherStatsValue) obj;
-
-        return rxPackets == that.rxPackets
-                && rxBytes == that.rxBytes
-                && rxErrors == that.rxErrors
-                && txPackets == that.txPackets
-                && txBytes == that.txBytes
-                && txErrors == that.txErrors;
-    }
-
-    @Override
-    public int hashCode() {
-        return Long.hashCode(rxPackets) ^ Long.hashCode(rxBytes) ^ Long.hashCode(rxErrors)
-                ^ Long.hashCode(txPackets) ^ Long.hashCode(txBytes) ^ Long.hashCode(txErrors);
-    }
-
-    @Override
-    public String toString() {
-        return String.format("rxPackets: %s, rxBytes: %s, rxErrors: %s, txPackets: %s, "
-                + "txBytes: %s, txErrors: %s", rxPackets, rxBytes, rxErrors, txPackets,
-                txBytes, txErrors);
-    }
-}
diff --git a/staticlibs/device/com/android/net/module/util/ip/IpNeighborMonitor.java b/staticlibs/device/com/android/net/module/util/ip/IpNeighborMonitor.java
index 88f8c9d..4a61794 100644
--- a/staticlibs/device/com/android/net/module/util/ip/IpNeighborMonitor.java
+++ b/staticlibs/device/com/android/net/module/util/ip/IpNeighborMonitor.java
@@ -22,6 +22,7 @@
 import static com.android.net.module.util.netlink.NetlinkConstants.hexify;
 import static com.android.net.module.util.netlink.NetlinkConstants.stringForNlMsgType;
 
+import android.annotation.NonNull;
 import android.net.MacAddress;
 import android.os.Handler;
 import android.system.ErrnoException;
@@ -80,15 +81,17 @@
      * An event about a neighbor.
      */
     public static class NeighborEvent {
-        final long elapsedMs;
-        final short msgType;
-        final int ifindex;
-        final InetAddress ip;
-        final short nudState;
-        final MacAddress macAddr;
+        public final long elapsedMs;
+        public final short msgType;
+        public final int ifindex;
+        @NonNull
+        public final InetAddress ip;
+        public final short nudState;
+        @NonNull
+        public final MacAddress macAddr;
 
-        public NeighborEvent(long elapsedMs, short msgType, int ifindex, InetAddress ip,
-                short nudState, MacAddress macAddr) {
+        public NeighborEvent(long elapsedMs, short msgType, int ifindex, @NonNull InetAddress ip,
+                short nudState, @NonNull MacAddress macAddr) {
             this.elapsedMs = elapsedMs;
             this.msgType = msgType;
             this.ifindex = ifindex;
@@ -101,7 +104,7 @@
             return (msgType != RTM_DELNEIGH) && StructNdMsg.isNudStateConnected(nudState);
         }
 
-        boolean isValid() {
+        public boolean isValid() {
             return (msgType != RTM_DELNEIGH) && StructNdMsg.isNudStateValid(nudState);
         }
 
diff --git a/staticlibs/framework/com/android/net/module/util/LocationPermissionChecker.java b/staticlibs/framework/com/android/net/module/util/LocationPermissionChecker.java
index e4ce9e8..cd1f31c 100644
--- a/staticlibs/framework/com/android/net/module/util/LocationPermissionChecker.java
+++ b/staticlibs/framework/com/android/net/module/util/LocationPermissionChecker.java
@@ -30,6 +30,8 @@
 import android.os.UserHandle;
 import android.util.Log;
 
+import androidx.annotation.RequiresApi;
+
 import com.android.internal.annotations.VisibleForTesting;
 
 import java.lang.annotation.Retention;
@@ -41,6 +43,7 @@
  *
  * @hide
  */
+@RequiresApi(Build.VERSION_CODES.R)
 public class LocationPermissionChecker {
 
     private static final String TAG = "LocationPermissionChecker";
diff --git a/staticlibs/framework/com/android/net/module/util/NetworkStackConstants.java b/staticlibs/framework/com/android/net/module/util/NetworkStackConstants.java
index 353fe69..1ad9c35 100644
--- a/staticlibs/framework/com/android/net/module/util/NetworkStackConstants.java
+++ b/staticlibs/framework/com/android/net/module/util/NetworkStackConstants.java
@@ -228,6 +228,35 @@
     public static final int TAG_SYSTEM_PROBE = 0xFFFFFF81;
     public static final int TAG_SYSTEM_DNS = 0xFFFFFF82;
 
+    /**
+     * A test URL used to override configuration settings and overlays for the network validation
+     * HTTPS URL, when set in {@link android.provider.DeviceConfig} configuration.
+     *
+     * <p>This URL will be ignored if the host is not "localhost" (it can only be used to test with
+     * a local test server), and must not be set in production scenarios (as enforced by CTS tests).
+     *
+     * <p>{@link #TEST_URL_EXPIRATION_TIME} must also be set to use this setting.
+     */
+    public static final String TEST_CAPTIVE_PORTAL_HTTPS_URL = "test_captive_portal_https_url";
+    /**
+     * A test URL used to override configuration settings and overlays for the network validation
+     * HTTP URL, when set in {@link android.provider.DeviceConfig} configuration.
+     *
+     * <p>This URL will be ignored if the host is not "localhost" (it can only be used to test with
+     * a local test server), and must not be set in production scenarios (as enforced by CTS tests).
+     *
+     * <p>{@link #TEST_URL_EXPIRATION_TIME} must also be set to use this setting.
+     */
+    public static final String TEST_CAPTIVE_PORTAL_HTTP_URL = "test_captive_portal_http_url";
+    /**
+     * Expiration time of the test URL, in ms, relative to {@link System#currentTimeMillis()}.
+     *
+     * <p>After this expiration time, test URLs will be ignored. They will also be ignored if
+     * the expiration time is more than 10 minutes in the future, to avoid misconfiguration
+     * following test runs.
+     */
+    public static final String TEST_URL_EXPIRATION_TIME = "test_url_expiration_time";
+
     // 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.
diff --git a/staticlibs/native/bpf_headers/include/bpf/BpfMap.h b/staticlibs/native/bpf_headers/include/bpf/BpfMap.h
index 297b200..47256fa 100644
--- a/staticlibs/native/bpf_headers/include/bpf/BpfMap.h
+++ b/staticlibs/native/bpf_headers/include/bpf/BpfMap.h
@@ -50,10 +50,8 @@
     // (later on, for testing, we still make available a copy assignment operator)
     BpfMap<Key, Value>(const BpfMap<Key, Value>&) = delete;
 
-  protected:
-    // flag must be within BPF_OBJ_FLAG_MASK, ie. 0, BPF_F_RDONLY, BPF_F_WRONLY
-    BpfMap<Key, Value>(const char* pathname, uint32_t flags) {
-        mMapFd.reset(mapRetrieve(pathname, flags));
+  private:
+    void abortOnKeyOrValueSizeMismatch() {
         if (!mMapFd.ok()) abort();
         if (isAtLeastKernelVersion(4, 14, 0)) {
             if (bpfGetFdKeySize(mMapFd) != sizeof(Key)) abort();
@@ -61,6 +59,13 @@
         }
     }
 
+  protected:
+    // flag must be within BPF_OBJ_FLAG_MASK, ie. 0, BPF_F_RDONLY, BPF_F_WRONLY
+    BpfMap<Key, Value>(const char* pathname, uint32_t flags) {
+        mMapFd.reset(mapRetrieve(pathname, flags));
+        abortOnKeyOrValueSizeMismatch();
+    }
+
   public:
     explicit BpfMap<Key, Value>(const char* pathname) : BpfMap<Key, Value>(pathname, 0) {}
 
@@ -117,14 +122,11 @@
         if (!mMapFd.ok()) {
             return ErrnoErrorf("Pinned map not accessible or does not exist: ({})", path);
         }
-        if (isAtLeastKernelVersion(4, 14, 0)) {
-            // Normally we should return an error here instead of calling abort,
-            // but this cannot happen at runtime without a massive code bug (K/V type mismatch)
-            // and as such it's better to just blow the system up and let the developer fix it.
-            // Crashes are much more likely to be noticed than logs and missing functionality.
-            if (bpfGetFdKeySize(mMapFd) != sizeof(Key)) abort();
-            if (bpfGetFdValueSize(mMapFd) != sizeof(Value)) abort();
-        }
+        // Normally we should return an error here instead of calling abort,
+        // but this cannot happen at runtime without a massive code bug (K/V type mismatch)
+        // and as such it's better to just blow the system up and let the developer fix it.
+        // Crashes are much more likely to be noticed than logs and missing functionality.
+        abortOnKeyOrValueSizeMismatch();
         return {};
     }
 
@@ -202,11 +204,7 @@
     // check BpfMap.isValid() and look at errno and see why systemcall() failed.
     [[clang::reinitializes]] void reset(int fd) {
         mMapFd.reset(fd);
-        if ((fd >= 0) && isAtLeastKernelVersion(4, 14, 0)) {
-            if (bpfGetFdKeySize(mMapFd) != sizeof(Key)) abort();
-            if (bpfGetFdValueSize(mMapFd) != sizeof(Value)) abort();
-            if (bpfGetFdMapFlags(mMapFd) != 0) abort(); // TODO: fix for BpfMapRO
-        }
+        if (mMapFd.ok()) abortOnKeyOrValueSizeMismatch();
     }
 #endif
 
diff --git a/staticlibs/native/bpf_headers/include/bpf/bpf_helpers.h b/staticlibs/native/bpf_headers/include/bpf/bpf_helpers.h
index ae3ad2c..a21e685 100644
--- a/staticlibs/native/bpf_headers/include/bpf/bpf_helpers.h
+++ b/staticlibs/native/bpf_headers/include/bpf/bpf_helpers.h
@@ -30,6 +30,16 @@
 // Android T / 13 Beta 3 (api level 33) - added support for 'netd_shared'
 #define BPFLOADER_T_BETA3_VERSION 13u
 
+// v0.18 added support for shared and pindir, but still ignores selinux_content
+// v0.19 added support for selinux_content along with the required selinux changes
+// and should be available starting with Android T Beta 4
+//
+// Android T / 13 (api level 33) - support for shared/selinux_context/pindir
+#define BPFLOADER_T_VERSION 19u
+
+// BpfLoader v0.25+ support obj@ver.o files
+#define BPFLOADER_OBJ_AT_VER_VERSION 25u
+
 /* For mainline module use, you can #define BPFLOADER_{MIN/MAX}_VER
  * before #include "bpf_helpers.h" to change which bpfloaders will
  * process the resulting .o file.
@@ -172,20 +182,39 @@
         return bpf_map_delete_elem_unsafe(&the_map, k);                                          \
     };
 
+#ifndef DEFAULT_BPF_MAP_SELINUX_CONTEXT
+#define DEFAULT_BPF_MAP_SELINUX_CONTEXT ""
+#endif
+
+#ifndef DEFAULT_BPF_MAP_PIN_SUBDIR
+#define DEFAULT_BPF_MAP_PIN_SUBDIR ""
+#endif
+
+#ifndef DEFAULT_BPF_MAP_UID
+#define DEFAULT_BPF_MAP_UID AID_ROOT
+#elif BPFLOADER_MIN_VER < 28u
+#error "Bpf Map UID must be left at default of AID_ROOT for BpfLoader prior to v0.28"
+#endif
+
 #define DEFINE_BPF_MAP_UGM(the_map, TYPE, KeyType, ValueType, num_entries, usr, grp, md) \
-    DEFINE_BPF_MAP_EXT(the_map, TYPE, KeyType, ValueType, num_entries, usr, grp, md, "", "", false)
+    DEFINE_BPF_MAP_EXT(the_map, TYPE, KeyType, ValueType, num_entries, usr, grp, md, \
+                       DEFAULT_BPF_MAP_SELINUX_CONTEXT, DEFAULT_BPF_MAP_PIN_SUBDIR, false)
 
 #define DEFINE_BPF_MAP(the_map, TYPE, KeyType, ValueType, num_entries) \
-    DEFINE_BPF_MAP_UGM(the_map, TYPE, KeyType, ValueType, num_entries, AID_ROOT, AID_ROOT, 0600)
+    DEFINE_BPF_MAP_UGM(the_map, TYPE, KeyType, ValueType, num_entries, \
+                       DEFAULT_BPF_MAP_UID, AID_ROOT, 0600)
 
 #define DEFINE_BPF_MAP_GWO(the_map, TYPE, KeyType, ValueType, num_entries, gid) \
-    DEFINE_BPF_MAP_UGM(the_map, TYPE, KeyType, ValueType, num_entries, AID_ROOT, gid, 0620)
+    DEFINE_BPF_MAP_UGM(the_map, TYPE, KeyType, ValueType, num_entries, \
+                       DEFAULT_BPF_MAP_UID, gid, 0620)
 
 #define DEFINE_BPF_MAP_GRO(the_map, TYPE, KeyType, ValueType, num_entries, gid) \
-    DEFINE_BPF_MAP_UGM(the_map, TYPE, KeyType, ValueType, num_entries, AID_ROOT, gid, 0640)
+    DEFINE_BPF_MAP_UGM(the_map, TYPE, KeyType, ValueType, num_entries, \
+                       DEFAULT_BPF_MAP_UID, gid, 0640)
 
 #define DEFINE_BPF_MAP_GRW(the_map, TYPE, KeyType, ValueType, num_entries, gid) \
-    DEFINE_BPF_MAP_UGM(the_map, TYPE, KeyType, ValueType, num_entries, AID_ROOT, gid, 0660)
+    DEFINE_BPF_MAP_UGM(the_map, TYPE, KeyType, ValueType, num_entries, \
+                       DEFAULT_BPF_MAP_UID, gid, 0660)
 
 static int (*bpf_probe_read)(void* dst, int size, void* unsafe_ptr) = (void*) BPF_FUNC_probe_read;
 static int (*bpf_probe_read_str)(void* dst, int size, void* unsafe_ptr) = (void*) BPF_FUNC_probe_read_str;
@@ -214,9 +243,18 @@
     SECTION(SECTION_NAME)                                                                          \
     int the_prog
 
+#ifndef DEFAULT_BPF_PROG_SELINUX_CONTEXT
+#define DEFAULT_BPF_PROG_SELINUX_CONTEXT ""
+#endif
+
+#ifndef DEFAULT_BPF_PROG_PIN_SUBDIR
+#define DEFAULT_BPF_PROG_PIN_SUBDIR ""
+#endif
+
 #define DEFINE_BPF_PROG_KVER_RANGE_OPT(SECTION_NAME, prog_uid, prog_gid, the_prog, min_kv, max_kv, \
                                        opt) \
-    DEFINE_BPF_PROG_EXT(SECTION_NAME, prog_uid, prog_gid, the_prog, min_kv, max_kv, opt, "", "")
+    DEFINE_BPF_PROG_EXT(SECTION_NAME, prog_uid, prog_gid, the_prog, min_kv, max_kv, opt, \
+                        DEFAULT_BPF_PROG_SELINUX_CONTEXT, DEFAULT_BPF_PROG_PIN_SUBDIR)
 
 // Programs (here used in the sense of functions/sections) marked optional are allowed to fail
 // to load (for example due to missing kernel patches).
diff --git a/staticlibs/native/bpf_headers/include/bpf/bpf_map_def.h b/staticlibs/native/bpf_headers/include/bpf/bpf_map_def.h
index 14a0295..2d47426 100644
--- a/staticlibs/native/bpf_headers/include/bpf/bpf_map_def.h
+++ b/staticlibs/native/bpf_headers/include/bpf/bpf_map_def.h
@@ -143,7 +143,7 @@
     //   unsigned int inner_map_idx;
     //   unsigned int numa_node;
 
-    unsigned int uid;   // uid_t
+    unsigned int zero;  // uid_t, for compat with old (buggy) bpfloader must be AID_ROOT == 0
     unsigned int gid;   // gid_t
     unsigned int mode;  // mode_t
 
@@ -171,13 +171,15 @@
 
     bool shared;  // use empty string as 'file' component of pin path - allows cross .o map sharing
     char pad0[3];  // manually pad up to 4 byte alignment, may be used for extensions in the future
+
+    unsigned int uid;   // uid_t
 };
 
 _Static_assert(sizeof(((struct bpf_map_def *)0)->selinux_context) == 32, "must be 32 bytes");
 _Static_assert(sizeof(((struct bpf_map_def *)0)->pin_subdir) == 32, "must be 32 bytes");
 
 // This needs to be updated whenever the above structure definition is expanded.
-_Static_assert(sizeof(struct bpf_map_def) == 116, "sizeof struct bpf_map_def != 116");
+_Static_assert(sizeof(struct bpf_map_def) == 120, "sizeof struct bpf_map_def != 120");
 _Static_assert(__alignof__(struct bpf_map_def) == 4, "__alignof__ struct bpf_map_def != 4");
 _Static_assert(_Alignof(struct bpf_map_def) == 4, "_Alignof struct bpf_map_def != 4");
 
diff --git a/staticlibs/native/bpf_syscall_wrappers/include/BpfSyscallWrappers.h b/staticlibs/native/bpf_syscall_wrappers/include/BpfSyscallWrappers.h
index 4b29c44..d5b7670 100644
--- a/staticlibs/native/bpf_syscall_wrappers/include/BpfSyscallWrappers.h
+++ b/staticlibs/native/bpf_syscall_wrappers/include/BpfSyscallWrappers.h
@@ -150,8 +150,12 @@
                                 });
 }
 
-// requires 4.14+ kernel
-
+// BPF_OBJ_GET_INFO_BY_FD requires 4.14+ kernel
+//
+// Note: some fields are only defined in newer kernels (ie. the map_info struct grows
+// over time), so we need to check that the field we're interested in is actually
+// supported/returned by the running kernel.  We do this by checking it is fully
+// within the bounds of the struct size as reported by the kernel.
 #define DEFINE_BPF_GET_FD_INFO(NAME, FIELD) \
 inline int bpfGetFd ## NAME(const BPF_FD_TYPE map_fd) { \
     struct bpf_map_info map_info = {}; \
diff --git a/staticlibs/native/netjniutils/Android.bp b/staticlibs/native/netjniutils/Android.bp
index d8e6a04..22fd1fa 100644
--- a/staticlibs/native/netjniutils/Android.bp
+++ b/staticlibs/native/netjniutils/Android.bp
@@ -19,6 +19,9 @@
 cc_library_static {
     name: "libnetjniutils",
     srcs: ["netjniutils.cpp"],
+    static_libs: [
+        "libmodules-utils-build",
+    ],
     header_libs: ["jni_headers"],
     shared_libs: ["liblog"],
     export_header_lib_headers: ["jni_headers"],
diff --git a/staticlibs/native/netjniutils/netjniutils.cpp b/staticlibs/native/netjniutils/netjniutils.cpp
index 210c6c3..8b7f903 100644
--- a/staticlibs/native/netjniutils/netjniutils.cpp
+++ b/staticlibs/native/netjniutils/netjniutils.cpp
@@ -15,6 +15,7 @@
 #define LOG_TAG "netjniutils"
 
 #include "netjniutils/netjniutils.h"
+#include <android-modules-utils/sdk_level.h>
 
 #include <dlfcn.h>
 #include <stdbool.h>
@@ -29,27 +30,6 @@
 
 namespace {
 
-bool IsAtLeastS() {
-  // TODO(b/158749603#comment19): move to android::modules::sdklevel::IsAtLeastS().
-  int api_level = android_get_device_api_level();
-
-  // Guarded check for branches that do not have __ANDROID_API_S__.
-#ifdef __ANDROID_API_S__
-  if (api_level >= __ANDROID_API_S__) {
-    return true;
-  }
-#endif
-
-  if (api_level < __ANDROID_API_R__) {
-    return false;
-  }
-
-  // Device looks like R or above, check codename as it could be (S or above).
-  static constexpr const char* kCodenameProperty = "ro.build.version.codename";
-  char codename[PROP_VALUE_MAX] = { 0 };
-  return (__system_property_get(kCodenameProperty, codename) > 0 &&
-          codename[0] >= 'S' && codename[1] == '\0');
-}
 
 int GetNativeFileDescriptorWithoutNdk(JNIEnv* env, jobject javaFd) {
   // Prior to Android S, we need to find the descriptor field in the FileDescriptor class. The
@@ -92,7 +72,8 @@
 }  //  namespace
 
 int GetNativeFileDescriptor(JNIEnv* env, jobject javaFd) {
-  static const bool preferNdkFileDescriptorApi = []() -> bool { return IsAtLeastS(); }();
+  static const bool preferNdkFileDescriptorApi = []() -> bool
+   { return android::modules::sdklevel::IsAtLeastS(); }();
   if (preferNdkFileDescriptorApi) {
     return GetNativeFileDescriptorWithNdk(env, javaFd);
   } else {
diff --git a/staticlibs/netd/Android.bp b/staticlibs/netd/Android.bp
index a648941..79bde9b 100644
--- a/staticlibs/netd/Android.bp
+++ b/staticlibs/netd/Android.bp
@@ -53,14 +53,6 @@
     min_sdk_version: "29",
 }
 
-// TODO: delete this one when AOSP is no longer auto-merge to git_sc-mainline-prod.
-cc_library_static {
-    name: "netd_aidl_interface-lateststable-cpp",
-    whole_static_libs: [
-        "netd_aidl_interface-V10-cpp",
-    ],
-}
-
 cc_defaults {
     name: "netd_aidl_interface_lateststable_cpp_static",
     static_libs: ["netd_aidl_interface-V10-cpp"],
diff --git a/staticlibs/netd/libnetdutils/include/netdutils/NetNativeTestBase.h b/staticlibs/netd/libnetdutils/include/netdutils/NetNativeTestBase.h
new file mode 100644
index 0000000..c8b30c6
--- /dev/null
+++ b/staticlibs/netd/libnetdutils/include/netdutils/NetNativeTestBase.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2022 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 <android-base/format.h>
+#include <android-base/logging.h>
+#include "gtest/gtest.h"
+
+using ::testing::TestInfo;
+using ::testing::UnitTest;
+
+#define DBG 1
+
+/*
+ * Test base class for net native tests to support common usage.
+ */
+class NetNativeTestBase : public ::testing::Test {
+  public:
+    // TODO: update the logging when gtest supports logging the life cycle on each test.
+    NetNativeTestBase() {
+        if (DBG) LOG(INFO) << getTestCaseLog(true);
+    }
+    ~NetNativeTestBase() {
+        if (DBG) LOG(INFO) << getTestCaseLog(false);
+    }
+
+    std::string getTestCaseLog(bool running) {
+        const TestInfo* const test_info = UnitTest::GetInstance()->current_test_info();
+        return fmt::format("{}: {}#{}", (running ? "started" : "finished"),
+                           test_info->test_suite_name(), test_info->name());
+    }
+};
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/BpfDumpTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/BpfDumpTest.java
new file mode 100644
index 0000000..b166b4a
--- /dev/null
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/BpfDumpTest.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2022 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;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class BpfDumpTest {
+    @Test
+    public void testToBase64EncodedString() {
+        final Struct.U32 key = new Struct.U32(123);
+        final Struct.U32 value = new Struct.U32(456);
+
+        // Verified in python:
+        //   import base64
+        //   print(base64.b64encode(b'\x7b\x00\x00\x00')) # key: ewAAAA==
+        //   print(base64.b64encode(b'\xc8\x01\x00\x00')) # value: yAEAAA==
+        assertEquals("7B000000", HexDump.toHexString(key.writeToBytes()));
+        assertEquals("C8010000", HexDump.toHexString(value.writeToBytes()));
+        assertEquals("ewAAAA==,yAEAAA==", BpfDump.toBase64EncodedString(key, value));
+    }
+}
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/CleanupTest.kt b/staticlibs/tests/unit/src/com/android/net/module/util/CleanupTest.kt
index 649b30e..851d09a 100644
--- a/staticlibs/tests/unit/src/com/android/net/module/util/CleanupTest.kt
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/CleanupTest.kt
@@ -175,6 +175,25 @@
     }
 
     @Test
+    fun testAssertionErrorInCatch() {
+        var x = 1
+        val thrown = assertFailsWith<AssertionError> {
+            tryTest {
+                x = 2
+                throw TestException1()
+            }.catch<TestException1> {
+                x = 3
+                fail("Test failure in catch")
+            } cleanup {
+                assertTrue(x == 3)
+                x = 4
+            }
+        }
+        assertTrue(x == 4)
+        assertTrue(thrown.suppressedExceptions.isEmpty())
+    }
+
+    @Test
     fun testMultipleCleanups() {
         var x = 1
         val thrown = assertFailsWith<TestException1> {
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/SharedLogTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/SharedLogTest.java
index 446e881..aa1bfee 100644
--- a/staticlibs/tests/unit/src/com/android/net/module/util/SharedLogTest.java
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/SharedLogTest.java
@@ -17,6 +17,8 @@
 package com.android.net.module.util;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
 import androidx.test.filters.SmallTest;
@@ -27,16 +29,24 @@
 
 import java.io.ByteArrayOutputStream;
 import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.function.Consumer;
 
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 public class SharedLogTest {
     private static final String TIMESTAMP_PATTERN = "\\d{2}:\\d{2}:\\d{2}";
     private static final String TIMESTAMP = "HH:MM:SS";
+    private static final String TAG = "top";
 
     @Test
     public void testBasicOperation() {
-        final SharedLog logTop = new SharedLog("top");
+        final SharedLog logTop = new SharedLog(TAG);
+        assertTrue(TAG.equals(logTop.getTag()));
+
         logTop.mark("first post!");
 
         final SharedLog logLevel2a = logTop.forSubComponent("twoA");
@@ -48,8 +58,10 @@
 
         final SharedLog logLevel3 = logLevel2a.forSubComponent("three");
         logTop.log("still logging");
-        logLevel3.log("3 >> 2");
+        logLevel2b.e(new Exception("Got another exception"));
+        logLevel3.i("3 >> 2");
         logLevel2a.mark("ok: last post");
+        logTop.logf("finished!");
 
         final String[] expected = {
             " - MARK first post!",
@@ -59,29 +71,53 @@
             " - [twoB] ERROR Wait, here's one: Test",
             " - [twoA] WARN second post?",
             " - still logging",
+            " - [twoB] ERROR java.lang.Exception: Got another exception",
             " - [twoA.three] 3 >> 2",
             " - [twoA] MARK ok: last post",
+            " - finished!",
         };
         // Verify the logs are all there and in the correct order.
-        verifyLogLines(expected, logTop);
+        assertDumpLogs(expected, logTop);
 
         // In fact, because they all share the same underlying LocalLog,
         // every subcomponent SharedLog's dump() is identical.
-        verifyLogLines(expected, logLevel2a);
-        verifyLogLines(expected, logLevel2b);
-        verifyLogLines(expected, logLevel3);
+        assertDumpLogs(expected, logLevel2a);
+        assertDumpLogs(expected, logLevel2b);
+        assertDumpLogs(expected, logLevel3);
     }
 
-    private static void verifyLogLines(String[] expected, SharedLog log) {
+    private static void assertDumpLogs(String[] expected, SharedLog log) {
+        verifyLogLines(expected, dump(log));
+        verifyLogLines(reverse(expected), reverseDump(log));
+    }
+
+    private static String dump(SharedLog log) {
+        return getSharedLogString(pw -> log.dump(null /* fd */, pw, null /* args */));
+    }
+
+    private static String reverseDump(SharedLog log) {
+        return getSharedLogString(pw -> log.reverseDump(pw));
+    }
+
+    private static String[] reverse(String[] ary) {
+        final List<String> ls = new ArrayList<>(Arrays.asList(ary));
+        Collections.reverse(ls);
+        return ls.toArray(new String[ary.length]);
+    }
+
+    private static String getSharedLogString(Consumer<PrintWriter> functor) {
         final ByteArrayOutputStream ostream = new ByteArrayOutputStream();
         final PrintWriter pw = new PrintWriter(ostream, true);
-        log.dump(null, pw, null);
+        functor.accept(pw);
 
         final String dumpOutput = ostream.toString();
-        assertTrue(dumpOutput != null);
-        assertTrue(!"".equals(dumpOutput));
+        assertNotNull(dumpOutput);
+        assertFalse("".equals(dumpOutput));
+        return dumpOutput;
+    }
 
-        final String[] lines = dumpOutput.split("\n");
+    private static void verifyLogLines(String[] expected, String gottenLogs) {
+        final String[] lines = gottenLogs.split("\n");
         assertEquals(expected.length, lines.length);
 
         for (int i = 0; i < expected.length; i++) {
diff --git a/staticlibs/tests/unit/src/com/android/testutils/DeviceInfoUtilsTest.java b/staticlibs/tests/unit/src/com/android/testutils/DeviceInfoUtilsTest.java
index 6f603d5..e46dd59 100644
--- a/staticlibs/tests/unit/src/com/android/testutils/DeviceInfoUtilsTest.java
+++ b/staticlibs/tests/unit/src/com/android/testutils/DeviceInfoUtilsTest.java
@@ -106,4 +106,18 @@
         assertFalse(v1.isAtLeast(v4));
         assertFalse(v1.isAtLeast(v5));
     }
+
+    @Test
+    public void testKernelVersionIsAtLeast() {
+        // Pick a lower kernel version 4.0 which was released at April 2015, the kernel
+        // version running on all test devices nowadays should be higher than it.
+        assertTrue(DeviceInfoUtils.isKernelVersionAtLeast("4.0"));
+
+        // Invalid kernel version.
+        assertTrue(DeviceInfoUtils.isKernelVersionAtLeast("0.0.0"));
+
+        // Pick a higher kernel version which isn't released yet, comparison should return false.
+        // Need to update the target version in the future to make sure the test still passes.
+        assertFalse(DeviceInfoUtils.isKernelVersionAtLeast("20.0.0"));
+    }
 }
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/ContextUtils.kt b/staticlibs/testutils/devicetests/com/android/testutils/ContextUtils.kt
index 814a75b..936b568 100644
--- a/staticlibs/testutils/devicetests/com/android/testutils/ContextUtils.kt
+++ b/staticlibs/testutils/devicetests/com/android/testutils/ContextUtils.kt
@@ -53,3 +53,15 @@
         asUserContext
     }.`when`(context).createContextAsUser(any(UserHandle::class.java), anyInt() /* flags */)
 }
+
+/**
+ * Helper function to mock the desired system service.
+ *
+ * @param context the mock context to set up the getSystemService and getSystemServiceName.
+ * @param clazz the system service class that intents to mock.
+ * @param service the system service name that intents to mock.
+ */
+fun <T> mockService(context: Context, clazz: Class<T>, name: String, service: T) {
+    doReturn(service).`when`(context).getSystemService(name)
+    doReturn(name).`when`(context).getSystemServiceName(clazz)
+}
\ No newline at end of file
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/DeviceInfoUtils.java b/staticlibs/testutils/devicetests/com/android/testutils/DeviceInfoUtils.java
index 1925b55..ea89eda 100644
--- a/staticlibs/testutils/devicetests/com/android/testutils/DeviceInfoUtils.java
+++ b/staticlibs/testutils/devicetests/com/android/testutils/DeviceInfoUtils.java
@@ -16,6 +16,7 @@
 
 package com.android.testutils;
 
+import android.os.VintfRuntimeInfo;
 import android.text.TextUtils;
 import android.util.Pair;
 
@@ -157,4 +158,18 @@
             return new KVersion(0, 0, 0);
         }
     }
+
+    /**
+     * Check if the current kernel version is at least satisfied with the given version.
+     *
+     * @param  version the start version to compare
+     * @return return true if the current version is at least satisfied with the given version.
+     *         otherwise, return false.
+     */
+    public static boolean isKernelVersionAtLeast(final String version) {
+        final String kernelVersion = VintfRuntimeInfo.getKernelVersion();
+        final KVersion current = DeviceInfoUtils.getMajorMinorSubminorVersion(kernelVersion);
+        final KVersion from = DeviceInfoUtils.getMajorMinorSubminorVersion(version);
+        return current.isAtLeast(from);
+    }
 }
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/TestPermissionUtil.kt b/staticlibs/testutils/devicetests/com/android/testutils/TestPermissionUtil.kt
index f557f18..a4dbd9a 100644
--- a/staticlibs/testutils/devicetests/com/android/testutils/TestPermissionUtil.kt
+++ b/staticlibs/testutils/devicetests/com/android/testutils/TestPermissionUtil.kt
@@ -19,6 +19,7 @@
 package com.android.testutils
 
 import androidx.test.platform.app.InstrumentationRegistry
+import com.android.modules.utils.build.SdkLevel
 import com.android.testutils.ExceptionUtils.ThrowingRunnable
 import com.android.testutils.ExceptionUtils.ThrowingSupplier
 
@@ -31,6 +32,23 @@
  */
 fun <T> runAsShell(vararg permissions: String, task: () -> T): T {
     val autom = InstrumentationRegistry.getInstrumentation().uiAutomation
+
+    // Calls to adoptShellPermissionIdentity do not nest, and dropShellPermissionIdentity drops all
+    // permissions. Thus, nesting calls will almost certainly cause test bugs, On S+, where we can
+    // detect this, refuse to do it.
+    //
+    // TODO: when R is deprecated, we could try to make this work instead.
+    // - Get the list of previously-adopted permissions.
+    // - Adopt the union of the previously-adopted and newly-requested permissions.
+    // - Run the task.
+    // - Adopt the previously-adopted permissions, dropping the ones just adopted.
+    //
+    // This would allow tests (and utility classes, such as the TestCarrierConfigReceiver attempted
+    // in aosp/2106007) to call runAsShell even within a test that has already adopted permissions.
+    if (SdkLevel.isAtLeastS() && !autom.getAdoptedShellPermissions().isNullOrEmpty()) {
+        throw IllegalStateException("adoptShellPermissionIdentity calls must not be nested")
+    }
+
     autom.adoptShellPermissionIdentity(*permissions)
     try {
         return task()
diff --git a/staticlibs/testutils/hostdevice/com/android/testutils/Cleanup.kt b/staticlibs/testutils/hostdevice/com/android/testutils/Cleanup.kt
index 45783d8..3db357b 100644
--- a/staticlibs/testutils/hostdevice/com/android/testutils/Cleanup.kt
+++ b/staticlibs/testutils/hostdevice/com/android/testutils/Cleanup.kt
@@ -90,7 +90,7 @@
         if (originalException !is E) return this
         return TryExpr(try {
             Result.success(block(originalException))
-        } catch (e: Exception) {
+        } catch (e: Throwable) {
             Result.failure(e)
         })
     }