Merge "Update permission check for offloadEngine registration" into main
diff --git a/.gitignore b/.gitignore
index ccff052..c9b6393 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,3 +6,7 @@
**/.idea
**/*.iml
**/*.ipr
+
+# VS Code project
+**/.vscode
+**/*.code-workspace
diff --git a/Tethering/apex/Android.bp b/Tethering/apex/Android.bp
index cf9b359..bb3dc24 100644
--- a/Tethering/apex/Android.bp
+++ b/Tethering/apex/Android.bp
@@ -110,7 +110,6 @@
],
apps: [
"ServiceConnectivityResources",
- "HalfSheetUX",
],
prebuilts: ["current_sdkinfo"],
manifest: "manifest.json",
@@ -203,6 +202,8 @@
// result in a build failure due to inconsistent flags.
package_prefixes: [
"android.nearby.aidl",
+ "android.remoteauth.aidl",
+ "android.remoteauth",
"android.net.apf",
"android.net.connectivity",
"android.net.http.apihelpers",
diff --git a/Tethering/apishim/30/com/android/networkstack/tethering/apishim/api30/BpfCoordinatorShimImpl.java b/Tethering/apishim/30/com/android/networkstack/tethering/apishim/api30/BpfCoordinatorShimImpl.java
index 4c9460b..0df9047 100644
--- a/Tethering/apishim/30/com/android/networkstack/tethering/apishim/api30/BpfCoordinatorShimImpl.java
+++ b/Tethering/apishim/30/com/android/networkstack/tethering/apishim/api30/BpfCoordinatorShimImpl.java
@@ -17,8 +17,6 @@
package com.android.networkstack.tethering.apishim.api30;
import android.net.INetd;
-import android.net.IpPrefix;
-import android.net.MacAddress;
import android.net.TetherStatsParcel;
import android.os.RemoteException;
import android.os.ServiceSpecificException;
@@ -33,7 +31,8 @@
import com.android.net.module.util.bpf.Tether4Value;
import com.android.net.module.util.bpf.TetherStatsValue;
import com.android.networkstack.tethering.BpfCoordinator.Dependencies;
-import com.android.networkstack.tethering.BpfCoordinator.Ipv6ForwardingRule;
+import com.android.networkstack.tethering.BpfCoordinator.Ipv6DownstreamRule;
+import com.android.networkstack.tethering.BpfCoordinator.Ipv6UpstreamRule;
/**
* Bpf coordinator class for API shims.
@@ -58,7 +57,17 @@
};
@Override
- public boolean tetherOffloadRuleAdd(@NonNull final Ipv6ForwardingRule rule) {
+ public boolean addIpv6UpstreamRule(@NonNull final Ipv6UpstreamRule rule) {
+ return true;
+ };
+
+ @Override
+ public boolean removeIpv6UpstreamRule(@NonNull final Ipv6UpstreamRule rule) {
+ return true;
+ }
+
+ @Override
+ public boolean addIpv6DownstreamRule(@NonNull final Ipv6DownstreamRule rule) {
try {
mNetd.tetherOffloadRuleAdd(rule.toTetherOffloadRuleParcel());
} catch (RemoteException | ServiceSpecificException e) {
@@ -70,7 +79,7 @@
};
@Override
- public boolean tetherOffloadRuleRemove(@NonNull final Ipv6ForwardingRule rule) {
+ public boolean removeIpv6DownstreamRule(@NonNull final Ipv6DownstreamRule rule) {
try {
mNetd.tetherOffloadRuleRemove(rule.toTetherOffloadRuleParcel());
} catch (RemoteException | ServiceSpecificException e) {
@@ -81,19 +90,6 @@
}
@Override
- public boolean startUpstreamIpv6Forwarding(int downstreamIfindex, int upstreamIfindex,
- @NonNull IpPrefix sourcePrefix, @NonNull MacAddress inDstMac,
- @NonNull MacAddress outSrcMac, @NonNull MacAddress outDstMac, int mtu) {
- return true;
- }
-
- @Override
- public boolean stopUpstreamIpv6Forwarding(int downstreamIfindex, int upstreamIfindex,
- @NonNull IpPrefix sourcePrefix, @NonNull MacAddress inDstMac) {
- return true;
- }
-
- @Override
@Nullable
public SparseArray<TetherStatsValue> tetherOffloadGetStats() {
final TetherStatsParcel[] tetherStatsList;
diff --git a/Tethering/apishim/31/com/android/networkstack/tethering/apishim/api31/BpfCoordinatorShimImpl.java b/Tethering/apishim/31/com/android/networkstack/tethering/apishim/api31/BpfCoordinatorShimImpl.java
index 119fbc6..a280046 100644
--- a/Tethering/apishim/31/com/android/networkstack/tethering/apishim/api31/BpfCoordinatorShimImpl.java
+++ b/Tethering/apishim/31/com/android/networkstack/tethering/apishim/api31/BpfCoordinatorShimImpl.java
@@ -20,8 +20,6 @@
import static com.android.net.module.util.NetworkStackConstants.RFC7421_PREFIX_LENGTH;
-import android.net.IpPrefix;
-import android.net.MacAddress;
import android.system.ErrnoException;
import android.system.Os;
import android.system.OsConstants;
@@ -39,7 +37,8 @@
import com.android.net.module.util.bpf.TetherStatsKey;
import com.android.net.module.util.bpf.TetherStatsValue;
import com.android.networkstack.tethering.BpfCoordinator.Dependencies;
-import com.android.networkstack.tethering.BpfCoordinator.Ipv6ForwardingRule;
+import com.android.networkstack.tethering.BpfCoordinator.Ipv6DownstreamRule;
+import com.android.networkstack.tethering.BpfCoordinator.Ipv6UpstreamRule;
import com.android.networkstack.tethering.BpfUtils;
import com.android.networkstack.tethering.Tether6Value;
import com.android.networkstack.tethering.TetherDevKey;
@@ -51,9 +50,6 @@
import java.io.FileDescriptor;
import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.util.Arrays;
/**
* Bpf coordinator class for API shims.
@@ -170,7 +166,40 @@
}
@Override
- public boolean tetherOffloadRuleAdd(@NonNull final Ipv6ForwardingRule rule) {
+ public boolean addIpv6UpstreamRule(@NonNull final Ipv6UpstreamRule rule) {
+ if (!isInitialized()) return false;
+ // RFC7421_PREFIX_LENGTH = 64 which is the most commonly used IPv6 subnet prefix length.
+ if (rule.sourcePrefix.getPrefixLength() != RFC7421_PREFIX_LENGTH) return false;
+
+ final TetherUpstream6Key key = rule.makeTetherUpstream6Key();
+ final Tether6Value value = rule.makeTether6Value();
+
+ try {
+ mBpfUpstream6Map.insertEntry(key, value);
+ } catch (ErrnoException | IllegalStateException e) {
+ mLog.e("Could not insert upstream IPv6 entry: " + e);
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public boolean removeIpv6UpstreamRule(@NonNull final Ipv6UpstreamRule rule) {
+ if (!isInitialized()) return false;
+ // RFC7421_PREFIX_LENGTH = 64 which is the most commonly used IPv6 subnet prefix length.
+ if (rule.sourcePrefix.getPrefixLength() != RFC7421_PREFIX_LENGTH) return false;
+
+ try {
+ mBpfUpstream6Map.deleteEntry(rule.makeTetherUpstream6Key());
+ } catch (ErrnoException e) {
+ mLog.e("Could not delete upstream IPv6 entry: " + e);
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public boolean addIpv6DownstreamRule(@NonNull final Ipv6DownstreamRule rule) {
if (!isInitialized()) return false;
final TetherDownstream6Key key = rule.makeTetherDownstream6Key();
@@ -187,7 +216,7 @@
}
@Override
- public boolean tetherOffloadRuleRemove(@NonNull final Ipv6ForwardingRule rule) {
+ public boolean removeIpv6DownstreamRule(@NonNull final Ipv6DownstreamRule rule) {
if (!isInitialized()) return false;
try {
@@ -202,51 +231,6 @@
return true;
}
- @NonNull
- private TetherUpstream6Key makeUpstream6Key(int downstreamIfindex, @NonNull MacAddress inDstMac,
- @NonNull IpPrefix sourcePrefix) {
- byte[] prefixBytes = Arrays.copyOf(sourcePrefix.getRawAddress(), 8);
- long prefix64 = ByteBuffer.wrap(prefixBytes).order(ByteOrder.BIG_ENDIAN).getLong();
- return new TetherUpstream6Key(downstreamIfindex, inDstMac, prefix64);
- }
-
- @Override
- public boolean startUpstreamIpv6Forwarding(int downstreamIfindex, int upstreamIfindex,
- @NonNull IpPrefix sourcePrefix, @NonNull MacAddress inDstMac,
- @NonNull MacAddress outSrcMac, @NonNull MacAddress outDstMac, int mtu) {
- if (!isInitialized()) return false;
- // RFC7421_PREFIX_LENGTH = 64 which is the most commonly used IPv6 subnet prefix length.
- if (sourcePrefix.getPrefixLength() != RFC7421_PREFIX_LENGTH) return false;
-
- final TetherUpstream6Key key = makeUpstream6Key(downstreamIfindex, inDstMac, sourcePrefix);
- final Tether6Value value = new Tether6Value(upstreamIfindex, outSrcMac,
- outDstMac, OsConstants.ETH_P_IPV6, mtu);
- try {
- mBpfUpstream6Map.insertEntry(key, value);
- } catch (ErrnoException | IllegalStateException e) {
- mLog.e("Could not insert upstream6 entry: " + e);
- return false;
- }
- return true;
- }
-
- @Override
- public boolean stopUpstreamIpv6Forwarding(int downstreamIfindex, int upstreamIfindex,
- @NonNull IpPrefix sourcePrefix, @NonNull MacAddress inDstMac) {
- if (!isInitialized()) return false;
- // RFC7421_PREFIX_LENGTH = 64 which is the most commonly used IPv6 subnet prefix length.
- if (sourcePrefix.getPrefixLength() != RFC7421_PREFIX_LENGTH) return false;
-
- final TetherUpstream6Key key = makeUpstream6Key(downstreamIfindex, inDstMac, sourcePrefix);
- try {
- mBpfUpstream6Map.deleteEntry(key);
- } catch (ErrnoException e) {
- mLog.e("Could not delete upstream IPv6 entry: " + e);
- return false;
- }
- return true;
- }
-
@Override
@Nullable
public SparseArray<TetherStatsValue> tetherOffloadGetStats() {
diff --git a/Tethering/apishim/common/com/android/networkstack/tethering/apishim/common/BpfCoordinatorShim.java b/Tethering/apishim/common/com/android/networkstack/tethering/apishim/common/BpfCoordinatorShim.java
index 25fa8bc..d28a397 100644
--- a/Tethering/apishim/common/com/android/networkstack/tethering/apishim/common/BpfCoordinatorShim.java
+++ b/Tethering/apishim/common/com/android/networkstack/tethering/apishim/common/BpfCoordinatorShim.java
@@ -16,8 +16,6 @@
package com.android.networkstack.tethering.apishim.common;
-import android.net.IpPrefix;
-import android.net.MacAddress;
import android.util.SparseArray;
import androidx.annotation.NonNull;
@@ -28,7 +26,8 @@
import com.android.net.module.util.bpf.Tether4Value;
import com.android.net.module.util.bpf.TetherStatsValue;
import com.android.networkstack.tethering.BpfCoordinator.Dependencies;
-import com.android.networkstack.tethering.BpfCoordinator.Ipv6ForwardingRule;
+import com.android.networkstack.tethering.BpfCoordinator.Ipv6DownstreamRule;
+import com.android.networkstack.tethering.BpfCoordinator.Ipv6UpstreamRule;
/**
* Bpf coordinator class for API shims.
@@ -54,53 +53,51 @@
public abstract boolean isInitialized();
/**
- * Adds a tethering offload rule to BPF map, or updates it if it already exists.
+ * Adds a tethering offload upstream rule to BPF map, or updates it if it already exists.
+ *
+ * An existing rule will be updated if the input interface, destination MAC and source prefix
+ * match. Otherwise, a new rule will be created. Note that this can be only called on handler
+ * thread.
+ *
+ * @param rule The rule to add or update.
+ * @return true if operation succeeded or was a no-op, false otherwise.
+ */
+ public abstract boolean addIpv6UpstreamRule(@NonNull Ipv6UpstreamRule rule);
+
+ /**
+ * Deletes a tethering offload upstream rule from the BPF map.
+ *
+ * An existing rule will be deleted if the input interface, destination MAC and source prefix
+ * match. It is not an error if there is no matching rule to delete.
+ *
+ * @param rule The rule to delete.
+ * @return true if operation succeeded or was a no-op, false otherwise.
+ */
+ public abstract boolean removeIpv6UpstreamRule(@NonNull Ipv6UpstreamRule rule);
+
+ /**
+ * Adds a tethering offload downstream rule to BPF map, or updates it if it already exists.
*
* Currently, only downstream /128 IPv6 entries are supported. An existing rule will be updated
* if the input interface and destination prefix match. Otherwise, a new rule will be created.
* Note that this can be only called on handler thread.
*
* @param rule The rule to add or update.
+ * @return true if operation succeeded or was a no-op, false otherwise.
*/
- public abstract boolean tetherOffloadRuleAdd(@NonNull Ipv6ForwardingRule rule);
+ public abstract boolean addIpv6DownstreamRule(@NonNull Ipv6DownstreamRule rule);
/**
- * Deletes a tethering offload rule from the BPF map.
+ * Deletes a tethering offload downstream rule from the BPF map.
*
* Currently, only downstream /128 IPv6 entries are supported. An existing rule will be deleted
* if the destination IP address and the source interface match. It is not an error if there is
* no matching rule to delete.
*
* @param rule The rule to delete.
+ * @return true if operation succeeded or was a no-op, false otherwise.
*/
- public abstract boolean tetherOffloadRuleRemove(@NonNull Ipv6ForwardingRule rule);
-
- /**
- * Starts IPv6 forwarding between the specified interfaces.
-
- * @param downstreamIfindex the downstream interface index
- * @param upstreamIfindex the upstream interface index
- * @param sourcePrefix the source IPv6 prefix
- * @param inDstMac the destination MAC address to use for XDP
- * @param outSrcMac the source MAC address to use for packets
- * @param outDstMac the destination MAC address to use for packets
- * @return true if operation succeeded or was a no-op, false otherwise
- */
- public abstract boolean startUpstreamIpv6Forwarding(int downstreamIfindex, int upstreamIfindex,
- @NonNull IpPrefix sourcePrefix, @NonNull MacAddress inDstMac,
- @NonNull MacAddress outSrcMac, @NonNull MacAddress outDstMac, int mtu);
-
- /**
- * Stops IPv6 forwarding between the specified interfaces.
-
- * @param downstreamIfindex the downstream interface index
- * @param upstreamIfindex the upstream interface index
- * @param sourcePrefix the valid source IPv6 prefix
- * @param inDstMac the destination MAC address to use for XDP
- * @return true if operation succeeded or was a no-op, false otherwise
- */
- public abstract boolean stopUpstreamIpv6Forwarding(int downstreamIfindex, int upstreamIfindex,
- @NonNull IpPrefix sourcePrefix, @NonNull MacAddress inDstMac);
+ public abstract boolean removeIpv6DownstreamRule(@NonNull Ipv6DownstreamRule rule);
/**
* Return BPF tethering offload statistics.
diff --git a/Tethering/src/android/net/ip/IpServer.java b/Tethering/src/android/net/ip/IpServer.java
index 56b5c2e..eadba58 100644
--- a/Tethering/src/android/net/ip/IpServer.java
+++ b/Tethering/src/android/net/ip/IpServer.java
@@ -77,7 +77,7 @@
import com.android.net.module.util.ip.IpNeighborMonitor.NeighborEvent;
import com.android.networkstack.tethering.BpfCoordinator;
import com.android.networkstack.tethering.BpfCoordinator.ClientInfo;
-import com.android.networkstack.tethering.BpfCoordinator.Ipv6ForwardingRule;
+import com.android.networkstack.tethering.BpfCoordinator.Ipv6DownstreamRule;
import com.android.networkstack.tethering.PrivateAddressCoordinator;
import com.android.networkstack.tethering.TetheringConfiguration;
import com.android.networkstack.tethering.metrics.TetheringMetrics;
@@ -283,6 +283,7 @@
private List<TetheredClient> mDhcpLeases = Collections.emptyList();
private int mLastIPv6UpstreamIfindex = 0;
+ private boolean mUpstreamSupportsBpf = false;
private class MyNeighborEventConsumer implements IpNeighborMonitor.NeighborEventConsumer {
public void accept(NeighborEvent e) {
@@ -327,8 +328,8 @@
// IP neighbor monitor monitors the neighbor events for adding/removing offload
// forwarding rules per client. If BPF offload is not supported, don't start listening
- // for neighbor events. See updateIpv6ForwardingRules, addIpv6ForwardingRule,
- // removeIpv6ForwardingRule.
+ // for neighbor events. See updateIpv6ForwardingRules, addIpv6DownstreamRule,
+ // removeIpv6DownstreamRule.
if (mUsingBpfOffload && !mIpNeighborMonitor.start()) {
mLog.e("Failed to create IpNeighborMonitor on " + mIfaceName);
}
@@ -779,15 +780,15 @@
// If v6only is null, we pass in null to setRaParams(), which handles
// deprecation of any existing RA data.
-
setRaParams(params);
- // Be aware that updateIpv6ForwardingRules use mLastIPv6LinkProperties, so this line should
- // be eariler than updateIpv6ForwardingRules.
- // TODO: avoid this dependencies and move this logic into BpfCoordinator.
- mLastIPv6LinkProperties = v6only;
- updateIpv6ForwardingRules(mLastIPv6UpstreamIfindex, upstreamIfIndex, null);
+ // Not support BPF on virtual upstream interface
+ final boolean upstreamSupportsBpf = upstreamIface != null && !isVcnInterface(upstreamIface);
+ updateIpv6ForwardingRules(
+ mLastIPv6UpstreamIfindex, upstreamIfIndex, upstreamSupportsBpf, null);
+ mLastIPv6LinkProperties = v6only;
mLastIPv6UpstreamIfindex = upstreamIfIndex;
+ mUpstreamSupportsBpf = upstreamSupportsBpf;
if (mDadProxy != null) {
mDadProxy.setUpstreamIface(upstreamIfaceParams);
}
@@ -890,21 +891,21 @@
}
}
- private void addIpv6ForwardingRule(Ipv6ForwardingRule rule) {
+ private void addIpv6DownstreamRule(Ipv6DownstreamRule rule) {
// Theoretically, we don't need this check because IP neighbor monitor doesn't start if BPF
// offload is disabled. Add this check just in case.
// TODO: Perhaps remove this protection check.
if (!mUsingBpfOffload) return;
- mBpfCoordinator.tetherOffloadRuleAdd(this, rule);
+ mBpfCoordinator.addIpv6DownstreamRule(this, rule);
}
- private void removeIpv6ForwardingRule(Ipv6ForwardingRule rule) {
+ private void removeIpv6DownstreamRule(Ipv6DownstreamRule rule) {
// TODO: Perhaps remove this protection check.
- // See the related comment in #addIpv6ForwardingRule.
+ // See the related comment in #addIpv6DownstreamRule.
if (!mUsingBpfOffload) return;
- mBpfCoordinator.tetherOffloadRuleRemove(this, rule);
+ mBpfCoordinator.removeIpv6DownstreamRule(this, rule);
}
private void clearIpv6ForwardingRules() {
@@ -915,26 +916,20 @@
private void updateIpv6ForwardingRule(int newIfindex) {
// TODO: Perhaps remove this protection check.
- // See the related comment in #addIpv6ForwardingRule.
+ // See the related comment in #addIpv6DownstreamRule.
if (!mUsingBpfOffload) return;
mBpfCoordinator.tetherOffloadRuleUpdate(this, newIfindex);
}
- private boolean isIpv6VcnNetworkInterface() {
- if (mLastIPv6LinkProperties == null) return false;
-
- return isVcnInterface(mLastIPv6LinkProperties.getInterfaceName());
- }
-
// Handles all updates to IPv6 forwarding rules. These can currently change only if the upstream
// changes or if a neighbor event is received.
private void updateIpv6ForwardingRules(int prevUpstreamIfindex, int upstreamIfindex,
- NeighborEvent e) {
- // If no longer have an upstream or it is virtual network, clear forwarding rules and do
+ boolean upstreamSupportsBpf, NeighborEvent e) {
+ // If no longer have an upstream or upstream not supports BPF, clear forwarding rules and do
// nothing else.
// TODO: Rather than always clear rules, ensure whether ipv6 ever enable first.
- if (upstreamIfindex == 0 || isIpv6VcnNetworkInterface()) {
+ if (upstreamIfindex == 0 || !upstreamSupportsBpf) {
clearIpv6ForwardingRules();
return;
}
@@ -954,22 +949,22 @@
}
// When deleting rules, we still need to pass a non-null MAC, even though it's ignored.
- // Do this here instead of in the Ipv6ForwardingRule constructor to ensure that we never
- // add rules with a null MAC, only delete them.
+ // Do this here instead of in the Ipv6DownstreamRule constructor to ensure that we
+ // never add rules with a null MAC, only delete them.
MacAddress dstMac = e.isValid() ? e.macAddr : NULL_MAC_ADDRESS;
- Ipv6ForwardingRule rule = new Ipv6ForwardingRule(upstreamIfindex,
- mInterfaceParams.index, (Inet6Address) e.ip, mInterfaceParams.macAddr, dstMac);
+ Ipv6DownstreamRule rule = new Ipv6DownstreamRule(upstreamIfindex, mInterfaceParams.index,
+ (Inet6Address) e.ip, mInterfaceParams.macAddr, dstMac);
if (e.isValid()) {
- addIpv6ForwardingRule(rule);
+ addIpv6DownstreamRule(rule);
} else {
- removeIpv6ForwardingRule(rule);
+ removeIpv6DownstreamRule(rule);
}
}
// TODO: consider moving into BpfCoordinator.
private void updateClientInfoIpv4(NeighborEvent e) {
// TODO: Perhaps remove this protection check.
- // See the related comment in #addIpv6ForwardingRule.
+ // See the related comment in #addIpv6DownstreamRule.
if (!mUsingBpfOffload) return;
if (e == null) return;
@@ -995,7 +990,8 @@
if (mInterfaceParams != null
&& mInterfaceParams.index == e.ifindex
&& mInterfaceParams.hasMacAddress) {
- updateIpv6ForwardingRules(mLastIPv6UpstreamIfindex, mLastIPv6UpstreamIfindex, e);
+ updateIpv6ForwardingRules(mLastIPv6UpstreamIfindex, mLastIPv6UpstreamIfindex,
+ mUpstreamSupportsBpf, e);
updateClientInfoIpv4(e);
}
}
diff --git a/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java b/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
index f22ccbd..7311125 100644
--- a/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
+++ b/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
@@ -88,6 +88,8 @@
import java.net.NetworkInterface;
import java.net.SocketException;
import java.net.UnknownHostException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@@ -235,8 +237,8 @@
// rules function without a valid IPv6 downstream interface index even if it may have one
// before. IpServer would need to call getInterfaceParams() in the constructor instead of when
// startIpv6() is called, and make mInterfaceParams final.
- private final HashMap<IpServer, LinkedHashMap<Inet6Address, Ipv6ForwardingRule>>
- mIpv6ForwardingRules = new LinkedHashMap<>();
+ private final HashMap<IpServer, LinkedHashMap<Inet6Address, Ipv6DownstreamRule>>
+ mIpv6DownstreamRules = new LinkedHashMap<>();
// Map of downstream client maps. Each of these maps represents the IPv4 clients for a given
// downstream. Needed to build IPv4 forwarding rules when conntrack events are received.
@@ -499,8 +501,8 @@
/**
* Stop BPF tethering offload stats polling.
* The data limit cleanup and the tether stats maps cleanup are not implemented here.
- * These cleanups rely on all IpServers calling #tetherOffloadRuleRemove. After the
- * last rule is removed from the upstream, #tetherOffloadRuleRemove does the cleanup
+ * These cleanups rely on all IpServers calling #removeIpv6DownstreamRule. After the
+ * last rule is removed from the upstream, #removeIpv6DownstreamRule does the cleanup
* functionality.
* Note that this can be only called on handler thread.
*/
@@ -589,22 +591,22 @@
}
/**
- * Add forwarding rule. After adding the first rule on a given upstream, must add the data
+ * Add IPv6 downstream rule. After adding the first rule on a given upstream, must add the data
* limit on the given upstream.
* Note that this can be only called on handler thread.
*/
- public void tetherOffloadRuleAdd(
- @NonNull final IpServer ipServer, @NonNull final Ipv6ForwardingRule rule) {
+ public void addIpv6DownstreamRule(
+ @NonNull final IpServer ipServer, @NonNull final Ipv6DownstreamRule rule) {
if (!isUsingBpf()) return;
// TODO: Perhaps avoid to add a duplicate rule.
- if (!mBpfCoordinatorShim.tetherOffloadRuleAdd(rule)) return;
+ if (!mBpfCoordinatorShim.addIpv6DownstreamRule(rule)) return;
- if (!mIpv6ForwardingRules.containsKey(ipServer)) {
- mIpv6ForwardingRules.put(ipServer, new LinkedHashMap<Inet6Address,
- Ipv6ForwardingRule>());
+ if (!mIpv6DownstreamRules.containsKey(ipServer)) {
+ mIpv6DownstreamRules.put(ipServer, new LinkedHashMap<Inet6Address,
+ Ipv6DownstreamRule>());
}
- LinkedHashMap<Inet6Address, Ipv6ForwardingRule> rules = mIpv6ForwardingRules.get(ipServer);
+ LinkedHashMap<Inet6Address, Ipv6DownstreamRule> rules = mIpv6DownstreamRules.get(ipServer);
// Add upstream and downstream interface index to dev map.
maybeAddDevMap(rule.upstreamIfindex, rule.downstreamIfindex);
@@ -613,15 +615,13 @@
maybeSetLimit(rule.upstreamIfindex);
if (!isAnyRuleFromDownstreamToUpstream(rule.downstreamIfindex, rule.upstreamIfindex)) {
- final int downstream = rule.downstreamIfindex;
- final int upstream = rule.upstreamIfindex;
// TODO: support upstream forwarding on non-point-to-point interfaces.
// TODO: get the MTU from LinkProperties and update the rules when it changes.
- if (!mBpfCoordinatorShim.startUpstreamIpv6Forwarding(downstream, upstream,
- IPV6_ZERO_PREFIX64, rule.srcMac, NULL_MAC_ADDRESS, NULL_MAC_ADDRESS,
- NetworkStackConstants.ETHER_MTU)) {
- mLog.e("Failed to enable upstream IPv6 forwarding from "
- + getIfName(downstream) + " to " + getIfName(upstream));
+ Ipv6UpstreamRule upstreamRule = new Ipv6UpstreamRule(rule.upstreamIfindex,
+ rule.downstreamIfindex, IPV6_ZERO_PREFIX64, rule.srcMac, NULL_MAC_ADDRESS,
+ NULL_MAC_ADDRESS);
+ if (!mBpfCoordinatorShim.addIpv6UpstreamRule(upstreamRule)) {
+ mLog.e("Failed to add upstream IPv6 forwarding rule: " + upstreamRule);
}
}
@@ -631,17 +631,17 @@
}
/**
- * Remove forwarding rule. After removing the last rule on a given upstream, must clear
+ * Remove IPv6 downstream rule. After removing the last rule on a given upstream, must clear
* data limit, update the last tether stats and remove the tether stats in the BPF maps.
* Note that this can be only called on handler thread.
*/
- public void tetherOffloadRuleRemove(
- @NonNull final IpServer ipServer, @NonNull final Ipv6ForwardingRule rule) {
+ public void removeIpv6DownstreamRule(
+ @NonNull final IpServer ipServer, @NonNull final Ipv6DownstreamRule rule) {
if (!isUsingBpf()) return;
- if (!mBpfCoordinatorShim.tetherOffloadRuleRemove(rule)) return;
+ if (!mBpfCoordinatorShim.removeIpv6DownstreamRule(rule)) return;
- LinkedHashMap<Inet6Address, Ipv6ForwardingRule> rules = mIpv6ForwardingRules.get(ipServer);
+ LinkedHashMap<Inet6Address, Ipv6DownstreamRule> rules = mIpv6DownstreamRules.get(ipServer);
if (rules == null) return;
// Must remove rules before calling #isAnyRuleOnUpstream because it needs to check if
@@ -652,17 +652,16 @@
// Remove the downstream entry if it has no more rule.
if (rules.isEmpty()) {
- mIpv6ForwardingRules.remove(ipServer);
+ mIpv6DownstreamRules.remove(ipServer);
}
// If no more rules between this upstream and downstream, stop upstream forwarding.
if (!isAnyRuleFromDownstreamToUpstream(rule.downstreamIfindex, rule.upstreamIfindex)) {
- final int downstream = rule.downstreamIfindex;
- final int upstream = rule.upstreamIfindex;
- if (!mBpfCoordinatorShim.stopUpstreamIpv6Forwarding(downstream, upstream,
- IPV6_ZERO_PREFIX64, rule.srcMac)) {
- mLog.e("Failed to disable upstream IPv6 forwarding from "
- + getIfName(downstream) + " to " + getIfName(upstream));
+ Ipv6UpstreamRule upstreamRule = new Ipv6UpstreamRule(rule.upstreamIfindex,
+ rule.downstreamIfindex, IPV6_ZERO_PREFIX64, rule.srcMac, NULL_MAC_ADDRESS,
+ NULL_MAC_ADDRESS);
+ if (!mBpfCoordinatorShim.removeIpv6UpstreamRule(upstreamRule)) {
+ mLog.e("Failed to remove upstream IPv6 forwarding rule: " + upstreamRule);
}
}
@@ -678,13 +677,13 @@
public void tetherOffloadRuleClear(@NonNull final IpServer ipServer) {
if (!isUsingBpf()) return;
- final LinkedHashMap<Inet6Address, Ipv6ForwardingRule> rules = mIpv6ForwardingRules.get(
- ipServer);
+ final LinkedHashMap<Inet6Address, Ipv6DownstreamRule> rules =
+ mIpv6DownstreamRules.get(ipServer);
if (rules == null) return;
// Need to build a rule list because the rule map may be changed in the iteration.
- for (final Ipv6ForwardingRule rule : new ArrayList<Ipv6ForwardingRule>(rules.values())) {
- tetherOffloadRuleRemove(ipServer, rule);
+ for (final Ipv6DownstreamRule rule : new ArrayList<Ipv6DownstreamRule>(rules.values())) {
+ removeIpv6DownstreamRule(ipServer, rule);
}
}
@@ -695,28 +694,28 @@
public void tetherOffloadRuleUpdate(@NonNull final IpServer ipServer, int newUpstreamIfindex) {
if (!isUsingBpf()) return;
- final LinkedHashMap<Inet6Address, Ipv6ForwardingRule> rules = mIpv6ForwardingRules.get(
- ipServer);
+ final LinkedHashMap<Inet6Address, Ipv6DownstreamRule> rules =
+ mIpv6DownstreamRules.get(ipServer);
if (rules == null) return;
// Need to build a rule list because the rule map may be changed in the iteration.
// First remove all the old rules, then add all the new rules. This is because the upstream
- // forwarding code in tetherOffloadRuleAdd cannot support rules on two upstreams at the
+ // forwarding code in addIpv6DownstreamRule cannot support rules on two upstreams at the
// same time. Deleting the rules first ensures that upstream forwarding is disabled on the
// old upstream when the last rule is removed from it, and re-enabled on the new upstream
// when the first rule is added to it.
// TODO: Once the IPv6 client processing code has moved from IpServer to BpfCoordinator, do
// something smarter.
- final ArrayList<Ipv6ForwardingRule> rulesCopy = new ArrayList<>(rules.values());
- for (final Ipv6ForwardingRule rule : rulesCopy) {
+ final ArrayList<Ipv6DownstreamRule> rulesCopy = new ArrayList<>(rules.values());
+ for (final Ipv6DownstreamRule rule : rulesCopy) {
// Remove the old rule before adding the new one because the map uses the same key for
// both rules. Reversing the processing order causes that the new rule is removed as
// unexpected.
// TODO: Add new rule first to reduce the latency which has no rule.
- tetherOffloadRuleRemove(ipServer, rule);
+ removeIpv6DownstreamRule(ipServer, rule);
}
- for (final Ipv6ForwardingRule rule : rulesCopy) {
- tetherOffloadRuleAdd(ipServer, rule.onNewUpstream(newUpstreamIfindex));
+ for (final Ipv6DownstreamRule rule : rulesCopy) {
+ addIpv6DownstreamRule(ipServer, rule.onNewUpstream(newUpstreamIfindex));
}
}
@@ -1142,14 +1141,14 @@
private void dumpIpv6ForwardingRulesByDownstream(@NonNull IndentingPrintWriter pw) {
pw.println("IPv6 Forwarding rules by downstream interface:");
pw.increaseIndent();
- if (mIpv6ForwardingRules.size() == 0) {
- pw.println("No IPv6 rules");
+ if (mIpv6DownstreamRules.size() == 0) {
+ pw.println("No downstream IPv6 rules");
pw.decreaseIndent();
return;
}
- for (Map.Entry<IpServer, LinkedHashMap<Inet6Address, Ipv6ForwardingRule>> entry :
- mIpv6ForwardingRules.entrySet()) {
+ for (Map.Entry<IpServer, LinkedHashMap<Inet6Address, Ipv6DownstreamRule>> entry :
+ mIpv6DownstreamRules.entrySet()) {
IpServer ipServer = entry.getKey();
// The rule downstream interface index is paired with the interface name from
// IpServer#interfaceName. See #startIPv6, #updateIpv6ForwardingRules in IpServer.
@@ -1158,8 +1157,8 @@
+ "[srcmac] [dstmac]");
pw.increaseIndent();
- LinkedHashMap<Inet6Address, Ipv6ForwardingRule> rules = entry.getValue();
- for (Ipv6ForwardingRule rule : rules.values()) {
+ LinkedHashMap<Inet6Address, Ipv6DownstreamRule> rules = entry.getValue();
+ for (Ipv6DownstreamRule rule : rules.values()) {
final int upstreamIfindex = rule.upstreamIfindex;
pw.println(String.format("%d(%s) %d(%s) %s [%s] [%s]", upstreamIfindex,
getIfName(upstreamIfindex), rule.downstreamIfindex,
@@ -1406,13 +1405,13 @@
pw.decreaseIndent();
}
- /** IPv6 forwarding rule class. */
- public static class Ipv6ForwardingRule {
- // The upstream6 and downstream6 rules are built as the following tables. Only raw ip
- // upstream interface is supported.
+ /** IPv6 upstream forwarding rule class. */
+ public static class Ipv6UpstreamRule {
+ // The upstream6 rules are built as the following tables. Only raw ip upstream interface is
+ // supported.
// TODO: support ether ip upstream interface.
//
- // NAT network topology:
+ // Tethering network topology:
//
// public network (rawip) private network
// | UE |
@@ -1422,15 +1421,15 @@
//
// upstream6 key and value:
//
- // +------+-------------+
- // | TetherUpstream6Key |
- // +------+------+------+
- // |field |iif |dstMac|
- // | | | |
- // +------+------+------+
- // |value |downst|downst|
- // | |ream |ream |
- // +------+------+------+
+ // +------+-------------------+
+ // | TetherUpstream6Key |
+ // +------+------+------+-----+
+ // |field |iif |dstMac|src64|
+ // | | | | |
+ // +------+------+------+-----+
+ // |value |downst|downst|upstr|
+ // | |ream |ream |eam |
+ // +------+------+------+-----+
//
// +------+----------------------------------+
// | |Tether6Value |
@@ -1442,6 +1441,92 @@
// | |am | | |IP | |
// +------+------+------+------+------+------+
//
+ public final int upstreamIfindex;
+ public final int downstreamIfindex;
+ @NonNull
+ public final IpPrefix sourcePrefix;
+ @NonNull
+ public final MacAddress inDstMac;
+ @NonNull
+ public final MacAddress outSrcMac;
+ @NonNull
+ public final MacAddress outDstMac;
+
+ public Ipv6UpstreamRule(int upstreamIfindex, int downstreamIfindex,
+ @NonNull IpPrefix sourcePrefix, @NonNull MacAddress inDstMac,
+ @NonNull MacAddress outSrcMac, @NonNull MacAddress outDstMac) {
+ this.upstreamIfindex = upstreamIfindex;
+ this.downstreamIfindex = downstreamIfindex;
+ this.sourcePrefix = sourcePrefix;
+ this.inDstMac = inDstMac;
+ this.outSrcMac = outSrcMac;
+ this.outDstMac = outDstMac;
+ }
+
+ /**
+ * Return a TetherUpstream6Key object built from the rule.
+ */
+ @NonNull
+ public TetherUpstream6Key makeTetherUpstream6Key() {
+ byte[] prefixBytes = Arrays.copyOf(sourcePrefix.getRawAddress(), 8);
+ long prefix64 = ByteBuffer.wrap(prefixBytes).order(ByteOrder.BIG_ENDIAN).getLong();
+ return new TetherUpstream6Key(downstreamIfindex, inDstMac, prefix64);
+ }
+
+ /**
+ * Return a Tether6Value object built from the rule.
+ */
+ @NonNull
+ public Tether6Value makeTether6Value() {
+ return new Tether6Value(upstreamIfindex, outDstMac, outSrcMac, ETH_P_IPV6,
+ NetworkStackConstants.ETHER_MTU);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof Ipv6UpstreamRule)) return false;
+ Ipv6UpstreamRule that = (Ipv6UpstreamRule) o;
+ return this.upstreamIfindex == that.upstreamIfindex
+ && this.downstreamIfindex == that.downstreamIfindex
+ && Objects.equals(this.sourcePrefix, that.sourcePrefix)
+ && Objects.equals(this.inDstMac, that.inDstMac)
+ && Objects.equals(this.outSrcMac, that.outSrcMac)
+ && Objects.equals(this.outDstMac, that.outDstMac);
+ }
+
+ @Override
+ public int hashCode() {
+ // TODO: if this is ever used in production code, don't pass ifindices
+ // to Objects.hash() to avoid autoboxing overhead.
+ return Objects.hash(upstreamIfindex, downstreamIfindex, sourcePrefix, inDstMac,
+ outSrcMac, outDstMac);
+ }
+
+ @Override
+ public String toString() {
+ return "upstreamIfindex: " + upstreamIfindex
+ + ", downstreamIfindex: " + downstreamIfindex
+ + ", sourcePrefix: " + sourcePrefix
+ + ", inDstMac: " + inDstMac
+ + ", outSrcMac: " + outSrcMac
+ + ", outDstMac: " + outDstMac;
+ }
+ }
+
+ /** IPv6 downstream forwarding rule class. */
+ public static class Ipv6DownstreamRule {
+ // The downstream6 rules are built as the following tables. Only raw ip upstream interface
+ // is supported.
+ // TODO: support ether ip upstream interface.
+ //
+ // Tethering network topology:
+ //
+ // public network (rawip) private network
+ // | UE |
+ // +------------+ V +------------+------------+ V +------------+
+ // | Sever +---------+ Upstream | Downstream +---------+ Client |
+ // +------------+ +------------+------------+ +------------+
+ //
// downstream6 key and value:
//
// +------+--------------------+
@@ -1475,11 +1560,11 @@
@NonNull
public final MacAddress dstMac;
- public Ipv6ForwardingRule(int upstreamIfindex, int downstreamIfIndex,
+ public Ipv6DownstreamRule(int upstreamIfindex, int downstreamIfindex,
@NonNull Inet6Address address, @NonNull MacAddress srcMac,
@NonNull MacAddress dstMac) {
this.upstreamIfindex = upstreamIfindex;
- this.downstreamIfindex = downstreamIfIndex;
+ this.downstreamIfindex = downstreamIfindex;
this.address = address;
this.srcMac = srcMac;
this.dstMac = dstMac;
@@ -1487,8 +1572,8 @@
/** Return a new rule object which updates with new upstream index. */
@NonNull
- public Ipv6ForwardingRule onNewUpstream(int newUpstreamIfindex) {
- return new Ipv6ForwardingRule(newUpstreamIfindex, downstreamIfindex, address, srcMac,
+ public Ipv6DownstreamRule onNewUpstream(int newUpstreamIfindex) {
+ return new Ipv6DownstreamRule(newUpstreamIfindex, downstreamIfindex, address, srcMac,
dstMac);
}
@@ -1528,8 +1613,8 @@
@Override
public boolean equals(Object o) {
- if (!(o instanceof Ipv6ForwardingRule)) return false;
- Ipv6ForwardingRule that = (Ipv6ForwardingRule) o;
+ if (!(o instanceof Ipv6DownstreamRule)) return false;
+ Ipv6DownstreamRule that = (Ipv6DownstreamRule) o;
return this.upstreamIfindex == that.upstreamIfindex
&& this.downstreamIfindex == that.downstreamIfindex
&& Objects.equals(this.address, that.address)
@@ -1870,9 +1955,9 @@
}
private int getInterfaceIndexFromRules(@NonNull String ifName) {
- for (LinkedHashMap<Inet6Address, Ipv6ForwardingRule> rules : mIpv6ForwardingRules
- .values()) {
- for (Ipv6ForwardingRule rule : rules.values()) {
+ for (LinkedHashMap<Inet6Address, Ipv6DownstreamRule> rules :
+ mIpv6DownstreamRules.values()) {
+ for (Ipv6DownstreamRule rule : rules.values()) {
final int upstreamIfindex = rule.upstreamIfindex;
if (TextUtils.equals(ifName, mInterfaceNames.get(upstreamIfindex))) {
return upstreamIfindex;
@@ -1963,9 +2048,9 @@
// TODO: Rename to isAnyIpv6RuleOnUpstream and define an isAnyRuleOnUpstream method that called
// both isAnyIpv6RuleOnUpstream and mBpfCoordinatorShim.isAnyIpv4RuleOnUpstream.
private boolean isAnyRuleOnUpstream(int upstreamIfindex) {
- for (LinkedHashMap<Inet6Address, Ipv6ForwardingRule> rules : mIpv6ForwardingRules
- .values()) {
- for (Ipv6ForwardingRule rule : rules.values()) {
+ for (LinkedHashMap<Inet6Address, Ipv6DownstreamRule> rules :
+ mIpv6DownstreamRules.values()) {
+ for (Ipv6DownstreamRule rule : rules.values()) {
if (upstreamIfindex == rule.upstreamIfindex) return true;
}
}
@@ -1973,9 +2058,9 @@
}
private boolean isAnyRuleFromDownstreamToUpstream(int downstreamIfindex, int upstreamIfindex) {
- for (LinkedHashMap<Inet6Address, Ipv6ForwardingRule> rules : mIpv6ForwardingRules
- .values()) {
- for (Ipv6ForwardingRule rule : rules.values()) {
+ for (LinkedHashMap<Inet6Address, Ipv6DownstreamRule> rules :
+ mIpv6DownstreamRules.values()) {
+ for (Ipv6DownstreamRule rule : rules.values()) {
if (downstreamIfindex == rule.downstreamIfindex
&& upstreamIfindex == rule.upstreamIfindex) {
return true;
@@ -2226,13 +2311,13 @@
CONNTRACK_TIMEOUT_UPDATE_INTERVAL_MS);
}
- // Return forwarding rule map. This is used for testing only.
+ // Return IPv6 downstream forwarding rule map. This is used for testing only.
// Note that this can be only called on handler thread.
@NonNull
@VisibleForTesting
- final HashMap<IpServer, LinkedHashMap<Inet6Address, Ipv6ForwardingRule>>
- getForwardingRulesForTesting() {
- return mIpv6ForwardingRules;
+ final HashMap<IpServer, LinkedHashMap<Inet6Address, Ipv6DownstreamRule>>
+ getIpv6DownstreamRulesForTesting() {
+ return mIpv6DownstreamRules;
}
// Return upstream interface name map. This is used for testing only.
diff --git a/Tethering/tests/unit/src/android/net/ip/IpServerTest.java b/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
index 464778f..19d70c6 100644
--- a/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
+++ b/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
@@ -114,7 +114,7 @@
import com.android.net.module.util.ip.IpNeighborMonitor.NeighborEventConsumer;
import com.android.networkstack.tethering.BpfCoordinator;
import com.android.networkstack.tethering.BpfCoordinator.ClientInfo;
-import com.android.networkstack.tethering.BpfCoordinator.Ipv6ForwardingRule;
+import com.android.networkstack.tethering.BpfCoordinator.Ipv6DownstreamRule;
import com.android.networkstack.tethering.PrivateAddressCoordinator;
import com.android.networkstack.tethering.Tether6Value;
import com.android.networkstack.tethering.TetherDevKey;
@@ -899,9 +899,9 @@
}
@NonNull
- private static Ipv6ForwardingRule makeForwardingRule(
- int upstreamIfindex, @NonNull InetAddress dst, @NonNull MacAddress dstMac) {
- return new Ipv6ForwardingRule(upstreamIfindex, TEST_IFACE_PARAMS.index,
+ private static Ipv6DownstreamRule makeDownstreamRule(int upstreamIfindex,
+ @NonNull InetAddress dst, @NonNull MacAddress dstMac) {
+ return new Ipv6DownstreamRule(upstreamIfindex, TEST_IFACE_PARAMS.index,
(Inet6Address) dst, TEST_IFACE_PARAMS.macAddr, dstMac);
}
@@ -1064,16 +1064,16 @@
// Events on this interface are received and sent to netd.
recvNewNeigh(myIfindex, neighA, NUD_REACHABLE, macA);
- verify(mBpfCoordinator).tetherOffloadRuleAdd(
- mIpServer, makeForwardingRule(UPSTREAM_IFINDEX, neighA, macA));
+ verify(mBpfCoordinator).addIpv6DownstreamRule(
+ mIpServer, makeDownstreamRule(UPSTREAM_IFINDEX, neighA, macA));
verifyTetherOffloadRuleAdd(null,
UPSTREAM_IFINDEX, UPSTREAM_IFACE_PARAMS.macAddr, neighA, macA);
verifyStartUpstreamIpv6Forwarding(null, UPSTREAM_IFINDEX);
resetNetdBpfMapAndCoordinator();
recvNewNeigh(myIfindex, neighB, NUD_REACHABLE, macB);
- verify(mBpfCoordinator).tetherOffloadRuleAdd(
- mIpServer, makeForwardingRule(UPSTREAM_IFINDEX, neighB, macB));
+ verify(mBpfCoordinator).addIpv6DownstreamRule(
+ mIpServer, makeDownstreamRule(UPSTREAM_IFINDEX, neighB, macB));
verifyTetherOffloadRuleAdd(null,
UPSTREAM_IFINDEX, UPSTREAM_IFACE_PARAMS.macAddr, neighB, macB);
verifyNoUpstreamIpv6ForwardingChange(null);
@@ -1088,8 +1088,8 @@
// A neighbor that is no longer valid causes the rule to be removed.
// NUD_FAILED events do not have a MAC address.
recvNewNeigh(myIfindex, neighA, NUD_FAILED, null);
- verify(mBpfCoordinator).tetherOffloadRuleRemove(
- mIpServer, makeForwardingRule(UPSTREAM_IFINDEX, neighA, macNull));
+ verify(mBpfCoordinator).removeIpv6DownstreamRule(
+ mIpServer, makeDownstreamRule(UPSTREAM_IFINDEX, neighA, macNull));
verifyTetherOffloadRuleRemove(null,
UPSTREAM_IFINDEX, UPSTREAM_IFACE_PARAMS.macAddr, neighA, macNull);
verifyNoUpstreamIpv6ForwardingChange(null);
@@ -1097,8 +1097,8 @@
// A neighbor that is deleted causes the rule to be removed.
recvDelNeigh(myIfindex, neighB, NUD_STALE, macB);
- verify(mBpfCoordinator).tetherOffloadRuleRemove(
- mIpServer, makeForwardingRule(UPSTREAM_IFINDEX, neighB, macNull));
+ verify(mBpfCoordinator).removeIpv6DownstreamRule(
+ mIpServer, makeDownstreamRule(UPSTREAM_IFINDEX, neighB, macNull));
verifyTetherOffloadRuleRemove(null,
UPSTREAM_IFINDEX, UPSTREAM_IFACE_PARAMS.macAddr, neighB, macNull);
verifyStopUpstreamIpv6Forwarding(null);
@@ -1155,13 +1155,13 @@
lp.setInterfaceName(UPSTREAM_IFACE);
dispatchTetherConnectionChanged(UPSTREAM_IFACE, lp, -1);
recvNewNeigh(myIfindex, neighB, NUD_REACHABLE, macB);
- verify(mBpfCoordinator).tetherOffloadRuleAdd(
- mIpServer, makeForwardingRule(UPSTREAM_IFINDEX, neighB, macB));
+ verify(mBpfCoordinator).addIpv6DownstreamRule(
+ mIpServer, makeDownstreamRule(UPSTREAM_IFINDEX, neighB, macB));
verifyTetherOffloadRuleAdd(null,
UPSTREAM_IFINDEX, UPSTREAM_IFACE_PARAMS.macAddr, neighB, macB);
verifyStartUpstreamIpv6Forwarding(null, UPSTREAM_IFINDEX);
- verify(mBpfCoordinator, never()).tetherOffloadRuleAdd(
- mIpServer, makeForwardingRule(UPSTREAM_IFINDEX, neighA, macA));
+ verify(mBpfCoordinator, never()).addIpv6DownstreamRule(
+ mIpServer, makeDownstreamRule(UPSTREAM_IFINDEX, neighA, macA));
verifyNeverTetherOffloadRuleAdd(
UPSTREAM_IFINDEX, UPSTREAM_IFACE_PARAMS.macAddr, neighA, macA);
@@ -1178,13 +1178,13 @@
dispatchTetherConnectionChanged(UPSTREAM_IFACE, lp, -1);
recvNewNeigh(myIfindex, neighA, NUD_REACHABLE, macA);
recvNewNeigh(myIfindex, neighB, NUD_REACHABLE, macB);
- verify(mBpfCoordinator).tetherOffloadRuleAdd(
- mIpServer, makeForwardingRule(UPSTREAM_IFINDEX, neighA, macA));
+ verify(mBpfCoordinator).addIpv6DownstreamRule(
+ mIpServer, makeDownstreamRule(UPSTREAM_IFINDEX, neighA, macA));
verifyTetherOffloadRuleAdd(null,
UPSTREAM_IFINDEX, UPSTREAM_IFACE_PARAMS.macAddr, neighA, macA);
verifyStartUpstreamIpv6Forwarding(null, UPSTREAM_IFINDEX);
- verify(mBpfCoordinator).tetherOffloadRuleAdd(
- mIpServer, makeForwardingRule(UPSTREAM_IFINDEX, neighB, macB));
+ verify(mBpfCoordinator).addIpv6DownstreamRule(
+ mIpServer, makeDownstreamRule(UPSTREAM_IFINDEX, neighB, macB));
verifyTetherOffloadRuleAdd(null,
UPSTREAM_IFINDEX, UPSTREAM_IFACE_PARAMS.macAddr, neighB, macB);
resetNetdBpfMapAndCoordinator();
@@ -1222,16 +1222,16 @@
resetNetdBpfMapAndCoordinator();
recvNewNeigh(myIfindex, neigh, NUD_REACHABLE, macA);
- verify(mBpfCoordinator).tetherOffloadRuleAdd(
- mIpServer, makeForwardingRule(UPSTREAM_IFINDEX, neigh, macA));
+ verify(mBpfCoordinator).addIpv6DownstreamRule(
+ mIpServer, makeDownstreamRule(UPSTREAM_IFINDEX, neigh, macA));
verifyTetherOffloadRuleAdd(null,
UPSTREAM_IFINDEX, UPSTREAM_IFACE_PARAMS.macAddr, neigh, macA);
verifyStartUpstreamIpv6Forwarding(null, UPSTREAM_IFINDEX);
resetNetdBpfMapAndCoordinator();
recvDelNeigh(myIfindex, neigh, NUD_STALE, macA);
- verify(mBpfCoordinator).tetherOffloadRuleRemove(
- mIpServer, makeForwardingRule(UPSTREAM_IFINDEX, neigh, macNull));
+ verify(mBpfCoordinator).removeIpv6DownstreamRule(
+ mIpServer, makeDownstreamRule(UPSTREAM_IFINDEX, neigh, macNull));
verifyTetherOffloadRuleRemove(null,
UPSTREAM_IFINDEX, UPSTREAM_IFACE_PARAMS.macAddr, neigh, macNull);
verifyStopUpstreamIpv6Forwarding(null);
@@ -1244,13 +1244,13 @@
resetNetdBpfMapAndCoordinator();
recvNewNeigh(myIfindex, neigh, NUD_REACHABLE, macA);
- verify(mBpfCoordinator, never()).tetherOffloadRuleAdd(any(), any());
+ verify(mBpfCoordinator, never()).addIpv6DownstreamRule(any(), any());
verifyNeverTetherOffloadRuleAdd();
verifyNoUpstreamIpv6ForwardingChange(null);
resetNetdBpfMapAndCoordinator();
recvDelNeigh(myIfindex, neigh, NUD_STALE, macA);
- verify(mBpfCoordinator, never()).tetherOffloadRuleRemove(any(), any());
+ verify(mBpfCoordinator, never()).removeIpv6DownstreamRule(any(), any());
verifyNeverTetherOffloadRuleRemove();
verifyNoUpstreamIpv6ForwardingChange(null);
resetNetdBpfMapAndCoordinator();
@@ -1534,8 +1534,8 @@
final InetAddress neigh = InetAddresses.parseNumericAddress("2001:db8::1");
final MacAddress mac = MacAddress.fromString("00:00:00:00:00:0a");
recvNewNeigh(myIfindex, neigh, NUD_REACHABLE, mac);
- verify(mBpfCoordinator, never()).tetherOffloadRuleAdd(
- mIpServer, makeForwardingRule(IPSEC_IFINDEX, neigh, mac));
+ verify(mBpfCoordinator, never()).addIpv6DownstreamRule(
+ mIpServer, makeDownstreamRule(IPSEC_IFINDEX, neigh, mac));
}
// TODO: move to BpfCoordinatorTest once IpNeighborMonitor is migrated to BpfCoordinator.
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java
index 8bc4c18..04eb430 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java
@@ -118,7 +118,8 @@
import com.android.net.module.util.netlink.NetlinkUtils;
import com.android.networkstack.tethering.BpfCoordinator.BpfConntrackEventConsumer;
import com.android.networkstack.tethering.BpfCoordinator.ClientInfo;
-import com.android.networkstack.tethering.BpfCoordinator.Ipv6ForwardingRule;
+import com.android.networkstack.tethering.BpfCoordinator.Ipv6DownstreamRule;
+import com.android.networkstack.tethering.BpfCoordinator.Ipv6UpstreamRule;
import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRule.IgnoreAfter;
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
@@ -192,6 +193,7 @@
private static final Inet4Address XLAT_LOCAL_IPV4ADDR =
(Inet4Address) InetAddresses.parseNumericAddress("192.0.0.46");
private static final IpPrefix NAT64_IP_PREFIX = new IpPrefix("64:ff9b::/96");
+ private static final IpPrefix IPV6_ZERO_PREFIX = new IpPrefix("::/64");
// Generally, public port and private port are the same in the NAT conntrack message.
// TODO: consider using different private port and public port for testing.
@@ -669,8 +671,8 @@
}
}
- private void verifyTetherOffloadRuleAdd(@Nullable InOrder inOrder,
- @NonNull Ipv6ForwardingRule rule) throws Exception {
+ private void verifyAddDownstreamRule(@Nullable InOrder inOrder,
+ @NonNull Ipv6DownstreamRule rule) throws Exception {
if (mDeps.isAtLeastS()) {
verifyWithOrder(inOrder, mBpfDownstream6Map).updateEntry(
rule.makeTetherDownstream6Key(), rule.makeTether6Value());
@@ -679,7 +681,7 @@
}
}
- private void verifyNeverTetherOffloadRuleAdd() throws Exception {
+ private void verifyNeverAddDownstreamRule() throws Exception {
if (mDeps.isAtLeastS()) {
verify(mBpfDownstream6Map, never()).updateEntry(any(), any());
} else {
@@ -687,8 +689,8 @@
}
}
- private void verifyTetherOffloadRuleRemove(@Nullable InOrder inOrder,
- @NonNull final Ipv6ForwardingRule rule) throws Exception {
+ private void verifyRemoveDownstreamRule(@Nullable InOrder inOrder,
+ @NonNull final Ipv6DownstreamRule rule) throws Exception {
if (mDeps.isAtLeastS()) {
verifyWithOrder(inOrder, mBpfDownstream6Map).deleteEntry(
rule.makeTetherDownstream6Key());
@@ -697,7 +699,7 @@
}
}
- private void verifyNeverTetherOffloadRuleRemove() throws Exception {
+ private void verifyNeverRemoveDownstreamRule() throws Exception {
if (mDeps.isAtLeastS()) {
verify(mBpfDownstream6Map, never()).deleteEntry(any());
} else {
@@ -768,17 +770,17 @@
// The #verifyTetherOffloadGetAndClearStats can't distinguish who has ever called
// mBpfStatsMap#getValue and get a wrong calling count which counts all.
final InOrder inOrder = inOrder(mNetd, mBpfDownstream6Map, mBpfLimitMap, mBpfStatsMap);
- final Ipv6ForwardingRule rule = buildTestForwardingRule(mobileIfIndex, NEIGH_A, MAC_A);
- coordinator.tetherOffloadRuleAdd(mIpServer, rule);
- verifyTetherOffloadRuleAdd(inOrder, rule);
+ final Ipv6DownstreamRule rule = buildTestDownstreamRule(mobileIfIndex, NEIGH_A, MAC_A);
+ coordinator.addIpv6DownstreamRule(mIpServer, rule);
+ verifyAddDownstreamRule(inOrder, rule);
verifyTetherOffloadSetInterfaceQuota(inOrder, mobileIfIndex, QUOTA_UNLIMITED,
true /* isInit */);
// Removing the last rule on current upstream immediately sends the cleanup stuff to netd.
updateStatsEntryForTetherOffloadGetAndClearStats(
buildTestTetherStatsParcel(mobileIfIndex, 0, 0, 0, 0));
- coordinator.tetherOffloadRuleRemove(mIpServer, rule);
- verifyTetherOffloadRuleRemove(inOrder, rule);
+ coordinator.removeIpv6DownstreamRule(mIpServer, rule);
+ verifyRemoveDownstreamRule(inOrder, rule);
verifyTetherOffloadGetAndClearStats(inOrder, mobileIfIndex);
}
@@ -947,7 +949,7 @@
public final MacAddress srcMac;
public final MacAddress dstMac;
- TetherOffloadRuleParcelMatcher(@NonNull Ipv6ForwardingRule rule) {
+ TetherOffloadRuleParcelMatcher(@NonNull Ipv6DownstreamRule rule) {
upstreamIfindex = rule.upstreamIfindex;
downstreamIfindex = rule.downstreamIfindex;
address = rule.address;
@@ -971,21 +973,28 @@
}
@NonNull
- private TetherOffloadRuleParcel matches(@NonNull Ipv6ForwardingRule rule) {
+ private TetherOffloadRuleParcel matches(@NonNull Ipv6DownstreamRule rule) {
return argThat(new TetherOffloadRuleParcelMatcher(rule));
}
@NonNull
- private static Ipv6ForwardingRule buildTestForwardingRule(
+ private static Ipv6UpstreamRule buildTestUpstreamRule(int upstreamIfindex) {
+ return new Ipv6UpstreamRule(upstreamIfindex, DOWNSTREAM_IFINDEX,
+ IPV6_ZERO_PREFIX, DOWNSTREAM_MAC, MacAddress.ALL_ZEROS_ADDRESS,
+ MacAddress.ALL_ZEROS_ADDRESS);
+ }
+
+ @NonNull
+ private static Ipv6DownstreamRule buildTestDownstreamRule(
int upstreamIfindex, @NonNull InetAddress address, @NonNull MacAddress dstMac) {
- return new Ipv6ForwardingRule(upstreamIfindex, DOWNSTREAM_IFINDEX, (Inet6Address) address,
- DOWNSTREAM_MAC, dstMac);
+ return new Ipv6DownstreamRule(upstreamIfindex, DOWNSTREAM_IFINDEX,
+ (Inet6Address) address, DOWNSTREAM_MAC, dstMac);
}
@Test
public void testRuleMakeTetherDownstream6Key() throws Exception {
final int mobileIfIndex = 100;
- final Ipv6ForwardingRule rule = buildTestForwardingRule(mobileIfIndex, NEIGH_A, MAC_A);
+ final Ipv6DownstreamRule rule = buildTestDownstreamRule(mobileIfIndex, NEIGH_A, MAC_A);
final TetherDownstream6Key key = rule.makeTetherDownstream6Key();
assertEquals(key.iif, mobileIfIndex);
@@ -998,7 +1007,7 @@
@Test
public void testRuleMakeTether6Value() throws Exception {
final int mobileIfIndex = 100;
- final Ipv6ForwardingRule rule = buildTestForwardingRule(mobileIfIndex, NEIGH_A, MAC_A);
+ final Ipv6DownstreamRule rule = buildTestDownstreamRule(mobileIfIndex, NEIGH_A, MAC_A);
final Tether6Value value = rule.makeTether6Value();
assertEquals(value.oif, DOWNSTREAM_IFINDEX);
@@ -1023,10 +1032,10 @@
// [1] Default limit.
// Set the unlimited quota as default if the service has never applied a data limit for a
// given upstream. Note that the data limit only be applied on an upstream which has rules.
- final Ipv6ForwardingRule rule = buildTestForwardingRule(mobileIfIndex, NEIGH_A, MAC_A);
+ final Ipv6DownstreamRule rule = buildTestDownstreamRule(mobileIfIndex, NEIGH_A, MAC_A);
final InOrder inOrder = inOrder(mNetd, mBpfDownstream6Map, mBpfLimitMap, mBpfStatsMap);
- coordinator.tetherOffloadRuleAdd(mIpServer, rule);
- verifyTetherOffloadRuleAdd(inOrder, rule);
+ coordinator.addIpv6DownstreamRule(mIpServer, rule);
+ verifyAddDownstreamRule(inOrder, rule);
verifyTetherOffloadSetInterfaceQuota(inOrder, mobileIfIndex, QUOTA_UNLIMITED,
true /* isInit */);
inOrder.verifyNoMoreInteractions();
@@ -1073,28 +1082,28 @@
verifyNeverTetherOffloadSetInterfaceQuota(inOrder);
// Adding the first rule on current upstream immediately sends the quota to netd.
- final Ipv6ForwardingRule ruleA = buildTestForwardingRule(mobileIfIndex, NEIGH_A, MAC_A);
- coordinator.tetherOffloadRuleAdd(mIpServer, ruleA);
- verifyTetherOffloadRuleAdd(inOrder, ruleA);
+ final Ipv6DownstreamRule ruleA = buildTestDownstreamRule(mobileIfIndex, NEIGH_A, MAC_A);
+ coordinator.addIpv6DownstreamRule(mIpServer, ruleA);
+ verifyAddDownstreamRule(inOrder, ruleA);
verifyTetherOffloadSetInterfaceQuota(inOrder, mobileIfIndex, limit, true /* isInit */);
inOrder.verifyNoMoreInteractions();
// Adding the second rule on current upstream does not send the quota to netd.
- final Ipv6ForwardingRule ruleB = buildTestForwardingRule(mobileIfIndex, NEIGH_B, MAC_B);
- coordinator.tetherOffloadRuleAdd(mIpServer, ruleB);
- verifyTetherOffloadRuleAdd(inOrder, ruleB);
+ final Ipv6DownstreamRule ruleB = buildTestDownstreamRule(mobileIfIndex, NEIGH_B, MAC_B);
+ coordinator.addIpv6DownstreamRule(mIpServer, ruleB);
+ verifyAddDownstreamRule(inOrder, ruleB);
verifyNeverTetherOffloadSetInterfaceQuota(inOrder);
// Removing the second rule on current upstream does not send the quota to netd.
- coordinator.tetherOffloadRuleRemove(mIpServer, ruleB);
- verifyTetherOffloadRuleRemove(inOrder, ruleB);
+ coordinator.removeIpv6DownstreamRule(mIpServer, ruleB);
+ verifyRemoveDownstreamRule(inOrder, ruleB);
verifyNeverTetherOffloadSetInterfaceQuota(inOrder);
// Removing the last rule on current upstream immediately sends the cleanup stuff to netd.
updateStatsEntryForTetherOffloadGetAndClearStats(
buildTestTetherStatsParcel(mobileIfIndex, 0, 0, 0, 0));
- coordinator.tetherOffloadRuleRemove(mIpServer, ruleA);
- verifyTetherOffloadRuleRemove(inOrder, ruleA);
+ coordinator.removeIpv6DownstreamRule(mIpServer, ruleA);
+ verifyRemoveDownstreamRule(inOrder, ruleA);
verifyTetherOffloadGetAndClearStats(inOrder, mobileIfIndex);
inOrder.verifyNoMoreInteractions();
}
@@ -1124,23 +1133,23 @@
// [1] Adding rules on the upstream Ethernet.
// Note that the default data limit is applied after the first rule is added.
- final Ipv6ForwardingRule ethernetRuleA = buildTestForwardingRule(
+ final Ipv6DownstreamRule ethernetRuleA = buildTestDownstreamRule(
ethIfIndex, NEIGH_A, MAC_A);
- final Ipv6ForwardingRule ethernetRuleB = buildTestForwardingRule(
+ final Ipv6DownstreamRule ethernetRuleB = buildTestDownstreamRule(
ethIfIndex, NEIGH_B, MAC_B);
- coordinator.tetherOffloadRuleAdd(mIpServer, ethernetRuleA);
- verifyTetherOffloadRuleAdd(inOrder, ethernetRuleA);
+ coordinator.addIpv6DownstreamRule(mIpServer, ethernetRuleA);
+ verifyAddDownstreamRule(inOrder, ethernetRuleA);
verifyTetherOffloadSetInterfaceQuota(inOrder, ethIfIndex, QUOTA_UNLIMITED,
true /* isInit */);
verifyStartUpstreamIpv6Forwarding(inOrder, DOWNSTREAM_IFINDEX, DOWNSTREAM_MAC, ethIfIndex);
- coordinator.tetherOffloadRuleAdd(mIpServer, ethernetRuleB);
- verifyTetherOffloadRuleAdd(inOrder, ethernetRuleB);
+ coordinator.addIpv6DownstreamRule(mIpServer, ethernetRuleB);
+ verifyAddDownstreamRule(inOrder, ethernetRuleB);
// [2] Update the existing rules from Ethernet to cellular.
- final Ipv6ForwardingRule mobileRuleA = buildTestForwardingRule(
+ final Ipv6DownstreamRule mobileRuleA = buildTestDownstreamRule(
mobileIfIndex, NEIGH_A, MAC_A);
- final Ipv6ForwardingRule mobileRuleB = buildTestForwardingRule(
+ final Ipv6DownstreamRule mobileRuleB = buildTestDownstreamRule(
mobileIfIndex, NEIGH_B, MAC_B);
updateStatsEntryForTetherOffloadGetAndClearStats(
buildTestTetherStatsParcel(ethIfIndex, 10, 20, 30, 40));
@@ -1148,23 +1157,23 @@
// Update the existing rules for upstream changes. The rules are removed and re-added one
// by one for updating upstream interface index by #tetherOffloadRuleUpdate.
coordinator.tetherOffloadRuleUpdate(mIpServer, mobileIfIndex);
- verifyTetherOffloadRuleRemove(inOrder, ethernetRuleA);
- verifyTetherOffloadRuleRemove(inOrder, ethernetRuleB);
+ verifyRemoveDownstreamRule(inOrder, ethernetRuleA);
+ verifyRemoveDownstreamRule(inOrder, ethernetRuleB);
verifyStopUpstreamIpv6Forwarding(inOrder, DOWNSTREAM_IFINDEX, DOWNSTREAM_MAC);
verifyTetherOffloadGetAndClearStats(inOrder, ethIfIndex);
- verifyTetherOffloadRuleAdd(inOrder, mobileRuleA);
+ verifyAddDownstreamRule(inOrder, mobileRuleA);
verifyTetherOffloadSetInterfaceQuota(inOrder, mobileIfIndex, QUOTA_UNLIMITED,
true /* isInit */);
verifyStartUpstreamIpv6Forwarding(inOrder, DOWNSTREAM_IFINDEX, DOWNSTREAM_MAC,
mobileIfIndex);
- verifyTetherOffloadRuleAdd(inOrder, mobileRuleB);
+ verifyAddDownstreamRule(inOrder, mobileRuleB);
// [3] Clear all rules for a given IpServer.
updateStatsEntryForTetherOffloadGetAndClearStats(
buildTestTetherStatsParcel(mobileIfIndex, 50, 60, 70, 80));
coordinator.tetherOffloadRuleClear(mIpServer);
- verifyTetherOffloadRuleRemove(inOrder, mobileRuleA);
- verifyTetherOffloadRuleRemove(inOrder, mobileRuleB);
+ verifyRemoveDownstreamRule(inOrder, mobileRuleA);
+ verifyRemoveDownstreamRule(inOrder, mobileRuleB);
verifyStopUpstreamIpv6Forwarding(inOrder, DOWNSTREAM_IFINDEX, DOWNSTREAM_MAC);
verifyTetherOffloadGetAndClearStats(inOrder, mobileIfIndex);
@@ -1201,37 +1210,37 @@
// The rule can't be added.
final InetAddress neigh = InetAddresses.parseNumericAddress("2001:db8::1");
final MacAddress mac = MacAddress.fromString("00:00:00:00:00:0a");
- final Ipv6ForwardingRule rule = buildTestForwardingRule(ifIndex, neigh, mac);
- coordinator.tetherOffloadRuleAdd(mIpServer, rule);
- verifyNeverTetherOffloadRuleAdd();
- LinkedHashMap<Inet6Address, Ipv6ForwardingRule> rules =
- coordinator.getForwardingRulesForTesting().get(mIpServer);
+ final Ipv6DownstreamRule rule = buildTestDownstreamRule(ifIndex, neigh, mac);
+ coordinator.addIpv6DownstreamRule(mIpServer, rule);
+ verifyNeverAddDownstreamRule();
+ LinkedHashMap<Inet6Address, Ipv6DownstreamRule> rules =
+ coordinator.getIpv6DownstreamRulesForTesting().get(mIpServer);
assertNull(rules);
// The rule can't be removed. This is not a realistic case because adding rule is not
// allowed. That implies no rule could be removed, cleared or updated. Verify these
// cases just in case.
- rules = new LinkedHashMap<Inet6Address, Ipv6ForwardingRule>();
+ rules = new LinkedHashMap<Inet6Address, Ipv6DownstreamRule>();
rules.put(rule.address, rule);
- coordinator.getForwardingRulesForTesting().put(mIpServer, rules);
- coordinator.tetherOffloadRuleRemove(mIpServer, rule);
- verifyNeverTetherOffloadRuleRemove();
- rules = coordinator.getForwardingRulesForTesting().get(mIpServer);
+ coordinator.getIpv6DownstreamRulesForTesting().put(mIpServer, rules);
+ coordinator.removeIpv6DownstreamRule(mIpServer, rule);
+ verifyNeverRemoveDownstreamRule();
+ rules = coordinator.getIpv6DownstreamRulesForTesting().get(mIpServer);
assertNotNull(rules);
assertEquals(1, rules.size());
// The rule can't be cleared.
coordinator.tetherOffloadRuleClear(mIpServer);
- verifyNeverTetherOffloadRuleRemove();
- rules = coordinator.getForwardingRulesForTesting().get(mIpServer);
+ verifyNeverRemoveDownstreamRule();
+ rules = coordinator.getIpv6DownstreamRulesForTesting().get(mIpServer);
assertNotNull(rules);
assertEquals(1, rules.size());
// The rule can't be updated.
coordinator.tetherOffloadRuleUpdate(mIpServer, rule.upstreamIfindex + 1 /* new */);
- verifyNeverTetherOffloadRuleRemove();
- verifyNeverTetherOffloadRuleAdd();
- rules = coordinator.getForwardingRulesForTesting().get(mIpServer);
+ verifyNeverRemoveDownstreamRule();
+ verifyNeverAddDownstreamRule();
+ rules = coordinator.getIpv6DownstreamRulesForTesting().get(mIpServer);
assertNotNull(rules);
assertEquals(1, rules.size());
}
@@ -1669,17 +1678,17 @@
final BpfCoordinator coordinator = makeBpfCoordinator();
coordinator.addUpstreamNameToLookupTable(UPSTREAM_IFINDEX, UPSTREAM_IFACE);
- final Ipv6ForwardingRule ruleA = buildTestForwardingRule(UPSTREAM_IFINDEX, NEIGH_A, MAC_A);
- final Ipv6ForwardingRule ruleB = buildTestForwardingRule(UPSTREAM_IFINDEX, NEIGH_B, MAC_B);
+ final Ipv6DownstreamRule ruleA = buildTestDownstreamRule(UPSTREAM_IFINDEX, NEIGH_A, MAC_A);
+ final Ipv6DownstreamRule ruleB = buildTestDownstreamRule(UPSTREAM_IFINDEX, NEIGH_B, MAC_B);
- coordinator.tetherOffloadRuleAdd(mIpServer, ruleA);
+ coordinator.addIpv6DownstreamRule(mIpServer, ruleA);
verify(mBpfDevMap).updateEntry(eq(new TetherDevKey(UPSTREAM_IFINDEX)),
eq(new TetherDevValue(UPSTREAM_IFINDEX)));
verify(mBpfDevMap).updateEntry(eq(new TetherDevKey(DOWNSTREAM_IFINDEX)),
eq(new TetherDevValue(DOWNSTREAM_IFINDEX)));
clearInvocations(mBpfDevMap);
- coordinator.tetherOffloadRuleAdd(mIpServer, ruleB);
+ coordinator.addIpv6DownstreamRule(mIpServer, ruleB);
verify(mBpfDevMap, never()).updateEntry(any(), any());
}
@@ -2139,9 +2148,15 @@
@Test
public void testIpv6ForwardingRuleToString() throws Exception {
- final Ipv6ForwardingRule rule = buildTestForwardingRule(UPSTREAM_IFINDEX, NEIGH_A, MAC_A);
+ final Ipv6DownstreamRule downstreamRule = buildTestDownstreamRule(UPSTREAM_IFINDEX, NEIGH_A,
+ MAC_A);
assertEquals("upstreamIfindex: 1001, downstreamIfindex: 2001, address: 2001:db8::1, "
- + "srcMac: 12:34:56:78:90:ab, dstMac: 00:00:00:00:00:0a", rule.toString());
+ + "srcMac: 12:34:56:78:90:ab, dstMac: 00:00:00:00:00:0a",
+ downstreamRule.toString());
+ final Ipv6UpstreamRule upstreamRule = buildTestUpstreamRule(UPSTREAM_IFINDEX);
+ assertEquals("upstreamIfindex: 1001, downstreamIfindex: 2001, sourcePrefix: ::/64, "
+ + "inDstMac: 12:34:56:78:90:ab, outSrcMac: 00:00:00:00:00:00, "
+ + "outDstMac: 00:00:00:00:00:00", upstreamRule.toString());
}
private void verifyDump(@NonNull final BpfCoordinator coordinator) {
@@ -2177,7 +2192,7 @@
// - dumpCounters
// * mBpfErrorMap
// - dumpIpv6ForwardingRulesByDownstream
- // * mIpv6ForwardingRules
+ // * mIpv6DownstreamRules
// dumpBpfForwardingRulesIpv4
mBpfDownstream4Map.insertEntry(
@@ -2188,7 +2203,7 @@
new TestUpstream4Value.Builder().build());
// dumpBpfForwardingRulesIpv6
- final Ipv6ForwardingRule rule = buildTestForwardingRule(UPSTREAM_IFINDEX, NEIGH_A, MAC_A);
+ final Ipv6DownstreamRule rule = buildTestDownstreamRule(UPSTREAM_IFINDEX, NEIGH_A, MAC_A);
mBpfDownstream6Map.insertEntry(rule.makeTetherDownstream6Key(), rule.makeTether6Value());
final TetherUpstream6Key upstream6Key = new TetherUpstream6Key(DOWNSTREAM_IFINDEX,
@@ -2218,12 +2233,12 @@
new S32(1000 /* count */));
// dumpIpv6ForwardingRulesByDownstream
- final HashMap<IpServer, LinkedHashMap<Inet6Address, Ipv6ForwardingRule>>
- ipv6ForwardingRules = coordinator.getForwardingRulesForTesting();
- final LinkedHashMap<Inet6Address, Ipv6ForwardingRule> addressRuleMap =
+ final HashMap<IpServer, LinkedHashMap<Inet6Address, Ipv6DownstreamRule>>
+ ipv6DownstreamRules = coordinator.getIpv6DownstreamRulesForTesting();
+ final LinkedHashMap<Inet6Address, Ipv6DownstreamRule> addressRuleMap =
new LinkedHashMap<>();
addressRuleMap.put(rule.address, rule);
- ipv6ForwardingRules.put(mIpServer, addressRuleMap);
+ ipv6DownstreamRules.put(mIpServer, addressRuleMap);
verifyDump(coordinator);
}
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-t/Android.bp b/framework-t/Android.bp
index 05b84c2..5ae1ef9 100644
--- a/framework-t/Android.bp
+++ b/framework-t/Android.bp
@@ -19,6 +19,14 @@
default_applicable_licenses: ["Android-Apache-2.0"],
}
+framework_remoteauth_srcs = [":framework-remoteauth-java-sources"]
+framework_remoteauth_api_srcs = []
+
+java_defaults {
+ name: "enable-remoteauth-targets",
+ enabled: true,
+}
+
// Include build rules from Sources.bp
build = ["Sources.bp"]
@@ -43,7 +51,7 @@
":framework-connectivity-tiramisu-updatable-sources",
":framework-nearby-java-sources",
":framework-thread-sources",
- ],
+ ] + framework_remoteauth_srcs,
libs: [
"unsupportedappusage",
"app-compat-annotations",
@@ -114,6 +122,7 @@
"framework-connectivity-t-defaults",
"enable-framework-connectivity-t-targets",
],
+ api_srcs: framework_remoteauth_api_srcs,
// Do not add static_libs to this library: put them in framework-connectivity instead.
// The jarjar rules are only so that references to jarjared utils in
// framework-connectivity-pre-jarjar match at runtime.
@@ -130,8 +139,10 @@
"android.net",
"android.net.nsd",
"android.nearby",
+ "android.remoteauth",
"com.android.connectivity",
"com.android.nearby",
+ "com.android.remoteauth",
],
hidden_api: {
@@ -153,6 +164,7 @@
"//packages/modules/Connectivity/service", // For R8 only
"//packages/modules/Connectivity/service-t",
"//packages/modules/Connectivity/nearby:__subpackages__",
+ "//packages/modules/Connectivity/remoteauth:__subpackages__",
"//frameworks/base",
// Tests using hidden APIs
diff --git a/framework/Android.bp b/framework/Android.bp
index 8de8097..e577e6d 100644
--- a/framework/Android.bp
+++ b/framework/Android.bp
@@ -62,7 +62,6 @@
":framework-connectivity-sources",
":net-utils-framework-common-srcs",
":framework-connectivity-api-shared-srcs",
- ":framework-remoteauth-java-sources",
],
aidl: {
generate_get_transaction_name: true,
@@ -81,9 +80,9 @@
impl_only_libs: [
// TODO: figure out why just using "framework-tethering" uses the stubs, even though both
// framework-connectivity and framework-tethering are in the same APEX.
+ "framework-location.stubs.module_lib",
"framework-tethering.impl",
"framework-wifi.stubs.module_lib",
- "net-utils-device-common",
],
static_libs: [
"mdns_aidl_interface-lateststable-java",
@@ -92,6 +91,9 @@
"modules-utils-preconditions",
"framework-connectivity-javastream-protos",
],
+ impl_only_static_libs: [
+ "net-utils-device-common-struct",
+ ],
libs: [
"androidx.annotation_annotation",
"app-compat-annotations",
@@ -113,12 +115,20 @@
"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
// to generate the connectivity stubs. That would create a circular dependency
// because the tethering impl depend on the connectivity stubs (e.g.,
// TetheringRequest depends on LinkAddress).
+ "framework-location.stubs.module_lib",
"framework-tethering.impl",
"framework-wifi.stubs.module_lib",
],
@@ -153,7 +163,6 @@
"//packages/modules/Connectivity/framework-t",
"//packages/modules/Connectivity/service",
"//packages/modules/Connectivity/service-t",
- "//packages/modules/Connectivity/remoteauth:__subpackages__",
"//frameworks/base/packages/Connectivity/service",
"//frameworks/base",
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/nearby/TEST_MAPPING b/nearby/TEST_MAPPING
index d68bcc9..7e9a375 100644
--- a/nearby/TEST_MAPPING
+++ b/nearby/TEST_MAPPING
@@ -2,20 +2,17 @@
"presubmit": [
{
"name": "NearbyUnitTests"
+ }
+ ],
+ "postsubmit": [
+ {
+ "name": "NearbyUnitTests"
},
{
"name": "NearbyIntegrationPrivilegedTests"
},
{
"name": "NearbyIntegrationUntrustedTests"
- },
- {
- "name": "NearbyIntegrationUiTests"
- }
- ],
- "postsubmit": [
- {
- "name": "NearbyUnitTests"
}
]
// TODO(b/193602229): uncomment once it's supported.
diff --git a/remoteauth/TEST_MAPPING b/remoteauth/TEST_MAPPING
index 5ad8da6..5061319 100644
--- a/remoteauth/TEST_MAPPING
+++ b/remoteauth/TEST_MAPPING
@@ -7,7 +7,7 @@
// TODO(b/193602229): uncomment once it's supported.
//"mainline-presubmit": [
// {
- // "name": "RemoteAuthUnitTests[com.google.android.tethering.apex]"
+ // "name": "RemoteAuthUnitTests[com.google.android.remoteauth.apex]"
// }
//]
}
diff --git a/remoteauth/framework/Android.bp b/remoteauth/framework/Android.bp
index 48d10b6..71b621a 100644
--- a/remoteauth/framework/Android.bp
+++ b/remoteauth/framework/Android.bp
@@ -25,7 +25,7 @@
],
path: "java",
visibility: [
- "//packages/modules/Connectivity/framework:__subpackages__",
+ "//packages/modules/Connectivity/framework-t:__subpackages__",
],
}
@@ -43,7 +43,13 @@
name: "framework-remoteauth-static",
srcs: [":framework-remoteauth-java-sources"],
sdk_version: "module_current",
- libs: [],
- static_libs: [],
+ libs: [
+ "androidx.annotation_annotation",
+ "framework-annotations-lib",
+ "framework-bluetooth",
+ ],
+ static_libs: [
+ "modules-utils-preconditions",
+ ],
visibility: ["//packages/modules/Connectivity/remoteauth/tests:__subpackages__"],
}
diff --git a/remoteauth/framework/java/android/remoteauth/DeviceDiscoveryCallback.java b/remoteauth/framework/java/android/remoteauth/DeviceDiscoveryCallback.java
new file mode 100644
index 0000000..1414f7e
--- /dev/null
+++ b/remoteauth/framework/java/android/remoteauth/DeviceDiscoveryCallback.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 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.remoteauth;
+
+import android.annotation.NonNull;
+
+import androidx.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Reports newly discovered remote devices.
+ *
+ * @hide
+ */
+// TODO(b/290092977): Add back after M-2023-11 release - @SystemApi(client = MODULE_LIBRARIES)
+public interface DeviceDiscoveryCallback {
+ /** The device is no longer seen in the discovery process. */
+ int STATE_LOST = 0;
+ /** The device is seen in the discovery process */
+ int STATE_SEEN = 1;
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({STATE_LOST, STATE_SEEN})
+ @interface State {}
+
+ /**
+ * Invoked for every change in remote device state.
+ *
+ * @param device remote device
+ * @param state indicates if found or lost
+ */
+ void onDeviceUpdate(@NonNull RemoteDevice device, @State int state);
+
+ /** Invoked when discovery is stopped due to timeout. */
+ void onTimeout();
+}
diff --git a/remoteauth/framework/java/android/remoteauth/IDeviceDiscoveryListener.aidl b/remoteauth/framework/java/android/remoteauth/IDeviceDiscoveryListener.aidl
new file mode 100644
index 0000000..2ad6a6a
--- /dev/null
+++ b/remoteauth/framework/java/android/remoteauth/IDeviceDiscoveryListener.aidl
@@ -0,0 +1,35 @@
+/*
+ * 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.remoteauth;
+
+import android.remoteauth.RemoteDevice;
+
+/**
+ * Binder callback for DeviceDiscoveryCallback.
+ *
+ * {@hide}
+ */
+oneway interface IDeviceDiscoveryListener {
+ /** Reports a {@link RemoteDevice} being discovered. */
+ void onDiscovered(in RemoteDevice remoteDevice);
+
+ /** Reports a {@link RemoteDevice} is no longer within range. */
+ void onLost(in RemoteDevice remoteDevice);
+
+ /** Reports a timeout of {@link RemoteDevice} was reached. */
+ void onTimeout();
+}
diff --git a/remoteauth/framework/java/android/remoteauth/IRemoteAuthService.aidl b/remoteauth/framework/java/android/remoteauth/IRemoteAuthService.aidl
new file mode 100644
index 0000000..f4387e3
--- /dev/null
+++ b/remoteauth/framework/java/android/remoteauth/IRemoteAuthService.aidl
@@ -0,0 +1,42 @@
+/*
+ * 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.remoteauth;
+
+import android.remoteauth.IDeviceDiscoveryListener;
+
+/**
+ * Interface for communicating with the RemoteAuthService.
+ * These methods are all require MANAGE_REMOTE_AUTH signature permission.
+ * @hide
+ */
+interface IRemoteAuthService {
+ // This is protected by the MANAGE_REMOTE_AUTH signature permission.
+ boolean isRemoteAuthSupported();
+
+ // This is protected by the MANAGE_REMOTE_AUTH signature permission.
+ boolean registerDiscoveryListener(in IDeviceDiscoveryListener deviceDiscoveryListener,
+ int userId,
+ int timeoutMs,
+ String packageName,
+ @nullable String attributionTag);
+
+ // This is protected by the MANAGE_REMOTE_AUTH signature permission.
+ void unregisterDiscoveryListener(in IDeviceDiscoveryListener deviceDiscoveryListener,
+ int userId,
+ String packageName,
+ @nullable String attributionTag);
+}
\ No newline at end of file
diff --git a/remoteauth/framework/java/android/remoteauth/RemoteAuthFrameworkInitializer.java b/remoteauth/framework/java/android/remoteauth/RemoteAuthFrameworkInitializer.java
new file mode 100644
index 0000000..112ffa8
--- /dev/null
+++ b/remoteauth/framework/java/android/remoteauth/RemoteAuthFrameworkInitializer.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 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.remoteauth;
+
+import android.app.SystemServiceRegistry;
+import android.content.Context;
+
+/**
+ * Class for initializing RemoteAuth service.
+ *
+ * @hide
+ */
+// TODO(b/290092977): Add back after M-2023-11 release - @SystemApi(client = MODULE_LIBRARIES)
+public final class RemoteAuthFrameworkInitializer {
+ private RemoteAuthFrameworkInitializer() {}
+
+ /**
+ * Called by {@link SystemServiceRegistry}'s static initializer and registers all Nearby
+ * services to {@link Context}, so that {@link Context#getSystemService} can return them.
+ *
+ * @throws IllegalStateException if this is called from anywhere besides {@link
+ * SystemServiceRegistry}
+ */
+ public static void registerServiceWrappers() {
+ // TODO(b/290092977): Change to Context.REMOTE_AUTH_SERVICE after aosp/2681375
+ // is automerges from aosp-main to udc-mainline-prod
+ SystemServiceRegistry.registerContextAwareService(
+ RemoteAuthManager.REMOTE_AUTH_SERVICE,
+ RemoteAuthManager.class,
+ (context, serviceBinder) -> {
+ IRemoteAuthService service = IRemoteAuthService.Stub.asInterface(serviceBinder);
+ return new RemoteAuthManager(context, service);
+ });
+ }
+}
diff --git a/remoteauth/framework/java/android/remoteauth/RemoteAuthManager.java b/remoteauth/framework/java/android/remoteauth/RemoteAuthManager.java
new file mode 100644
index 0000000..038af2a
--- /dev/null
+++ b/remoteauth/framework/java/android/remoteauth/RemoteAuthManager.java
@@ -0,0 +1,238 @@
+/*
+ * Copyright 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.remoteauth;
+
+import static android.remoteauth.DeviceDiscoveryCallback.STATE_LOST;
+import static android.remoteauth.DeviceDiscoveryCallback.STATE_SEEN;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.NonNull;
+import android.annotation.SuppressLint;
+import android.annotation.SystemService;
+import android.annotation.UserIdInt;
+import android.content.Context;
+import android.os.RemoteException;
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.Preconditions;
+
+import java.lang.ref.WeakReference;
+import java.util.Objects;
+import java.util.WeakHashMap;
+import java.util.concurrent.Executor;
+
+/**
+ * A system service providing a way to perform remote authentication-related operations such as
+ * discovering, registering and authenticating via remote authenticator.
+ *
+ * <p>To get a {@link RemoteAuthManager} instance, call the <code>
+ * Context.getSystemService(Context.REMOTE_AUTH_SERVICE)</code>.
+ *
+ * @hide
+ */
+// TODO(b/290092977): Add back after M-2023-11 release - @SystemApi(client = MODULE_LIBRARIES)
+// TODO(b/290092977): Change to Context.REMOTE_AUTH_SERVICE after aosp/2681375
+// is automerges from aosp-main to udc-mainline-prod
+@SystemService(RemoteAuthManager.REMOTE_AUTH_SERVICE)
+public class RemoteAuthManager {
+ private static final String TAG = "RemoteAuthManager";
+
+ /** @hide */
+ public static final String REMOTE_AUTH_SERVICE = "remote_auth";
+
+ private final Context mContext;
+ private final IRemoteAuthService mService;
+
+ @GuardedBy("mDiscoveryListeners")
+ private final WeakHashMap<
+ DeviceDiscoveryCallback, WeakReference<DeviceDiscoveryListenerTransport>>
+ mDiscoveryListeners = new WeakHashMap<>();
+
+ /** @hide */
+ public RemoteAuthManager(@NonNull Context context, @NonNull IRemoteAuthService service) {
+ Objects.requireNonNull(context);
+ Objects.requireNonNull(service);
+ mContext = context;
+ mService = service;
+ }
+
+ /**
+ * Returns if this device can be enrolled in the feature.
+ *
+ * @return true if this device can be enrolled
+ * @hide
+ */
+ // TODO(b/290092977): Add back after M-2023-11 release - @SystemApi(client = MODULE_LIBRARIES)
+ // TODO(b/297301535): @RequiresPermission(MANAGE_REMOTE_AUTH)
+ public boolean isRemoteAuthSupported() {
+ try {
+ return mService.isRemoteAuthSupported();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Starts remote authenticator discovery process with timeout. Devices that are capable to
+ * operate as remote authenticators are reported via callback. The discovery stops by calling
+ * stopDiscovery or after a timeout.
+ *
+ * @param timeoutMs the duration in milliseconds after which discovery will stop automatically
+ * @param executor the callback will be executed in the executor thread
+ * @param callback to be used by the caller to get notifications about remote devices
+ * @return {@code true} if discovery began successfully, {@code false} otherwise
+ * @hide
+ */
+ // TODO(b/290092977): Add back after M-2023-11 release - @SystemApi(client = MODULE_LIBRARIES)
+ // TODO(b/297301535): @RequiresPermission(MANAGE_REMOTE_AUTH)
+ public boolean startDiscovery(
+ int timeoutMs,
+ @CallbackExecutor @NonNull Executor executor,
+ @NonNull DeviceDiscoveryCallback callback) {
+ try {
+ Preconditions.checkNotNull(callback, "invalid null callback");
+ Preconditions.checkArgument(timeoutMs > 0, "invalid timeoutMs, must be > 0");
+ Preconditions.checkNotNull(executor, "invalid null executor");
+ DeviceDiscoveryListenerTransport transport;
+ synchronized (mDiscoveryListeners) {
+ WeakReference<DeviceDiscoveryListenerTransport> reference =
+ mDiscoveryListeners.get(callback);
+ transport = (reference != null) ? reference.get() : null;
+ if (transport == null) {
+ transport =
+ new DeviceDiscoveryListenerTransport(
+ callback, mContext.getUser().getIdentifier(), executor);
+ }
+
+ boolean result =
+ mService.registerDiscoveryListener(
+ transport,
+ mContext.getUser().getIdentifier(),
+ timeoutMs,
+ mContext.getPackageName(),
+ mContext.getAttributionTag());
+ if (result) {
+ mDiscoveryListeners.put(callback, new WeakReference<>(transport));
+ return true;
+ }
+ }
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ return false;
+ }
+
+ /**
+ * Removes this listener from device discovery notifications. The given callback is guaranteed
+ * not to receive any invocations that happen after this method is invoked.
+ *
+ * @param callback the callback for the previously started discovery to be ended
+ * @hide
+ */
+ // Suppressed lint: Registration methods should have overload that accepts delivery Executor.
+ // Already have executor in startDiscovery() method.
+ @SuppressLint("ExecutorRegistration")
+ // TODO(b/290092977): Add back after M-2023-11 release - @SystemApi(client = MODULE_LIBRARIES)
+ // TODO(b/297301535): @RequiresPermission(MANAGE_REMOTE_AUTH)
+ public void stopDiscovery(@NonNull DeviceDiscoveryCallback callback) {
+ Preconditions.checkNotNull(callback, "invalid null scanCallback");
+ try {
+ DeviceDiscoveryListenerTransport transport;
+ synchronized (mDiscoveryListeners) {
+ WeakReference<DeviceDiscoveryListenerTransport> reference =
+ mDiscoveryListeners.remove(callback);
+ transport = (reference != null) ? reference.get() : null;
+ }
+ if (transport != null) {
+ mService.unregisterDiscoveryListener(
+ transport,
+ transport.getUserId(),
+ mContext.getPackageName(),
+ mContext.getAttributionTag());
+ } else {
+ Log.d(
+ TAG,
+ "Cannot stop discovery with this callback "
+ + "because it is not registered.");
+ }
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ private class DeviceDiscoveryListenerTransport extends IDeviceDiscoveryListener.Stub {
+
+ private volatile @NonNull DeviceDiscoveryCallback mDeviceDiscoveryCallback;
+ private Executor mExecutor;
+ private @UserIdInt int mUserId;
+
+ DeviceDiscoveryListenerTransport(
+ DeviceDiscoveryCallback deviceDiscoveryCallback,
+ @UserIdInt int userId,
+ @CallbackExecutor Executor executor) {
+ Preconditions.checkNotNull(deviceDiscoveryCallback, "invalid null callback");
+ mDeviceDiscoveryCallback = deviceDiscoveryCallback;
+ mUserId = userId;
+ mExecutor = executor;
+ }
+
+ @UserIdInt
+ int getUserId() {
+ return mUserId;
+ }
+
+ @Override
+ public void onDiscovered(RemoteDevice remoteDevice) throws RemoteException {
+ if (remoteDevice == null) {
+ Log.w(TAG, "onDiscovered is called with null device");
+ return;
+ }
+ Log.i(TAG, "Notifying the caller about discovered: " + remoteDevice);
+ mExecutor.execute(
+ () -> {
+ mDeviceDiscoveryCallback.onDeviceUpdate(remoteDevice, STATE_SEEN);
+ });
+ }
+
+ @Override
+ public void onLost(RemoteDevice remoteDevice) throws RemoteException {
+ if (remoteDevice == null) {
+ Log.w(TAG, "onLost is called with null device");
+ return;
+ }
+ Log.i(TAG, "Notifying the caller about lost: " + remoteDevice);
+ mExecutor.execute(
+ () -> {
+ mDeviceDiscoveryCallback.onDeviceUpdate(remoteDevice, STATE_LOST);
+ });
+ }
+
+ @Override
+ public void onTimeout() {
+ Log.i(TAG, "Notifying the caller about discovery timeout");
+ mExecutor.execute(
+ () -> {
+ mDeviceDiscoveryCallback.onTimeout();
+ });
+ synchronized (mDiscoveryListeners) {
+ mDiscoveryListeners.remove(mDeviceDiscoveryCallback);
+ }
+ mDeviceDiscoveryCallback = null;
+ }
+ }
+}
diff --git a/remoteauth/framework/java/android/remoteauth/RemoteDevice.aidl b/remoteauth/framework/java/android/remoteauth/RemoteDevice.aidl
new file mode 100644
index 0000000..ea38be2
--- /dev/null
+++ b/remoteauth/framework/java/android/remoteauth/RemoteDevice.aidl
@@ -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 android.remoteauth;
+
+parcelable RemoteDevice;
\ No newline at end of file
diff --git a/remoteauth/framework/java/android/remoteauth/RemoteDevice.java b/remoteauth/framework/java/android/remoteauth/RemoteDevice.java
new file mode 100644
index 0000000..b6ede2e
--- /dev/null
+++ b/remoteauth/framework/java/android/remoteauth/RemoteDevice.java
@@ -0,0 +1,200 @@
+/*
+ * Copyright 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.remoteauth;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+
+/**
+ * Remote device that can be registered as remote authenticator.
+ *
+ * @hide
+ */
+// TODO(b/295407748) Change to use @DataClass
+// TODO(b/290092977): Add back after M-2023-11 release - @SystemApi(client = MODULE_LIBRARIES)
+public final class RemoteDevice implements Parcelable {
+ /** The remote device is not registered as remote authenticator. */
+ public static final int STATE_NOT_REGISTERED = 0;
+ /** The remote device is registered as remote authenticator. */
+ public static final int STATE_REGISTERED = 1;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({STATE_NOT_REGISTERED, STATE_REGISTERED})
+ @interface RegistrationState {}
+
+ @NonNull private final String mName;
+ private final @RegistrationState int mRegistrationState;
+ private final int mConnectionId;
+
+ public static final @NonNull Creator<RemoteDevice> CREATOR =
+ new Creator<>() {
+ @Override
+ public RemoteDevice createFromParcel(Parcel in) {
+ RemoteDevice.Builder builder = new RemoteDevice.Builder();
+ builder.setName(in.readString());
+ builder.setRegistrationState(in.readInt());
+ builder.setConnectionId(in.readInt());
+
+ return builder.build();
+ }
+
+ @Override
+ public RemoteDevice[] newArray(int size) {
+ return new RemoteDevice[size];
+ }
+ };
+
+ private RemoteDevice(
+ @Nullable String name,
+ @RegistrationState int registrationState,
+ @NonNull int connectionId) {
+ this.mName = name;
+ this.mRegistrationState = registrationState;
+ this.mConnectionId = connectionId;
+ }
+
+ /** Gets the name of the {@link RemoteDevice} device. */
+ @Nullable
+ public String getName() {
+ return mName;
+ }
+
+ /** Returns registration state of the {@link RemoteDevice}. */
+ public @RegistrationState int getRegistrationState() {
+ return mRegistrationState;
+ }
+
+ /** Returns connection id of the {@link RemoteDevice}. */
+ @NonNull
+ public int getConnectionId() {
+ return mConnectionId;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /** Returns a string representation of {@link RemoteDevice}. */
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("RemoteDevice [");
+ sb.append("name=").append(mName).append(", ");
+ sb.append("registered=").append(mRegistrationState).append(", ");
+ sb.append("connectionId=").append(mConnectionId);
+ sb.append("]");
+ return sb.toString();
+ }
+
+ /** Returns true if this {@link RemoteDevice} object is equals to other. */
+ @Override
+ public boolean equals(Object other) {
+ if (other instanceof RemoteDevice) {
+ RemoteDevice otherDevice = (RemoteDevice) other;
+ return Objects.equals(this.mName, otherDevice.mName)
+ && this.getRegistrationState() == otherDevice.getRegistrationState()
+ && this.mConnectionId == otherDevice.mConnectionId;
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mName, mRegistrationState, mConnectionId);
+ }
+
+ /**
+ * Helper function for writing {@link RemoteDevice} to a Parcel.
+ *
+ * @param dest The Parcel in which the object should be written.
+ * @param flags Additional flags about how the object should be written.
+ */
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ String name = getName();
+ dest.writeString(name);
+ dest.writeInt(getRegistrationState());
+ dest.writeInt(getConnectionId());
+ }
+
+ /** Builder for {@link RemoteDevice} objects. */
+ public static final class Builder {
+ @Nullable private String mName;
+ // represents if device is already registered
+ private @RegistrationState int mRegistrationState;
+ private int mConnectionId;
+
+ private Builder() {
+ }
+
+ public Builder(final int connectionId) {
+ this.mConnectionId = connectionId;
+ }
+
+ /**
+ * Sets the name of the {@link RemoteDevice} device.
+ *
+ * @param name of the {@link RemoteDevice}. Can be {@code null} if there is no name.
+ */
+ @NonNull
+ public RemoteDevice.Builder setName(@Nullable String name) {
+ this.mName = name;
+ return this;
+ }
+
+ /**
+ * Sets the registration state of the {@link RemoteDevice} device.
+ *
+ * @param registrationState of the {@link RemoteDevice}.
+ */
+ @NonNull
+ public RemoteDevice.Builder setRegistrationState(@RegistrationState int registrationState) {
+ this.mRegistrationState = registrationState;
+ return this;
+ }
+
+ /**
+ * Sets the connectionInfo of the {@link RemoteDevice} device.
+ *
+ * @param connectionId of the RemoteDevice.
+ */
+ @NonNull
+ public RemoteDevice.Builder setConnectionId(int connectionId) {
+ this.mConnectionId = connectionId;
+ return this;
+ }
+
+ /**
+ * Creates the {@link RemoteDevice} instance.
+ *
+ * @return the configured {@link RemoteDevice} instance.
+ */
+ @NonNull
+ public RemoteDevice build() {
+ return new RemoteDevice(mName, mRegistrationState, mConnectionId);
+ }
+ }
+}
diff --git a/remoteauth/service/Android.bp b/remoteauth/service/Android.bp
index 5c5a2fb..dfaf8cf 100644
--- a/remoteauth/service/Android.bp
+++ b/remoteauth/service/Android.bp
@@ -25,12 +25,31 @@
java_library {
name: "service-remoteauth-pre-jarjar",
srcs: [":remoteauth-service-srcs"],
-
+ required: ["libremoteauth_jni_rust_defaults"],
defaults: [
- "framework-system-server-module-defaults"
+ "enable-remoteauth-targets",
+ "framework-system-server-module-defaults",
],
- libs: [],
- static_libs: [],
+ libs: [
+ "androidx.annotation_annotation",
+ "framework-bluetooth",
+ "error_prone_annotations",
+ "framework-configinfrastructure",
+ "framework-connectivity-pre-jarjar",
+ "framework-connectivity-t-pre-jarjar",
+ "framework-statsd",
+ ],
+ static_libs: [
+ "guava",
+ "libprotobuf-java-lite",
+ "fast-pair-lite-protos",
+ "modules-utils-build",
+ "modules-utils-handlerexecutor",
+ "modules-utils-preconditions",
+ "modules-utils-backgroundthread",
+ "presence-lite-protos",
+ "uwb_androidx_backend",
+ ],
sdk_version: "system_server_current",
// This is included in service-connectivity which is 30+
// TODO (b/293613362): allow APEXes to have service jars with higher min_sdk than the APEX
@@ -53,7 +72,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..b659bf7 100644
--- a/remoteauth/service/java/com/android/server/remoteauth/README.md
+++ b/remoteauth/service/java/com/android/server/remoteauth/README.md
@@ -1,4 +1,10 @@
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.
+
+## Util
+Common utilities.
diff --git a/remoteauth/service/java/com/android/server/remoteauth/RemoteAuthService.java b/remoteauth/service/java/com/android/server/remoteauth/RemoteAuthService.java
new file mode 100644
index 0000000..9374ace
--- /dev/null
+++ b/remoteauth/service/java/com/android/server/remoteauth/RemoteAuthService.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 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;
+
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.content.Context;
+import android.remoteauth.IDeviceDiscoveryListener;
+import android.remoteauth.IRemoteAuthService;
+
+import com.android.internal.util.Preconditions;
+
+/** Service implementing remoteauth functionality. */
+public class RemoteAuthService extends IRemoteAuthService.Stub {
+ public static final String TAG = "RemoteAuthService";
+ public static final String SERVICE_NAME = Context.REMOTE_AUTH_SERVICE;
+
+ public RemoteAuthService(Context context) {
+ Preconditions.checkNotNull(context);
+ // TODO(b/290280702): Create here RemoteConnectivityManager and RangingManager
+ }
+
+ @Override
+ public boolean isRemoteAuthSupported() {
+ // TODO(b/297301535): checkPermission(mContext, MANAGE_REMOTE_AUTH);
+ // TODO(b/290676192): integrate with RangingManager
+ // (check if UWB is supported by this device)
+ return true;
+ }
+
+ @Override
+ public boolean registerDiscoveryListener(
+ IDeviceDiscoveryListener deviceDiscoveryListener,
+ @UserIdInt int userId,
+ int timeoutMs,
+ String packageName,
+ @Nullable String attributionTag) {
+ // TODO(b/297301535): checkPermission(mContext, MANAGE_REMOTE_AUTH);
+ // TODO(b/290280702): implement register discovery logic
+ return true;
+ }
+
+ @Override
+ public void unregisterDiscoveryListener(
+ IDeviceDiscoveryListener deviceDiscoveryListener,
+ @UserIdInt int userId,
+ String packageName,
+ @Nullable String attributionTag) {
+ // TODO(b/297301535): checkPermission(mContext, MANAGE_REMOTE_AUTH);
+ // TODO(b/290094221): implement unregister logic
+ }
+
+ private static void checkPermission(Context context, String permission) {
+ context.enforceCallingOrSelfPermission(permission,
+ "Must have " + permission + " permission.");
+ }
+}
diff --git a/remoteauth/service/java/com/android/server/remoteauth/jni/INativeRemoteAuthService.java b/remoteauth/service/java/com/android/server/remoteauth/jni/INativeRemoteAuthService.java
new file mode 100644
index 0000000..f79ec7e
--- /dev/null
+++ b/remoteauth/service/java/com/android/server/remoteauth/jni/INativeRemoteAuthService.java
@@ -0,0 +1,67 @@
+/*
+ * 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.jni;
+
+/**
+ * Interface defining a proxy between Rust and Java implementation of RemoteAuth protocol.
+ *
+ * @hide
+ */
+public interface INativeRemoteAuthService {
+ /**
+ * Interface for RemoteAuth PAL
+ *
+ * @hide
+ */
+ interface IPlatform {
+ /**
+ * Sends message to the remote authenticator
+ *
+ * @param connectionId connection ID of the {@link android.remoteauth.RemoteAuthenticator}
+ * @param request payload of the request
+ * @param callback to be used to pass the response result
+ *
+ * @hide
+ */
+ void sendRequest(int connectionId, byte[] request, ResponseCallback callback);
+
+ /**
+ * Interface for a callback to send a response back.
+ *
+ * @hide
+ */
+ interface ResponseCallback {
+ /**
+ * Invoked when message sending succeeds.
+ *
+ * @param response contains response
+ *
+ * @hide
+ */
+ void onSuccess(byte[] response);
+
+ /**
+ * Invoked when message sending fails.
+ *
+ * @param errorCode indicating the error
+ *
+ * @hide
+ */
+ void onFailure(int errorCode);
+ }
+ }
+}
diff --git a/remoteauth/service/java/com/android/server/remoteauth/jni/NativeRemoteAuthService.java b/remoteauth/service/java/com/android/server/remoteauth/jni/NativeRemoteAuthService.java
new file mode 100644
index 0000000..39c2a74
--- /dev/null
+++ b/remoteauth/service/java/com/android/server/remoteauth/jni/NativeRemoteAuthService.java
@@ -0,0 +1,92 @@
+/*
+ * 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.jni;
+
+import com.android.internal.annotations.Keep;
+import com.android.server.remoteauth.jni.INativeRemoteAuthService.IPlatform;
+
+/**
+ * A service providing a proxy between Rust implementation and {@link
+ * com.android.server.remoteauth.RemoteAuthService}.
+ *
+ * @hide
+ */
+public class NativeRemoteAuthService {
+ private static final String TAG = NativeRemoteAuthService.class.getSimpleName();
+
+ private IPlatform mPlatform;
+ public final Object mNativeLock = new Object();
+
+ // Constructor should receive pointers to:
+ // ConnectivityManager, RangingManager and DB
+ public NativeRemoteAuthService() {
+ System.loadLibrary("remoteauth_jni_rust");
+ synchronized (mNativeLock) {
+ native_init();
+ }
+ }
+
+ public void setDeviceListener(final IPlatform platform) {
+ mPlatform = platform;
+ }
+
+ /**
+ * Sends message to the remote authenticator
+ *
+ * @param connectionId connection ID of the {@link android.remoteauth.RemoteAuthenticator}
+ * @param request payload of the request
+ * @param responseHandle a handle associated with the request, used to pass the response to the
+ * platform
+ * @param platformHandle a handle associated with the platform object, used to pass the response
+ * to the specific platform
+ *
+ * @hide
+ */
+ @Keep
+ public void sendRequest(
+ int connectionId, byte[] request, long responseHandle, long platformHandle) {
+ mPlatform.sendRequest(
+ connectionId,
+ request,
+ new IPlatform.ResponseCallback() {
+ @Override
+ public void onSuccess(byte[] response) {
+ synchronized (mNativeLock) {
+ native_on_send_request_success(
+ response, platformHandle, responseHandle);
+ }
+ }
+
+ @Override
+ public void onFailure(int errorCode) {
+ synchronized (mNativeLock) {
+ native_on_send_request_error(errorCode, platformHandle, responseHandle);
+ }
+ }
+ });
+ }
+
+ /* Native functions implemented in JNI */
+ // This function should be implemented in remoteauth_jni_android_protocol
+ private native boolean native_init();
+
+ private native void native_on_send_request_success(
+ byte[] appResponse, long platformHandle, long responseHandle);
+
+ private native void native_on_send_request_error(
+ int errorCode, long platformHandle, long responseHandle);
+}
diff --git a/remoteauth/service/java/com/android/server/remoteauth/jni/PlatformBadHandleException.java b/remoteauth/service/java/com/android/server/remoteauth/jni/PlatformBadHandleException.java
new file mode 100644
index 0000000..3ae9838
--- /dev/null
+++ b/remoteauth/service/java/com/android/server/remoteauth/jni/PlatformBadHandleException.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.
+ */
+
+/**
+ * Represents an unrecoverable error (invalid handle) that has occurred during accessing the
+ * platform.
+ */
+package com.android.server.remoteauth.jni;
+
+import com.android.internal.annotations.Keep;
+/**
+ * Exception thrown by native platform rust implementation of {@link
+ * com.android.server.remoteauth.RemoteAuthService}.
+ *
+ * @hide
+ */
+@Keep
+public class PlatformBadHandleException extends Exception {
+ public PlatformBadHandleException(final String message) {
+ super(message);
+ }
+
+ public PlatformBadHandleException(final Exception e) {
+ super(e);
+ }
+
+ public PlatformBadHandleException(final String message, final Exception e) {
+ super(message, e);
+ }
+}
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..36aa585
--- /dev/null
+++ b/remoteauth/service/java/com/android/server/remoteauth/ranging/RangingCapabilities.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 android.annotation.NonNull;
+import android.annotation.Nullable;
+
+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;
+ private final androidx.core.uwb.backend.impl.internal.RangingCapabilities
+ mUwbRangingCapabilities;
+
+ /**
+ * Gets the list of supported ranging methods of the device.
+ *
+ * @return list of {@link RangingMethod}
+ */
+ public ImmutableList<Integer> getSupportedRangingMethods() {
+ return mSupportedRangingMethods;
+ }
+
+ /**
+ * Gets the UWB ranging capabilities of the device.
+ *
+ * @return UWB ranging capabilities, null if UWB is not a supported {@link RangingMethod} in
+ * {@link #getSupportedRangingMethods}.
+ */
+ @Nullable
+ public androidx.core.uwb.backend.impl.internal.RangingCapabilities getUwbRangingCapabilities() {
+ return mUwbRangingCapabilities;
+ }
+
+ private RangingCapabilities(
+ List<Integer> supportedRangingMethods,
+ androidx.core.uwb.backend.impl.internal.RangingCapabilities uwbRangingCapabilities) {
+ mSupportedRangingMethods = ImmutableList.copyOf(supportedRangingMethods);
+ mUwbRangingCapabilities = uwbRangingCapabilities;
+ }
+
+ /** Builder class for {@link RangingCapabilities}. */
+ public static final class Builder {
+ private List<Integer> mSupportedRangingMethods = new ArrayList<>();
+ private androidx.core.uwb.backend.impl.internal.RangingCapabilities mUwbRangingCapabilities;
+
+ /** Adds a supported {@link RangingMethod} */
+ public Builder addSupportedRangingMethods(@RangingMethod int rangingMethod) {
+ mSupportedRangingMethods.add(rangingMethod);
+ return this;
+ }
+
+ /** Sets the uwb ranging capabilities. */
+ public Builder setUwbRangingCapabilities(
+ @NonNull
+ androidx.core.uwb.backend.impl.internal.RangingCapabilities
+ uwbRangingCapabilities) {
+ mUwbRangingCapabilities = uwbRangingCapabilities;
+ return this;
+ }
+
+ /** Builds {@link RangingCapabilities}. */
+ public RangingCapabilities build() {
+ return new RangingCapabilities(mSupportedRangingMethods, mUwbRangingCapabilities);
+ }
+ }
+}
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..cf0db76
--- /dev/null
+++ b/remoteauth/service/java/com/android/server/remoteauth/ranging/RangingManager.java
@@ -0,0 +1,120 @@
+/*
+ * 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 android.content.pm.PackageManager.FEATURE_UWB;
+
+import static com.android.server.remoteauth.ranging.RangingCapabilities.RANGING_METHOD_UNKNOWN;
+import static com.android.server.remoteauth.ranging.RangingCapabilities.RANGING_METHOD_UWB;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.os.Build;
+import android.util.Log;
+
+import androidx.core.uwb.backend.impl.internal.UwbFeatureFlags;
+import androidx.core.uwb.backend.impl.internal.UwbServiceImpl;
+
+import com.android.internal.util.Preconditions;
+
+/**
+ * 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 {
+ private static final String TAG = "RangingManager";
+
+ private Context mContext;
+ @NonNull private RangingCapabilities mCachedRangingCapabilities;
+ @NonNull private UwbServiceImpl mUwbServiceImpl;
+
+ public RangingManager(@NonNull Context context) {
+ mContext = context;
+ if (mContext.getPackageManager().hasSystemFeature(FEATURE_UWB)) {
+ initiateUwb();
+ }
+ }
+
+ /**
+ * Shutdown and stop all listeners and tasks. After shutdown, RangingManager shall not be used
+ * anymore.
+ */
+ public void shutdown() {
+ if (mUwbServiceImpl != null) {
+ mUwbServiceImpl.shutdown();
+ }
+ Log.i(TAG, "shutdown");
+ }
+
+ /**
+ * Gets the {@link RangingCapabilities} of this device.
+ *
+ * @return RangingCapabilities.
+ */
+ @NonNull
+ public RangingCapabilities getRangingCapabilities() {
+ if (mCachedRangingCapabilities == null) {
+ RangingCapabilities.Builder builder = new RangingCapabilities.Builder();
+ if (mUwbServiceImpl != null) {
+ builder.addSupportedRangingMethods(RANGING_METHOD_UWB);
+ builder.setUwbRangingCapabilities(mUwbServiceImpl.getRangingCapabilities());
+ }
+ mCachedRangingCapabilities = builder.build();
+ }
+ return mCachedRangingCapabilities;
+ }
+
+ /**
+ * 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. Null if session creation failed.
+ * @throws IllegalArgumentException if sessionParameters is invalid.
+ */
+ @Nullable
+ public RangingSession createSession(@NonNull SessionParameters sessionParameters) {
+ Preconditions.checkNotNull(sessionParameters, "sessionParameters must not be null");
+ switch (sessionParameters.getRangingMethod()) {
+ case RANGING_METHOD_UWB:
+ if (mUwbServiceImpl == null) {
+ Log.w(TAG, "createSession with UWB failed - UWB not supported");
+ break;
+ }
+ return new UwbRangingSession(mContext, sessionParameters, mUwbServiceImpl);
+ case RANGING_METHOD_UNKNOWN:
+ break;
+ }
+ return null;
+ }
+
+ /** Initiation required for ranging with UWB. */
+ private void initiateUwb() {
+ UwbFeatureFlags uwbFeatureFlags =
+ new UwbFeatureFlags.Builder()
+ .setSkipRangingCapabilitiesCheck(
+ Build.VERSION.SDK_INT <= Build.VERSION_CODES.S_V2)
+ .setReversedByteOrderFiraParams(
+ Build.VERSION.SDK_INT <= Build.VERSION_CODES.TIRAMISU)
+ .build();
+ mUwbServiceImpl = new UwbServiceImpl(mContext, uwbFeatureFlags);
+ Log.i(TAG, "RangingManager initiateUwb complete");
+ }
+}
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..adb36c5
--- /dev/null
+++ b/remoteauth/service/java/com/android/server/remoteauth/ranging/RangingSession.java
@@ -0,0 +1,254 @@
+/*
+ * 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 android.content.Context;
+import android.util.Log;
+
+import androidx.annotation.IntDef;
+
+import com.android.internal.util.Preconditions;
+import com.android.server.remoteauth.util.Crypto;
+
+import com.google.common.hash.Hashing;
+
+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 {
+ private static final String TAG = "RangingSession";
+
+ /** Types of ranging error. */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(
+ value = {
+ RANGING_ERROR_UNKNOWN,
+ RANGING_ERROR_INVALID_PARAMETERS,
+ RANGING_ERROR_STOPPED_BY_REQUEST,
+ RANGING_ERROR_STOPPED_BY_PEER,
+ RANGING_ERROR_FAILED_TO_START,
+ RANGING_ERROR_FAILED_TO_STOP,
+ RANGING_ERROR_SYSTEM_ERROR,
+ RANGING_ERROR_SYSTEM_TIMEOUT,
+ })
+ public @interface RangingError {}
+
+ /** Unknown ranging error type. */
+ public static final int RANGING_ERROR_UNKNOWN = 0x0;
+
+ /** Ranging error due to invalid parameters. */
+ public static final int RANGING_ERROR_INVALID_PARAMETERS = 0x1;
+
+ /** Ranging error due to stopped by calling {@link #stop}. */
+ public static final int RANGING_ERROR_STOPPED_BY_REQUEST = 0x2;
+
+ /** Ranging error due to stopped by the peer device. */
+ public static final int RANGING_ERROR_STOPPED_BY_PEER = 0x3;
+
+ /** Ranging error due to failure to start ranging. */
+ public static final int RANGING_ERROR_FAILED_TO_START = 0x4;
+
+ /** Ranging error due to failure to stop ranging. */
+ public static final int RANGING_ERROR_FAILED_TO_STOP = 0x5;
+
+ /**
+ * Ranging error due to system error cause by changes such as privacy policy, power management
+ * policy, permissions, and more.
+ */
+ public static final int RANGING_ERROR_SYSTEM_ERROR = 0x6;
+
+ /** Ranging error due to system timeout in retry attempts. */
+ public static final int RANGING_ERROR_SYSTEM_TIMEOUT = 0x7;
+
+ /** 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(
+ @NonNull SessionInfo sessionInfo, @NonNull RangingReport rangingReport);
+
+ /**
+ * Call upon any ranging error events.
+ *
+ * @param sessionInfo info about this ranging session.
+ * @param rangingError error type
+ */
+ void onError(@NonNull SessionInfo sessionInfo, @RangingError int rangingError);
+ }
+
+ protected Context mContext;
+ protected SessionInfo mSessionInfo;
+ protected float mLowerProximityBoundaryM;
+ protected float mUpperProximityBoundaryM;
+ protected boolean mAutoDeriveParams;
+ protected byte[] mBaseKey;
+ protected byte[] mSyncData;
+ protected int mSyncCounter;
+ protected byte[] mDerivedData;
+ protected int mDerivedDataLength;
+
+ protected RangingSession(
+ @NonNull Context context,
+ @NonNull SessionParameters sessionParameters,
+ int derivedDataLength) {
+ Preconditions.checkNotNull(context);
+ Preconditions.checkNotNull(sessionParameters);
+ mContext = context;
+ mSessionInfo =
+ new SessionInfo.Builder()
+ .setDeviceId(sessionParameters.getDeviceId())
+ .setRangingMethod(sessionParameters.getRangingMethod())
+ .build();
+ mLowerProximityBoundaryM = sessionParameters.getLowerProximityBoundaryM();
+ mUpperProximityBoundaryM = sessionParameters.getUpperProximityBoundaryM();
+ mAutoDeriveParams = sessionParameters.getAutoDeriveParams();
+ Log.i(
+ TAG,
+ "Creating a new RangingSession {info = "
+ + mSessionInfo
+ + ", autoDeriveParams = "
+ + mAutoDeriveParams
+ + "}");
+ if (mAutoDeriveParams) {
+ Preconditions.checkArgument(
+ derivedDataLength > 0, "derivedDataLength must be greater than 0");
+ mDerivedDataLength = derivedDataLength;
+ resetBaseKey(sessionParameters.getBaseKey());
+ resetSyncData(sessionParameters.getSyncData());
+ }
+ }
+
+ /**
+ * 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.
+ * @throws NullPointerException if params are null.
+ * @throws IllegalArgumentException if rangingParameters is invalid.
+ */
+ 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.
+ * @throws NullPointerException if baseKey is null.
+ * @throws IllegalArgumentException if baseKey has invalid length.
+ */
+ public void resetBaseKey(@NonNull byte[] baseKey) {
+ if (!mAutoDeriveParams) {
+ Log.w(TAG, "autoDeriveParams is disabled, new baseKey is ignored.");
+ return;
+ }
+ Preconditions.checkNotNull(baseKey);
+ if (baseKey.length != 16 && baseKey.length != 32) {
+ throw new IllegalArgumentException("Invalid baseKey length: " + baseKey.length);
+ }
+ mBaseKey = baseKey;
+ updateDerivedData();
+ Log.i(TAG, "resetBaseKey");
+ }
+
+ /**
+ * 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.
+ * @throws NullPointerException if baseKey is null.
+ * @throws IllegalArgumentException if syncData has invalid length.
+ */
+ public void resetSyncData(@NonNull byte[] syncData) {
+ if (!mAutoDeriveParams) {
+ Log.w(TAG, "autoDeriveParams is disabled, new syncData is ignored.");
+ return;
+ }
+ Preconditions.checkNotNull(syncData);
+ if (syncData.length != 16) {
+ throw new IllegalArgumentException("Invalid syncData length: " + syncData.length);
+ }
+ mSyncData = syncData;
+ mSyncCounter = 0;
+ updateDerivedData();
+ Log.i(TAG, "resetSyncData");
+ }
+
+ /** Recomputes mDerivedData using the latest mBaseKey, mSyncData, and mSyncCounter. */
+ protected boolean updateDerivedData() {
+ if (!mAutoDeriveParams) {
+ Log.w(TAG, "autoDeriveParams is disabled, updateDerivedData is skipped.");
+ return false;
+ }
+ if (mBaseKey == null
+ || mBaseKey.length == 0
+ || mSyncData == null
+ || mSyncData.length == 0) {
+ Log.w(TAG, "updateDerivedData: Missing baseKey/syncData");
+ return false;
+ }
+ byte[] hashedSyncData =
+ Hashing.sha256()
+ .newHasher()
+ .putBytes(mSyncData)
+ .putInt(mSyncCounter)
+ .hash()
+ .asBytes();
+ byte[] newDerivedData = Crypto.computeHkdf(mBaseKey, hashedSyncData, mDerivedDataLength);
+ if (newDerivedData == null) {
+ Log.w(TAG, "updateDerivedData: computeHkdf failed");
+ return false;
+ }
+ mDerivedData = newDerivedData;
+ mSyncCounter++;
+ Log.i(TAG, "updateDerivedData");
+ return true;
+ }
+}
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..0ec640c
--- /dev/null
+++ b/remoteauth/service/java/com/android/server/remoteauth/ranging/SessionInfo.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 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;
+ }
+
+ @Override
+ public String toString() {
+ return "SessionInfo { "
+ + "DeviceId = "
+ + mDeviceId
+ + "RangingMethod = "
+ + mRangingMethod
+ + " }";
+ }
+
+ /** 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..2f71244
--- /dev/null
+++ b/remoteauth/service/java/com/android/server/remoteauth/ranging/SessionParameters.java
@@ -0,0 +1,263 @@
+/*
+ * 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 androidx.annotation.IntDef;
+
+import com.android.internal.util.Preconditions;
+import com.android.server.remoteauth.ranging.RangingCapabilities.RangingMethod;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * 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 {
+
+ /** Ranging device role. */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(
+ value = {
+ DEVICE_ROLE_UNKNOWN,
+ DEVICE_ROLE_INITIATOR,
+ DEVICE_ROLE_RESPONDER,
+ })
+ public @interface DeviceRole {}
+
+ /** Unknown device role. */
+ public static final int DEVICE_ROLE_UNKNOWN = 0x0;
+
+ /** Device that initiates the ranging. */
+ public static final int DEVICE_ROLE_INITIATOR = 0x1;
+
+ /** Device that responds to ranging. */
+ public static final int DEVICE_ROLE_RESPONDER = 0x2;
+
+ /* Required parameters */
+ private final String mDeviceId;
+ @RangingMethod private final int mRangingMethod;
+ @DeviceRole private final int mDeviceRole;
+
+ /* 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;
+ }
+
+ @DeviceRole
+ public int getDeviceRole() {
+ return mDeviceRole;
+ }
+
+ 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,
+ @DeviceRole int deviceRole,
+ float lowerProximityBoundaryM,
+ float upperProximityBoundaryM,
+ boolean autoDeriveParams,
+ byte[] baseKey,
+ byte[] syncData) {
+ mDeviceId = deviceId;
+ mRangingMethod = rangingMethod;
+ mDeviceRole = deviceRole;
+ 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;
+ @DeviceRole private int mDeviceRole = DEVICE_ROLE_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 {@link DeviceRole} to be used for the {@link RangingSession}. */
+ public Builder setDeviceRole(@DeviceRole int deviceRole) {
+ mDeviceRole = deviceRole;
+ 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(mDeviceRole != DEVICE_ROLE_UNKNOWN, "Unknown deviceRole");
+ 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,
+ mDeviceRole,
+ mLowerProximityBoundaryM,
+ mUpperProximityBoundaryM,
+ mAutoDeriveParams,
+ mBaseKey,
+ mSyncData);
+ }
+ }
+}
diff --git a/remoteauth/service/java/com/android/server/remoteauth/ranging/UwbRangingSession.java b/remoteauth/service/java/com/android/server/remoteauth/ranging/UwbRangingSession.java
new file mode 100644
index 0000000..2015b66
--- /dev/null
+++ b/remoteauth/service/java/com/android/server/remoteauth/ranging/UwbRangingSession.java
@@ -0,0 +1,44 @@
+/*
+ * 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 android.content.Context;
+
+import androidx.core.uwb.backend.impl.internal.UwbServiceImpl;
+
+import java.util.concurrent.Executor;
+
+/** UWB (ultra wide-band) implementation of {@link RangingSession}. */
+public class UwbRangingSession extends RangingSession {
+ private static final int DERIVED_DATA_LENGTH = 1;
+
+ public UwbRangingSession(
+ @NonNull Context context,
+ @NonNull SessionParameters sessionParameters,
+ @NonNull UwbServiceImpl uwbServiceImpl) {
+ super(context, sessionParameters, DERIVED_DATA_LENGTH);
+ }
+
+ @Override
+ public void start(
+ @NonNull RangingParameters rangingParameters,
+ @NonNull Executor executor,
+ @NonNull RangingCallback rangingCallback) {}
+
+ @Override
+ public void stop() {}
+}
diff --git a/remoteauth/service/java/com/android/server/remoteauth/util/Crypto.java b/remoteauth/service/java/com/android/server/remoteauth/util/Crypto.java
new file mode 100644
index 0000000..573597f
--- /dev/null
+++ b/remoteauth/service/java/com/android/server/remoteauth/util/Crypto.java
@@ -0,0 +1,106 @@
+/*
+ * 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.util;
+
+import android.util.Log;
+
+import androidx.annotation.Nullable;
+
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+
+import javax.crypto.Mac;
+import javax.crypto.spec.SecretKeySpec;
+
+/** Utility class of cryptographic functions. */
+public final class Crypto {
+ private static final String TAG = "Crypto";
+ private static final String HMAC_SHA256_ALGORITHM = "HmacSHA256";
+
+ /**
+ * A HAMC sha256 based HKDF algorithm to pseudo randomly hash data and salt into a byte array of
+ * given size.
+ *
+ * @param ikm the input keying material.
+ * @param salt A possibly non-secret random value.
+ * @param size The length of the generated pseudorandom string in bytes. The maximal size is
+ * 255.DigestSize, where DigestSize is the size of the underlying HMAC.
+ * @return size pseudorandom bytes, null if failed.
+ */
+ // Based on
+ // google3/third_party/tink/java_src/src/main/java/com/google/crypto/tink/subtle/Hkdf.java
+ @Nullable
+ public static byte[] computeHkdf(byte[] ikm, byte[] salt, int size) {
+ Mac mac;
+ try {
+ mac = Mac.getInstance(HMAC_SHA256_ALGORITHM);
+ } catch (NoSuchAlgorithmException e) {
+ Log.w(TAG, "HMAC_SHA256_ALGORITHM is not supported.", e);
+ return null;
+ }
+
+ if (size > 255 * mac.getMacLength()) {
+ Log.w(TAG, "Size too large. " + size + " > " + 255 * mac.getMacLength());
+ return null;
+ }
+
+ if (ikm == null || ikm.length == 0) {
+ Log.w(TAG, "Ikm cannot be empty.");
+ return null;
+ }
+
+ if (salt == null || salt.length == 0) {
+ Log.w(TAG, "Salt cannot be empty.");
+ return null;
+ }
+
+ try {
+ mac.init(new SecretKeySpec(salt, HMAC_SHA256_ALGORITHM));
+ } catch (InvalidKeyException e) {
+ Log.w(TAG, "Invalid key.", e);
+ return null;
+ }
+
+ byte[] prk = mac.doFinal(ikm);
+ byte[] result = new byte[size];
+ try {
+ mac.init(new SecretKeySpec(prk, HMAC_SHA256_ALGORITHM));
+ } catch (InvalidKeyException e) {
+ Log.w(TAG, "Invalid key.", e);
+ return null;
+ }
+
+ byte[] digest = new byte[0];
+ int ctr = 1;
+ int pos = 0;
+ while (true) {
+ mac.update(digest);
+ mac.update((byte) ctr);
+ digest = mac.doFinal();
+ if (pos + digest.length < size) {
+ System.arraycopy(digest, 0, result, pos, digest.length);
+ pos += digest.length;
+ ctr++;
+ } else {
+ System.arraycopy(digest, 0, result, pos, size - pos);
+ break;
+ }
+ }
+
+ return result;
+ }
+}
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..a816c94
--- /dev/null
+++ b/remoteauth/service/jni/src/lib.rs
@@ -0,0 +1,25 @@
+// 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;
+mod utils;
+
+pub mod remoteauth_jni_android_platform;
+pub mod remoteauth_jni_android_protocol;
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..1967c1a
--- /dev/null
+++ b/remoteauth/service/jni/src/remoteauth_jni_android_platform.rs
@@ -0,0 +1,293 @@
+// 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 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, 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)
+}
+
+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().unwrap().insert(handle, Arc::clone(&item));
+}
+
+pub trait ResponseCallback {
+ fn on_response(&mut self, response: Vec<u8>);
+ fn on_error(&mut self, error_code: i32);
+}
+
+pub trait Platform {
+ /// Send a binary message to the remote with the given connection id and return the response.
+ fn send_request(
+ &mut self,
+ connection_id: i32,
+ request: &[u8],
+ callback: Box<dyn ResponseCallback + Send>,
+ ) -> anyhow::Result<()>;
+}
+//////////////////////////////////
+
+pub struct JavaPlatform {
+ platform_handle: i64,
+ vm: &'static Arc<JavaVM>,
+ platform_native_obj: GlobalRef,
+ send_request_method_id: JMethodID,
+ map_futures: Mutex<HashMap<i64, Box<dyn ResponseCallback + Send>>>,
+ atomic_handle: AtomicI64,
+}
+
+impl JavaPlatform {
+ // Method to create JavaPlatform
+ pub fn create(
+ java_platform_native: JObject<'_>,
+ ) -> Result<Arc<Mutex<impl Platform>>, JNIError> {
+ 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));
+ 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),
+ })
+ })
+ }
+}
+
+impl Platform for JavaPlatform {
+ fn send_request(
+ &mut self,
+ connection_id: i32,
+ request: &[u8],
+ callback: Box<dyn ResponseCallback + Send>,
+ ) -> anyhow::Result<()> {
+ let type_signature = TypeSignature::from_str(SEND_REQUEST_MSIG)
+ .map_err(|e| anyhow!("JNI: Invalid type signature: {:?}", e))?;
+
+ let response_handle = self.atomic_handle.fetch_add(1, Ordering::SeqCst);
+ self.map_futures.lock().unwrap().insert(response_handle, callback);
+ 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))?;
+ Ok(())
+ }
+}
+
+impl JavaPlatform {
+ 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(mut callback) = self.map_futures.lock().unwrap().remove(&response_handle) {
+ callback.on_response(response.to_vec());
+ } else {
+ error!(
+ "Failed to find TX for {} and {}:{}",
+ function_name!(),
+ self.platform_handle,
+ response_handle
+ );
+ }
+ }
+
+ 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(mut callback) = self.map_futures.lock().unwrap().remove(&response_handle) {
+ callback.on_error(error_code);
+ } else {
+ error!(
+ "Failed to find callback 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!());
+ native_on_send_request_success(env, app_response, platform_handle, response_handle);
+}
+
+fn native_on_send_request_success(
+ env: JNIEnv<'_>,
+ app_response: jbyteArray,
+ platform_handle: jlong,
+ response_handle: jlong,
+) {
+ if let Some(platform) = HANDLE_MAPPING.lock().unwrap().get(&platform_handle) {
+ let response =
+ env.convert_byte_array(app_response).map_err(|_| JNIError::InvalidCtorReturn).unwrap();
+ let mut platform = (*platform).lock().unwrap();
+ platform.on_send_request_success(&response, response_handle);
+ } 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!());
+ native_on_send_request_error(env, error_code, platform_handle, response_handle);
+}
+
+fn native_on_send_request_error(
+ env: JNIEnv<'_>,
+ error_code: jint,
+ platform_handle: jlong,
+ response_handle: jlong,
+) {
+ if let Some(platform) = HANDLE_MAPPING.lock().unwrap().get(&platform_handle) {
+ let platform = (*platform).lock().unwrap();
+ platform.on_send_request_error(error_code, response_handle);
+ } 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/remoteauth_jni_android_protocol.rs b/remoteauth/service/jni/src/remoteauth_jni_android_protocol.rs
new file mode 100644
index 0000000..1f73207
--- /dev/null
+++ b/remoteauth/service/jni/src/remoteauth_jni_android_protocol.rs
@@ -0,0 +1,40 @@
+/*
+ * 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.
+ */
+
+use crate::unique_jvm;
+use crate::utils::get_boolean_result;
+use jni::objects::JObject;
+use jni::sys::jboolean;
+use jni::JNIEnv;
+
+#[no_mangle]
+pub extern "system" fn Java_com_android_server_remoteauth_jni_NativeRemoteAuthJavaPlatform_native_init(
+ env: JNIEnv,
+ _: JObject,
+) -> jboolean {
+ logger::init(
+ logger::Config::default()
+ .with_tag_on_device("remoteauth")
+ .with_min_level(log::Level::Trace)
+ .with_filter("trace,jni=info"),
+ );
+ get_boolean_result(native_init(env), "native_init")
+}
+
+fn native_init(env: JNIEnv) -> anyhow::Result<()> {
+ let jvm = env.get_java_vm()?;
+ unique_jvm::set_once(jvm)
+}
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/service/jni/src/utils.rs b/remoteauth/service/jni/src/utils.rs
new file mode 100644
index 0000000..e61b895
--- /dev/null
+++ b/remoteauth/service/jni/src/utils.rs
@@ -0,0 +1,29 @@
+/*
+ * 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.
+ */
+
+use jni::sys::jboolean;
+use log::error;
+
+pub(crate) fn get_boolean_result<T>(result: anyhow::Result<T>, error_msg: &str) -> jboolean {
+ match result {
+ Ok(_) => true,
+ Err(e) => {
+ error!("{} failed with {:?}", error_msg, &e);
+ false
+ }
+ }
+ .into()
+}
diff --git a/remoteauth/tests/unit/Android.bp b/remoteauth/tests/unit/Android.bp
index 8c08a1b..37c78c7 100644
--- a/remoteauth/tests/unit/Android.bp
+++ b/remoteauth/tests/unit/Android.bp
@@ -18,7 +18,10 @@
android_test {
name: "RemoteAuthUnitTests",
- defaults: ["mts-target-sdk-version-current"],
+ defaults: [
+ "enable-remoteauth-targets",
+ "mts-target-sdk-version-current"
+ ],
sdk_version: "test_current",
min_sdk_version: "31",
@@ -35,12 +38,20 @@
static_libs: [
"androidx.test.ext.junit",
"androidx.test.rules",
+ "com.uwb.support.generic",
"framework-remoteauth-static",
"junit",
+ "libprotobuf-java-lite",
+ "mockito-target-extended-minus-junit4",
"platform-test-annotations",
"service-remoteauth-pre-jarjar",
"truth-prebuilt",
],
+ // these are needed for Extended Mockito
+ jni_libs: [
+ "libdexmakerjvmtiagent",
+ "libstaticjvmtiagent",
+ ],
test_suites: [
"general-tests",
"mts-tethering",
diff --git a/remoteauth/tests/unit/src/android/remoteauth/RemoteAuthManagerTest.java b/remoteauth/tests/unit/src/android/remoteauth/RemoteAuthManagerTest.java
index 5cf3e6b..6b43355 100644
--- a/remoteauth/tests/unit/src/android/remoteauth/RemoteAuthManagerTest.java
+++ b/remoteauth/tests/unit/src/android/remoteauth/RemoteAuthManagerTest.java
@@ -23,6 +23,7 @@
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
+import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -31,6 +32,9 @@
@SmallTest
@RunWith(AndroidJUnit4.class)
public class RemoteAuthManagerTest {
+ @Before
+ public void setUp() {}
+
@Test
public void testStub() {
assertTrue(true);
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..8135b4f
--- /dev/null
+++ b/remoteauth/tests/unit/src/com/android/server/remoteauth/ranging/RangingCapabilitiesTest.java
@@ -0,0 +1,52 @@
+/*
+ * 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 {
+ private static final androidx.core.uwb.backend.impl.internal.RangingCapabilities
+ TEST_UWB_RANGING_CAPABILITIES =
+ new androidx.core.uwb.backend.impl.internal.RangingCapabilities(
+ /* supportsDistance= */ true,
+ /* supportsAzimuthalAngle= */ true,
+ /* supportsElevationAngle= */ true);
+
+ @Test
+ public void testBuildingRangingCapabilities_success() {
+ final RangingCapabilities rangingCapabilities =
+ new RangingCapabilities.Builder()
+ .addSupportedRangingMethods(RANGING_METHOD_UWB)
+ .setUwbRangingCapabilities(TEST_UWB_RANGING_CAPABILITIES)
+ .build();
+
+ assertEquals(rangingCapabilities.getSupportedRangingMethods().size(), 1);
+ assertEquals(
+ (int) rangingCapabilities.getSupportedRangingMethods().get(0), RANGING_METHOD_UWB);
+ assertEquals(
+ rangingCapabilities.getUwbRangingCapabilities(), TEST_UWB_RANGING_CAPABILITIES);
+ }
+}
diff --git a/remoteauth/tests/unit/src/com/android/server/remoteauth/ranging/RangingManagerTest.java b/remoteauth/tests/unit/src/com/android/server/remoteauth/ranging/RangingManagerTest.java
new file mode 100644
index 0000000..6e343bb
--- /dev/null
+++ b/remoteauth/tests/unit/src/com/android/server/remoteauth/ranging/RangingManagerTest.java
@@ -0,0 +1,220 @@
+/*
+ * 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 android.content.pm.PackageManager.FEATURE_UWB;
+import static android.uwb.UwbManager.AdapterStateCallback.STATE_ENABLED_INACTIVE;
+
+import static androidx.core.uwb.backend.impl.internal.RangingCapabilities.FIRA_DEFAULT_SUPPORTED_CONFIG_IDS;
+import static androidx.core.uwb.backend.impl.internal.Utils.CONFIG_PROVISIONED_INDIVIDUAL_MULTICAST_DS_TWR;
+import static androidx.core.uwb.backend.impl.internal.Utils.CONFIG_PROVISIONED_MULTICAST_DS_TWR;
+import static androidx.core.uwb.backend.impl.internal.Utils.CONFIG_PROVISIONED_UNICAST_DS_TWR;
+import static androidx.core.uwb.backend.impl.internal.Utils.CONFIG_PROVISIONED_UNICAST_DS_TWR_NO_AOA;
+
+import static com.android.server.remoteauth.ranging.RangingCapabilities.RANGING_METHOD_UWB;
+import static com.android.server.remoteauth.ranging.SessionParameters.DEVICE_ROLE_INITIATOR;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.uwb.UwbManager;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.server.remoteauth.ranging.RangingCapabilities.RangingMethod;
+import com.android.server.remoteauth.ranging.SessionParameters.DeviceRole;
+
+import com.google.uwb.support.fira.FiraParams;
+import com.google.uwb.support.fira.FiraSpecificationParams;
+import com.google.uwb.support.generic.GenericSpecificationParams;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.EnumSet;
+import java.util.List;
+
+/** Unit test for {@link RangingManager}. */
+@RunWith(AndroidJUnit4.class)
+public class RangingManagerTest {
+ private static final List<Integer> TEST_UWB_SUPPORTED_CHANNELS = List.of(8, 9);
+ private static final FiraSpecificationParams TEST_FIRA_SPEC =
+ new FiraSpecificationParams.Builder()
+ .setSupportedChannels(TEST_UWB_SUPPORTED_CHANNELS)
+ .setStsCapabilities(EnumSet.allOf(FiraParams.StsCapabilityFlag.class))
+ .build();
+ private static final GenericSpecificationParams TEST_GENERIC_SPEC =
+ new GenericSpecificationParams.Builder()
+ .setFiraSpecificationParams(TEST_FIRA_SPEC)
+ .build();
+ private static final String TEST_DEVICE_ID = "test_device_id";
+ @RangingMethod private static final int TEST_RANGING_METHOD = RANGING_METHOD_UWB;
+ @DeviceRole private static final int TEST_DEVICE_ROLE = DEVICE_ROLE_INITIATOR;
+ 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
+ };
+ private static final SessionParameters TEST_SESSION_PARAMETER =
+ new SessionParameters.Builder()
+ .setDeviceId(TEST_DEVICE_ID)
+ .setRangingMethod(TEST_RANGING_METHOD)
+ .setDeviceRole(TEST_DEVICE_ROLE)
+ .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();
+
+ @Mock private Context mContext;
+ @Mock private PackageManager mPackageManager;
+ @Mock private UwbManager mUwbManager;
+
+ private RangingManager mRangingManager;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+
+ when(mContext.getSystemService(UwbManager.class)).thenReturn(mUwbManager);
+ when(mContext.getPackageManager()).thenReturn(mPackageManager);
+ when(mPackageManager.hasSystemFeature(FEATURE_UWB)).thenReturn(false);
+ when(mUwbManager.getAdapterState()).thenReturn(STATE_ENABLED_INACTIVE);
+ when(mUwbManager.getSpecificationInfo()).thenReturn(TEST_GENERIC_SPEC.toBundle());
+ }
+
+ @Test
+ public void testConstruction() {
+ mRangingManager = new RangingManager(mContext);
+ verifyZeroInteractions(mUwbManager);
+ }
+
+ @Test
+ public void testConstruction_withUwbEnabled() {
+ when(mPackageManager.hasSystemFeature(FEATURE_UWB)).thenReturn(true);
+
+ mRangingManager = new RangingManager(mContext);
+
+ verify(mUwbManager).getAdapterState();
+ verify(mUwbManager).registerAdapterStateCallback(any(), any());
+ }
+
+ @Test
+ public void testShutdown_withUwbEnabled() {
+ when(mPackageManager.hasSystemFeature(FEATURE_UWB)).thenReturn(true);
+
+ mRangingManager = new RangingManager(mContext);
+ mRangingManager.shutdown();
+
+ verify(mUwbManager).registerAdapterStateCallback(any(), any());
+ verify(mUwbManager).unregisterAdapterStateCallback(any());
+ }
+
+ @Test
+ public void testGetRangingCapabilities() {
+ mRangingManager = new RangingManager(mContext);
+ RangingCapabilities capabilities = mRangingManager.getRangingCapabilities();
+
+ assertEquals(capabilities.getSupportedRangingMethods().size(), 0);
+ assertEquals(capabilities.getUwbRangingCapabilities(), null);
+ }
+
+ @Test
+ public void testGetRangingCapabilities_withUwbEnabled() {
+ when(mPackageManager.hasSystemFeature(FEATURE_UWB)).thenReturn(true);
+
+ mRangingManager = new RangingManager(mContext);
+ RangingCapabilities capabilities = mRangingManager.getRangingCapabilities();
+
+ List<Integer> supportedConfigIds = new ArrayList<>(FIRA_DEFAULT_SUPPORTED_CONFIG_IDS);
+ supportedConfigIds.add(CONFIG_PROVISIONED_UNICAST_DS_TWR);
+ supportedConfigIds.add(CONFIG_PROVISIONED_MULTICAST_DS_TWR);
+ supportedConfigIds.add(CONFIG_PROVISIONED_UNICAST_DS_TWR_NO_AOA);
+ supportedConfigIds.add(CONFIG_PROVISIONED_INDIVIDUAL_MULTICAST_DS_TWR);
+
+ verify(mUwbManager, times(1)).getSpecificationInfo();
+ assertEquals(capabilities.getSupportedRangingMethods().size(), 1);
+ assertEquals((int) capabilities.getSupportedRangingMethods().get(0), RANGING_METHOD_UWB);
+ androidx.core.uwb.backend.impl.internal.RangingCapabilities uwbCapabilities =
+ capabilities.getUwbRangingCapabilities();
+ assertNotNull(uwbCapabilities);
+ assertArrayEquals(
+ uwbCapabilities.getSupportedChannels().toArray(),
+ TEST_UWB_SUPPORTED_CHANNELS.toArray());
+ assertArrayEquals(
+ uwbCapabilities.getSupportedConfigIds().toArray(), supportedConfigIds.toArray());
+ }
+
+ @Test
+ public void testGetRangingCapabilities_multipleCalls() {
+ when(mPackageManager.hasSystemFeature(FEATURE_UWB)).thenReturn(true);
+
+ mRangingManager = new RangingManager(mContext);
+ RangingCapabilities capabilities1 = mRangingManager.getRangingCapabilities();
+ RangingCapabilities capabilities2 = mRangingManager.getRangingCapabilities();
+ RangingCapabilities capabilities3 = mRangingManager.getRangingCapabilities();
+
+ verify(mUwbManager, times(1)).getSpecificationInfo();
+ assertEquals(capabilities1, capabilities2);
+ assertEquals(capabilities2, capabilities3);
+ }
+
+ @Test
+ public void testCreateSession_nullSessionParameters() {
+ mRangingManager = new RangingManager(mContext);
+
+ assertThrows(NullPointerException.class, () -> mRangingManager.createSession(null));
+ }
+
+ @Test
+ public void testCreateSession_uwbSessionWithUwbDisabled() {
+ mRangingManager = new RangingManager(mContext);
+
+ assertNull(mRangingManager.createSession(TEST_SESSION_PARAMETER));
+ }
+
+ @Test
+ public void testCreateSession_uwbSession() {
+ when(mPackageManager.hasSystemFeature(FEATURE_UWB)).thenReturn(true);
+ mRangingManager = new RangingManager(mContext);
+
+ assertNotNull(mRangingManager.createSession(TEST_SESSION_PARAMETER));
+ }
+}
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/RangingSessionTest.java b/remoteauth/tests/unit/src/com/android/server/remoteauth/ranging/RangingSessionTest.java
new file mode 100644
index 0000000..0e547d6
--- /dev/null
+++ b/remoteauth/tests/unit/src/com/android/server/remoteauth/ranging/RangingSessionTest.java
@@ -0,0 +1,237 @@
+/*
+ * 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 com.android.server.remoteauth.ranging.SessionParameters.DEVICE_ROLE_INITIATOR;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
+
+import android.content.Context;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.server.remoteauth.ranging.RangingCapabilities.RangingMethod;
+import com.android.server.remoteauth.ranging.SessionParameters.DeviceRole;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.concurrent.Executor;
+
+/** Unit test for {@link RangingSession}. */
+@RunWith(AndroidJUnit4.class)
+public class RangingSessionTest {
+
+ private static final String TEST_DEVICE_ID = "test_device_id";
+ @RangingMethod private static final int TEST_RANGING_METHOD = RANGING_METHOD_UWB;
+ @DeviceRole private static final int TEST_DEVICE_ROLE = DEVICE_ROLE_INITIATOR;
+ 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 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_BASE_KEY2 =
+ new byte[] {
+ 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x0,
+ 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7
+ };
+ 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
+ };
+ private static final byte[] TEST_SYNC_DATA2 =
+ new byte[] {
+ 0x00, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
+ 0x0f, 0x00
+ };
+
+ private static final SessionParameters TEST_SESSION_PARAMETER_WITH_AD =
+ new SessionParameters.Builder()
+ .setDeviceId(TEST_DEVICE_ID)
+ .setRangingMethod(TEST_RANGING_METHOD)
+ .setDeviceRole(TEST_DEVICE_ROLE)
+ .setLowerProximityBoundaryM(TEST_LOWER_PROXIMITY_BOUNDARY_M)
+ .setUpperProximityBoundaryM(TEST_UPPER_PROXIMITY_BOUNDARY_M)
+ .setAutoDeriveParams(true)
+ .setBaseKey(TEST_BASE_KEY)
+ .setSyncData(TEST_SYNC_DATA)
+ .build();
+ private static final SessionParameters TEST_SESSION_PARAMETER_WO_AD =
+ new SessionParameters.Builder()
+ .setDeviceId(TEST_DEVICE_ID)
+ .setRangingMethod(TEST_RANGING_METHOD)
+ .setDeviceRole(TEST_DEVICE_ROLE)
+ .setLowerProximityBoundaryM(TEST_LOWER_PROXIMITY_BOUNDARY_M)
+ .setUpperProximityBoundaryM(TEST_UPPER_PROXIMITY_BOUNDARY_M)
+ .setAutoDeriveParams(false)
+ .setBaseKey(TEST_BASE_KEY)
+ .setSyncData(TEST_SYNC_DATA)
+ .build();
+ private static final int TEST_DERIVE_DATA_LENGTH = 40;
+
+ /** Wrapper class for testing {@link RangingSession}. */
+ public static class RangingSessionWrapper extends RangingSession {
+ public RangingSessionWrapper(
+ Context context, SessionParameters sessionParameters, int derivedDataLength) {
+ super(context, sessionParameters, derivedDataLength);
+ }
+
+ @Override
+ public void start(
+ RangingParameters rangingParameters,
+ Executor executor,
+ RangingCallback rangingCallback) {}
+
+ @Override
+ public void stop() {}
+
+ @Override
+ public boolean updateDerivedData() {
+ return super.updateDerivedData();
+ }
+
+ public byte[] baseKey() {
+ return mBaseKey;
+ }
+
+ public byte[] syncData() {
+ return mSyncData;
+ }
+
+ public byte[] derivedData() {
+ return mDerivedData;
+ }
+
+ public int syncCounter() {
+ return mSyncCounter;
+ }
+ }
+
+ @Mock private Context mContext;
+
+ private RangingSessionWrapper mRangingSessionWithAD;
+ private RangingSessionWrapper mRangingSessionWithoutAD;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+
+ mRangingSessionWithAD =
+ new RangingSessionWrapper(
+ mContext, TEST_SESSION_PARAMETER_WITH_AD, TEST_DERIVE_DATA_LENGTH);
+ mRangingSessionWithoutAD =
+ new RangingSessionWrapper(mContext, TEST_SESSION_PARAMETER_WO_AD, 0);
+ }
+
+ @Test
+ public void testResetBaseKey_autoDeriveDisabled() {
+ assertNull(mRangingSessionWithoutAD.baseKey());
+ mRangingSessionWithoutAD.resetBaseKey(TEST_BASE_KEY2);
+ assertNull(mRangingSessionWithoutAD.baseKey());
+ }
+
+ @Test
+ public void testResetBaseKey_nullBaseKey() {
+ assertThrows(NullPointerException.class, () -> mRangingSessionWithAD.resetBaseKey(null));
+ }
+
+ @Test
+ public void testResetBaseKey_invalidBaseKey() {
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> mRangingSessionWithAD.resetBaseKey(new byte[] {0x1, 0x2, 0x3, 0x4}));
+ }
+
+ @Test
+ public void testResetBaseKey_success() {
+ mRangingSessionWithAD.resetBaseKey(TEST_BASE_KEY2);
+ assertArrayEquals(mRangingSessionWithAD.baseKey(), TEST_BASE_KEY2);
+ assertEquals(mRangingSessionWithAD.syncCounter(), 2);
+
+ mRangingSessionWithAD.resetBaseKey(TEST_BASE_KEY);
+ assertArrayEquals(mRangingSessionWithAD.baseKey(), TEST_BASE_KEY);
+ assertEquals(mRangingSessionWithAD.syncCounter(), 3);
+ }
+
+ @Test
+ public void testResetSyncData_autoDeriveDisabled() {
+ assertNull(mRangingSessionWithoutAD.syncData());
+ mRangingSessionWithoutAD.resetSyncData(TEST_SYNC_DATA2);
+ assertNull(mRangingSessionWithoutAD.syncData());
+ }
+
+ @Test
+ public void testResetSyncData_nullSyncData() {
+ assertThrows(NullPointerException.class, () -> mRangingSessionWithAD.resetSyncData(null));
+ }
+
+ @Test
+ public void testResetSyncData_invalidSyncData() {
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> mRangingSessionWithAD.resetSyncData(new byte[] {0x1, 0x2, 0x3, 0x4}));
+ }
+
+ @Test
+ public void testResetSyncData_success() {
+ mRangingSessionWithAD.resetSyncData(TEST_SYNC_DATA2);
+ assertArrayEquals(mRangingSessionWithAD.syncData(), TEST_SYNC_DATA2);
+ assertEquals(mRangingSessionWithAD.syncCounter(), 1);
+
+ mRangingSessionWithAD.resetSyncData(TEST_SYNC_DATA);
+ assertArrayEquals(mRangingSessionWithAD.syncData(), TEST_SYNC_DATA);
+ assertEquals(mRangingSessionWithAD.syncCounter(), 1);
+ }
+
+ @Test
+ public void testUpdateDerivedData_autoDeriveDisabled() {
+ assertFalse(mRangingSessionWithoutAD.updateDerivedData());
+ assertEquals(mRangingSessionWithoutAD.syncCounter(), 0);
+ }
+
+ @Test
+ public void testUpdateDerivedData_hkdfFailed() {
+ // Max derivedDataLength is 32*255
+ RangingSessionWrapper rangingSession =
+ new RangingSessionWrapper(
+ mContext, TEST_SESSION_PARAMETER_WITH_AD, /* derivedDataLength= */ 10000);
+ assertNull(rangingSession.derivedData());
+ assertFalse(rangingSession.updateDerivedData());
+ assertEquals(rangingSession.syncCounter(), 0);
+ assertNull(rangingSession.derivedData());
+ }
+
+ @Test
+ public void testUpdateDerivedData_success() {
+ assertNotNull(mRangingSessionWithAD.derivedData());
+ assertTrue(mRangingSessionWithAD.updateDerivedData());
+ assertEquals(mRangingSessionWithAD.syncCounter(), 2);
+ assertNotNull(mRangingSessionWithAD.derivedData());
+ }
+}
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..522623e
--- /dev/null
+++ b/remoteauth/tests/unit/src/com/android/server/remoteauth/ranging/SessionParametersTest.java
@@ -0,0 +1,235 @@
+/*
+ * 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 com.android.server.remoteauth.ranging.SessionParameters.DEVICE_ROLE_INITIATOR;
+
+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 com.android.server.remoteauth.ranging.SessionParameters.DeviceRole;
+
+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;
+ @DeviceRole private static final int TEST_DEVICE_ROLE = DEVICE_ROLE_INITIATOR;
+ 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)
+ .setDeviceRole(TEST_DEVICE_ROLE)
+ .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)
+ .setDeviceRole(TEST_DEVICE_ROLE)
+ .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)
+ .setDeviceRole(TEST_DEVICE_ROLE)
+ .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_invalidDeviceRole() {
+ 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)
+ .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)
+ .setDeviceRole(TEST_DEVICE_ROLE)
+ .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)
+ .setDeviceRole(TEST_DEVICE_ROLE)
+ .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)
+ .setDeviceRole(TEST_DEVICE_ROLE)
+ .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)
+ .setDeviceRole(TEST_DEVICE_ROLE)
+ .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)
+ .setDeviceRole(TEST_DEVICE_ROLE)
+ .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)
+ .setDeviceRole(TEST_DEVICE_ROLE)
+ .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)
+ .setDeviceRole(TEST_DEVICE_ROLE)
+ .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/remoteauth/tests/unit/src/com/android/server/remoteauth/util/CryptoTest.java b/remoteauth/tests/unit/src/com/android/server/remoteauth/util/CryptoTest.java
new file mode 100644
index 0000000..eb7a8c5
--- /dev/null
+++ b/remoteauth/tests/unit/src/com/android/server/remoteauth/util/CryptoTest.java
@@ -0,0 +1,55 @@
+/*
+ * 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.util;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/** Unit test for {@link Crypto}. */
+@RunWith(AndroidJUnit4.class)
+public class CryptoTest {
+ private static final byte[] TEST_IKM =
+ new byte[] {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07};
+ private static final byte[] TEST_SALT =
+ new byte[] {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00};
+ private static final int TEST_SIZE = 40;
+
+ @Test
+ public void testComputeHkdf_exceedMaxSize() {
+ // Max size is 32*255
+ assertNull(Crypto.computeHkdf(TEST_IKM, TEST_SALT, /* size= */ 10000));
+ }
+
+ @Test
+ public void testComputeHkdf_emptySalt() {
+ assertNull(Crypto.computeHkdf(TEST_IKM, new byte[] {}, TEST_SIZE));
+ }
+
+ @Test
+ public void testComputeHkdf_emptyIkm() {
+ assertNull(Crypto.computeHkdf(new byte[] {}, TEST_SALT, TEST_SIZE));
+ }
+
+ @Test
+ public void testComputeHkdf_success() {
+ assertNotNull(Crypto.computeHkdf(TEST_IKM, TEST_SALT, TEST_SIZE));
+ }
+}
diff --git a/service-t/Android.bp b/service-t/Android.bp
index ce5bb92..08527a3 100644
--- a/service-t/Android.bp
+++ b/service-t/Android.bp
@@ -19,6 +19,8 @@
default_applicable_licenses: ["Android-Apache-2.0"],
}
+service_remoteauth_pre_jarjar_lib = "service-remoteauth-pre-jarjar"
+
// Include build rules from Sources.bp
build = ["Sources.bp"]
@@ -56,6 +58,7 @@
"service-connectivity-pre-jarjar",
"service-nearby-pre-jarjar",
"service-thread-pre-jarjar",
+ service_remoteauth_pre_jarjar_lib,
"ServiceConnectivityResources",
"unsupportedappusage",
],
diff --git a/service-t/src/com/android/metrics/NetworkNsdReportedMetrics.java b/service-t/src/com/android/metrics/NetworkNsdReportedMetrics.java
index 052019f..597c06f 100644
--- a/service-t/src/com/android/metrics/NetworkNsdReportedMetrics.java
+++ b/service-t/src/com/android/metrics/NetworkNsdReportedMetrics.java
@@ -160,9 +160,10 @@
* @param foundCallbackCount The count of found service callbacks before stop discovery.
* @param lostCallbackCount The count of lost service callbacks before stop discovery.
* @param servicesCount The count of found services.
+ * @param sentQueryCount The count of sent queries before stop discovery.
*/
public void reportServiceDiscoveryStop(int transactionId, long durationMs,
- int foundCallbackCount, int lostCallbackCount, int servicesCount) {
+ int foundCallbackCount, int lostCallbackCount, int servicesCount, int sentQueryCount) {
final Builder builder = makeReportedBuilder();
builder.setTransactionId(transactionId);
builder.setType(NsdEventType.NET_DISCOVER);
@@ -171,6 +172,7 @@
builder.setFoundCallbackCount(foundCallbackCount);
builder.setLostCallbackCount(lostCallbackCount);
builder.setFoundServiceCount(servicesCount);
+ builder.setSentQueryCount(sentQueryCount);
mDependencies.statsWrite(builder.build());
}
@@ -180,15 +182,17 @@
* @param transactionId The transaction id of service resolution.
* @param durationMs The duration of resolving services.
* @param isServiceFromCache Whether the resolved service is from cache.
+ * @param sentQueryCount The count of sent queries during resolving.
*/
public void reportServiceResolved(int transactionId, long durationMs,
- boolean isServiceFromCache) {
+ boolean isServiceFromCache, int sentQueryCount) {
final Builder builder = makeReportedBuilder();
builder.setTransactionId(transactionId);
builder.setType(NsdEventType.NET_RESOLVE);
builder.setQueryResult(MdnsQueryResult.MQR_SERVICE_RESOLVED);
builder.setEventDurationMillisec(durationMs);
builder.setIsKnownService(isServiceFromCache);
+ builder.setSentQueryCount(sentQueryCount);
mDependencies.statsWrite(builder.build());
}
@@ -221,4 +225,55 @@
builder.setEventDurationMillisec(durationMs);
mDependencies.statsWrite(builder.build());
}
+
+ /**
+ * Report service info callback registered metric data.
+ *
+ * @param transactionId The transaction id of service info callback registration.
+ */
+ public void reportServiceInfoCallbackRegistered(int transactionId) {
+ final Builder builder = makeReportedBuilder();
+ builder.setTransactionId(transactionId);
+ builder.setType(NsdEventType.NET_SERVICE_INFO_CALLBACK);
+ builder.setQueryResult(MdnsQueryResult.MQR_SERVICE_INFO_CALLBACK_REGISTERED);
+ mDependencies.statsWrite(builder.build());
+ }
+
+ /**
+ * Report service info callback registration failed metric data.
+ *
+ * @param transactionId The transaction id of service callback registration.
+ */
+ public void reportServiceInfoCallbackRegistrationFailed(int transactionId) {
+ final Builder builder = makeReportedBuilder();
+ builder.setTransactionId(transactionId);
+ builder.setType(NsdEventType.NET_SERVICE_INFO_CALLBACK);
+ builder.setQueryResult(MdnsQueryResult.MQR_SERVICE_INFO_CALLBACK_REGISTRATION_FAILED);
+ mDependencies.statsWrite(builder.build());
+ }
+
+ /**
+ * Report service callback unregistered metric data.
+ *
+ * @param transactionId The transaction id of service callback registration.
+ * @param durationMs The duration of service callback stayed registered.
+ * @param updateCallbackCount The count of service update callbacks during this registration.
+ * @param lostCallbackCount The count of service lost callbacks during this registration.
+ * @param isServiceFromCache Whether the resolved service is from cache.
+ * @param sentQueryCount The count of sent queries during this registration.
+ */
+ public void reportServiceInfoCallbackUnregistered(int transactionId, long durationMs,
+ int updateCallbackCount, int lostCallbackCount, boolean isServiceFromCache,
+ int sentQueryCount) {
+ final Builder builder = makeReportedBuilder();
+ builder.setTransactionId(transactionId);
+ builder.setType(NsdEventType.NET_SERVICE_INFO_CALLBACK);
+ builder.setQueryResult(MdnsQueryResult.MQR_SERVICE_INFO_CALLBACK_UNREGISTERED);
+ builder.setEventDurationMillisec(durationMs);
+ builder.setFoundCallbackCount(updateCallbackCount);
+ builder.setLostCallbackCount(lostCallbackCount);
+ builder.setIsKnownService(isServiceFromCache);
+ builder.setSentQueryCount(sentQueryCount);
+ mDependencies.statsWrite(builder.build());
+ }
}
diff --git a/service-t/src/com/android/server/ConnectivityServiceInitializer.java b/service-t/src/com/android/server/ConnectivityServiceInitializer.java
index 626c2eb..624c5df 100644
--- a/service-t/src/com/android/server/ConnectivityServiceInitializer.java
+++ b/service-t/src/com/android/server/ConnectivityServiceInitializer.java
@@ -25,6 +25,7 @@
import com.android.server.ethernet.EthernetService;
import com.android.server.ethernet.EthernetServiceImpl;
import com.android.server.nearby.NearbyService;
+import com.android.server.remoteauth.RemoteAuthService;
/**
* Connectivity service initializer for core networking. This is called by system server to create
@@ -38,6 +39,7 @@
private final NsdService mNsdService;
private final NearbyService mNearbyService;
private final EthernetServiceImpl mEthernetServiceImpl;
+ private final RemoteAuthService mRemoteAuthService;
public ConnectivityServiceInitializer(Context context) {
super(context);
@@ -49,6 +51,7 @@
mConnectivityNative = createConnectivityNativeService(context);
mNsdService = createNsdService(context);
mNearbyService = createNearbyService(context);
+ mRemoteAuthService = createRemoteAuthService(context);
}
@Override
@@ -85,6 +88,11 @@
/* allowIsolated= */ false);
}
+ if (mRemoteAuthService != null) {
+ Log.i(TAG, "Registering " + RemoteAuthService.SERVICE_NAME);
+ publishBinderService(RemoteAuthService.SERVICE_NAME, mRemoteAuthService,
+ /* allowIsolated= */ false);
+ }
}
@Override
@@ -140,6 +148,19 @@
}
}
+ /** Return RemoteAuth service instance */
+ private RemoteAuthService createRemoteAuthService(final Context context) {
+ if (!SdkLevel.isAtLeastV()) return null;
+ try {
+ return new RemoteAuthService(context);
+ } catch (UnsupportedOperationException e) {
+ // RemoteAuth is not yet supported in all branches
+ // TODO: remove catch clause when it is available.
+ Log.i(TAG, "Skipping unsupported service " + RemoteAuthService.SERVICE_NAME);
+ return null;
+ }
+ }
+
/**
* Return EthernetServiceImpl instance or null if current SDK is lower than T or Ethernet
* service isn't necessary.
diff --git a/service-t/src/com/android/server/NsdService.java b/service-t/src/com/android/server/NsdService.java
index 415159d..27e97f1 100644
--- a/service-t/src/com/android/server/NsdService.java
+++ b/service-t/src/com/android/server/NsdService.java
@@ -176,6 +176,8 @@
private static final int MAX_SERVICES_COUNT_METRIC_PER_CLIENT = 100;
@VisibleForTesting
static final int NO_TRANSACTION = -1;
+ private static final int NO_SENT_QUERY_COUNT = 0;
+ private static final int DISCOVERY_QUERY_SENT_CALLBACK = 1000;
private static final SharedLog LOGGER = new SharedLog("serviceDiscovery");
private final Context mContext;
@@ -292,7 +294,8 @@
public void onSearchFailedToStart() { }
@Override
- public void onDiscoveryQuerySent(@NonNull List<String> subtypes, int transactionId) { }
+ public void onDiscoveryQuerySent(@NonNull List<String> subtypes,
+ int sentQueryTransactionId) { }
@Override
public void onFailedToParseMdnsResponse(int receivedPacketNumber, int errorCode) { }
@@ -319,6 +322,13 @@
NsdManager.SERVICE_LOST,
new MdnsEvent(mClientRequestId, serviceInfo));
}
+
+ @Override
+ public void onDiscoveryQuerySent(@NonNull List<String> subtypes,
+ int sentQueryTransactionId) {
+ mNsdStateMachine.sendMessage(MDNS_DISCOVERY_MANAGER_EVENT, mTransactionId,
+ DISCOVERY_QUERY_SENT_CALLBACK, new MdnsEvent(mClientRequestId));
+ }
}
private class ResolutionListener extends MdnsListener {
@@ -334,6 +344,13 @@
NsdManager.RESOLVE_SERVICE_SUCCEEDED,
new MdnsEvent(mClientRequestId, serviceInfo, isServiceFromCache));
}
+
+ @Override
+ public void onDiscoveryQuerySent(@NonNull List<String> subtypes,
+ int sentQueryTransactionId) {
+ mNsdStateMachine.sendMessage(MDNS_DISCOVERY_MANAGER_EVENT, mTransactionId,
+ DISCOVERY_QUERY_SENT_CALLBACK, new MdnsEvent(mClientRequestId));
+ }
}
private class ServiceInfoListener extends MdnsListener {
@@ -364,6 +381,13 @@
NsdManager.SERVICE_UPDATED_LOST,
new MdnsEvent(mClientRequestId, serviceInfo));
}
+
+ @Override
+ public void onDiscoveryQuerySent(@NonNull List<String> subtypes,
+ int sentQueryTransactionId) {
+ mNsdStateMachine.sendMessage(MDNS_DISCOVERY_MANAGER_EVENT, mTransactionId,
+ DISCOVERY_QUERY_SENT_CALLBACK, new MdnsEvent(mClientRequestId));
+ }
}
private class SocketRequestMonitor implements MdnsSocketProvider.SocketRequestMonitor {
@@ -469,15 +493,19 @@
*/
private static class MdnsEvent {
final int mClientRequestId;
- @NonNull
+ @Nullable
final MdnsServiceInfo mMdnsServiceInfo;
final boolean mIsServiceFromCache;
- MdnsEvent(int clientRequestId, @NonNull MdnsServiceInfo mdnsServiceInfo) {
+ MdnsEvent(int clientRequestId) {
+ this(clientRequestId, null /* mdnsServiceInfo */, false /* isServiceFromCache */);
+ }
+
+ MdnsEvent(int clientRequestId, @Nullable MdnsServiceInfo mdnsServiceInfo) {
this(clientRequestId, mdnsServiceInfo, false /* isServiceFromCache */);
}
- MdnsEvent(int clientRequestId, @NonNull MdnsServiceInfo mdnsServiceInfo,
+ MdnsEvent(int clientRequestId, @Nullable MdnsServiceInfo mdnsServiceInfo,
boolean isServiceFromCache) {
mClientRequestId = clientRequestId;
mMdnsServiceInfo = mdnsServiceInfo;
@@ -1118,6 +1146,7 @@
resolveServiceType, listener, options);
storeDiscoveryManagerRequestMap(clientRequestId, transactionId, listener,
clientInfo, info.getNetwork());
+ clientInfo.onServiceInfoCallbackRegistered(transactionId);
clientInfo.log("Register a ServiceInfoListener " + transactionId
+ " for service type:" + resolveServiceType);
break;
@@ -1144,7 +1173,7 @@
if (request instanceof DiscoveryManagerRequest) {
stopDiscoveryManagerRequest(
request, clientRequestId, transactionId, clientInfo);
- clientInfo.onServiceInfoCallbackUnregistered(clientRequestId);
+ clientInfo.onServiceInfoCallbackUnregistered(clientRequestId, request);
clientInfo.log("Unregister the ServiceInfoListener " + transactionId);
} else {
loge("Unregister failed with non-DiscoveryManagerRequest.");
@@ -1166,7 +1195,7 @@
// TODO: Limits the number of registrations created by a given class.
mOffloadEngines.register(offloadEngineInfo.mOffloadEngine,
offloadEngineInfo);
- // TODO: Sends all the existing OffloadServiceInfos back.
+ sendAllOffloadServiceInfos(offloadEngineInfo);
break;
case NsdManager.UNREGISTER_OFFLOAD_ENGINE:
mOffloadEngines.unregister((IOffloadEngine) msg.obj);
@@ -1414,17 +1443,25 @@
final MdnsEvent event = (MdnsEvent) obj;
final int clientRequestId = event.mClientRequestId;
+ final ClientRequest request = clientInfo.mClientRequests.get(clientRequestId);
+ if (request == null) {
+ Log.e(TAG, "Unknown client request. clientRequestId=" + clientRequestId);
+ return false;
+ }
+
+ // Deal with the discovery sent callback
+ if (code == DISCOVERY_QUERY_SENT_CALLBACK) {
+ request.onQuerySent();
+ return true;
+ }
+
+ // Deal with other callbacks.
final NsdServiceInfo info = buildNsdServiceInfoFromMdnsEvent(event, code);
// Errors are already logged if null
if (info == null) return false;
mServiceLogs.log(String.format(
"MdnsDiscoveryManager event code=%s transactionId=%d",
NsdManager.nameOf(code), transactionId));
- final ClientRequest request = clientInfo.mClientRequests.get(clientRequestId);
- if (request == null) {
- Log.e(TAG, "Unknown client request. clientRequestId=" + clientRequestId);
- return false;
- }
switch (code) {
case NsdManager.SERVICE_FOUND:
clientInfo.onServiceFound(clientRequestId, info, request);
@@ -1482,11 +1519,17 @@
final List<InetAddress> addresses = getInetAddresses(serviceInfo);
info.setHostAddresses(addresses);
- clientInfo.onServiceUpdated(clientRequestId, info);
+ clientInfo.onServiceUpdated(clientRequestId, info, request);
+ // Set the ServiceFromCache flag only if the service is actually being
+ // retrieved from the cache. This flag should not be overridden by later
+ // service updates, which may not be cached.
+ if (event.mIsServiceFromCache) {
+ request.setServiceFromCache(true);
+ }
break;
}
case NsdManager.SERVICE_UPDATED_LOST:
- clientInfo.onServiceUpdatedLost(clientRequestId);
+ clientInfo.onServiceUpdatedLost(clientRequestId, request);
break;
default:
return false;
@@ -1838,6 +1881,21 @@
}
}
+ private void sendAllOffloadServiceInfos(@NonNull OffloadEngineInfo offloadEngineInfo) {
+ final String targetInterface = offloadEngineInfo.mInterfaceName;
+ final IOffloadEngine offloadEngine = offloadEngineInfo.mOffloadEngine;
+ final List<MdnsAdvertiser.OffloadServiceInfoWrapper> offloadWrappers =
+ mAdvertiser.getAllInterfaceOffloadServiceInfos(targetInterface);
+ for (MdnsAdvertiser.OffloadServiceInfoWrapper wrapper : offloadWrappers) {
+ try {
+ offloadEngine.onOffloadServiceUpdated(wrapper.mOffloadServiceInfo);
+ } catch (RemoteException e) {
+ // Can happen in regular cases, do not log a stacktrace
+ Log.i(TAG, "Failed to send offload callback, remote died: " + e.getMessage());
+ }
+ }
+ }
+
private void sendOffloadServiceInfosUpdate(@NonNull String targetInterfaceName,
@NonNull OffloadServiceInfo offloadServiceInfo, boolean isRemove) {
final int count = mOffloadEngines.beginBroadcast();
@@ -1861,7 +1919,7 @@
}
} catch (RemoteException e) {
// Can happen in regular cases, do not log a stacktrace
- Log.i(TAG, "Failed to send offload callback, remote died", e);
+ Log.i(TAG, "Failed to send offload callback, remote died: " + e.getMessage());
}
}
} finally {
@@ -2238,6 +2296,7 @@
private int mLostServiceCount = 0;
private final Set<String> mServices = new ArraySet<>();
private boolean mIsServiceFromCache = false;
+ private int mSentQueryCount = NO_SENT_QUERY_COUNT;
private ClientRequest(int transactionId, long startTimeMs) {
mTransactionId = transactionId;
@@ -2278,6 +2337,14 @@
public boolean isServiceFromCache() {
return mIsServiceFromCache;
}
+
+ public void onQuerySent() {
+ mSentQueryCount++;
+ }
+
+ public int getSentQueryCount() {
+ return mSentQueryCount;
+ }
}
private static class LegacyClientRequest extends ClientRequest {
@@ -2412,10 +2479,18 @@
request.calculateRequestDurationMs(mClock.elapsedRealtime()),
request.getFoundServiceCount(),
request.getLostServiceCount(),
- request.getServicesCount());
+ request.getServicesCount(),
+ request.getSentQueryCount());
} else if (listener instanceof ResolutionListener) {
mMetrics.reportServiceResolutionStop(transactionId,
request.calculateRequestDurationMs(mClock.elapsedRealtime()));
+ } else if (listener instanceof ServiceInfoListener) {
+ mMetrics.reportServiceInfoCallbackUnregistered(transactionId,
+ request.calculateRequestDurationMs(mClock.elapsedRealtime()),
+ request.getFoundServiceCount(),
+ request.getLostServiceCount(),
+ request.isServiceFromCache(),
+ request.getSentQueryCount());
}
continue;
}
@@ -2438,7 +2513,8 @@
request.calculateRequestDurationMs(mClock.elapsedRealtime()),
request.getFoundServiceCount(),
request.getLostServiceCount(),
- request.getServicesCount());
+ request.getServicesCount(),
+ NO_SENT_QUERY_COUNT);
break;
case NsdManager.RESOLVE_SERVICE:
stopResolveService(transactionId);
@@ -2546,7 +2622,8 @@
request.calculateRequestDurationMs(mClock.elapsedRealtime()),
request.getFoundServiceCount(),
request.getLostServiceCount(),
- request.getServicesCount());
+ request.getServicesCount(),
+ request.getSentQueryCount());
try {
mCb.onStopDiscoverySucceeded(listenerKey);
} catch (RemoteException e) {
@@ -2614,7 +2691,8 @@
mMetrics.reportServiceResolved(
request.mTransactionId,
request.calculateRequestDurationMs(mClock.elapsedRealtime()),
- request.isServiceFromCache());
+ request.isServiceFromCache(),
+ request.getSentQueryCount());
try {
mCb.onResolveServiceSucceeded(listenerKey, info);
} catch (RemoteException e) {
@@ -2642,6 +2720,7 @@
}
void onServiceInfoCallbackRegistrationFailed(int listenerKey, int error) {
+ mMetrics.reportServiceInfoCallbackRegistrationFailed(NO_TRANSACTION);
try {
mCb.onServiceInfoCallbackRegistrationFailed(listenerKey, error);
} catch (RemoteException e) {
@@ -2649,7 +2728,12 @@
}
}
- void onServiceUpdated(int listenerKey, NsdServiceInfo info) {
+ void onServiceInfoCallbackRegistered(int transactionId) {
+ mMetrics.reportServiceInfoCallbackRegistered(transactionId);
+ }
+
+ void onServiceUpdated(int listenerKey, NsdServiceInfo info, ClientRequest request) {
+ request.onServiceFound(info.getServiceName());
try {
mCb.onServiceUpdated(listenerKey, info);
} catch (RemoteException e) {
@@ -2657,7 +2741,8 @@
}
}
- void onServiceUpdatedLost(int listenerKey) {
+ void onServiceUpdatedLost(int listenerKey, ClientRequest request) {
+ request.onServiceLost();
try {
mCb.onServiceUpdatedLost(listenerKey);
} catch (RemoteException e) {
@@ -2665,7 +2750,14 @@
}
}
- void onServiceInfoCallbackUnregistered(int listenerKey) {
+ void onServiceInfoCallbackUnregistered(int listenerKey, ClientRequest request) {
+ mMetrics.reportServiceInfoCallbackUnregistered(
+ request.mTransactionId,
+ request.calculateRequestDurationMs(mClock.elapsedRealtime()),
+ request.getFoundServiceCount(),
+ request.getLostServiceCount(),
+ request.isServiceFromCache(),
+ request.getSentQueryCount());
try {
mCb.onServiceInfoCallbackUnregistered(listenerKey);
} catch (RemoteException e) {
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsAdvertiser.java b/service-t/src/com/android/server/connectivity/mdns/MdnsAdvertiser.java
index dd72d11..f43df45 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsAdvertiser.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsAdvertiser.java
@@ -37,6 +37,7 @@
import com.android.server.connectivity.mdns.util.MdnsUtils;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.UUID;
@@ -117,6 +118,17 @@
}
}
+ /**
+ * Gets the current status of the OffloadServiceInfos per interface.
+ * @param interfaceName the target interfaceName
+ * @return the list of current offloaded services.
+ */
+ @NonNull
+ public List<OffloadServiceInfoWrapper> getAllInterfaceOffloadServiceInfos(
+ @NonNull String interfaceName) {
+ return mInterfaceOffloadServices.getOrDefault(interfaceName, Collections.emptyList());
+ }
+
private final MdnsInterfaceAdvertiser.Callback mInterfaceAdvertiserCb =
new MdnsInterfaceAdvertiser.Callback() {
@Override
@@ -385,9 +397,12 @@
}
}
- private static class OffloadServiceInfoWrapper {
- private final @NonNull OffloadServiceInfo mOffloadServiceInfo;
- private final int mServiceId;
+ /**
+ * The wrapper class for OffloadServiceInfo including the serviceId.
+ */
+ public static class OffloadServiceInfoWrapper {
+ public final @NonNull OffloadServiceInfo mOffloadServiceInfo;
+ public final int mServiceId;
OffloadServiceInfoWrapper(int serviceId, OffloadServiceInfo offloadServiceInfo) {
mOffloadServiceInfo = offloadServiceInfo;
diff --git a/service-t/src/com/android/server/ethernet/EthernetNetworkFactory.java b/service-t/src/com/android/server/ethernet/EthernetNetworkFactory.java
index ece10f3..0b54fdd 100644
--- a/service-t/src/com/android/server/ethernet/EthernetNetworkFactory.java
+++ b/service-t/src/com/android/server/ethernet/EthernetNetworkFactory.java
@@ -328,24 +328,24 @@
@Override
public void onProvisioningSuccess(LinkProperties newLp) {
- safelyPostOnHandler(() -> onIpLayerStarted(newLp));
+ safelyPostOnHandler(() -> handleOnProvisioningSuccess(newLp));
}
@Override
public void onProvisioningFailure(LinkProperties newLp) {
// This cannot happen due to provisioning timeout, because our timeout is 0. It can
// happen due to errors while provisioning or on provisioning loss.
- safelyPostOnHandler(() -> onIpLayerStopped());
+ safelyPostOnHandler(() -> handleOnProvisioningFailure());
}
@Override
public void onLinkPropertiesChange(LinkProperties newLp) {
- safelyPostOnHandler(() -> updateLinkProperties(newLp));
+ safelyPostOnHandler(() -> handleOnLinkPropertiesChange(newLp));
}
@Override
public void onReachabilityLost(String logMsg) {
- safelyPostOnHandler(() -> updateNeighborLostEvent(logMsg));
+ safelyPostOnHandler(() -> handleOnReachabilityLost(logMsg));
}
@Override
@@ -499,7 +499,7 @@
mIpClient.startProvisioning(createProvisioningConfiguration(mIpConfig));
}
- void onIpLayerStarted(@NonNull final LinkProperties linkProperties) {
+ private void handleOnProvisioningSuccess(@NonNull final LinkProperties linkProperties) {
if (mNetworkAgent != null) {
Log.e(TAG, "Already have a NetworkAgent - aborting new request");
stop();
@@ -533,7 +533,7 @@
mNetworkAgent.markConnected();
}
- void onIpLayerStopped() {
+ private void handleOnProvisioningFailure() {
// There is no point in continuing if the interface is gone as stop() will be triggered
// by removeInterface() when processed on the handler thread and start() won't
// work for a non-existent interface.
@@ -553,15 +553,15 @@
}
}
- void updateLinkProperties(LinkProperties linkProperties) {
+ private void handleOnLinkPropertiesChange(LinkProperties linkProperties) {
mLinkProperties = linkProperties;
if (mNetworkAgent != null) {
mNetworkAgent.sendLinkPropertiesImpl(linkProperties);
}
}
- void updateNeighborLostEvent(String logMsg) {
- Log.i(TAG, "updateNeighborLostEvent " + logMsg);
+ private void handleOnReachabilityLost(String logMsg) {
+ Log.i(TAG, "handleOnReachabilityLost " + logMsg);
if (mIpConfig.getIpAssignment() == IpAssignment.STATIC) {
// Ignore NUD failures for static IP configurations, where restarting the IpClient
// will not fix connectivity.
diff --git a/service/Android.bp b/service/Android.bp
index 9ae3d6c..8e59e86 100644
--- a/service/Android.bp
+++ b/service/Android.bp
@@ -19,6 +19,12 @@
default_applicable_licenses: ["Android-Apache-2.0"],
}
+service_remoteauth_pre_jarjar_lib = "service-remoteauth-pre-jarjar"
+
+// The above variables may have different values
+// depending on the branch, and this comment helps
+// separate them from the rest of the file to avoid merge conflicts
+
aidl_interface {
name: "connectivity_native_aidl_interface",
local_include_dir: "binder",
@@ -236,7 +242,7 @@
"service-connectivity-pre-jarjar",
"service-connectivity-tiramisu-pre-jarjar",
"service-nearby-pre-jarjar",
- "service-remoteauth-pre-jarjar",
+ service_remoteauth_pre_jarjar_lib,
"service-thread-pre-jarjar",
],
// The below libraries are not actually needed to build since no source is compiled
@@ -361,7 +367,7 @@
java_genrule {
name: "service-remoteauth-jarjar-gen",
tool_files: [
- ":service-remoteauth-pre-jarjar{.jar}",
+ ":" + service_remoteauth_pre_jarjar_lib + "{.jar}",
"jarjar-excludes.txt",
],
tools: [
@@ -369,7 +375,7 @@
],
out: ["service_remoteauth_jarjar_rules.txt"],
cmd: "$(location jarjar-rules-generator) " +
- "$(location :service-remoteauth-pre-jarjar{.jar}) " +
+ "$(location :" + service_remoteauth_pre_jarjar_lib + "{.jar}) " +
"--prefix com.android.server.remoteauth " +
"--excludes $(location jarjar-excludes.txt) " +
"--output $(out)",
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/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index ab5024c..04d0b93 100755
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -318,7 +318,6 @@
import java.io.InterruptedIOException;
import java.io.PrintWriter;
import java.io.Writer;
-import java.lang.IllegalArgumentException;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
@@ -11108,16 +11107,20 @@
@Override
public void onInterfaceLinkStateChanged(@NonNull String iface, boolean up) {
- for (NetworkAgentInfo nai : mNetworkAgentInfos) {
- nai.clatd.interfaceLinkStateChanged(iface, up);
- }
+ mHandler.post(() -> {
+ for (NetworkAgentInfo nai : mNetworkAgentInfos) {
+ nai.clatd.interfaceLinkStateChanged(iface, up);
+ }
+ });
}
@Override
public void onInterfaceRemoved(@NonNull String iface) {
- for (NetworkAgentInfo nai : mNetworkAgentInfos) {
- nai.clatd.interfaceRemoved(iface);
- }
+ mHandler.post(() -> {
+ for (NetworkAgentInfo nai : mNetworkAgentInfos) {
+ nai.clatd.interfaceRemoved(iface);
+ }
+ });
}
}
diff --git a/tests/cts/hostside/src/com/android/cts/net/HostsideNetworkTestCase.java b/tests/cts/hostside/src/com/android/cts/net/HostsideNetworkTestCase.java
index b89ab1f..3358fd7 100644
--- a/tests/cts/hostside/src/com/android/cts/net/HostsideNetworkTestCase.java
+++ b/tests/cts/hostside/src/com/android/cts/net/HostsideNetworkTestCase.java
@@ -48,7 +48,7 @@
@BeforeClassWithInfo
public static void setUpOnceBase(TestInformation testInfo) throws Exception {
DeviceSdkLevel deviceSdkLevel = new DeviceSdkLevel(testInfo.getDevice());
- String testApk = deviceSdkLevel.isDeviceAtLeastT() ? TEST_APK_NEXT : TEST_APK;
+ String testApk = deviceSdkLevel.isDeviceAtLeastV() ? TEST_APK_NEXT : TEST_APK;
uninstallPackage(testInfo, TEST_PKG, false);
installPackage(testInfo, testApk);
diff --git a/tests/cts/net/src/android/net/cts/BatteryStatsManagerTest.java b/tests/cts/net/src/android/net/cts/BatteryStatsManagerTest.java
index 3c71c90..466514c 100644
--- a/tests/cts/net/src/android/net/cts/BatteryStatsManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/BatteryStatsManagerTest.java
@@ -18,6 +18,7 @@
import static android.Manifest.permission.UPDATE_DEVICE_STATS;
import static android.content.pm.PackageManager.FEATURE_TELEPHONY;
+import static android.content.pm.PackageManager.FEATURE_WIFI;
import static androidx.test.InstrumentationRegistry.getContext;
@@ -118,8 +119,10 @@
// side effect is the point of using --write here.
executeShellCommand("dumpsys batterystats --write");
- // Make sure wifi is disabled.
- mCtsNetUtils.ensureWifiDisconnected(null /* wifiNetworkToCheck */);
+ if (mPm.hasSystemFeature(FEATURE_WIFI)) {
+ // Make sure wifi is disabled.
+ mCtsNetUtils.ensureWifiDisconnected(null /* wifiNetworkToCheck */);
+ }
verifyGetCellBatteryStats();
verifyGetWifiBatteryStats();
@@ -128,6 +131,9 @@
// Reset battery settings.
executeShellCommand("dumpsys batterystats disable no-auto-reset");
executeShellCommand("cmd battery reset");
+ if (mPm.hasSystemFeature(FEATURE_WIFI)) {
+ mCtsNetUtils.ensureWifiConnected();
+ }
}
}
@@ -153,23 +159,31 @@
// The mobile battery stats are updated when a network stops being the default network.
// ConnectivityService will call BatteryStatsManager.reportMobileRadioPowerState when
// removing data activity tracking.
- mCtsNetUtils.ensureWifiConnected();
+ try {
+ mCtsNetUtils.setMobileDataEnabled(false);
- // There's rate limit to update mobile battery so if ConnectivityService calls
- // BatteryStatsManager.reportMobileRadioPowerState when default network changed,
- // the mobile stats might not be updated. But if the mobile update due to other
- // reasons (plug/unplug, battery level change, etc) will be unaffected. Thus here
- // dumps the battery stats to trigger a full sync of data.
- executeShellCommand("dumpsys batterystats");
+ // There's rate limit to update mobile battery so if ConnectivityService calls
+ // BatteryStatsManager.reportMobileRadioPowerState when default network changed,
+ // the mobile stats might not be updated. But if the mobile update due to other
+ // reasons (plug/unplug, battery level change, etc) will be unaffected. Thus here
+ // dumps the battery stats to trigger a full sync of data.
+ executeShellCommand("dumpsys batterystats");
- // Check cellular battery stats are updated.
- runAsShell(UPDATE_DEVICE_STATS,
- () -> assertStatsEventually(mBsm::getCellularBatteryStats,
- cellularStatsAfter -> cellularBatteryStatsIncreased(
- cellularStatsBefore, cellularStatsAfter)));
+ // Check cellular battery stats are updated.
+ runAsShell(UPDATE_DEVICE_STATS,
+ () -> assertStatsEventually(mBsm::getCellularBatteryStats,
+ cellularStatsAfter -> cellularBatteryStatsIncreased(
+ cellularStatsBefore, cellularStatsAfter)));
+ } finally {
+ mCtsNetUtils.setMobileDataEnabled(true);
+ }
}
private void verifyGetWifiBatteryStats() throws Exception {
+ if (!mPm.hasSystemFeature(FEATURE_WIFI)) {
+ return;
+ }
+
final Network wifiNetwork = mCtsNetUtils.ensureWifiConnected();
final URL url = new URL(TEST_URL);
@@ -199,9 +213,9 @@
@Test
public void testReportNetworkInterfaceForTransports_throwsSecurityException()
throws Exception {
- Network wifiNetwork = mCtsNetUtils.ensureWifiConnected();
- final String iface = mCm.getLinkProperties(wifiNetwork).getInterfaceName();
- final int[] transportType = mCm.getNetworkCapabilities(wifiNetwork).getTransportTypes();
+ final Network network = mCm.getActiveNetwork();
+ final String iface = mCm.getLinkProperties(network).getInterfaceName();
+ final int[] transportType = mCm.getNetworkCapabilities(network).getTransportTypes();
assertThrows(SecurityException.class,
() -> mBsm.reportNetworkInterfaceForTransports(iface, transportType));
}
diff --git a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
index bce08df..3a76cc2 100644
--- a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
@@ -1025,11 +1025,13 @@
final String goodPrivateDnsServer = "dns.google";
mCtsNetUtils.storePrivateDnsSetting();
final TestableNetworkCallback cb = new TestableNetworkCallback();
- registerNetworkCallback(makeWifiNetworkRequest(), cb);
+ final NetworkRequest networkRequest = new NetworkRequest.Builder()
+ .addCapability(NET_CAPABILITY_INTERNET).build();
+ registerNetworkCallback(networkRequest, cb);
+ final Network networkForPrivateDns = mCm.getActiveNetwork();
try {
// Verifying the good private DNS sever
mCtsNetUtils.setPrivateDnsStrictMode(goodPrivateDnsServer);
- final Network networkForPrivateDns = mCtsNetUtils.ensureWifiConnected();
cb.eventuallyExpect(CallbackEntry.NETWORK_CAPS_UPDATED, NETWORK_CALLBACK_TIMEOUT_MS,
entry -> hasPrivateDnsValidated(entry, networkForPrivateDns));
@@ -1040,8 +1042,11 @@
.isPrivateDnsBroken()) && networkForPrivateDns.equals(entry.getNetwork()));
} finally {
mCtsNetUtils.restorePrivateDnsSetting();
- // Toggle wifi to make sure it is re-validated
- reconnectWifi();
+ // Toggle network to make sure it is re-validated
+ mCm.reportNetworkConnectivity(networkForPrivateDns, true);
+ cb.eventuallyExpect(CallbackEntry.NETWORK_CAPS_UPDATED, NETWORK_CALLBACK_TIMEOUT_MS,
+ entry -> !(((CallbackEntry.CapabilitiesChanged) entry).getCaps()
+ .isPrivateDnsBroken()) && networkForPrivateDns.equals(entry.getNetwork()));
}
}
@@ -1127,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));
@@ -1271,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 */);
}
@@ -1306,9 +1309,12 @@
@AppModeFull(reason = "Cannot get WifiManager in instant app mode")
@Test
public void testRequestNetworkCallback_onUnavailable() {
- final boolean previousWifiEnabledState = mWifiManager.isWifiEnabled();
- if (previousWifiEnabledState) {
- mCtsNetUtils.ensureWifiDisconnected(null);
+ boolean previousWifiEnabledState = false;
+ if (mPackageManager.hasSystemFeature(FEATURE_WIFI)) {
+ previousWifiEnabledState = mWifiManager.isWifiEnabled();
+ if (previousWifiEnabledState) {
+ mCtsNetUtils.ensureWifiDisconnected(null);
+ }
}
final TestNetworkCallback callback = new TestNetworkCallback();
@@ -1344,6 +1350,8 @@
@AppModeFull(reason = "Cannot get WifiManager in instant app mode")
@Test
public void testToggleWifiConnectivityAction() throws Exception {
+ assumeTrue(mPackageManager.hasSystemFeature(FEATURE_WIFI));
+
// toggleWifi calls connectToWifi and disconnectFromWifi, which both wait for
// CONNECTIVITY_ACTION broadcasts.
mCtsNetUtils.toggleWifi();
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/net/src/android/net/cts/NsdManagerTest.kt b/tests/cts/net/src/android/net/cts/NsdManagerTest.kt
index 49a6ef1..508e924 100644
--- a/tests/cts/net/src/android/net/cts/NsdManagerTest.kt
+++ b/tests/cts/net/src/android/net/cts/NsdManagerTest.kt
@@ -163,6 +163,7 @@
private val cm by lazy { context.getSystemService(ConnectivityManager::class.java)!! }
private val serviceName = "NsdTest%09d".format(Random().nextInt(1_000_000_000))
+ private val serviceName2 = "NsdTest%09d".format(Random().nextInt(1_000_000_000))
private val serviceType = "_nmt%09d._tcp".format(Random().nextInt(1_000_000_000))
private val handlerThread = HandlerThread(NsdManagerTest::class.java.simpleName)
private val ctsNetUtils by lazy{ CtsNetUtils(context) }
@@ -890,10 +891,11 @@
}
}
- fun checkOffloadServiceInfo(serviceInfo: OffloadServiceInfo) {
- assertEquals(serviceName, serviceInfo.key.serviceName)
- assertEquals(serviceType, serviceInfo.key.serviceType)
- assertEquals(listOf<String>("_subtype"), serviceInfo.subtypes)
+ fun checkOffloadServiceInfo(serviceInfo: OffloadServiceInfo, si: NsdServiceInfo) {
+ val expectedServiceType = si.serviceType.split(",")[0]
+ assertEquals(si.serviceName, serviceInfo.key.serviceName)
+ assertEquals(expectedServiceType, serviceInfo.key.serviceType)
+ assertEquals(listOf("_subtype"), serviceInfo.subtypes)
assertTrue(serviceInfo.hostname.startsWith("Android_"))
assertTrue(serviceInfo.hostname.endsWith("local"))
assertEquals(0, serviceInfo.priority)
@@ -907,36 +909,63 @@
// The offload callbacks are only supported with the new backend,
// enabled with target SDK U+.
assumeTrue(isAtLeastU() || targetSdkVersion > Build.VERSION_CODES.TIRAMISU)
+
+ // TODO: also have a test that use an executor that runs in a different thread, and pass
+ // in the thread ID NsdServiceInfo to check it
+ val si1 = NsdServiceInfo()
+ si1.serviceType = "$serviceType,_subtype"
+ si1.serviceName = serviceName
+ si1.network = testNetwork1.network
+ si1.port = 23456
+ val record1 = NsdRegistrationRecord()
+
+ val si2 = NsdServiceInfo()
+ si2.serviceType = "$serviceType,_subtype"
+ si2.serviceName = serviceName2
+ si2.network = testNetwork1.network
+ si2.port = 12345
+ val record2 = NsdRegistrationRecord()
val offloadEngine = TestNsdOffloadEngine()
- runAsShell(NETWORK_SETTINGS) {
- nsdManager.registerOffloadEngine(testNetwork1.iface.interfaceName,
- OffloadEngine.OFFLOAD_TYPE_REPLY.toLong(),
- OffloadEngine.OFFLOAD_CAPABILITY_BYPASS_MULTICAST_LOCK.toLong(),
- { it.run() }, offloadEngine)
- }
- val si = NsdServiceInfo()
- si.serviceType = "$serviceType,_subtype"
- si.serviceName = serviceName
- si.network = testNetwork1.network
- si.port = 12345
- val record = NsdRegistrationRecord()
- nsdManager.registerService(si, NsdManager.PROTOCOL_DNS_SD, record)
- val addOrUpdateEvent = offloadEngine
- .expectCallbackEventually<TestNsdOffloadEngine.OffloadEvent.AddOrUpdateEvent> {
- it.info.key.serviceName == serviceName
+ tryTest {
+ // Register service before the OffloadEngine is registered.
+ nsdManager.registerService(si1, NsdManager.PROTOCOL_DNS_SD, record1)
+ record1.expectCallback<ServiceRegistered>()
+ runAsShell(NETWORK_SETTINGS) {
+ nsdManager.registerOffloadEngine(testNetwork1.iface.interfaceName,
+ OffloadEngine.OFFLOAD_TYPE_REPLY.toLong(),
+ OffloadEngine.OFFLOAD_CAPABILITY_BYPASS_MULTICAST_LOCK.toLong(),
+ { it.run() }, offloadEngine)
}
- checkOffloadServiceInfo(addOrUpdateEvent.info)
+ val addOrUpdateEvent1 = offloadEngine
+ .expectCallbackEventually<TestNsdOffloadEngine.OffloadEvent.AddOrUpdateEvent> {
+ it.info.key.serviceName == si1.serviceName
+ }
+ checkOffloadServiceInfo(addOrUpdateEvent1.info, si1)
- nsdManager.unregisterService(record)
- val unregisterEvent = offloadEngine
- .expectCallbackEventually<TestNsdOffloadEngine.OffloadEvent.RemoveEvent> {
- it.info.key.serviceName == serviceName
+ // Register service after OffloadEngine is registered.
+ nsdManager.registerService(si2, NsdManager.PROTOCOL_DNS_SD, record2)
+ record2.expectCallback<ServiceRegistered>()
+ val addOrUpdateEvent2 = offloadEngine
+ .expectCallbackEventually<TestNsdOffloadEngine.OffloadEvent.AddOrUpdateEvent> {
+ it.info.key.serviceName == si2.serviceName
+ }
+ checkOffloadServiceInfo(addOrUpdateEvent2.info, si2)
+
+ nsdManager.unregisterService(record2)
+ record2.expectCallback<ServiceUnregistered>()
+ val unregisterEvent = offloadEngine
+ .expectCallbackEventually<TestNsdOffloadEngine.OffloadEvent.RemoveEvent> {
+ it.info.key.serviceName == si2.serviceName
+ }
+ checkOffloadServiceInfo(unregisterEvent.info, si2)
+ } cleanupStep {
+ runAsShell(NETWORK_SETTINGS) {
+ nsdManager.unregisterOffloadEngine(offloadEngine)
}
- checkOffloadServiceInfo(unregisterEvent.info)
-
- runAsShell(NETWORK_SETTINGS) {
- nsdManager.unregisterOffloadEngine(offloadEngine)
+ } cleanup {
+ nsdManager.unregisterService(record1)
+ record1.expectCallback<ServiceUnregistered>()
}
}
diff --git a/tests/cts/net/util/java/android/net/cts/util/CtsNetUtils.java b/tests/cts/net/util/java/android/net/cts/util/CtsNetUtils.java
index 21f1358..aa09b84 100644
--- a/tests/cts/net/util/java/android/net/cts/util/CtsNetUtils.java
+++ b/tests/cts/net/util/java/android/net/cts/util/CtsNetUtils.java
@@ -16,6 +16,7 @@
package android.net.cts.util;
+import static android.Manifest.permission.MODIFY_PHONE_STATE;
import static android.Manifest.permission.NETWORK_SETTINGS;
import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
@@ -54,6 +55,8 @@
import android.os.IBinder;
import android.system.Os;
import android.system.OsConstants;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.Log;
@@ -567,6 +570,42 @@
}
/**
+ * Enables or disables the mobile data and waits for the state to change.
+ *
+ * @param enabled - true to enable, false to disable the mobile data.
+ */
+ public void setMobileDataEnabled(boolean enabled) throws InterruptedException {
+ final TelephonyManager tm = mContext.getSystemService(TelephonyManager.class)
+ .createForSubscriptionId(SubscriptionManager.getDefaultDataSubscriptionId());
+ final NetworkRequest request = new NetworkRequest.Builder()
+ .addTransportType(TRANSPORT_CELLULAR)
+ .addCapability(NET_CAPABILITY_INTERNET)
+ .build();
+ final TestNetworkCallback callback = new TestNetworkCallback();
+ mCm.requestNetwork(request, callback);
+
+ try {
+ if (!enabled) {
+ assertNotNull("Cannot disable mobile data unless mobile data is connected",
+ callback.waitForAvailable());
+ }
+
+ runAsShell(MODIFY_PHONE_STATE, () -> tm.setDataEnabledForReason(
+ TelephonyManager.DATA_ENABLED_REASON_USER, enabled));
+ if (enabled) {
+ assertNotNull("Enabling mobile data did not connect mobile data",
+ callback.waitForAvailable());
+ } else {
+ assertNotNull("Disabling mobile data did not disconnect mobile data",
+ callback.waitForLost());
+ }
+
+ } finally {
+ mCm.unregisterNetworkCallback(callback);
+ }
+ }
+
+ /**
* Receiver that captures the last connectivity change's network type and state. Recognizes
* both {@code CONNECTIVITY_ACTION} and {@code NETWORK_CALLBACK_ACTION} intents.
*/
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/metrics/NetworkNsdReportedMetricsTest.kt b/tests/unit/java/com/android/metrics/NetworkNsdReportedMetricsTest.kt
index 97aa575..7f893df 100644
--- a/tests/unit/java/com/android/metrics/NetworkNsdReportedMetricsTest.kt
+++ b/tests/unit/java/com/android/metrics/NetworkNsdReportedMetricsTest.kt
@@ -141,9 +141,10 @@
val foundCallbackCount = 100
val lostCallbackCount = 49
val servicesCount = 75
+ val sentQueryCount = 150
val metrics = NetworkNsdReportedMetrics(true /* isLegacy */, clientId, deps)
- metrics.reportServiceDiscoveryStop(
- transactionId, durationMs, foundCallbackCount, lostCallbackCount, servicesCount)
+ metrics.reportServiceDiscoveryStop(transactionId, durationMs, foundCallbackCount,
+ lostCallbackCount, servicesCount, sentQueryCount)
val eventCaptor = ArgumentCaptor.forClass(NetworkNsdReported::class.java)
verify(deps).statsWrite(eventCaptor.capture())
@@ -158,6 +159,7 @@
assertEquals(lostCallbackCount, it.lostCallbackCount)
assertEquals(servicesCount, it.foundServiceCount)
assertEquals(durationMs, it.eventDurationMillisec)
+ assertEquals(sentQueryCount, it.sentQueryCount)
}
}
@@ -166,8 +168,10 @@
val clientId = 99
val transactionId = 100
val durationMs = 10L
+ val sentQueryCount = 0
val metrics = NetworkNsdReportedMetrics(true /* isLegacy */, clientId, deps)
- metrics.reportServiceResolved(transactionId, durationMs, true /* isServiceFromCache */)
+ metrics.reportServiceResolved(transactionId, durationMs, true /* isServiceFromCache */,
+ sentQueryCount)
val eventCaptor = ArgumentCaptor.forClass(NetworkNsdReported::class.java)
verify(deps).statsWrite(eventCaptor.capture())
@@ -179,6 +183,7 @@
assertEquals(MdnsQueryResult.MQR_SERVICE_RESOLVED, it.queryResult)
assertTrue(it.isKnownService)
assertEquals(durationMs, it.eventDurationMillisec)
+ assertEquals(sentQueryCount, it.sentQueryCount)
}
}
@@ -221,4 +226,70 @@
assertEquals(durationMs, it.eventDurationMillisec)
}
}
+
+ @Test
+ fun testReportServiceInfoCallbackRegistered() {
+ val clientId = 99
+ val transactionId = 100
+ val metrics = NetworkNsdReportedMetrics(false /* isLegacy */, clientId, deps)
+ metrics.reportServiceInfoCallbackRegistered(transactionId)
+
+ val eventCaptor = ArgumentCaptor.forClass(NetworkNsdReported::class.java)
+ verify(deps).statsWrite(eventCaptor.capture())
+ eventCaptor.value.let {
+ assertFalse(it.isLegacy)
+ assertEquals(clientId, it.clientId)
+ assertEquals(transactionId, it.transactionId)
+ assertEquals(NsdEventType.NET_SERVICE_INFO_CALLBACK, it.type)
+ assertEquals(MdnsQueryResult.MQR_SERVICE_INFO_CALLBACK_REGISTERED, it.queryResult)
+ }
+ }
+
+ @Test
+ fun testReportServiceInfoCallbackRegistrationFailed() {
+ val clientId = 99
+ val transactionId = 100
+ val metrics = NetworkNsdReportedMetrics(true /* isLegacy */, clientId, deps)
+ metrics.reportServiceInfoCallbackRegistrationFailed(transactionId)
+
+ val eventCaptor = ArgumentCaptor.forClass(NetworkNsdReported::class.java)
+ verify(deps).statsWrite(eventCaptor.capture())
+ eventCaptor.value.let {
+ assertTrue(it.isLegacy)
+ assertEquals(clientId, it.clientId)
+ assertEquals(transactionId, it.transactionId)
+ assertEquals(NsdEventType.NET_SERVICE_INFO_CALLBACK, it.type)
+ assertEquals(
+ MdnsQueryResult.MQR_SERVICE_INFO_CALLBACK_REGISTRATION_FAILED, it.queryResult)
+ }
+ }
+
+ @Test
+ fun testReportServiceInfoCallbackUnregistered() {
+ val clientId = 99
+ val transactionId = 100
+ val durationMs = 10L
+ val updateCallbackCount = 100
+ val lostCallbackCount = 10
+ val sentQueryCount = 150
+ val metrics = NetworkNsdReportedMetrics(false /* isLegacy */, clientId, deps)
+ metrics.reportServiceInfoCallbackUnregistered(transactionId, durationMs,
+ updateCallbackCount, lostCallbackCount, false /* isServiceFromCache */,
+ sentQueryCount)
+
+ val eventCaptor = ArgumentCaptor.forClass(NetworkNsdReported::class.java)
+ verify(deps).statsWrite(eventCaptor.capture())
+ eventCaptor.value.let {
+ assertFalse(it.isLegacy)
+ assertEquals(clientId, it.clientId)
+ assertEquals(transactionId, it.transactionId)
+ assertEquals(NsdEventType.NET_SERVICE_INFO_CALLBACK, it.type)
+ assertEquals(MdnsQueryResult.MQR_SERVICE_INFO_CALLBACK_UNREGISTERED, it.queryResult)
+ assertEquals(durationMs, it.eventDurationMillisec)
+ assertEquals(updateCallbackCount, it.foundCallbackCount)
+ assertEquals(lostCallbackCount, it.lostCallbackCount)
+ assertFalse(it.isKnownService)
+ assertEquals(sentQueryCount, it.sentQueryCount)
+ }
+ }
}
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..f8e3166 100755
--- a/tests/unit/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
@@ -424,7 +424,6 @@
import com.android.testutils.FunctionalUtils.ThrowingRunnable;
import com.android.testutils.HandlerUtils;
import com.android.testutils.RecorderCallback.CallbackEntry;
-import com.android.testutils.SkipPresubmit;
import com.android.testutils.TestableNetworkCallback;
import com.android.testutils.TestableNetworkOfferCallback;
@@ -7430,7 +7429,6 @@
assertPinnedToWifiWithCellDefault();
}
- @SkipPresubmit(reason = "Out of SLO flakiness")
@Test
public void testNetworkCallbackMaximum() throws Exception {
final int MAX_REQUESTS = 100;
@@ -7549,6 +7547,19 @@
NetworkCallback networkCallback = new NetworkCallback();
mCm.requestNetwork(networkRequest, networkCallback);
mCm.unregisterNetworkCallback(networkCallback);
+ // While requestNetwork increases the count synchronously, unregister decreases it
+ // asynchronously on a handler, so unregistering doesn't immediately free up
+ // a slot : calling unregister-register when max requests are registered throws.
+ // Potential fix : ConnectivityService catches TooManyRequestsException once when
+ // creating NetworkRequestInfo and waits for handler thread (see
+ // https://r.android.com/2707373 for impl). However, this complexity is not equal to
+ // the issue ; the purpose of having "max requests" is only to help apps detect leaks.
+ // Apps relying on exact enforcement or rapid request registration should reconsider.
+ //
+ // In this test, test thread registering all before handler thread decrements can cause
+ // flakes. A single waitForIdle at (e.g.) MAX_REQUESTS / 2 processes decrements up to
+ // that point, fixing the flake.
+ if (MAX_REQUESTS / 2 == i) waitForIdle();
}
waitForIdle();
@@ -7556,6 +7567,8 @@
NetworkCallback networkCallback = new NetworkCallback();
mCm.registerNetworkCallback(networkRequest, networkCallback);
mCm.unregisterNetworkCallback(networkCallback);
+ // See comment above for the reasons for this wait.
+ if (MAX_REQUESTS / 2 == i) waitForIdle();
}
waitForIdle();
@@ -7563,6 +7576,8 @@
NetworkCallback networkCallback = new NetworkCallback();
mCm.registerDefaultNetworkCallback(networkCallback);
mCm.unregisterNetworkCallback(networkCallback);
+ // See comment above for the reasons for this wait.
+ if (MAX_REQUESTS / 2 == i) waitForIdle();
}
waitForIdle();
@@ -7570,6 +7585,8 @@
NetworkCallback networkCallback = new NetworkCallback();
mCm.registerDefaultNetworkCallback(networkCallback);
mCm.unregisterNetworkCallback(networkCallback);
+ // See comment above for the reasons for this wait.
+ if (MAX_REQUESTS / 2 == i) waitForIdle();
}
waitForIdle();
@@ -7579,6 +7596,8 @@
mCm.registerDefaultNetworkCallbackForUid(1000000 + i, networkCallback,
new Handler(ConnectivityThread.getInstanceLooper()));
mCm.unregisterNetworkCallback(networkCallback);
+ // See comment above for the reasons for this wait.
+ if (MAX_REQUESTS / 2 == i) waitForIdle();
}
});
waitForIdle();
@@ -7588,6 +7607,8 @@
mContext, 0 /* requestCode */, new Intent("e" + i), FLAG_IMMUTABLE);
mCm.requestNetwork(networkRequest, pendingIntent);
mCm.unregisterNetworkCallback(pendingIntent);
+ // See comment above for the reasons for this wait.
+ if (MAX_REQUESTS / 2 == i) waitForIdle();
}
waitForIdle();
@@ -7596,6 +7617,8 @@
mContext, 0 /* requestCode */, new Intent("f" + i), FLAG_IMMUTABLE);
mCm.registerNetworkCallback(networkRequest, pendingIntent);
mCm.unregisterNetworkCallback(pendingIntent);
+ // See comment above for the reasons for this wait.
+ if (MAX_REQUESTS / 2 == i) waitForIdle();
}
}
@@ -16390,6 +16413,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 +16429,6 @@
@Test
public void testSetPreferenceWithMultiplePreferences()
throws Exception {
- final InOrder inOrder = inOrder(mMockNetd);
-
final UserHandle testHandle = setupEnterpriseNetwork();
mServiceContext.setWorkProfile(testHandle, true);
registerDefaultNetworkCallbacks();
@@ -16436,6 +16466,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 +16630,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);
diff --git a/tests/unit/java/com/android/server/NsdServiceTest.java b/tests/unit/java/com/android/server/NsdServiceTest.java
index 46ddeee..695cfe8 100644
--- a/tests/unit/java/com/android/server/NsdServiceTest.java
+++ b/tests/unit/java/com/android/server/NsdServiceTest.java
@@ -484,8 +484,8 @@
final ArgumentCaptor<NsdServiceInfo> resInfoCaptor =
ArgumentCaptor.forClass(NsdServiceInfo.class);
verify(resolveListener, timeout(TIMEOUT_MS)).onServiceResolved(resInfoCaptor.capture());
- verify(mMetrics).reportServiceResolved(
- getAddrId, 10L /* durationMs */, false /* isServiceFromCache */);
+ verify(mMetrics).reportServiceResolved(getAddrId, 10L /* durationMs */,
+ false /* isServiceFromCache */, 0 /* sentQueryCount */);
final NsdServiceInfo resolvedService = resInfoCaptor.getValue();
assertEquals(SERVICE_NAME, resolvedService.getServiceName());
@@ -834,13 +834,17 @@
client.registerServiceInfoCallback(request, Runnable::run, serviceInfoCallback);
waitForIdle();
// Verify the registration callback start.
- final ArgumentCaptor<MdnsServiceBrowserListener> listenerCaptor =
- ArgumentCaptor.forClass(MdnsServiceBrowserListener.class);
+ final ArgumentCaptor<MdnsListener> listenerCaptor =
+ ArgumentCaptor.forClass(MdnsListener.class);
verify(mSocketProvider).startMonitoringSockets();
verify(mDiscoveryManager).registerListener(eq(serviceTypeWithLocalDomain),
listenerCaptor.capture(), argThat(options -> network.equals(options.getNetwork())));
- final MdnsServiceBrowserListener listener = listenerCaptor.getValue();
+ final MdnsListener listener = listenerCaptor.getValue();
+ final int servInfoId = listener.mTransactionId;
+ // Verify the service info callback registered.
+ verify(mMetrics).reportServiceInfoCallbackRegistered(servInfoId);
+
final MdnsServiceInfo mdnsServiceInfo = new MdnsServiceInfo(
SERVICE_NAME,
serviceTypeWithLocalDomain.split("\\."),
@@ -854,8 +858,11 @@
1234,
network);
+ // Callbacks for query sent.
+ listener.onDiscoveryQuerySent(Collections.emptyList(), 1 /* transactionId */);
+
// Verify onServiceFound callback
- listener.onServiceFound(mdnsServiceInfo, false /* isServiceFromCache */);
+ listener.onServiceFound(mdnsServiceInfo, true /* isServiceFromCache */);
final ArgumentCaptor<NsdServiceInfo> updateInfoCaptor =
ArgumentCaptor.forClass(NsdServiceInfo.class);
verify(serviceInfoCallback, timeout(TIMEOUT_MS).times(1))
@@ -890,10 +897,18 @@
List.of(parseNumericAddress(v4Address), parseNumericAddress(v6Address)),
PORT, IFACE_IDX_ANY, new Network(999));
+ // Service lost then recovered.
+ listener.onServiceRemoved(updatedServiceInfo);
+ listener.onServiceFound(updatedServiceInfo, false /* isServiceFromCache */);
+
// Verify service callback unregistration.
+ doReturn(TEST_TIME_MS + 10L).when(mClock).elapsedRealtime();
client.unregisterServiceInfoCallback(serviceInfoCallback);
waitForIdle();
verify(serviceInfoCallback, timeout(TIMEOUT_MS)).onServiceInfoCallbackUnregistered();
+ verify(mMetrics).reportServiceInfoCallbackUnregistered(servInfoId, 10L /* durationMs */,
+ 3 /* updateCallbackCount */, 1 /* lostCallbackCount */,
+ true /* isServiceFromCache */, 1 /* sentQueryCount */);
}
@Test
@@ -909,6 +924,7 @@
// Fail to register service callback.
verify(serviceInfoCallback, timeout(TIMEOUT_MS))
.onServiceInfoCallbackRegistrationFailed(eq(FAILURE_BAD_PARAMETERS));
+ verify(mMetrics).reportServiceInfoCallbackRegistrationFailed(NO_TRANSACTION);
}
@Test
@@ -985,6 +1001,11 @@
final int discId = listener.mTransactionId;
verify(mMetrics).reportServiceDiscoveryStarted(discId);
+ // Callbacks for query sent.
+ listener.onDiscoveryQuerySent(Collections.emptyList(), 1 /* transactionId */);
+ listener.onDiscoveryQuerySent(Collections.emptyList(), 2 /* transactionId */);
+ listener.onDiscoveryQuerySent(Collections.emptyList(), 3 /* transactionId */);
+
final MdnsServiceInfo foundInfo = new MdnsServiceInfo(
SERVICE_NAME, /* serviceInstanceName */
serviceTypeWithLocalDomain.split("\\."), /* serviceType */
@@ -1033,7 +1054,8 @@
verify(discListener, timeout(TIMEOUT_MS)).onDiscoveryStopped(SERVICE_TYPE);
verify(mSocketProvider, timeout(CLEANUP_DELAY_MS + TIMEOUT_MS)).requestStopWhenInactive();
verify(mMetrics).reportServiceDiscoveryStop(discId, 10L /* durationMs */,
- 1 /* foundCallbackCount */, 1 /* lostCallbackCount */, 1 /* servicesCount */);
+ 1 /* foundCallbackCount */, 1 /* lostCallbackCount */, 1 /* servicesCount */,
+ 3 /* sentQueryCount */);
}
@Test
@@ -1145,8 +1167,8 @@
final ArgumentCaptor<NsdServiceInfo> infoCaptor =
ArgumentCaptor.forClass(NsdServiceInfo.class);
verify(resolveListener, timeout(TIMEOUT_MS)).onServiceResolved(infoCaptor.capture());
- verify(mMetrics).reportServiceResolved(
- listener.mTransactionId, 10 /* durationMs */, true /* isServiceFromCache */);
+ verify(mMetrics).reportServiceResolved(listener.mTransactionId, 10 /* durationMs */,
+ true /* isServiceFromCache */, 0 /* sendQueryCount */);
final NsdServiceInfo info = infoCaptor.getValue();
assertEquals(SERVICE_NAME, info.getServiceName());
diff --git a/tests/unit/java/com/android/server/connectivity/VpnTest.java b/tests/unit/java/com/android/server/connectivity/VpnTest.java
index 385f831..4f0d46f 100644
--- a/tests/unit/java/com/android/server/connectivity/VpnTest.java
+++ b/tests/unit/java/com/android/server/connectivity/VpnTest.java
@@ -57,6 +57,7 @@
import static com.android.server.connectivity.Vpn.PREFERRED_IKE_PROTOCOL_IPV6_ESP;
import static com.android.server.connectivity.Vpn.PREFERRED_IKE_PROTOCOL_IPV6_UDP;
import static com.android.testutils.Cleanup.testAndCleanup;
+import static com.android.testutils.HandlerUtils.waitForIdleSerialExecutor;
import static com.android.testutils.MiscAsserts.assertThrows;
import static org.junit.Assert.assertArrayEquals;
@@ -2500,6 +2501,40 @@
}
@Test
+ public void testStartPlatformVpn_underlyingNetworkNotChange() throws Exception {
+ final PlatformVpnSnapshot vpnSnapShot = verifySetupPlatformVpn(
+ createIkeConfig(createIkeConnectInfo(), true /* isMobikeEnabled */));
+ // Trigger update on the same network should not cause underlying network change in NC of
+ // the VPN network
+ vpnSnapShot.nwCb.onAvailable(TEST_NETWORK);
+ vpnSnapShot.nwCb.onCapabilitiesChanged(TEST_NETWORK,
+ new NetworkCapabilities.Builder()
+ .setSubscriptionIds(Set.of(TEST_SUB_ID))
+ .build());
+ // Verify setNetwork() called but no underlying network update
+ verify(mIkeSessionWrapper, timeout(TEST_TIMEOUT_MS)).setNetwork(eq(TEST_NETWORK),
+ eq(ESP_IP_VERSION_AUTO) /* ipVersion */,
+ eq(ESP_ENCAP_TYPE_AUTO) /* encapType */,
+ eq(DEFAULT_UDP_PORT_4500_NAT_TIMEOUT_SEC_INT) /* keepaliveDelay */);
+ verify(mMockNetworkAgent, never())
+ .doSetUnderlyingNetworks(any());
+
+ vpnSnapShot.nwCb.onAvailable(TEST_NETWORK_2);
+ vpnSnapShot.nwCb.onCapabilitiesChanged(TEST_NETWORK_2,
+ new NetworkCapabilities.Builder().build());
+
+ // A new network should trigger both setNetwork() and a underlying network update.
+ verify(mIkeSessionWrapper, timeout(TEST_TIMEOUT_MS)).setNetwork(eq(TEST_NETWORK_2),
+ eq(ESP_IP_VERSION_AUTO) /* ipVersion */,
+ eq(ESP_ENCAP_TYPE_AUTO) /* encapType */,
+ eq(DEFAULT_UDP_PORT_4500_NAT_TIMEOUT_SEC_INT) /* keepaliveDelay */);
+ verify(mMockNetworkAgent).doSetUnderlyingNetworks(
+ Collections.singletonList(TEST_NETWORK_2));
+
+ vpnSnapShot.vpn.mVpnRunner.exitVpnRunner();
+ }
+
+ @Test
public void testStartPlatformVpnMobility_mobikeEnabled() throws Exception {
final PlatformVpnSnapshot vpnSnapShot = verifySetupPlatformVpn(
createIkeConfig(createIkeConnectInfo(), true /* isMobikeEnabled */));
@@ -2523,6 +2558,12 @@
eq(ESP_IP_VERSION_AUTO) /* ipVersion */,
eq(ESP_ENCAP_TYPE_AUTO) /* encapType */,
eq(DEFAULT_UDP_PORT_4500_NAT_TIMEOUT_SEC_INT) /* keepaliveDelay */);
+ // Verify mNetworkCapabilities is updated
+ assertEquals(
+ Collections.singletonList(TEST_NETWORK_2),
+ vpnSnapShot.vpn.mNetworkCapabilities.getUnderlyingNetworks());
+ verify(mMockNetworkAgent)
+ .doSetUnderlyingNetworks(Collections.singletonList(TEST_NETWORK_2));
// Mock the MOBIKE procedure
vpnSnapShot.ikeCb.onIkeSessionConnectionInfoChanged(createIkeConnectInfo_2());
@@ -2535,15 +2576,11 @@
// Expect 2 times: one for initial setup and one for MOBIKE
verifyApplyTunnelModeTransforms(2);
- // Verify mNetworkCapabilities and mNetworkAgent are updated
- assertEquals(
- Collections.singletonList(TEST_NETWORK_2),
- vpnSnapShot.vpn.mNetworkCapabilities.getUnderlyingNetworks());
- verify(mMockNetworkAgent)
- .doSetUnderlyingNetworks(Collections.singletonList(TEST_NETWORK_2));
+ // Verify mNetworkAgent is updated
verify(mMockNetworkAgent).doSendLinkProperties(argThat(lp -> lp.getMtu() == newMtu));
verify(mMockNetworkAgent, never()).unregister();
-
+ // No further doSetUnderlyingNetworks interaction. The interaction count should stay one.
+ verify(mMockNetworkAgent, times(1)).doSetUnderlyingNetworks(any());
vpnSnapShot.vpn.mVpnRunner.exitVpnRunner();
}
@@ -2559,6 +2596,15 @@
// Mock new network available & MOBIKE procedures
vpnSnapShot.nwCb.onAvailable(TEST_NETWORK_2);
+ vpnSnapShot.nwCb.onCapabilitiesChanged(TEST_NETWORK_2,
+ new NetworkCapabilities.Builder().build());
+ // Verify mNetworkCapabilities is updated
+ verify(mMockNetworkAgent, timeout(TEST_TIMEOUT_MS))
+ .doSetUnderlyingNetworks(Collections.singletonList(TEST_NETWORK_2));
+ assertEquals(
+ Collections.singletonList(TEST_NETWORK_2),
+ vpnSnapShot.vpn.mNetworkCapabilities.getUnderlyingNetworks());
+
vpnSnapShot.ikeCb.onIkeSessionConnectionInfoChanged(createIkeConnectInfo_2());
vpnSnapShot.childCb.onIpSecTransformsMigrated(
createIpSecTransform(), createIpSecTransform());
@@ -2817,15 +2863,34 @@
// Verify MOBIKE is triggered
verifyMobikeTriggered(vpnSnapShot.vpn.mNetworkCapabilities.getUnderlyingNetworks(),
0 /* retryIndex */);
+ // Validation failure on VPN network should trigger a re-evaluation request for the
+ // underlying network.
+ verify(mConnectivityManager).reportNetworkConnectivity(TEST_NETWORK, false);
reset(mIkev2SessionCreator);
+ reset(mExecutor);
// Send validation status update.
// Recovered and get network validated. It should not trigger the ike session reset.
((Vpn.IkeV2VpnRunner) vpnSnapShot.vpn.mVpnRunner).onValidationStatus(
NetworkAgent.VALIDATION_STATUS_VALID);
+ // Verify that the retry count is reset. The mValidationFailRetryCount will not be reset
+ // until the executor finishes the execute() call, so wait until the all tasks are executed.
+ waitForIdleSerialExecutor(mExecutor, TEST_TIMEOUT_MS);
+ assertEquals(0,
+ ((Vpn.IkeV2VpnRunner) vpnSnapShot.vpn.mVpnRunner).mValidationFailRetryCount);
verify(mIkev2SessionCreator, never()).createIkeSession(
any(), any(), any(), any(), any(), any());
+
+ reset(mIkeSessionWrapper);
+ reset(mExecutor);
+
+ // Another validation fail should trigger another reportNetworkConnectivity
+ ((Vpn.IkeV2VpnRunner) vpnSnapShot.vpn.mVpnRunner).onValidationStatus(
+ NetworkAgent.VALIDATION_STATUS_NOT_VALID);
+ verifyMobikeTriggered(vpnSnapShot.vpn.mNetworkCapabilities.getUnderlyingNetworks(),
+ 0 /* retryIndex */);
+ verify(mConnectivityManager, times(2)).reportNetworkConnectivity(TEST_NETWORK, false);
}
@Test
@@ -2839,7 +2904,9 @@
NetworkAgent.VALIDATION_STATUS_NOT_VALID);
verifyMobikeTriggered(vpnSnapShot.vpn.mNetworkCapabilities.getUnderlyingNetworks(),
retry++);
-
+ // Validation failure on VPN network should trigger a re-evaluation request for the
+ // underlying network.
+ verify(mConnectivityManager).reportNetworkConnectivity(TEST_NETWORK, false);
reset(mIkev2SessionCreator);
// Second validation status update.
@@ -2847,6 +2914,8 @@
NetworkAgent.VALIDATION_STATUS_NOT_VALID);
verifyMobikeTriggered(vpnSnapShot.vpn.mNetworkCapabilities.getUnderlyingNetworks(),
retry++);
+ // Call to reportNetworkConnectivity should only happen once. No further interaction.
+ verify(mConnectivityManager, times(1)).reportNetworkConnectivity(TEST_NETWORK, false);
// Use real delay to verify reset session will not be performed if there is an existing
// recovery for resetting the session.
@@ -2863,6 +2932,8 @@
eq(TimeUnit.MILLISECONDS));
final List<Long> delays = delayCaptor.getAllValues();
assertEquals(expectedDelay, (long) delays.get(delays.size() - 1));
+ // Call to reportNetworkConnectivity should only happen once. No further interaction.
+ verify(mConnectivityManager, times(1)).reportNetworkConnectivity(TEST_NETWORK, false);
// Another invalid status reported should not trigger other scheduled recovery.
expectedDelay = mTestDeps.getValidationFailRecoveryMs(retry++);
@@ -2874,6 +2945,8 @@
// Verify that session being reset
verify(mIkev2SessionCreator, timeout(TEST_TIMEOUT_MS + expectedDelay))
.createIkeSession(any(), any(), any(), any(), any(), any());
+ // Call to reportNetworkConnectivity should only happen once. No further interaction.
+ verify(mConnectivityManager, times(1)).reportNetworkConnectivity(TEST_NETWORK, false);
}
@Test