Merge "Verify reevaluation triggered when VPN validation fails" into main
diff --git a/bpf_progs/netd.c b/bpf_progs/netd.c
index 74b09e7..e2e6d02 100644
--- a/bpf_progs/netd.c
+++ b/bpf_progs/netd.c
@@ -92,6 +92,8 @@
 DEFINE_BPF_MAP_NO_NETD(iface_stats_map, HASH, uint32_t, StatsValue, IFACE_STATS_MAP_SIZE)
 DEFINE_BPF_MAP_NO_NETD(uid_owner_map, HASH, uint32_t, UidOwnerValue, UID_OWNER_MAP_SIZE)
 DEFINE_BPF_MAP_RW_NETD(uid_permission_map, HASH, uint32_t, uint8_t, UID_OWNER_MAP_SIZE)
+DEFINE_BPF_MAP_NO_NETD(ingress_discard_map, HASH, IngressDiscardKey, IngressDiscardValue,
+                       INGRESS_DISCARD_MAP_SIZE)
 
 /* never actually used from ebpf */
 DEFINE_BPF_MAP_NO_NETD(iface_index_name_map, HASH, uint32_t, IfaceValue, IFACE_INDEX_NAME_MAP_SIZE)
@@ -343,6 +345,35 @@
     return *config;
 }
 
+static __always_inline inline bool ingress_should_discard(struct __sk_buff* skb,
+                                                          const unsigned kver) {
+    // Require 4.19, since earlier kernels don't have bpf_skb_load_bytes_relative() which
+    // provides relative to L3 header reads.  Without that we could fetch the wrong bytes.
+    // Additionally earlier bpf verifiers are much harder to please.
+    if (kver < KVER(4, 19, 0)) return false;
+
+    IngressDiscardKey k = {};
+    if (skb->protocol == htons(ETH_P_IP)) {
+        k.daddr.s6_addr32[2] = htonl(0xFFFF);
+        (void)bpf_skb_load_bytes_net(skb, IP4_OFFSET(daddr), &k.daddr.s6_addr32[3], 4, kver);
+    } else if (skb->protocol == htons(ETH_P_IPV6)) {
+        (void)bpf_skb_load_bytes_net(skb, IP6_OFFSET(daddr), &k.daddr, sizeof(k.daddr), kver);
+    } else {
+        return false; // non IPv4/IPv6, so no IP to match on
+    }
+
+    // we didn't check for load success, because destination bytes will be zeroed if
+    // bpf_skb_load_bytes_net() fails, instead we rely on daddr of '::' and '::ffff:0.0.0.0'
+    // never being present in the map itself
+
+    IngressDiscardValue* v = bpf_ingress_discard_map_lookup_elem(&k);
+    if (!v) return false;  // lookup failure -> no protection in place -> allow
+    // if (skb->ifindex == 1) return false;  // allow 'lo', but can't happen - see callsite
+    if (skb->ifindex == v->iif[0]) return false;  // allowed interface
+    if (skb->ifindex == v->iif[1]) return false;  // allowed interface
+    return true;  // disallowed interface
+}
+
 // DROP_IF_SET is set of rules that DROP if rule is globally enabled, and per-uid bit is set
 #define DROP_IF_SET (STANDBY_MATCH | OEM_DENY_1_MATCH | OEM_DENY_2_MATCH | OEM_DENY_3_MATCH)
 // DROP_IF_UNSET is set of rules that should DROP if globally enabled, and per-uid bit is NOT set
@@ -368,6 +399,7 @@
     if (enabledRules & (DROP_IF_SET | DROP_IF_UNSET) & (uidRules ^ DROP_IF_UNSET)) return DROP;
 
     if (!egress && skb->ifindex != 1) {
+        if (ingress_should_discard(skb, kver)) return DROP;
         if (uidRules & IIF_MATCH) {
             if (allowed_iif && skb->ifindex != allowed_iif) {
                 // Drops packets not coming from lo nor the allowed interface
@@ -413,7 +445,8 @@
     // Always allow and never count clat traffic. Only the IPv4 traffic on the stacked
     // interface is accounted for and subject to usage restrictions.
     // CLAT IPv6 TX sockets are *always* tagged with CLAT uid, see tagSocketAsClat()
-    if (uid == AID_CLAT) return PASS;
+    // CLAT daemon receives via an untagged AF_PACKET socket.
+    if (egress && uid == AID_CLAT) return PASS;
 
     int match = bpf_owner_match(skb, sock_uid, egress, kver);
 
diff --git a/bpf_progs/netd.h b/bpf_progs/netd.h
index dcf6d6a..836e998 100644
--- a/bpf_progs/netd.h
+++ b/bpf_progs/netd.h
@@ -122,6 +122,7 @@
 static const int IFACE_STATS_MAP_SIZE = 1000;
 static const int CONFIGURATION_MAP_SIZE = 2;
 static const int UID_OWNER_MAP_SIZE = 4000;
+static const int INGRESS_DISCARD_MAP_SIZE = 100;
 static const int PACKET_TRACE_BUF_SIZE = 32 * 1024;
 
 #ifdef __cplusplus
@@ -166,6 +167,7 @@
 #define CONFIGURATION_MAP_PATH BPF_NETD_PATH "map_netd_configuration_map"
 #define UID_OWNER_MAP_PATH BPF_NETD_PATH "map_netd_uid_owner_map"
 #define UID_PERMISSION_MAP_PATH BPF_NETD_PATH "map_netd_uid_permission_map"
+#define INGRESS_DISCARD_MAP_PATH BPF_NETD_PATH "map_netd_ingress_discard_map"
 #define PACKET_TRACE_RINGBUF_PATH BPF_NETD_PATH "map_netd_packet_trace_ringbuf"
 #define PACKET_TRACE_ENABLED_MAP_PATH BPF_NETD_PATH "map_netd_packet_trace_enabled_map"
 
@@ -214,6 +216,18 @@
 } UidOwnerValue;
 STRUCT_SIZE(UidOwnerValue, 2 * 4);  // 8
 
+typedef struct {
+    // The destination ip of the incoming packet.  IPv4 uses IPv4-mapped IPv6 address format.
+    struct in6_addr daddr;
+} IngressDiscardKey;
+STRUCT_SIZE(IngressDiscardKey, 16);  // 16
+
+typedef struct {
+    // Allowed interface indexes.  Use same value multiple times if you just want to match 1 value.
+    uint32_t iif[2];
+} IngressDiscardValue;
+STRUCT_SIZE(IngressDiscardValue, 2 * 4);  // 8
+
 // Entry in the configuration map that stores which UID rules are enabled.
 #define UID_RULES_CONFIGURATION_KEY 0
 // Entry in the configuration map that stores which stats map is currently in use.
diff --git a/framework/Android.bp b/framework/Android.bp
index 813e296..e663764 100644
--- a/framework/Android.bp
+++ b/framework/Android.bp
@@ -82,7 +82,6 @@
         // framework-connectivity and framework-tethering are in the same APEX.
         "framework-tethering.impl",
         "framework-wifi.stubs.module_lib",
-        "net-utils-device-common",
     ],
     static_libs: [
         "mdns_aidl_interface-lateststable-java",
@@ -91,6 +90,9 @@
         "modules-utils-preconditions",
         "framework-connectivity-javastream-protos",
     ],
+    impl_only_static_libs: [
+        "net-utils-device-common-struct",
+    ],
     libs: [
         "androidx.annotation_annotation",
         "app-compat-annotations",
@@ -112,6 +114,13 @@
         "httpclient_api",
         "httpclient_impl",
         "http_client_logging",
+        // Framework-connectivity-pre-jarjar is identical to framework-connectivity
+        // implementation, but without the jarjar rules. However, framework-connectivity
+        // is not based on framework-connectivity-pre-jarjar, it's rebuilt from source
+        // to generate the SDK stubs.
+        // Even if the library is included in "impl_only_static_libs" of defaults. This is still
+        // needed because java_library which doesn't understand "impl_only_static_libs".
+        "net-utils-device-common-struct",
     ],
     libs: [
         // This cannot be in the defaults clause above because if it were, it would be used
diff --git a/framework/src/android/net/BpfNetMapsConstants.java b/framework/src/android/net/BpfNetMapsConstants.java
new file mode 100644
index 0000000..2191682
--- /dev/null
+++ b/framework/src/android/net/BpfNetMapsConstants.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.util.Pair;
+
+import com.android.net.module.util.Struct;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * BpfNetMaps related constants that can be shared among modules.
+ *
+ * @hide
+ */
+// Note that this class should be put into bootclasspath instead of static libraries.
+// Because modules could have different copies of this class if this is statically linked,
+// which would be problematic if the definitions in these modules are not synchronized.
+public class BpfNetMapsConstants {
+    // Prevent this class from being accidental instantiated.
+    private BpfNetMapsConstants() {}
+
+    public static final String CONFIGURATION_MAP_PATH =
+            "/sys/fs/bpf/netd_shared/map_netd_configuration_map";
+    public static final String UID_OWNER_MAP_PATH =
+            "/sys/fs/bpf/netd_shared/map_netd_uid_owner_map";
+    public static final String UID_PERMISSION_MAP_PATH =
+            "/sys/fs/bpf/netd_shared/map_netd_uid_permission_map";
+    public static final String COOKIE_TAG_MAP_PATH =
+            "/sys/fs/bpf/netd_shared/map_netd_cookie_tag_map";
+    public static final Struct.S32 UID_RULES_CONFIGURATION_KEY = new Struct.S32(0);
+    public static final Struct.S32 CURRENT_STATS_MAP_CONFIGURATION_KEY = new Struct.S32(1);
+
+    // LINT.IfChange(match_type)
+    public static final long NO_MATCH = 0;
+    public static final long HAPPY_BOX_MATCH = (1 << 0);
+    public static final long PENALTY_BOX_MATCH = (1 << 1);
+    public static final long DOZABLE_MATCH = (1 << 2);
+    public static final long STANDBY_MATCH = (1 << 3);
+    public static final long POWERSAVE_MATCH = (1 << 4);
+    public static final long RESTRICTED_MATCH = (1 << 5);
+    public static final long LOW_POWER_STANDBY_MATCH = (1 << 6);
+    public static final long IIF_MATCH = (1 << 7);
+    public static final long LOCKDOWN_VPN_MATCH = (1 << 8);
+    public static final long OEM_DENY_1_MATCH = (1 << 9);
+    public static final long OEM_DENY_2_MATCH = (1 << 10);
+    public static final long OEM_DENY_3_MATCH = (1 << 11);
+    // LINT.ThenChange(packages/modules/Connectivity/bpf_progs/netd.h)
+
+    public static final List<Pair<Long, String>> MATCH_LIST = Arrays.asList(
+            Pair.create(HAPPY_BOX_MATCH, "HAPPY_BOX_MATCH"),
+            Pair.create(PENALTY_BOX_MATCH, "PENALTY_BOX_MATCH"),
+            Pair.create(DOZABLE_MATCH, "DOZABLE_MATCH"),
+            Pair.create(STANDBY_MATCH, "STANDBY_MATCH"),
+            Pair.create(POWERSAVE_MATCH, "POWERSAVE_MATCH"),
+            Pair.create(RESTRICTED_MATCH, "RESTRICTED_MATCH"),
+            Pair.create(LOW_POWER_STANDBY_MATCH, "LOW_POWER_STANDBY_MATCH"),
+            Pair.create(IIF_MATCH, "IIF_MATCH"),
+            Pair.create(LOCKDOWN_VPN_MATCH, "LOCKDOWN_VPN_MATCH"),
+            Pair.create(OEM_DENY_1_MATCH, "OEM_DENY_1_MATCH"),
+            Pair.create(OEM_DENY_2_MATCH, "OEM_DENY_2_MATCH"),
+            Pair.create(OEM_DENY_3_MATCH, "OEM_DENY_3_MATCH")
+    );
+}
diff --git a/framework/src/android/net/BpfNetMapsUtils.java b/framework/src/android/net/BpfNetMapsUtils.java
new file mode 100644
index 0000000..d464e3d
--- /dev/null
+++ b/framework/src/android/net/BpfNetMapsUtils.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import static android.net.BpfNetMapsConstants.DOZABLE_MATCH;
+import static android.net.BpfNetMapsConstants.LOW_POWER_STANDBY_MATCH;
+import static android.net.BpfNetMapsConstants.MATCH_LIST;
+import static android.net.BpfNetMapsConstants.NO_MATCH;
+import static android.net.BpfNetMapsConstants.OEM_DENY_1_MATCH;
+import static android.net.BpfNetMapsConstants.OEM_DENY_2_MATCH;
+import static android.net.BpfNetMapsConstants.OEM_DENY_3_MATCH;
+import static android.net.BpfNetMapsConstants.POWERSAVE_MATCH;
+import static android.net.BpfNetMapsConstants.RESTRICTED_MATCH;
+import static android.net.BpfNetMapsConstants.STANDBY_MATCH;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_DOZABLE;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_LOW_POWER_STANDBY;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_OEM_DENY_1;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_OEM_DENY_2;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_OEM_DENY_3;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_POWERSAVE;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_RESTRICTED;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_STANDBY;
+import static android.system.OsConstants.EINVAL;
+
+import android.os.ServiceSpecificException;
+import android.util.Pair;
+
+import java.util.StringJoiner;
+
+/**
+ * The classes and the methods for BpfNetMaps utilization.
+ *
+ * @hide
+ */
+// Note that this class should be put into bootclasspath instead of static libraries.
+// Because modules could have different copies of this class if this is statically linked,
+// which would be problematic if the definitions in these modules are not synchronized.
+public class BpfNetMapsUtils {
+    // Prevent this class from being accidental instantiated.
+    private BpfNetMapsUtils() {}
+
+    /**
+     * Get corresponding match from firewall chain.
+     */
+    public static long getMatchByFirewallChain(final int chain) {
+        switch (chain) {
+            case FIREWALL_CHAIN_DOZABLE:
+                return DOZABLE_MATCH;
+            case FIREWALL_CHAIN_STANDBY:
+                return STANDBY_MATCH;
+            case FIREWALL_CHAIN_POWERSAVE:
+                return POWERSAVE_MATCH;
+            case FIREWALL_CHAIN_RESTRICTED:
+                return RESTRICTED_MATCH;
+            case FIREWALL_CHAIN_LOW_POWER_STANDBY:
+                return LOW_POWER_STANDBY_MATCH;
+            case FIREWALL_CHAIN_OEM_DENY_1:
+                return OEM_DENY_1_MATCH;
+            case FIREWALL_CHAIN_OEM_DENY_2:
+                return OEM_DENY_2_MATCH;
+            case FIREWALL_CHAIN_OEM_DENY_3:
+                return OEM_DENY_3_MATCH;
+            default:
+                throw new ServiceSpecificException(EINVAL, "Invalid firewall chain: " + chain);
+        }
+    }
+
+    /**
+     * Get if the chain is allow list or not.
+     *
+     * ALLOWLIST means the firewall denies all by default, uids must be explicitly allowed
+     * DENYLIST means the firewall allows all by default, uids must be explicitly denyed
+     */
+    public static boolean isFirewallAllowList(final int chain) {
+        switch (chain) {
+            case FIREWALL_CHAIN_DOZABLE:
+            case FIREWALL_CHAIN_POWERSAVE:
+            case FIREWALL_CHAIN_RESTRICTED:
+            case FIREWALL_CHAIN_LOW_POWER_STANDBY:
+                return true;
+            case FIREWALL_CHAIN_STANDBY:
+            case FIREWALL_CHAIN_OEM_DENY_1:
+            case FIREWALL_CHAIN_OEM_DENY_2:
+            case FIREWALL_CHAIN_OEM_DENY_3:
+                return false;
+            default:
+                throw new ServiceSpecificException(EINVAL, "Invalid firewall chain: " + chain);
+        }
+    }
+
+    /**
+     * Get match string representation from the given match bitmap.
+     */
+    public static String matchToString(long matchMask) {
+        if (matchMask == NO_MATCH) {
+            return "NO_MATCH";
+        }
+
+        final StringJoiner sj = new StringJoiner(" ");
+        for (final Pair<Long, String> match : MATCH_LIST) {
+            final long matchFlag = match.first;
+            final String matchName = match.second;
+            if ((matchMask & matchFlag) != 0) {
+                sj.add(matchName);
+                matchMask &= ~matchFlag;
+            }
+        }
+        if (matchMask != 0) {
+            sj.add("UNKNOWN_MATCH(" + matchMask + ")");
+        }
+        return sj.toString();
+    }
+}
diff --git a/remoteauth/service/Android.bp b/remoteauth/service/Android.bp
index c3a9fb3..2ba59da 100644
--- a/remoteauth/service/Android.bp
+++ b/remoteauth/service/Android.bp
@@ -25,9 +25,9 @@
 java_library {
     name: "service-remoteauth-pre-jarjar",
     srcs: [":remoteauth-service-srcs"],
-
+    required: ["libremoteauth_jni_rust_defaults"],
     defaults: [
-        "framework-system-server-module-defaults"
+        "framework-system-server-module-defaults",
     ],
     libs: [
         "androidx.annotation_annotation",
@@ -39,6 +39,7 @@
         "framework-statsd",
     ],
     static_libs: [
+        "guava",
         "libprotobuf-java-lite",
         "fast-pair-lite-protos",
         "modules-utils-build",
@@ -69,7 +70,7 @@
     name: "statslog-remoteauth-java-gen",
     tools: ["stats-log-api-gen"],
     cmd: "$(location stats-log-api-gen) --java $(out) --module remoteauth " +
-         " --javaPackage com.android.server.remoteauth.proto --javaClass RemoteAuthStatsLog" +
-         " --minApiLevel 33",
+        " --javaPackage com.android.server.remoteauth.proto --javaClass RemoteAuthStatsLog" +
+        " --minApiLevel 33",
     out: ["com/android/server/remoteauth/proto/RemoteAuthStatsLog.java"],
 }
diff --git a/remoteauth/service/java/com/android/server/remoteauth/README.md b/remoteauth/service/java/com/android/server/remoteauth/README.md
index 2f8b096..b2b5aab 100644
--- a/remoteauth/service/java/com/android/server/remoteauth/README.md
+++ b/remoteauth/service/java/com/android/server/remoteauth/README.md
@@ -1,4 +1,8 @@
 This is the source root for the RemoteAuthService
 
-## Remote connectivity manager
+## Connectivity
 Provides the connectivity manager to manage connections with the peer device.
+
+## Ranging
+Provides the ranging manager to perform ranging with the peer devices.
+
diff --git a/remoteauth/service/java/com/android/server/remoteauth/ranging/RangingCapabilities.java b/remoteauth/service/java/com/android/server/remoteauth/ranging/RangingCapabilities.java
new file mode 100644
index 0000000..2b5efff
--- /dev/null
+++ b/remoteauth/service/java/com/android/server/remoteauth/ranging/RangingCapabilities.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2023 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.server.remoteauth.ranging;
+
+import androidx.annotation.IntDef;
+
+import com.google.common.collect.ImmutableList;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
+
+/** The ranging capabilities of the device. */
+public class RangingCapabilities {
+
+    /** Possible ranging methods */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(
+            value = {
+                RANGING_METHOD_UNKNOWN,
+                RANGING_METHOD_UWB,
+            })
+    public @interface RangingMethod {}
+
+    /** Unknown ranging method. */
+    public static final int RANGING_METHOD_UNKNOWN = 0x0;
+
+    /** Ultra-wideband ranging. */
+    public static final int RANGING_METHOD_UWB = 0x1;
+
+    private final ImmutableList<Integer> mSupportedRangingMethods;
+
+    /**
+     * Gets the list of supported ranging methods of the device.
+     *
+     * @return list of {@link RangingMethod}
+     */
+    public ImmutableList<Integer> getSupportedRangingMethods() {
+        return mSupportedRangingMethods;
+    }
+
+    private RangingCapabilities(List<Integer> supportedRangingMethods) {
+        mSupportedRangingMethods = ImmutableList.copyOf(supportedRangingMethods);
+    }
+
+    /** Builder class for {@link RangingCapabilities}. */
+    public static final class Builder {
+        private List<Integer> mSupportedRangingMethods = new ArrayList<>();
+
+        /** Adds a supported {@link RangingMethod} */
+        public Builder addSupportedRangingMethods(@RangingMethod int rangingMethod) {
+            mSupportedRangingMethods.add(rangingMethod);
+            return this;
+        }
+
+        /** Builds {@link RangingCapabilities}. */
+        public RangingCapabilities build() {
+            return new RangingCapabilities(mSupportedRangingMethods);
+        }
+    }
+}
diff --git a/remoteauth/service/java/com/android/server/remoteauth/ranging/RangingManager.java b/remoteauth/service/java/com/android/server/remoteauth/ranging/RangingManager.java
new file mode 100644
index 0000000..989b5ed
--- /dev/null
+++ b/remoteauth/service/java/com/android/server/remoteauth/ranging/RangingManager.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2023 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.server.remoteauth.ranging;
+
+import android.content.Context;
+
+/**
+ * Manages the creation of generic device to device ranging session and obtaining device's ranging
+ * capabilities.
+ *
+ * <p>Out-of-band channel for ranging capabilities/parameters exchange is assumed being handled
+ * outside of this class.
+ */
+public class RangingManager {
+
+    public RangingManager(Context context) {}
+
+    /**
+     * Gets the {@link RangingCapabilities} of this device.
+     *
+     * @return RangingCapabilities.
+     */
+    public RangingCapabilities getRangingCapabilities() {
+        return null;
+    }
+
+    /**
+     * Creates a {@link RangingSession} based on the given {@link SessionParameters}, which shall be
+     * provided based on the rangingCapabilities of the device.
+     *
+     * @param sessionParameters parameters used to setup the session.
+     * @return the created RangingSession.
+     */
+    public RangingSession createSession(SessionParameters sessionParameters) {
+        return null;
+    }
+}
diff --git a/remoteauth/service/java/com/android/server/remoteauth/ranging/RangingParameters.java b/remoteauth/service/java/com/android/server/remoteauth/ranging/RangingParameters.java
new file mode 100644
index 0000000..923730c
--- /dev/null
+++ b/remoteauth/service/java/com/android/server/remoteauth/ranging/RangingParameters.java
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2023 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.server.remoteauth.ranging;
+
+/** The set of parameters to start ranging. */
+public class RangingParameters {}
diff --git a/remoteauth/service/java/com/android/server/remoteauth/ranging/RangingReport.java b/remoteauth/service/java/com/android/server/remoteauth/ranging/RangingReport.java
new file mode 100644
index 0000000..5e582b1
--- /dev/null
+++ b/remoteauth/service/java/com/android/server/remoteauth/ranging/RangingReport.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2023 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.server.remoteauth.ranging;
+
+import androidx.annotation.IntDef;
+
+/** Holds ranging report data. */
+public class RangingReport {
+
+    /**
+     * State of the proximity based on detected distance compared against specified near and far
+     * boundaries.
+     */
+    @IntDef(
+            value = {
+                PROXIMITY_STATE_UNKNOWN,
+                PROXIMITY_STATE_INSIDE,
+                PROXIMITY_STATE_OUTSIDE,
+            })
+    public @interface ProximityState {}
+
+    /** Unknown proximity state. */
+    public static final int PROXIMITY_STATE_UNKNOWN = 0x0;
+
+    /**
+     * Proximity is inside the lower and upper proximity boundary. lowerProximityBoundaryM <=
+     * proximity <= upperProximityBoundaryM
+     */
+    public static final int PROXIMITY_STATE_INSIDE = 0x1;
+
+    /**
+     * Proximity is outside the lower and upper proximity boundary. proximity <
+     * lowerProximityBoundaryM OR upperProximityBoundaryM < proximity
+     */
+    public static final int PROXIMITY_STATE_OUTSIDE = 0x2;
+
+    private final float mDistanceM;
+    @ProximityState private final int mProximityState;
+
+    /**
+     * Gets the distance measurement in meters.
+     *
+     * <p>Value may be negative for devices in very close proximity.
+     *
+     * @return distance in meters
+     */
+    public float getDistanceM() {
+        return mDistanceM;
+    }
+
+    /**
+     * Gets the {@link ProximityState}.
+     *
+     * <p>The state is computed based on {@link #getDistanceM} and proximity related session
+     * parameters.
+     *
+     * @return proximity state
+     */
+    @ProximityState
+    public int getProximityState() {
+        return mProximityState;
+    }
+
+    private RangingReport(float distanceM, @ProximityState int proximityState) {
+        mDistanceM = distanceM;
+        mProximityState = proximityState;
+    }
+
+    /** Builder class for {@link RangingReport}. */
+    public static final class Builder {
+        private float mDistanceM;
+        @ProximityState private int mProximityState;
+
+        /** Sets the distance in meters. */
+        public Builder setDistanceM(float distanceM) {
+            mDistanceM = distanceM;
+            return this;
+        }
+
+        /** Sets the proximity state. */
+        public Builder setProximityState(@ProximityState int proximityState) {
+            mProximityState = proximityState;
+            return this;
+        }
+
+        /** Builds {@link RangingReport}. */
+        public RangingReport build() {
+            return new RangingReport(mDistanceM, mProximityState);
+        }
+    }
+}
diff --git a/remoteauth/service/java/com/android/server/remoteauth/ranging/RangingSession.java b/remoteauth/service/java/com/android/server/remoteauth/ranging/RangingSession.java
new file mode 100644
index 0000000..9ef6bda
--- /dev/null
+++ b/remoteauth/service/java/com/android/server/remoteauth/ranging/RangingSession.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2023 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.server.remoteauth.ranging;
+
+import android.annotation.NonNull;
+
+import androidx.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.concurrent.Executor;
+
+/**
+ * The controller for starting and stopping ranging during which callers receive callbacks with
+ * {@link RangingReport}s and {@link RangingError}s."
+ *
+ * <p>A session can be started and stopped multiple times. After starting, updates ({@link
+ * RangingReport}, {@link RangingError}, etc) will be reported via the provided {@link
+ * RangingCallback}. BaseKey and SyncData are used for auto derivation of supported ranging
+ * parameters, which will be implementation specific.
+ *
+ * <p>Ranging method specific implementation shall be implemented in the extended class.
+ */
+public abstract class RangingSession {
+
+    /** Types of ranging error. */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(
+            value = {
+                RANGING_ERROR_UNKNOWN,
+            })
+    public @interface RangingError {}
+
+    /** Unknown ranging error type. */
+    public static final int RANGING_ERROR_UNKNOWN = 0x0;
+
+    /** Interface for ranging update callbacks. */
+    public interface RangingCallback {
+        /**
+         * Call upon new {@link RangingReport}.
+         *
+         * @param sessionInfo info about this ranging session.
+         * @param rangingReport new ranging report
+         */
+        void onRangingReport(SessionInfo sessionInfo, RangingReport rangingReport);
+
+        /**
+         * Call upon any ranging error events.
+         *
+         * @param sessionInfo info about this ranging session.
+         * @param rangingError error type
+         */
+        void onError(SessionInfo sessionInfo, @RangingError int rangingError);
+    }
+
+    /**
+     * Starts ranging based on the given {@link RangingParameters}.
+     *
+     * <p>Start can be called again after {@link #stop()} has been called, else it will result in a
+     * no-op.
+     *
+     * @param rangingParameters parameters to start the ranging.
+     * @param executor Executor to run the rangingCallback.
+     * @param rangingCallback callback to notify of ranging events.
+     */
+    public abstract void start(
+            @NonNull RangingParameters rangingParameters,
+            @NonNull Executor executor,
+            @NonNull RangingCallback rangingCallback);
+
+    /**
+     * Stops ranging.
+     *
+     * <p>Calling stop without first calling {@link #start()} will result in a no-op.
+     */
+    public abstract void stop();
+
+    /**
+     * Resets the base key that's used to derive all possible ranging parameters. The baseKey shall
+     * be reset whenever there is a risk that it may no longer be valid and secured. For example,
+     * the secure connection between the devices is lost.
+     *
+     * @param baseKey new baseKey must be 16 or 32 bytes.
+     */
+    public void resetBaseKey(byte[] baseKey) {}
+
+    /**
+     * Resets the synchronization by giving a new syncData used for ranging parameters derivation.
+     * Resetting the syncData is not required before each {@link #start}, but the more time the
+     * derivations are done before resetting syncData, the higher the risk the derivation will be
+     * out of sync between the devices. Therefore, syncData shall be refreshed in a best effort
+     * manner.
+     *
+     * @param syncData new syncData must be 16 bytes.
+     */
+    public void resetSyncData(byte[] syncData) {}
+}
diff --git a/remoteauth/service/java/com/android/server/remoteauth/ranging/SessionInfo.java b/remoteauth/service/java/com/android/server/remoteauth/ranging/SessionInfo.java
new file mode 100644
index 0000000..5e4fc48
--- /dev/null
+++ b/remoteauth/service/java/com/android/server/remoteauth/ranging/SessionInfo.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2023 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.server.remoteauth.ranging;
+
+import static com.android.server.remoteauth.ranging.RangingCapabilities.RANGING_METHOD_UNKNOWN;
+
+import com.android.internal.util.Preconditions;
+import com.android.server.remoteauth.ranging.RangingCapabilities.RangingMethod;
+
+/** Information about the {@link RangingSession}. */
+public class SessionInfo {
+
+    private final String mDeviceId;
+    @RangingMethod private final int mRangingMethod;
+
+    public String getDeviceId() {
+        return mDeviceId;
+    }
+
+    @RangingMethod
+    public int getRangingMethod() {
+        return mRangingMethod;
+    }
+
+    private SessionInfo(String deviceId, @RangingMethod int rangingMethod) {
+        mDeviceId = deviceId;
+        mRangingMethod = rangingMethod;
+    }
+
+    /** Builder class for {@link SessionInfo}. */
+    public static final class Builder {
+        private String mDeviceId = "";
+        @RangingMethod private int mRangingMethod = RANGING_METHOD_UNKNOWN;
+
+        /** Sets the device id. */
+        public Builder setDeviceId(String deviceId) {
+            mDeviceId = deviceId;
+            return this;
+        }
+
+        /** Sets the ranging method. */
+        public Builder setRangingMethod(@RangingMethod int rangingMethod) {
+            mRangingMethod = rangingMethod;
+            return this;
+        }
+
+        /** Builds {@link SessionInfo}. */
+        public SessionInfo build() {
+            Preconditions.checkArgument(!mDeviceId.isEmpty(), "deviceId must not be empty.");
+            Preconditions.checkArgument(
+                    mRangingMethod != RANGING_METHOD_UNKNOWN, "Unknown rangingMethod");
+            return new SessionInfo(mDeviceId, mRangingMethod);
+        }
+    }
+}
diff --git a/remoteauth/service/java/com/android/server/remoteauth/ranging/SessionParameters.java b/remoteauth/service/java/com/android/server/remoteauth/ranging/SessionParameters.java
new file mode 100644
index 0000000..33c3203
--- /dev/null
+++ b/remoteauth/service/java/com/android/server/remoteauth/ranging/SessionParameters.java
@@ -0,0 +1,222 @@
+/*
+ * Copyright (C) 2023 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.server.remoteauth.ranging;
+
+import static com.android.server.remoteauth.ranging.RangingCapabilities.RANGING_METHOD_UNKNOWN;
+
+import android.annotation.NonNull;
+
+import com.android.internal.util.Preconditions;
+import com.android.server.remoteauth.ranging.RangingCapabilities.RangingMethod;
+
+/**
+ * The set of parameters to create a ranging session.
+ *
+ * <p>Required parameters must be provided, else {@link Builder} will throw an exception. The
+ * optional parameters only need to be provided if the functionality is necessary to the session,
+ * see the setter functions of the {@link Builder} for detailed info of each parameter.
+ */
+public class SessionParameters {
+
+    /* Required parameters */
+    private final String mDeviceId;
+    @RangingMethod private final int mRangingMethod;
+
+    /* Optional parameters */
+    private final float mLowerProximityBoundaryM;
+    private final float mUpperProximityBoundaryM;
+    private final boolean mAutoDeriveParams;
+    private final byte[] mBaseKey;
+    private final byte[] mSyncData;
+
+    public String getDeviceId() {
+        return mDeviceId;
+    }
+
+    @RangingMethod
+    public int getRangingMethod() {
+        return mRangingMethod;
+    }
+
+    public float getLowerProximityBoundaryM() {
+        return mLowerProximityBoundaryM;
+    }
+
+    public float getUpperProximityBoundaryM() {
+        return mUpperProximityBoundaryM;
+    }
+
+    public boolean getAutoDeriveParams() {
+        return mAutoDeriveParams;
+    }
+
+    public byte[] getBaseKey() {
+        return mBaseKey;
+    }
+
+    public byte[] getSyncData() {
+        return mSyncData;
+    }
+
+    private SessionParameters(
+            String deviceId,
+            @RangingMethod int rangingMethod,
+            float lowerProximityBoundaryM,
+            float upperProximityBoundaryM,
+            boolean autoDeriveParams,
+            byte[] baseKey,
+            byte[] syncData) {
+        mDeviceId = deviceId;
+        mRangingMethod = rangingMethod;
+        mLowerProximityBoundaryM = lowerProximityBoundaryM;
+        mUpperProximityBoundaryM = upperProximityBoundaryM;
+        mAutoDeriveParams = autoDeriveParams;
+        mBaseKey = baseKey;
+        mSyncData = syncData;
+    }
+
+    /** Builder class for {@link SessionParameters}. */
+    public static final class Builder {
+        private String mDeviceId = new String("");
+        @RangingMethod private int mRangingMethod = RANGING_METHOD_UNKNOWN;
+        private float mLowerProximityBoundaryM;
+        private float mUpperProximityBoundaryM;
+        private boolean mAutoDeriveParams = false;
+        private byte[] mBaseKey = new byte[] {};
+        private byte[] mSyncData = new byte[] {};
+
+        /**
+         * Sets the device id.
+         *
+         * <p>This is used as the identity included in the {@link SessionInfo} for all {@link
+         * RangingCallback}s.
+         */
+        public Builder setDeviceId(@NonNull String deviceId) {
+            mDeviceId = deviceId;
+            return this;
+        }
+
+        /**
+         * Sets the {@link RangingMethod} to be used for the {@link RangingSession}.
+         *
+         * <p>Note: The ranging method should be ones in the list return by {@link
+         * RangingCapabilities#getSupportedRangingMethods};
+         */
+        public Builder setRangingMethod(@RangingMethod int rangingMethod) {
+            mRangingMethod = rangingMethod;
+            return this;
+        }
+
+        /**
+         * Sets the lower proximity boundary in meters, must be greater than or equals to zero.
+         *
+         * <p>This value is used to compute the {@link ProximityState} = {@link
+         * PROXIMITY_STATE_INSIDE} if lowerProximityBoundaryM <= proximity <=
+         * upperProximityBoundaryM, else {@link PROXIMITY_STATE_OUTSIDE}.
+         */
+        public Builder setLowerProximityBoundaryM(float lowerProximityBoundaryM) {
+            mLowerProximityBoundaryM = lowerProximityBoundaryM;
+            return this;
+        }
+
+        /**
+         * Sets the upper proximity boundary in meters, must be greater than or equals to
+         * lowerProximityBoundaryM.
+         *
+         * <p>This value is used to compute the {@link ProximityState} = {@link
+         * PROXIMITY_STATE_INSIDE} if lowerProximityBoundaryM <= proximity <=
+         * upperProximityBoundaryM, else {@link PROXIMITY_STATE_OUTSIDE}.
+         */
+        public Builder setUpperProximityBoundaryM(float upperProximityBoundaryM) {
+            mUpperProximityBoundaryM = upperProximityBoundaryM;
+            return this;
+        }
+
+        /**
+         * Sets the auto derive ranging parameters flag. Defaults to false.
+         *
+         * <p>This enables the {@link RangingSession} to automatically derive all possible {@link
+         * RangingParameters} at each {@link RangingSession#start} using the provided {@link
+         * #setBaseKey} and {@link #setSyncData}, which shall be securely shared between the ranging
+         * devices out of band.
+         */
+        public Builder setAutoDeriveParams(boolean autoDeriveParams) {
+            mAutoDeriveParams = autoDeriveParams;
+            return this;
+        }
+
+        /**
+         * Sets the base key. Only required if {@link #setAutoDeriveParams} is set to true.
+         *
+         * @param baseKey baseKey must be 16 or 32 bytes.
+         * @throws NullPointerException if baseKey is null
+         */
+        public Builder setBaseKey(@NonNull byte[] baseKey) {
+            Preconditions.checkNotNull(baseKey);
+            mBaseKey = baseKey;
+            return this;
+        }
+
+        /**
+         * Sets the sync data. Only required if {@link #setAutoDeriveParams} is set to true.
+         *
+         * @param syncData syncData must be 16 bytes.
+         * @throws NullPointerException if syncData is null
+         */
+        public Builder setSyncData(@NonNull byte[] syncData) {
+            Preconditions.checkNotNull(syncData);
+            mSyncData = syncData;
+            return this;
+        }
+
+        /**
+         * Builds {@link SessionParameters}.
+         *
+         * @throws IllegalArgumentException if any parameter is invalid.
+         */
+        public SessionParameters build() {
+            Preconditions.checkArgument(!mDeviceId.isEmpty(), "deviceId must not be empty.");
+            Preconditions.checkArgument(
+                    mRangingMethod != RANGING_METHOD_UNKNOWN, "Unknown rangingMethod");
+            Preconditions.checkArgument(
+                    mLowerProximityBoundaryM >= 0,
+                    "Negative lowerProximityBoundaryM: " + mLowerProximityBoundaryM);
+            Preconditions.checkArgument(
+                    mLowerProximityBoundaryM <= mUpperProximityBoundaryM,
+                    "lowerProximityBoundaryM is greater than upperProximityBoundaryM: "
+                            + mLowerProximityBoundaryM
+                            + " > "
+                            + mUpperProximityBoundaryM);
+            // If mAutoDeriveParams is false, mBaseKey and mSyncData will not be used.
+            if (mAutoDeriveParams) {
+                Preconditions.checkArgument(
+                        mBaseKey.length == 16 || mBaseKey.length == 32,
+                        "Invalid baseKey length: " + mBaseKey.length);
+                Preconditions.checkArgument(
+                        mSyncData.length == 16, "Invalid syncData length: " + mSyncData.length);
+            }
+
+            return new SessionParameters(
+                    mDeviceId,
+                    mRangingMethod,
+                    mLowerProximityBoundaryM,
+                    mUpperProximityBoundaryM,
+                    mAutoDeriveParams,
+                    mBaseKey,
+                    mSyncData);
+        }
+    }
+}
diff --git a/remoteauth/service/jni/Android.bp b/remoteauth/service/jni/Android.bp
new file mode 100644
index 0000000..e6e8a43
--- /dev/null
+++ b/remoteauth/service/jni/Android.bp
@@ -0,0 +1,76 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+rust_defaults {
+    name: "libremoteauth_jni_rust_defaults",
+    crate_name: "remoteauth_jni_rust",
+    lints: "android",
+    clippy_lints: "android",
+    min_sdk_version: "35",
+    srcs: ["src/lib.rs"],
+    rustlibs: [
+        "libbinder_rs",
+        "libjni",
+        "liblazy_static",
+        "liblog_rust",
+        "liblogger",
+        "libnum_traits",
+        "libthiserror",
+        "libtokio",
+        "libanyhow",
+    ],
+    proc_macros: [
+        "libasync_trait",
+    ],
+    prefer_rlib: true,
+    apex_available: [
+        "com.android.remoteauth",
+    ],
+    host_supported: true,
+}
+
+rust_test {
+    name: "libremoteauth_jni_rust_tests",
+    defaults: ["libremoteauth_jni_rust_defaults"],
+    rustlibs: [
+    ],
+    target: {
+        android: {
+            test_suites: [
+                "general-tests",
+            ],
+            test_config_template: "remoteauth_rust_test_config_template.xml",
+        },
+        host: {
+            test_suites: [
+                "general-tests",
+            ],
+            data_libs: [
+                "libandroid_runtime_lazy",
+                "libbase",
+                "libbinder",
+                "libbinder_ndk",
+                "libcutils",
+                "liblog",
+                "libutils",
+            ],
+        },
+    },
+    test_options: {
+        unit_test: true,
+    },
+    // Support multilib variants (using different suffix per sub-architecture), which is needed on
+    // build targets with secondary architectures, as the MTS test suite packaging logic flattens
+    // all test artifacts into a single `testcases` directory.
+    compile_multilib: "both",
+    multilib: {
+        lib32: {
+            suffix: "32",
+        },
+        lib64: {
+            suffix: "64",
+        },
+    },
+    auto_gen_config: true,
+}
diff --git a/remoteauth/service/jni/remoteauth_rust_test_config_template.xml b/remoteauth/service/jni/remoteauth_rust_test_config_template.xml
new file mode 100644
index 0000000..673b451
--- /dev/null
+++ b/remoteauth/service/jni/remoteauth_rust_test_config_template.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2023 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.
+  -->
+<configuration description="Configuration for {MODULE} Rust tests">
+    <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
+        <option name="cleanup" value="true" />
+        <option name="push" value="{MODULE}->/data/local/tmp/{MODULE}" />
+        <option name="append-bitness" value="true" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.rust.RustBinaryTest" >
+        <option name="test-device-path" value="/data/local/tmp" />
+        <option name="module-name" value="{MODULE}" />
+    </test>
+    <object type="module_controller"
+            class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController">
+        <option name="mainline-module-package-name" value="com.google.android.remoteauth" />
+    </object>
+</configuration>
\ No newline at end of file
diff --git a/remoteauth/service/jni/src/jnames.rs b/remoteauth/service/jni/src/jnames.rs
new file mode 100644
index 0000000..d7cc908
--- /dev/null
+++ b/remoteauth/service/jni/src/jnames.rs
@@ -0,0 +1,17 @@
+// Copyright 2023 Google LLC
+//
+// 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.
+
+//! Name of java classes and methods for RemoteAuth platform:
+pub(crate) const SEND_REQUEST_MNAME: &str = "sendRequest";
+pub(crate) const SEND_REQUEST_MSIG: &str = "(I[BII)V";
diff --git a/remoteauth/service/jni/src/lib.rs b/remoteauth/service/jni/src/lib.rs
new file mode 100644
index 0000000..0c18679
--- /dev/null
+++ b/remoteauth/service/jni/src/lib.rs
@@ -0,0 +1,24 @@
+// Copyright 2023 Google LLC
+//
+// 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.
+
+//! New rust RemoteAuth JNI library.
+//!
+//! This library takes the JNI calls from RemoteAuthService to the remoteauth protocol library
+//! and from protocol library to platform (Java interface)
+
+mod jnames;
+mod unique_jvm;
+
+//pub mod remoteauth_jni_android_protocol;
+pub mod remoteauth_jni_android_platform;
diff --git a/remoteauth/service/jni/src/remoteauth_jni_android_platform.rs b/remoteauth/service/jni/src/remoteauth_jni_android_platform.rs
new file mode 100644
index 0000000..4597561
--- /dev/null
+++ b/remoteauth/service/jni/src/remoteauth_jni_android_platform.rs
@@ -0,0 +1,306 @@
+// Copyright 2023 Google LLC
+//
+// 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.
+
+use crate::jnames::{SEND_REQUEST_MNAME, SEND_REQUEST_MSIG};
+use crate::unique_jvm;
+use anyhow::anyhow;
+use async_trait::async_trait;
+use jni::errors::Error as JNIError;
+use jni::objects::{GlobalRef, JMethodID, JObject, JValue};
+use jni::signature::TypeSignature;
+use jni::sys::{jbyteArray, jint, jlong, jvalue};
+use jni::{JNIEnv, JavaVM};
+use lazy_static::lazy_static;
+use log::{debug, error, info};
+use std::collections::HashMap;
+use std::sync::{
+    atomic::{AtomicI64, Ordering},
+    Arc,
+};
+use tokio::{
+    runtime::Runtime,
+    sync::{mpsc, Mutex},
+};
+
+/// Macro capturing the name of the function calling this macro.
+///
+/// function_name()! -> &'static str
+/// Returns the function name as 'static reference.
+macro_rules! function_name {
+    () => {{
+        // Declares function f inside current function.
+        fn f() {}
+        fn type_name_of<T>(_: T) -> &'static str {
+            std::any::type_name::<T>()
+        }
+        // type name of f is struct_or_crate_name::calling_function_name::f
+        let name = type_name_of(f);
+        // Find and cut the rest of the path:
+        // Third to last character, up to the first semicolon: is calling_function_name
+        match &name[..name.len() - 3].rfind(':') {
+            Some(pos) => &name[pos + 1..name.len() - 3],
+            None => &name[..name.len() - 3],
+        }
+    }};
+}
+
+lazy_static! {
+    static ref HANDLE_MAPPING: Mutex<HashMap<i64, Arc<Mutex<JavaPlatform>>>> =
+        Mutex::new(HashMap::new());
+    static ref HANDLE_RN: AtomicI64 = AtomicI64::new(0);
+}
+
+fn generate_platform_handle() -> i64 {
+    HANDLE_RN.fetch_add(1, Ordering::SeqCst)
+}
+
+async fn insert_platform_handle(handle: i64, item: Arc<Mutex<JavaPlatform>>) {
+    if 0 == handle {
+        // Init once
+        logger::init(
+            logger::Config::default()
+                .with_tag_on_device("remoteauth")
+                .with_min_level(log::Level::Trace)
+                .with_filter("trace,jni=info"),
+        );
+    }
+    HANDLE_MAPPING.lock().await.insert(handle, Arc::clone(&item));
+}
+
+#[async_trait]
+pub trait Platform {
+    /// Send a binary message to the remote with the given connection id and return the response.
+    async fn send_request(&mut self, connection_id: i32, request: &[u8])
+        -> anyhow::Result<Vec<u8>>;
+}
+//////////////////////////////////
+
+pub struct JavaPlatform {
+    platform_handle: i64,
+    vm: &'static Arc<JavaVM>,
+    platform_native_obj: GlobalRef,
+    send_request_method_id: JMethodID,
+    map_futures: Mutex<HashMap<i64, mpsc::Sender<Vec<u8>>>>,
+    atomic_handle: AtomicI64,
+}
+
+impl JavaPlatform {
+    // Method to create JavaPlatform
+    pub async fn create<'a>(
+        env: JNIEnv<'a>,
+        java_platform_native: JObject<'a>,
+    ) -> Result<Arc<Mutex<impl Platform>>, JNIError> {
+        let jvm = env.get_java_vm()?;
+        let _ = unique_jvm::set_once(jvm);
+        let platform_handle = generate_platform_handle();
+        let platform = Arc::new(Mutex::new(JavaPlatform::new(
+            platform_handle,
+            unique_jvm::get_static_ref().ok_or(JNIError::InvalidCtorReturn)?,
+            java_platform_native,
+        )?));
+        insert_platform_handle(platform_handle, Arc::clone(&platform)).await;
+        Ok(Arc::clone(&platform))
+    }
+
+    fn new(
+        platform_handle: i64,
+        vm: &'static Arc<JavaVM>,
+        java_platform_native: JObject,
+    ) -> Result<JavaPlatform, JNIError> {
+        vm.attach_current_thread().and_then(|env| {
+            let platform_class = env.get_object_class(java_platform_native)?;
+            let platform_native_obj = env.new_global_ref(java_platform_native)?;
+            let send_request_method: JMethodID =
+                env.get_method_id(platform_class, SEND_REQUEST_MNAME, SEND_REQUEST_MSIG)?;
+
+            Ok(Self {
+                platform_handle,
+                vm,
+                platform_native_obj,
+                send_request_method_id: send_request_method,
+                map_futures: Mutex::new(HashMap::new()),
+                atomic_handle: AtomicI64::new(0),
+            })
+        })
+    }
+}
+
+#[async_trait]
+impl Platform for JavaPlatform {
+    async fn send_request(
+        &mut self,
+        connection_id: i32,
+        request: &[u8],
+    ) -> anyhow::Result<Vec<u8>> {
+        let type_signature = TypeSignature::from_str(SEND_REQUEST_MSIG)
+            .map_err(|e| anyhow!("JNI: Invalid type signature: {:?}", e))?;
+
+        let (tx, mut rx) = mpsc::channel(1);
+        let response_handle = self.atomic_handle.fetch_add(1, Ordering::SeqCst);
+        self.map_futures.lock().await.insert(response_handle, tx);
+        self.vm
+            .attach_current_thread()
+            .and_then(|env| {
+                let request_jbytearray = env.byte_array_from_slice(request)?;
+                // Safety: request_jbytearray is safely instantiated above.
+                let request_jobject = unsafe { JObject::from_raw(request_jbytearray) };
+
+                let _ = env.call_method_unchecked(
+                    self.platform_native_obj.as_obj(),
+                    self.send_request_method_id,
+                    type_signature.ret,
+                    &[
+                        jvalue::from(JValue::Int(connection_id)),
+                        jvalue::from(JValue::Object(request_jobject)),
+                        jvalue::from(JValue::Long(response_handle)),
+                        jvalue::from(JValue::Long(self.platform_handle)),
+                    ],
+                );
+                Ok(info!(
+                    "{} successfully sent-message, waiting for response {}:{}",
+                    function_name!(),
+                    self.platform_handle,
+                    response_handle
+                ))
+            })
+            .map_err(|e| anyhow!("JNI: Failed to attach current thread: {:?}", e))?;
+
+        rx.recv().await.ok_or(anyhow!("{} failed in awaiting for a result", function_name!()))
+    }
+}
+
+impl JavaPlatform {
+    async fn on_send_request_success(&mut self, response: &[u8], response_handle: i64) {
+        info!(
+            "{} completed successfully {}:{}",
+            function_name!(),
+            self.platform_handle,
+            response_handle
+        );
+        if let Some(tx) = self.map_futures.lock().await.remove(&response_handle) {
+            let _ = tx.send(response.to_vec()).await;
+        } else {
+            error!(
+                "Failed to find TX for {} and {}:{}",
+                function_name!(),
+                self.platform_handle,
+                response_handle
+            );
+        }
+    }
+
+    async fn on_send_request_error(&self, error_code: i32, response_handle: i64) {
+        error!(
+            "{} completed with error {} {}:{}",
+            function_name!(),
+            error_code,
+            self.platform_handle,
+            response_handle
+        );
+        if let Some(tx) = self.map_futures.lock().await.remove(&response_handle) {
+            // `rx.recv()` ends with `Err`
+            drop(tx);
+        } else {
+            error!(
+                "Failed to find TX for {} and {}:{}",
+                function_name!(),
+                self.platform_handle,
+                response_handle
+            );
+        }
+    }
+}
+
+#[no_mangle]
+pub extern "system" fn Java_com_android_server_remoteauth_jni_NativeRemoteAuthJavaPlatform_native_on_send_request_success(
+    env: JNIEnv,
+    _: JObject,
+    app_response: jbyteArray,
+    platform_handle: jlong,
+    response_handle: jlong,
+) {
+    debug!("{}: enter", function_name!());
+    Runtime::new().unwrap().block_on(native_on_send_request_success(
+        env,
+        app_response,
+        platform_handle,
+        response_handle,
+    ));
+}
+
+async fn native_on_send_request_success(
+    env: JNIEnv<'_>,
+    app_response: jbyteArray,
+    platform_handle: jlong,
+    response_handle: jlong,
+) {
+    if let Some(platform) = HANDLE_MAPPING.lock().await.get(&platform_handle) {
+        let response =
+            env.convert_byte_array(app_response).map_err(|_| JNIError::InvalidCtorReturn).unwrap();
+        let mut platform = (*platform).lock().await;
+        platform.on_send_request_success(&response, response_handle).await;
+    } else {
+        let _ = env.throw_new(
+            "com/android/server/remoteauth/jni/BadHandleException",
+            format!("Failed to find Platform with ID {} in {}", platform_handle, function_name!()),
+        );
+    }
+}
+
+#[no_mangle]
+pub extern "system" fn Java_com_android_server_remoteauth_jni_NativeRemoteAuthJavaPlatform_native_on_send_request_error(
+    env: JNIEnv,
+    _: JObject,
+    error_code: jint,
+    platform_handle: jlong,
+    response_handle: jlong,
+) {
+    debug!("{}: enter", function_name!());
+    Runtime::new().unwrap().block_on(native_on_send_request_error(
+        env,
+        error_code,
+        platform_handle,
+        response_handle,
+    ));
+}
+
+async fn native_on_send_request_error(
+    env: JNIEnv<'_>,
+    error_code: jint,
+    platform_handle: jlong,
+    response_handle: jlong,
+) {
+    if let Some(platform) = HANDLE_MAPPING.lock().await.get(&platform_handle) {
+        let platform = (*platform).lock().await;
+        platform.on_send_request_error(error_code, response_handle).await;
+    } else {
+        let _ = env.throw_new(
+            "com/android/server/remoteauth/jni/BadHandleException",
+            format!("Failed to find Platform with ID {} in {}", platform_handle, function_name!()),
+        );
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    //use super::*;
+
+    //use tokio::runtime::Builder;
+
+    /// Checks validity of the function_name! macro.
+    #[test]
+    fn test_function_name() {
+        assert_eq!(function_name!(), "test_function_name");
+    }
+}
diff --git a/remoteauth/service/jni/src/unique_jvm.rs b/remoteauth/service/jni/src/unique_jvm.rs
new file mode 100644
index 0000000..46cc361
--- /dev/null
+++ b/remoteauth/service/jni/src/unique_jvm.rs
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+//! takes a JavaVM to a static reference.
+//!
+//! JavaVM is shared as multiple JavaVM within a single process is not allowed
+//! per [JNI spec](https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/invocation.html)
+//! The unique JavaVM need to be shared over (potentially) different threads.
+
+use std::sync::{Arc, Once};
+
+use anyhow::Result;
+use jni::JavaVM;
+
+static mut JVM: Option<Arc<JavaVM>> = None;
+static INIT: Once = Once::new();
+/// set_once sets the unique JavaVM that can be then accessed using get_static_ref()
+///
+/// The function shall only be called once.
+pub(crate) fn set_once(jvm: JavaVM) -> Result<()> {
+    // Safety: follows [this pattern](https://doc.rust-lang.org/std/sync/struct.Once.html).
+    // Modification to static mut is nested inside call_once.
+    unsafe {
+        INIT.call_once(|| {
+            JVM = Some(Arc::new(jvm));
+        });
+    }
+    Ok(())
+}
+/// Gets a 'static reference to the unique JavaVM. Returns None if set_once() was never called.
+pub(crate) fn get_static_ref() -> Option<&'static Arc<JavaVM>> {
+    // Safety: follows [this pattern](https://doc.rust-lang.org/std/sync/struct.Once.html).
+    // Modification to static mut is nested inside call_once.
+    unsafe { JVM.as_ref() }
+}
diff --git a/remoteauth/tests/unit/src/com/android/server/remoteauth/ranging/RangingCapabilitiesTest.java b/remoteauth/tests/unit/src/com/android/server/remoteauth/ranging/RangingCapabilitiesTest.java
new file mode 100644
index 0000000..e6b6e3b
--- /dev/null
+++ b/remoteauth/tests/unit/src/com/android/server/remoteauth/ranging/RangingCapabilitiesTest.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2023 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.server.remoteauth.ranging;
+
+import static com.android.server.remoteauth.ranging.RangingCapabilities.RANGING_METHOD_UWB;
+
+import static org.junit.Assert.assertEquals;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/** Unit test for {@link RangingCapabilities}. */
+@RunWith(AndroidJUnit4.class)
+public class RangingCapabilitiesTest {
+
+    @Test
+    public void testBuildingRangingCapabilities_success() {
+        final RangingCapabilities rangingCapabilities =
+                new RangingCapabilities.Builder()
+                        .addSupportedRangingMethods(RANGING_METHOD_UWB)
+                        .build();
+
+        assertEquals(rangingCapabilities.getSupportedRangingMethods().size(), 1);
+        assertEquals(
+                (int) rangingCapabilities.getSupportedRangingMethods().get(0), RANGING_METHOD_UWB);
+    }
+}
diff --git a/remoteauth/tests/unit/src/com/android/server/remoteauth/ranging/RangingReportTest.java b/remoteauth/tests/unit/src/com/android/server/remoteauth/ranging/RangingReportTest.java
new file mode 100644
index 0000000..6ac56ea
--- /dev/null
+++ b/remoteauth/tests/unit/src/com/android/server/remoteauth/ranging/RangingReportTest.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2023 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.server.remoteauth.ranging;
+
+import static com.android.server.remoteauth.ranging.RangingReport.PROXIMITY_STATE_INSIDE;
+
+import static org.junit.Assert.assertEquals;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.server.remoteauth.ranging.RangingReport.ProximityState;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/** Unit test for {@link RangingReport}. */
+@RunWith(AndroidJUnit4.class)
+public class RangingReportTest {
+
+    private static final float TEST_DISTANCE_M = 1.5f;
+    @ProximityState private static final int TEST_PROXIMITY_STATE = PROXIMITY_STATE_INSIDE;
+
+    @Test
+    public void testBuildingRangingReport_success() {
+        final RangingReport rangingReport =
+                new RangingReport.Builder()
+                        .setDistanceM(TEST_DISTANCE_M)
+                        .setProximityState(TEST_PROXIMITY_STATE)
+                        .build();
+
+        assertEquals(rangingReport.getDistanceM(), TEST_DISTANCE_M, 0.0f);
+        assertEquals(rangingReport.getProximityState(), TEST_PROXIMITY_STATE);
+    }
+}
diff --git a/remoteauth/tests/unit/src/com/android/server/remoteauth/ranging/SessionInfoTest.java b/remoteauth/tests/unit/src/com/android/server/remoteauth/ranging/SessionInfoTest.java
new file mode 100644
index 0000000..9364092
--- /dev/null
+++ b/remoteauth/tests/unit/src/com/android/server/remoteauth/ranging/SessionInfoTest.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2023 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.server.remoteauth.ranging;
+
+import static com.android.server.remoteauth.ranging.RangingCapabilities.RANGING_METHOD_UWB;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.server.remoteauth.ranging.RangingCapabilities.RangingMethod;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/** Unit test for {@link SessionInfo}. */
+@RunWith(AndroidJUnit4.class)
+public class SessionInfoTest {
+
+    private static final String TEST_DEVICE_ID = new String("test_device_id");
+    private static final @RangingMethod int TEST_RANGING_METHOD = RANGING_METHOD_UWB;
+
+    @Test
+    public void testBuildingSessionInfo_success() {
+        final SessionInfo sessionInfo =
+                new SessionInfo.Builder()
+                        .setDeviceId(TEST_DEVICE_ID)
+                        .setRangingMethod(TEST_RANGING_METHOD)
+                        .build();
+
+        assertEquals(sessionInfo.getDeviceId(), TEST_DEVICE_ID);
+        assertEquals(sessionInfo.getRangingMethod(), TEST_RANGING_METHOD);
+    }
+
+    @Test
+    public void testBuildingSessionInfo_invalidDeviceId() {
+        final SessionInfo.Builder builder =
+                new SessionInfo.Builder().setRangingMethod(TEST_RANGING_METHOD);
+
+        assertThrows(IllegalArgumentException.class, () -> builder.build());
+    }
+
+    @Test
+    public void testBuildingSessionInfo_invalidRangingMethod() {
+        final SessionInfo.Builder builder = new SessionInfo.Builder().setDeviceId(TEST_DEVICE_ID);
+
+        assertThrows(IllegalArgumentException.class, () -> builder.build());
+    }
+}
diff --git a/remoteauth/tests/unit/src/com/android/server/remoteauth/ranging/SessionParametersTest.java b/remoteauth/tests/unit/src/com/android/server/remoteauth/ranging/SessionParametersTest.java
new file mode 100644
index 0000000..357fdf9
--- /dev/null
+++ b/remoteauth/tests/unit/src/com/android/server/remoteauth/ranging/SessionParametersTest.java
@@ -0,0 +1,208 @@
+/*
+ * Copyright (C) 2023 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.server.remoteauth.ranging;
+
+import static com.android.server.remoteauth.ranging.RangingCapabilities.RANGING_METHOD_UWB;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.server.remoteauth.ranging.RangingCapabilities.RangingMethod;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/** Unit tests for {@link SessionParameters}. */
+@RunWith(AndroidJUnit4.class)
+public class SessionParametersTest {
+
+    private static final String TEST_DEVICE_ID = "test_device_id";
+    @RangingMethod private static final int TEST_RANGING_METHOD = RANGING_METHOD_UWB;
+    private static final float TEST_LOWER_PROXIMITY_BOUNDARY_M = 1.0f;
+    private static final float TEST_UPPER_PROXIMITY_BOUNDARY_M = 2.5f;
+    private static final boolean TEST_AUTO_DERIVE_PARAMS = true;
+    private static final byte[] TEST_BASE_KEY =
+            new byte[] {
+                0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d,
+                0x0e, 0x0f
+            };
+    private static final byte[] TEST_SYNC_DATA =
+            new byte[] {
+                0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
+                0x0f, 0x00
+            };
+
+    @Test
+    public void testBuildingSessionParameters_success() {
+        final SessionParameters sessionParameters =
+                new SessionParameters.Builder()
+                        .setDeviceId(TEST_DEVICE_ID)
+                        .setRangingMethod(TEST_RANGING_METHOD)
+                        .setLowerProximityBoundaryM(TEST_LOWER_PROXIMITY_BOUNDARY_M)
+                        .setUpperProximityBoundaryM(TEST_UPPER_PROXIMITY_BOUNDARY_M)
+                        .setAutoDeriveParams(TEST_AUTO_DERIVE_PARAMS)
+                        .setBaseKey(TEST_BASE_KEY)
+                        .setSyncData(TEST_SYNC_DATA)
+                        .build();
+
+        assertEquals(sessionParameters.getDeviceId(), TEST_DEVICE_ID);
+        assertEquals(sessionParameters.getRangingMethod(), TEST_RANGING_METHOD);
+        assertEquals(
+                sessionParameters.getLowerProximityBoundaryM(),
+                TEST_LOWER_PROXIMITY_BOUNDARY_M,
+                0.0f);
+        assertEquals(
+                sessionParameters.getUpperProximityBoundaryM(),
+                TEST_UPPER_PROXIMITY_BOUNDARY_M,
+                0.0f);
+        assertEquals(sessionParameters.getAutoDeriveParams(), TEST_AUTO_DERIVE_PARAMS);
+        assertArrayEquals(sessionParameters.getBaseKey(), TEST_BASE_KEY);
+        assertArrayEquals(sessionParameters.getSyncData(), TEST_SYNC_DATA);
+    }
+
+    @Test
+    public void testBuildingSessionParameters_invalidDeviceId() {
+        final SessionParameters.Builder builder =
+                new SessionParameters.Builder()
+                        .setRangingMethod(TEST_RANGING_METHOD)
+                        .setLowerProximityBoundaryM(TEST_LOWER_PROXIMITY_BOUNDARY_M)
+                        .setUpperProximityBoundaryM(TEST_UPPER_PROXIMITY_BOUNDARY_M)
+                        .setBaseKey(TEST_BASE_KEY)
+                        .setSyncData(TEST_SYNC_DATA);
+
+        assertThrows(IllegalArgumentException.class, () -> builder.build());
+    }
+
+    @Test
+    public void testBuildingSessionParameters_invalidRangingMethod() {
+        final SessionParameters.Builder builder =
+                new SessionParameters.Builder()
+                        .setDeviceId(TEST_DEVICE_ID)
+                        .setLowerProximityBoundaryM(TEST_LOWER_PROXIMITY_BOUNDARY_M)
+                        .setUpperProximityBoundaryM(TEST_UPPER_PROXIMITY_BOUNDARY_M)
+                        .setBaseKey(TEST_BASE_KEY)
+                        .setSyncData(TEST_SYNC_DATA);
+
+        assertThrows(IllegalArgumentException.class, () -> builder.build());
+    }
+
+    @Test
+    public void testBuildingSessionParameters_invalidLowerProximityBoundaryM() {
+        final SessionParameters.Builder builder =
+                new SessionParameters.Builder()
+                        .setDeviceId(TEST_DEVICE_ID)
+                        .setRangingMethod(TEST_RANGING_METHOD)
+                        .setLowerProximityBoundaryM(-1.0f)
+                        .setUpperProximityBoundaryM(TEST_UPPER_PROXIMITY_BOUNDARY_M)
+                        .setBaseKey(TEST_BASE_KEY)
+                        .setSyncData(TEST_SYNC_DATA);
+
+        assertThrows(IllegalArgumentException.class, () -> builder.build());
+    }
+
+    @Test
+    public void testBuildingSessionParameters_invalidUpperProximityBoundaryM() {
+        final SessionParameters.Builder builder =
+                new SessionParameters.Builder()
+                        .setDeviceId(TEST_DEVICE_ID)
+                        .setRangingMethod(TEST_RANGING_METHOD)
+                        .setLowerProximityBoundaryM(TEST_LOWER_PROXIMITY_BOUNDARY_M)
+                        .setUpperProximityBoundaryM(TEST_LOWER_PROXIMITY_BOUNDARY_M - 0.1f)
+                        .setBaseKey(TEST_BASE_KEY)
+                        .setSyncData(TEST_SYNC_DATA);
+
+        assertThrows(IllegalArgumentException.class, () -> builder.build());
+    }
+
+    @Test
+    public void testBuildingSessionParameters_disableAutoDeriveParams() {
+        final boolean autoDeriveParams = false;
+        final SessionParameters sessionParameters =
+                new SessionParameters.Builder()
+                        .setDeviceId(TEST_DEVICE_ID)
+                        .setRangingMethod(TEST_RANGING_METHOD)
+                        .setLowerProximityBoundaryM(TEST_LOWER_PROXIMITY_BOUNDARY_M)
+                        .setUpperProximityBoundaryM(TEST_UPPER_PROXIMITY_BOUNDARY_M)
+                        .setAutoDeriveParams(autoDeriveParams)
+                        .build();
+
+        assertEquals(sessionParameters.getAutoDeriveParams(), autoDeriveParams);
+        assertArrayEquals(sessionParameters.getBaseKey(), new byte[] {});
+        assertArrayEquals(sessionParameters.getSyncData(), new byte[] {});
+    }
+
+    @Test
+    public void testBuildingSessionParameters_emptyBaseKey() {
+        final SessionParameters.Builder builder =
+                new SessionParameters.Builder()
+                        .setDeviceId(TEST_DEVICE_ID)
+                        .setRangingMethod(TEST_RANGING_METHOD)
+                        .setLowerProximityBoundaryM(TEST_LOWER_PROXIMITY_BOUNDARY_M)
+                        .setUpperProximityBoundaryM(TEST_UPPER_PROXIMITY_BOUNDARY_M)
+                        .setAutoDeriveParams(TEST_AUTO_DERIVE_PARAMS)
+                        .setSyncData(TEST_SYNC_DATA);
+
+        assertThrows(IllegalArgumentException.class, () -> builder.build());
+    }
+
+    @Test
+    public void testBuildingSessionParameters_invalidBaseKey() {
+        final SessionParameters.Builder builder =
+                new SessionParameters.Builder()
+                        .setDeviceId(TEST_DEVICE_ID)
+                        .setRangingMethod(TEST_RANGING_METHOD)
+                        .setLowerProximityBoundaryM(TEST_LOWER_PROXIMITY_BOUNDARY_M)
+                        .setUpperProximityBoundaryM(TEST_UPPER_PROXIMITY_BOUNDARY_M)
+                        .setAutoDeriveParams(TEST_AUTO_DERIVE_PARAMS)
+                        .setBaseKey(new byte[] {0x00, 0x01, 0x02, 0x13})
+                        .setSyncData(TEST_SYNC_DATA);
+
+        assertThrows(IllegalArgumentException.class, () -> builder.build());
+    }
+
+    @Test
+    public void testBuildingSessionParameters_emptySyncData() {
+        final SessionParameters.Builder builder =
+                new SessionParameters.Builder()
+                        .setDeviceId(TEST_DEVICE_ID)
+                        .setRangingMethod(TEST_RANGING_METHOD)
+                        .setLowerProximityBoundaryM(TEST_LOWER_PROXIMITY_BOUNDARY_M)
+                        .setUpperProximityBoundaryM(TEST_UPPER_PROXIMITY_BOUNDARY_M)
+                        .setAutoDeriveParams(TEST_AUTO_DERIVE_PARAMS)
+                        .setBaseKey(TEST_BASE_KEY);
+
+        assertThrows(IllegalArgumentException.class, () -> builder.build());
+    }
+
+    @Test
+    public void testBuildingSessionParameters_invalidSyncData() {
+        final SessionParameters.Builder builder =
+                new SessionParameters.Builder()
+                        .setDeviceId(TEST_DEVICE_ID)
+                        .setRangingMethod(TEST_RANGING_METHOD)
+                        .setLowerProximityBoundaryM(TEST_LOWER_PROXIMITY_BOUNDARY_M)
+                        .setUpperProximityBoundaryM(TEST_UPPER_PROXIMITY_BOUNDARY_M)
+                        .setAutoDeriveParams(TEST_AUTO_DERIVE_PARAMS)
+                        .setBaseKey(TEST_BASE_KEY)
+                        .setSyncData(new byte[] {0x00, 0x01, 0x02, 0x13});
+
+        assertThrows(IllegalArgumentException.class, () -> builder.build());
+    }
+}
diff --git a/service/src/com/android/server/BpfNetMaps.java b/service/src/com/android/server/BpfNetMaps.java
index 2842cc3..62520dc 100644
--- a/service/src/com/android/server/BpfNetMaps.java
+++ b/service/src/com/android/server/BpfNetMaps.java
@@ -16,6 +16,18 @@
 
 package com.android.server;
 
+import static android.net.BpfNetMapsConstants.CONFIGURATION_MAP_PATH;
+import static android.net.BpfNetMapsConstants.COOKIE_TAG_MAP_PATH;
+import static android.net.BpfNetMapsConstants.CURRENT_STATS_MAP_CONFIGURATION_KEY;
+import static android.net.BpfNetMapsConstants.HAPPY_BOX_MATCH;
+import static android.net.BpfNetMapsConstants.IIF_MATCH;
+import static android.net.BpfNetMapsConstants.LOCKDOWN_VPN_MATCH;
+import static android.net.BpfNetMapsConstants.PENALTY_BOX_MATCH;
+import static android.net.BpfNetMapsConstants.UID_OWNER_MAP_PATH;
+import static android.net.BpfNetMapsConstants.UID_PERMISSION_MAP_PATH;
+import static android.net.BpfNetMapsConstants.UID_RULES_CONFIGURATION_KEY;
+import static android.net.BpfNetMapsUtils.getMatchByFirewallChain;
+import static android.net.BpfNetMapsUtils.matchToString;
 import static android.net.ConnectivityManager.FIREWALL_CHAIN_DOZABLE;
 import static android.net.ConnectivityManager.FIREWALL_CHAIN_LOW_POWER_STANDBY;
 import static android.net.ConnectivityManager.FIREWALL_CHAIN_OEM_DENY_1;
@@ -107,16 +119,6 @@
     // BpfNetMaps is an only writer of this entry.
     private static final Object sCurrentStatsMapConfigLock = new Object();
 
-    private static final String CONFIGURATION_MAP_PATH =
-            "/sys/fs/bpf/netd_shared/map_netd_configuration_map";
-    private static final String UID_OWNER_MAP_PATH =
-            "/sys/fs/bpf/netd_shared/map_netd_uid_owner_map";
-    private static final String UID_PERMISSION_MAP_PATH =
-            "/sys/fs/bpf/netd_shared/map_netd_uid_permission_map";
-    private static final String COOKIE_TAG_MAP_PATH =
-            "/sys/fs/bpf/netd_shared/map_netd_cookie_tag_map";
-    private static final S32 UID_RULES_CONFIGURATION_KEY = new S32(0);
-    private static final S32 CURRENT_STATS_MAP_CONFIGURATION_KEY = new S32(1);
     private static final long UID_RULES_DEFAULT_CONFIGURATION = 0;
     private static final long STATS_SELECT_MAP_A = 0;
     private static final long STATS_SELECT_MAP_B = 1;
@@ -127,40 +129,10 @@
     private static IBpfMap<S32, U8> sUidPermissionMap = null;
     private static IBpfMap<CookieTagMapKey, CookieTagMapValue> sCookieTagMap = null;
 
-    // LINT.IfChange(match_type)
-    @VisibleForTesting public static final long NO_MATCH = 0;
-    @VisibleForTesting public static final long HAPPY_BOX_MATCH = (1 << 0);
-    @VisibleForTesting public static final long PENALTY_BOX_MATCH = (1 << 1);
-    @VisibleForTesting public static final long DOZABLE_MATCH = (1 << 2);
-    @VisibleForTesting public static final long STANDBY_MATCH = (1 << 3);
-    @VisibleForTesting public static final long POWERSAVE_MATCH = (1 << 4);
-    @VisibleForTesting public static final long RESTRICTED_MATCH = (1 << 5);
-    @VisibleForTesting public static final long LOW_POWER_STANDBY_MATCH = (1 << 6);
-    @VisibleForTesting public static final long IIF_MATCH = (1 << 7);
-    @VisibleForTesting public static final long LOCKDOWN_VPN_MATCH = (1 << 8);
-    @VisibleForTesting public static final long OEM_DENY_1_MATCH = (1 << 9);
-    @VisibleForTesting public static final long OEM_DENY_2_MATCH = (1 << 10);
-    @VisibleForTesting public static final long OEM_DENY_3_MATCH = (1 << 11);
-    // LINT.ThenChange(packages/modules/Connectivity/bpf_progs/netd.h)
-
     private static final List<Pair<Integer, String>> PERMISSION_LIST = Arrays.asList(
             Pair.create(PERMISSION_INTERNET, "PERMISSION_INTERNET"),
             Pair.create(PERMISSION_UPDATE_DEVICE_STATS, "PERMISSION_UPDATE_DEVICE_STATS")
     );
-    private static final List<Pair<Long, String>> MATCH_LIST = Arrays.asList(
-            Pair.create(HAPPY_BOX_MATCH, "HAPPY_BOX_MATCH"),
-            Pair.create(PENALTY_BOX_MATCH, "PENALTY_BOX_MATCH"),
-            Pair.create(DOZABLE_MATCH, "DOZABLE_MATCH"),
-            Pair.create(STANDBY_MATCH, "STANDBY_MATCH"),
-            Pair.create(POWERSAVE_MATCH, "POWERSAVE_MATCH"),
-            Pair.create(RESTRICTED_MATCH, "RESTRICTED_MATCH"),
-            Pair.create(LOW_POWER_STANDBY_MATCH, "LOW_POWER_STANDBY_MATCH"),
-            Pair.create(IIF_MATCH, "IIF_MATCH"),
-            Pair.create(LOCKDOWN_VPN_MATCH, "LOCKDOWN_VPN_MATCH"),
-            Pair.create(OEM_DENY_1_MATCH, "OEM_DENY_1_MATCH"),
-            Pair.create(OEM_DENY_2_MATCH, "OEM_DENY_2_MATCH"),
-            Pair.create(OEM_DENY_3_MATCH, "OEM_DENY_3_MATCH")
-    );
 
     /**
      * Set sEnableJavaBpfMap for test.
@@ -353,33 +325,6 @@
     }
 
     /**
-     * Get corresponding match from firewall chain.
-     */
-    @VisibleForTesting
-    public long getMatchByFirewallChain(final int chain) {
-        switch (chain) {
-            case FIREWALL_CHAIN_DOZABLE:
-                return DOZABLE_MATCH;
-            case FIREWALL_CHAIN_STANDBY:
-                return STANDBY_MATCH;
-            case FIREWALL_CHAIN_POWERSAVE:
-                return POWERSAVE_MATCH;
-            case FIREWALL_CHAIN_RESTRICTED:
-                return RESTRICTED_MATCH;
-            case FIREWALL_CHAIN_LOW_POWER_STANDBY:
-                return LOW_POWER_STANDBY_MATCH;
-            case FIREWALL_CHAIN_OEM_DENY_1:
-                return OEM_DENY_1_MATCH;
-            case FIREWALL_CHAIN_OEM_DENY_2:
-                return OEM_DENY_2_MATCH;
-            case FIREWALL_CHAIN_OEM_DENY_3:
-                return OEM_DENY_3_MATCH;
-            default:
-                throw new ServiceSpecificException(EINVAL, "Invalid firewall chain: " + chain);
-        }
-    }
-
-    /**
      * Get if the chain is allow list or not.
      *
      * ALLOWLIST means the firewall denies all by default, uids must be explicitly allowed
@@ -1049,26 +994,6 @@
         return sj.toString();
     }
 
-    private String matchToString(long matchMask) {
-        if (matchMask == NO_MATCH) {
-            return "NO_MATCH";
-        }
-
-        final StringJoiner sj = new StringJoiner(" ");
-        for (Pair<Long, String> match: MATCH_LIST) {
-            final long matchFlag = match.first;
-            final String matchName = match.second;
-            if ((matchMask & matchFlag) != 0) {
-                sj.add(matchName);
-                matchMask &= ~matchFlag;
-            }
-        }
-        if (matchMask != 0) {
-            sj.add("UNKNOWN_MATCH(" + matchMask + ")");
-        }
-        return sj.toString();
-    }
-
     private void dumpOwnerMatchConfig(final IndentingPrintWriter pw) {
         try {
             final long match = sConfigurationMap.getValue(UID_RULES_CONFIGURATION_KEY).val;
diff --git a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
index 77cea1a..3a76cc2 100644
--- a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
@@ -1132,7 +1132,6 @@
      */
     @AppModeFull(reason = "Cannot get WifiManager in instant app mode")
     @Test
-    @SkipMainlinePresubmit(reason = "Out of SLO flakiness")
     public void testRegisterNetworkCallback_withPendingIntent() {
         assumeTrue(mPackageManager.hasSystemFeature(FEATURE_WIFI));
 
@@ -1276,7 +1275,6 @@
 
     @AppModeFull(reason = "Cannot get WifiManager in instant app mode")
     @Test
-    @SkipMainlinePresubmit(reason = "Out of SLO flakiness")
     public void testRegisterNetworkRequest_identicalPendingIntents() throws Exception {
         runIdenticalPendingIntentsRequestTest(false /* useListen */);
     }
diff --git a/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt b/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt
index 3146b41..b7e5205 100644
--- a/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt
+++ b/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt
@@ -385,6 +385,9 @@
         }
         registeredCallbacks.forEach { cm.unregisterNetworkCallback(it) }
         releaseTetheredInterface()
+        // Force releaseTetheredInterface() to be processed before starting the next test by calling
+        // setEthernetEnabled(true) which always waits on a callback.
+        setEthernetEnabled(true)
     }
 
     // Setting the carrier up / down relies on TUNSETCARRIER which was added in kernel version 5.0.
@@ -635,6 +638,9 @@
             // do nothing -- the TimeoutException indicates that no interface is available for
             // tethering.
             releaseTetheredInterface()
+            // Force releaseTetheredInterface() to be processed before proceeding by calling
+            // setEthernetEnabled(true) which always waits on a callback.
+            setEthernetEnabled(true)
         }
     }
 
diff --git a/tests/cts/netpermission/internetpermission/AndroidTest.xml b/tests/cts/netpermission/internetpermission/AndroidTest.xml
index e326844..ad9a731 100644
--- a/tests/cts/netpermission/internetpermission/AndroidTest.xml
+++ b/tests/cts/netpermission/internetpermission/AndroidTest.xml
@@ -16,6 +16,7 @@
 <configuration description="Config for CTS internet permission test cases">
     <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="networking" />
+    <option name="config-descriptor:metadata" key="token" value="SIM_CARD" />
     <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
     <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
diff --git a/tests/cts/netpermission/updatestatspermission/AndroidTest.xml b/tests/cts/netpermission/updatestatspermission/AndroidTest.xml
index a1019fa..fb6c814 100644
--- a/tests/cts/netpermission/updatestatspermission/AndroidTest.xml
+++ b/tests/cts/netpermission/updatestatspermission/AndroidTest.xml
@@ -16,6 +16,7 @@
 <configuration description="Config for CTS update stats permission test cases">
     <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="networking" />
+    <option name="config-descriptor:metadata" key="token" value="SIM_CARD" />
     <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
     <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
diff --git a/tests/mts/bpf_existence_test.cpp b/tests/mts/bpf_existence_test.cpp
index 442d69f..15263cc 100644
--- a/tests/mts/bpf_existence_test.cpp
+++ b/tests/mts/bpf_existence_test.cpp
@@ -95,6 +95,7 @@
     NETD "map_netd_cookie_tag_map",
     NETD "map_netd_iface_index_name_map",
     NETD "map_netd_iface_stats_map",
+    NETD "map_netd_ingress_discard_map",
     NETD "map_netd_stats_map_A",
     NETD "map_netd_stats_map_B",
     NETD "map_netd_uid_counterset_map",
diff --git a/tests/unit/java/com/android/server/BpfNetMapsTest.java b/tests/unit/java/com/android/server/BpfNetMapsTest.java
index 19fa41d..5f280c6 100644
--- a/tests/unit/java/com/android/server/BpfNetMapsTest.java
+++ b/tests/unit/java/com/android/server/BpfNetMapsTest.java
@@ -16,6 +16,21 @@
 
 package com.android.server;
 
+import static android.net.BpfNetMapsConstants.CURRENT_STATS_MAP_CONFIGURATION_KEY;
+import static android.net.BpfNetMapsConstants.DOZABLE_MATCH;
+import static android.net.BpfNetMapsConstants.HAPPY_BOX_MATCH;
+import static android.net.BpfNetMapsConstants.IIF_MATCH;
+import static android.net.BpfNetMapsConstants.LOCKDOWN_VPN_MATCH;
+import static android.net.BpfNetMapsConstants.LOW_POWER_STANDBY_MATCH;
+import static android.net.BpfNetMapsConstants.NO_MATCH;
+import static android.net.BpfNetMapsConstants.OEM_DENY_1_MATCH;
+import static android.net.BpfNetMapsConstants.OEM_DENY_2_MATCH;
+import static android.net.BpfNetMapsConstants.OEM_DENY_3_MATCH;
+import static android.net.BpfNetMapsConstants.PENALTY_BOX_MATCH;
+import static android.net.BpfNetMapsConstants.POWERSAVE_MATCH;
+import static android.net.BpfNetMapsConstants.RESTRICTED_MATCH;
+import static android.net.BpfNetMapsConstants.STANDBY_MATCH;
+import static android.net.BpfNetMapsConstants.UID_RULES_CONFIGURATION_KEY;
 import static android.net.ConnectivityManager.FIREWALL_CHAIN_DOZABLE;
 import static android.net.ConnectivityManager.FIREWALL_CHAIN_LOW_POWER_STANDBY;
 import static android.net.ConnectivityManager.FIREWALL_CHAIN_OEM_DENY_1;
@@ -33,19 +48,6 @@
 import static android.system.OsConstants.EINVAL;
 import static android.system.OsConstants.EPERM;
 
-import static com.android.server.BpfNetMaps.DOZABLE_MATCH;
-import static com.android.server.BpfNetMaps.HAPPY_BOX_MATCH;
-import static com.android.server.BpfNetMaps.IIF_MATCH;
-import static com.android.server.BpfNetMaps.LOCKDOWN_VPN_MATCH;
-import static com.android.server.BpfNetMaps.LOW_POWER_STANDBY_MATCH;
-import static com.android.server.BpfNetMaps.NO_MATCH;
-import static com.android.server.BpfNetMaps.OEM_DENY_1_MATCH;
-import static com.android.server.BpfNetMaps.OEM_DENY_2_MATCH;
-import static com.android.server.BpfNetMaps.OEM_DENY_3_MATCH;
-import static com.android.server.BpfNetMaps.PENALTY_BOX_MATCH;
-import static com.android.server.BpfNetMaps.POWERSAVE_MATCH;
-import static com.android.server.BpfNetMaps.RESTRICTED_MATCH;
-import static com.android.server.BpfNetMaps.STANDBY_MATCH;
 import static com.android.server.ConnectivityStatsLog.NETWORK_BPF_MAP_INFO;
 
 import static org.junit.Assert.assertEquals;
@@ -62,6 +64,7 @@
 
 import android.app.StatsManager;
 import android.content.Context;
+import android.net.BpfNetMapsUtils;
 import android.net.INetd;
 import android.os.Build;
 import android.os.ServiceSpecificException;
@@ -112,8 +115,6 @@
     private static final int NO_IIF = 0;
     private static final int NULL_IIF = 0;
     private static final String CHAINNAME = "fw_dozable";
-    private static final S32 UID_RULES_CONFIGURATION_KEY = new S32(0);
-    private static final S32 CURRENT_STATS_MAP_CONFIGURATION_KEY = new S32(1);
     private static final List<Integer> FIREWALL_CHAINS = List.of(
             FIREWALL_CHAIN_DOZABLE,
             FIREWALL_CHAIN_STANDBY,
@@ -170,7 +171,7 @@
     private long getMatch(final List<Integer> chains) {
         long match = 0;
         for (final int chain: chains) {
-            match |= mBpfNetMaps.getMatchByFirewallChain(chain);
+            match |= BpfNetMapsUtils.getMatchByFirewallChain(chain);
         }
         return match;
     }
@@ -239,7 +240,7 @@
     private void doTestSetChildChain(final List<Integer> testChains) throws Exception {
         long expectedMatch = 0;
         for (final int chain: testChains) {
-            expectedMatch |= mBpfNetMaps.getMatchByFirewallChain(chain);
+            expectedMatch |= BpfNetMapsUtils.getMatchByFirewallChain(chain);
         }
 
         assertEquals(0, mConfigurationMap.getValue(UID_RULES_CONFIGURATION_KEY).val);
diff --git a/tests/unit/java/com/android/server/ConnectivityServiceTest.java b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
index 708697c..9b99b81 100755
--- a/tests/unit/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
@@ -16390,6 +16390,15 @@
         // Other callbacks will be unregistered by tearDown()
     }
 
+    private NetworkCallback requestForEnterpriseId(@NetworkCapabilities.EnterpriseId final int id) {
+        final NetworkCapabilities nc = new NetworkCapabilities.Builder()
+                .addCapability(NET_CAPABILITY_ENTERPRISE).addEnterpriseId(id).build();
+        final NetworkRequest req = new NetworkRequest.Builder().setCapabilities(nc).build();
+        final NetworkCallback cb = new TestableNetworkCallback();
+        mCm.requestNetwork(req, cb);
+        return cb;
+    }
+
     /**
      * Make sure per profile network preferences behave as expected when multiple slices with
      * multiple different apps within same user profile is configured.
@@ -16397,8 +16406,6 @@
     @Test
     public void testSetPreferenceWithMultiplePreferences()
             throws Exception {
-        final InOrder inOrder = inOrder(mMockNetd);
-
         final UserHandle testHandle = setupEnterpriseNetwork();
         mServiceContext.setWorkProfile(testHandle, true);
         registerDefaultNetworkCallbacks();
@@ -16436,6 +16443,12 @@
         final TestNetworkAgentWrapper workAgent4 = makeEnterpriseNetworkAgent(NET_ENTERPRISE_ID_4);
         final TestNetworkAgentWrapper workAgent5 = makeEnterpriseNetworkAgent(NET_ENTERPRISE_ID_5);
 
+        final NetworkCallback keepupCb1 = requestForEnterpriseId(NET_ENTERPRISE_ID_1);
+        final NetworkCallback keepupCb2 = requestForEnterpriseId(NET_ENTERPRISE_ID_2);
+        final NetworkCallback keepupCb3 = requestForEnterpriseId(NET_ENTERPRISE_ID_3);
+        final NetworkCallback keepupCb4 = requestForEnterpriseId(NET_ENTERPRISE_ID_4);
+        final NetworkCallback keepupCb5 = requestForEnterpriseId(NET_ENTERPRISE_ID_5);
+
         workAgent1.connect(true);
         workAgent2.connect(true);
         workAgent3.connect(true);
@@ -16594,6 +16607,12 @@
         appCb4.expectAvailableCallbacksValidated(mCellAgent);
         mCellAgent.disconnect();
 
+        mCm.unregisterNetworkCallback(keepupCb1);
+        mCm.unregisterNetworkCallback(keepupCb2);
+        mCm.unregisterNetworkCallback(keepupCb3);
+        mCm.unregisterNetworkCallback(keepupCb4);
+        mCm.unregisterNetworkCallback(keepupCb5);
+
         mCm.unregisterNetworkCallback(appCb1);
         mCm.unregisterNetworkCallback(appCb2);
         mCm.unregisterNetworkCallback(appCb3);