Merge "[Thread] add missing guava dependency in tests" into main
diff --git a/Tethering/common/TetheringLib/api/lint-baseline.txt b/Tethering/common/TetheringLib/api/lint-baseline.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/Tethering/common/TetheringLib/api/lint-baseline.txt
diff --git a/Tethering/common/TetheringLib/api/module-lib-lint-baseline.txt b/Tethering/common/TetheringLib/api/module-lib-lint-baseline.txt
new file mode 100644
index 0000000..1d09598
--- /dev/null
+++ b/Tethering/common/TetheringLib/api/module-lib-lint-baseline.txt
@@ -0,0 +1,23 @@
+// Baseline format: 1.0
+BroadcastBehavior: android.net.TetheringManager#ACTION_TETHER_STATE_CHANGED:
+ Field 'ACTION_TETHER_STATE_CHANGED' is missing @BroadcastBehavior
+
+
+RequiresPermission: android.net.TetheringManager#requestLatestTetheringEntitlementResult(int, boolean, java.util.concurrent.Executor, android.net.TetheringManager.OnTetheringEntitlementResultListener):
+ Method 'requestLatestTetheringEntitlementResult' documentation mentions permissions already declared by @RequiresPermission
+RequiresPermission: android.net.TetheringManager#startTethering(android.net.TetheringManager.TetheringRequest, java.util.concurrent.Executor, android.net.TetheringManager.StartTetheringCallback):
+ Method 'startTethering' documentation mentions permissions already declared by @RequiresPermission
+RequiresPermission: android.net.TetheringManager#startTethering(int, java.util.concurrent.Executor, android.net.TetheringManager.StartTetheringCallback):
+ Method 'startTethering' documentation mentions permissions already declared by @RequiresPermission
+RequiresPermission: android.net.TetheringManager#stopAllTethering():
+ Method 'stopAllTethering' documentation mentions permissions already declared by @RequiresPermission
+RequiresPermission: android.net.TetheringManager#stopTethering(int):
+ Method 'stopTethering' documentation mentions permissions already declared by @RequiresPermission
+
+
+SdkConstant: android.net.TetheringManager#ACTION_TETHER_STATE_CHANGED:
+ Field 'ACTION_TETHER_STATE_CHANGED' is missing @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+
+
+Todo: android.net.TetheringConstants:
+ Documentation mentions 'TODO'
diff --git a/Tethering/common/TetheringLib/api/system-lint-baseline.txt b/Tethering/common/TetheringLib/api/system-lint-baseline.txt
new file mode 100644
index 0000000..e678ce1
--- /dev/null
+++ b/Tethering/common/TetheringLib/api/system-lint-baseline.txt
@@ -0,0 +1,17 @@
+// Baseline format: 1.0
+BroadcastBehavior: android.net.TetheringManager#ACTION_TETHER_STATE_CHANGED:
+ Field 'ACTION_TETHER_STATE_CHANGED' is missing @BroadcastBehavior
+
+
+RequiresPermission: android.net.TetheringManager#requestLatestTetheringEntitlementResult(int, boolean, java.util.concurrent.Executor, android.net.TetheringManager.OnTetheringEntitlementResultListener):
+ Method 'requestLatestTetheringEntitlementResult' documentation mentions permissions already declared by @RequiresPermission
+RequiresPermission: android.net.TetheringManager#startTethering(android.net.TetheringManager.TetheringRequest, java.util.concurrent.Executor, android.net.TetheringManager.StartTetheringCallback):
+ Method 'startTethering' documentation mentions permissions already declared by @RequiresPermission
+RequiresPermission: android.net.TetheringManager#stopAllTethering():
+ Method 'stopAllTethering' documentation mentions permissions already declared by @RequiresPermission
+RequiresPermission: android.net.TetheringManager#stopTethering(int):
+ Method 'stopTethering' documentation mentions permissions already declared by @RequiresPermission
+
+
+SdkConstant: android.net.TetheringManager#ACTION_TETHER_STATE_CHANGED:
+ Field 'ACTION_TETHER_STATE_CHANGED' is missing @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
diff --git a/framework-t/api/module-lib-lint-baseline.txt b/framework-t/api/module-lib-lint-baseline.txt
index 3158bd4..6f954df 100644
--- a/framework-t/api/module-lib-lint-baseline.txt
+++ b/framework-t/api/module-lib-lint-baseline.txt
@@ -5,3 +5,17 @@
Methods must not mention RuntimeException subclasses in throws clauses (was `java.lang.SecurityException`)
BannedThrow: android.app.usage.NetworkStatsManager#queryTaggedSummary(android.net.NetworkTemplate, long, long):
Methods must not mention RuntimeException subclasses in throws clauses (was `java.lang.SecurityException`)
+
+
+MissingPermission: android.net.IpSecManager#startTunnelModeTransformMigration(android.net.IpSecTransform, java.net.InetAddress, java.net.InetAddress):
+ Feature field FEATURE_IPSEC_TUNNEL_MIGRATION required by method android.net.IpSecManager.startTunnelModeTransformMigration(android.net.IpSecTransform, java.net.InetAddress, java.net.InetAddress) is hidden or removed
+
+
+RequiresPermission: android.app.usage.NetworkStatsManager#registerUsageCallback(android.net.NetworkTemplate, long, java.util.concurrent.Executor, android.app.usage.NetworkStatsManager.UsageCallback):
+ Method 'registerUsageCallback' documentation mentions permissions already declared by @RequiresPermission
+RequiresPermission: android.net.EthernetManager#disableInterface(String, java.util.concurrent.Executor, android.os.OutcomeReceiver<java.lang.String,android.net.EthernetNetworkManagementException>):
+ Method 'disableInterface' documentation mentions permissions already declared by @RequiresPermission
+RequiresPermission: android.net.EthernetManager#enableInterface(String, java.util.concurrent.Executor, android.os.OutcomeReceiver<java.lang.String,android.net.EthernetNetworkManagementException>):
+ Method 'enableInterface' documentation mentions permissions already declared by @RequiresPermission
+RequiresPermission: android.net.EthernetManager#updateConfiguration(String, android.net.EthernetNetworkUpdateRequest, java.util.concurrent.Executor, android.os.OutcomeReceiver<java.lang.String,android.net.EthernetNetworkManagementException>):
+ Method 'updateConfiguration' documentation mentions permissions already declared by @RequiresPermission
diff --git a/framework-t/api/system-current.txt b/framework-t/api/system-current.txt
index b285d85..05cf9e8 100644
--- a/framework-t/api/system-current.txt
+++ b/framework-t/api/system-current.txt
@@ -500,8 +500,8 @@
method @RequiresPermission(allOf={android.Manifest.permission.ACCESS_NETWORK_STATE, "android.permission.THREAD_NETWORK_PRIVILEGED"}) public void registerOperationalDatasetCallback(@NonNull java.util.concurrent.Executor, @NonNull android.net.thread.ThreadNetworkController.OperationalDatasetCallback);
method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public void registerStateCallback(@NonNull java.util.concurrent.Executor, @NonNull android.net.thread.ThreadNetworkController.StateCallback);
method @RequiresPermission("android.permission.THREAD_NETWORK_PRIVILEGED") public void scheduleMigration(@NonNull android.net.thread.PendingOperationalDataset, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.net.thread.ThreadNetworkException>);
- method public void unregisterOperationalDatasetCallback(@NonNull android.net.thread.ThreadNetworkController.OperationalDatasetCallback);
- method public void unregisterStateCallback(@NonNull android.net.thread.ThreadNetworkController.StateCallback);
+ method @RequiresPermission(allOf={android.Manifest.permission.ACCESS_NETWORK_STATE, "android.permission.THREAD_NETWORK_PRIVILEGED"}) public void unregisterOperationalDatasetCallback(@NonNull android.net.thread.ThreadNetworkController.OperationalDatasetCallback);
+ method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public void unregisterStateCallback(@NonNull android.net.thread.ThreadNetworkController.StateCallback);
field public static final int DEVICE_ROLE_CHILD = 2; // 0x2
field public static final int DEVICE_ROLE_DETACHED = 1; // 0x1
field public static final int DEVICE_ROLE_LEADER = 4; // 0x4
diff --git a/framework-t/api/system-lint-baseline.txt b/framework-t/api/system-lint-baseline.txt
index c6055f6..4f7af87 100644
--- a/framework-t/api/system-lint-baseline.txt
+++ b/framework-t/api/system-lint-baseline.txt
@@ -7,6 +7,18 @@
Methods must not throw generic exceptions (`java.lang.Throwable`)
+MissingPermission: android.net.IpSecManager#startTunnelModeTransformMigration(android.net.IpSecTransform, java.net.InetAddress, java.net.InetAddress):
+ Feature field FEATURE_IPSEC_TUNNEL_MIGRATION required by method android.net.IpSecManager.startTunnelModeTransformMigration(android.net.IpSecTransform, java.net.InetAddress, java.net.InetAddress) is hidden or removed
+
+
+RequiresPermission: android.net.EthernetManager#disableInterface(String, java.util.concurrent.Executor, android.os.OutcomeReceiver<java.lang.String,android.net.EthernetNetworkManagementException>):
+ Method 'disableInterface' documentation mentions permissions already declared by @RequiresPermission
+RequiresPermission: android.net.EthernetManager#enableInterface(String, java.util.concurrent.Executor, android.os.OutcomeReceiver<java.lang.String,android.net.EthernetNetworkManagementException>):
+ Method 'enableInterface' documentation mentions permissions already declared by @RequiresPermission
+RequiresPermission: android.net.EthernetManager#updateConfiguration(String, android.net.EthernetNetworkUpdateRequest, java.util.concurrent.Executor, android.os.OutcomeReceiver<java.lang.String,android.net.EthernetNetworkManagementException>):
+ Method 'updateConfiguration' documentation mentions permissions already declared by @RequiresPermission
+
+
UnflaggedApi: android.nearby.CredentialElement#equals(Object):
New API must be flagged with @FlaggedApi: method android.nearby.CredentialElement.equals(Object)
UnflaggedApi: android.nearby.CredentialElement#hashCode():
diff --git a/framework/api/lint-baseline.txt b/framework/api/lint-baseline.txt
index 2f4004a..4465bcb 100644
--- a/framework/api/lint-baseline.txt
+++ b/framework/api/lint-baseline.txt
@@ -1,4 +1,19 @@
// Baseline format: 1.0
+BroadcastBehavior: android.net.ConnectivityManager#ACTION_BACKGROUND_DATA_SETTING_CHANGED:
+ Field 'ACTION_BACKGROUND_DATA_SETTING_CHANGED' is missing @BroadcastBehavior
+
+
+RequiresPermission: android.net.ConnectivityManager#requestNetwork(android.net.NetworkRequest, android.app.PendingIntent):
+ Method 'requestNetwork' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.net.ConnectivityManager#requestNetwork(android.net.NetworkRequest, android.net.ConnectivityManager.NetworkCallback):
+ Method 'requestNetwork' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.net.NetworkCapabilities#getOwnerUid():
+ Method 'getOwnerUid' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.net.http.BidirectionalStream.Builder#setTrafficStatsUid(int):
+ Method 'setTrafficStatsUid' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.net.http.UrlRequest.Builder#setTrafficStatsUid(int):
+ Method 'setTrafficStatsUid' documentation mentions permissions without declaring @RequiresPermission
+
+
VisiblySynchronized: android.net.NetworkInfo#toString():
Internal locks must not be exposed (synchronizing on this or class is still
- externally observable): method android.net.NetworkInfo.toString()
diff --git a/framework/api/module-lib-current.txt b/framework/api/module-lib-current.txt
index bfb4981..026d8a9 100644
--- a/framework/api/module-lib-current.txt
+++ b/framework/api/module-lib-current.txt
@@ -235,6 +235,7 @@
public final class VpnTransportInfo implements android.os.Parcelable android.net.TransportInfo {
ctor @Deprecated public VpnTransportInfo(int, @Nullable String);
+ method public long getApplicableRedactions();
method @Nullable public String getSessionId();
method @NonNull public android.net.VpnTransportInfo makeCopy(long);
}
diff --git a/framework/api/module-lib-lint-baseline.txt b/framework/api/module-lib-lint-baseline.txt
new file mode 100644
index 0000000..53a8c5e
--- /dev/null
+++ b/framework/api/module-lib-lint-baseline.txt
@@ -0,0 +1,33 @@
+// Baseline format: 1.0
+BroadcastBehavior: android.net.ConnectivityManager#ACTION_BACKGROUND_DATA_SETTING_CHANGED:
+ Field 'ACTION_BACKGROUND_DATA_SETTING_CHANGED' is missing @BroadcastBehavior
+
+
+RequiresPermission: android.net.ConnectivityManager#isTetheringSupported():
+ Method 'isTetheringSupported' documentation mentions permissions already declared by @RequiresPermission
+RequiresPermission: android.net.ConnectivityManager#requestNetwork(android.net.NetworkRequest, android.app.PendingIntent):
+ Method 'requestNetwork' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.net.ConnectivityManager#requestNetwork(android.net.NetworkRequest, android.net.ConnectivityManager.NetworkCallback):
+ Method 'requestNetwork' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.net.ConnectivityManager#requestRouteToHostAddress(int, java.net.InetAddress):
+ Method 'requestRouteToHostAddress' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.net.LinkProperties#getCaptivePortalApiUrl():
+ Method 'getCaptivePortalApiUrl' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.net.LinkProperties#getCaptivePortalData():
+ Method 'getCaptivePortalData' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.net.NetworkCapabilities#getOwnerUid():
+ Method 'getOwnerUid' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.net.NetworkCapabilities#getUnderlyingNetworks():
+ Method 'getUnderlyingNetworks' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.net.NetworkCapabilities.Builder#setAllowedUids(java.util.Set<java.lang.Integer>):
+ Method 'setAllowedUids' documentation mentions permissions already declared by @RequiresPermission
+RequiresPermission: android.net.NetworkCapabilities.Builder#setSignalStrength(int):
+ Method 'setSignalStrength' documentation mentions permissions already declared by @RequiresPermission
+RequiresPermission: android.net.NetworkCapabilities.Builder#setUnderlyingNetworks(java.util.List<android.net.Network>):
+ Method 'setUnderlyingNetworks' documentation mentions permissions already declared by @RequiresPermission
+RequiresPermission: android.net.NetworkRequest.Builder#setSignalStrength(int):
+ Method 'setSignalStrength' documentation mentions permissions already declared by @RequiresPermission
+RequiresPermission: android.net.http.BidirectionalStream.Builder#setTrafficStatsUid(int):
+ Method 'setTrafficStatsUid' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.net.http.UrlRequest.Builder#setTrafficStatsUid(int):
+ Method 'setTrafficStatsUid' documentation mentions permissions without declaring @RequiresPermission
diff --git a/framework/api/system-lint-baseline.txt b/framework/api/system-lint-baseline.txt
index 9a97707..3ac97c0 100644
--- a/framework/api/system-lint-baseline.txt
+++ b/framework/api/system-lint-baseline.txt
@@ -1 +1,29 @@
// Baseline format: 1.0
+BroadcastBehavior: android.net.ConnectivityManager#ACTION_BACKGROUND_DATA_SETTING_CHANGED:
+ Field 'ACTION_BACKGROUND_DATA_SETTING_CHANGED' is missing @BroadcastBehavior
+
+
+RequiresPermission: android.net.ConnectivityManager#isTetheringSupported():
+ Method 'isTetheringSupported' documentation mentions permissions already declared by @RequiresPermission
+RequiresPermission: android.net.ConnectivityManager#requestNetwork(android.net.NetworkRequest, android.app.PendingIntent):
+ Method 'requestNetwork' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.net.ConnectivityManager#requestNetwork(android.net.NetworkRequest, android.net.ConnectivityManager.NetworkCallback):
+ Method 'requestNetwork' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.net.LinkProperties#getCaptivePortalApiUrl():
+ Method 'getCaptivePortalApiUrl' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.net.LinkProperties#getCaptivePortalData():
+ Method 'getCaptivePortalData' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.net.NetworkCapabilities#getOwnerUid():
+ Method 'getOwnerUid' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.net.NetworkCapabilities#getUnderlyingNetworks():
+ Method 'getUnderlyingNetworks' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.net.NetworkCapabilities.Builder#setSignalStrength(int):
+ Method 'setSignalStrength' documentation mentions permissions already declared by @RequiresPermission
+RequiresPermission: android.net.NetworkCapabilities.Builder#setUnderlyingNetworks(java.util.List<android.net.Network>):
+ Method 'setUnderlyingNetworks' documentation mentions permissions already declared by @RequiresPermission
+RequiresPermission: android.net.NetworkRequest.Builder#setSignalStrength(int):
+ Method 'setSignalStrength' documentation mentions permissions already declared by @RequiresPermission
+RequiresPermission: android.net.http.BidirectionalStream.Builder#setTrafficStatsUid(int):
+ Method 'setTrafficStatsUid' documentation mentions permissions without declaring @RequiresPermission
+RequiresPermission: android.net.http.UrlRequest.Builder#setTrafficStatsUid(int):
+ Method 'setTrafficStatsUid' documentation mentions permissions without declaring @RequiresPermission
diff --git a/framework/src/android/net/LocalNetworkConfig.java b/framework/src/android/net/LocalNetworkConfig.java
index e1b33e8..17b1064 100644
--- a/framework/src/android/net/LocalNetworkConfig.java
+++ b/framework/src/android/net/LocalNetworkConfig.java
@@ -62,11 +62,19 @@
return mUpstreamSelector;
}
- public @NonNull MulticastRoutingConfig getUpstreamMulticastRoutingConfig() {
+ /**
+ * Get the upstream multicast routing config
+ */
+ @NonNull
+ public MulticastRoutingConfig getUpstreamMulticastRoutingConfig() {
return mUpstreamMulticastRoutingConfig;
}
- public @NonNull MulticastRoutingConfig getDownstreamMulticastRoutingConfig() {
+ /**
+ * Get the downstream multicast routing config
+ */
+ @NonNull
+ public MulticastRoutingConfig getDownstreamMulticastRoutingConfig() {
return mDownstreamMulticastRoutingConfig;
}
@@ -109,13 +117,13 @@
public static final class Builder {
@Nullable
- NetworkRequest mUpstreamSelector;
+ private NetworkRequest mUpstreamSelector;
@Nullable
- MulticastRoutingConfig mUpstreamMulticastRoutingConfig;
+ private MulticastRoutingConfig mUpstreamMulticastRoutingConfig;
@Nullable
- MulticastRoutingConfig mDownstreamMulticastRoutingConfig;
+ private MulticastRoutingConfig mDownstreamMulticastRoutingConfig;
/**
* Create a Builder
diff --git a/framework/src/android/net/MulticastRoutingConfig.java b/framework/src/android/net/MulticastRoutingConfig.java
index 6f4ab11..4a3e1be 100644
--- a/framework/src/android/net/MulticastRoutingConfig.java
+++ b/framework/src/android/net/MulticastRoutingConfig.java
@@ -21,6 +21,7 @@
import android.annotation.Nullable;
import android.os.Parcel;
import android.os.Parcelable;
+import android.text.TextUtils;
import android.util.ArraySet;
import android.util.Log;
@@ -30,7 +31,9 @@
import java.net.UnknownHostException;
import java.util.Arrays;
import java.util.Collections;
+import java.util.Objects;
import java.util.Set;
+import java.util.StringJoiner;
/**
* A class representing a configuration for multicast routing.
@@ -38,8 +41,8 @@
* Internal usage to Connectivity
* @hide
*/
-// TODO : @SystemApi
-public class MulticastRoutingConfig implements Parcelable {
+// @SystemApi(client = MODULE_LIBRARIES)
+public final class MulticastRoutingConfig implements Parcelable {
private static final String TAG = MulticastRoutingConfig.class.getSimpleName();
/** Do not forward any multicast packets. */
@@ -55,6 +58,7 @@
*/
public static final int FORWARD_WITH_MIN_SCOPE = 2;
+ /** @hide */
@Retention(RetentionPolicy.SOURCE)
@IntDef(prefix = { "FORWARD_" }, value = {
FORWARD_NONE,
@@ -68,6 +72,7 @@
*/
public static final int MULTICAST_SCOPE_NONE = -1;
+ /** @hide */
public static final MulticastRoutingConfig CONFIG_FORWARD_NONE =
new MulticastRoutingConfig(FORWARD_NONE, MULTICAST_SCOPE_NONE, null);
@@ -102,7 +107,7 @@
* Returns the minimal group address scope that is allowed for forwarding.
* If the forwarding mode is not FORWARD_WITH_MIN_SCOPE, will be MULTICAST_SCOPE_NONE.
*/
- public int getMinScope() {
+ public int getMinimumScope() {
return mMinScope;
}
@@ -111,7 +116,7 @@
* The list will be empty if the forwarding mode is not FORWARD_SELECTED.
*/
@NonNull
- public Set<Inet6Address> getMulticastListeningAddresses() {
+ public Set<Inet6Address> getListeningAddresses() {
return mListeningAddresses;
}
@@ -133,7 +138,7 @@
}
@Override
- public void writeToParcel(Parcel dest, int flags) {
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeInt(mForwardingMode);
dest.writeInt(mMinScope);
dest.writeInt(mListeningAddresses.size());
@@ -147,9 +152,10 @@
return 0;
}
+ @NonNull
public static final Creator<MulticastRoutingConfig> CREATOR = new Creator<>() {
@Override
- public MulticastRoutingConfig createFromParcel(Parcel in) {
+ public MulticastRoutingConfig createFromParcel(@NonNull Parcel in) {
return new MulticastRoutingConfig(in);
}
@@ -159,15 +165,6 @@
}
};
- @Override
- public String toString() {
- return "MulticastRoutingConfig{"
- + "ForwardingMode=" + forwardingModeToString(mForwardingMode)
- + ", MinScope=" + mMinScope
- + ", ListeningAddresses=" + mListeningAddresses
- + '}';
- }
-
private static String forwardingModeToString(final int forwardingMode) {
switch (forwardingMode) {
case FORWARD_NONE: return "NONE";
@@ -177,43 +174,58 @@
}
}
- public static class Builder {
+ public static final class Builder {
@MulticastForwardingMode
private final int mForwardingMode;
private int mMinScope;
private final ArraySet<Inet6Address> mListeningAddresses;
- private Builder(@MulticastForwardingMode final int mode, int scope) {
+ // The two constructors with runtime checks for the mode and scope are arguably
+ // less convenient than three static factory methods, but API guidelines mandates
+ // that Builders are built with a constructor and not factory methods.
+ /**
+ * Create a new builder for forwarding mode FORWARD_NONE or FORWARD_SELECTED.
+ *
+ * <p>On a Builder for FORWARD_NONE, no properties can be set.
+ * <p>On a Builder for FORWARD_SELECTED, listening addresses can be added and removed
+ * but the minimum scope can't be set.
+ *
+ * @param mode {@link #FORWARD_NONE} or {@link #FORWARD_SELECTED}. Any other
+ * value will result in IllegalArgumentException.
+ * @see #Builder(int, int)
+ */
+ public Builder(@MulticastForwardingMode final int mode) {
+ if (FORWARD_NONE != mode && FORWARD_SELECTED != mode) {
+ if (FORWARD_WITH_MIN_SCOPE == mode) {
+ throw new IllegalArgumentException("FORWARD_WITH_MIN_SCOPE requires "
+ + "passing the scope as a second argument");
+ } else {
+ throw new IllegalArgumentException("Unknown forwarding mode : " + mode);
+ }
+ }
mForwardingMode = mode;
- mMinScope = scope;
+ mMinScope = MULTICAST_SCOPE_NONE;
mListeningAddresses = new ArraySet<>();
}
/**
- * Create a builder that forwards nothing.
- * No properties can be set on such a builder.
- */
- public static Builder newBuilderForwardingNone() {
- return new Builder(FORWARD_NONE, MULTICAST_SCOPE_NONE);
- }
-
- /**
- * Create a builder that forwards packets above a certain scope
+ * Create a new builder for forwarding mode FORWARD_WITH_MIN_SCOPE.
*
- * The scope can be changed on this builder, but not the listening addresses.
- * @param scope the initial scope
- */
- public static Builder newBuilderWithMinScope(final int scope) {
- return new Builder(FORWARD_WITH_MIN_SCOPE, scope);
- }
-
- /**
- * Create a builder that forwards a specified list of listening addresses.
+ * <p>On this Builder the scope can be set with {@link #setMinimumScope}, but
+ * listening addresses can't be added or removed.
*
- * Addresses can be added and removed from this builder, but the scope can't be set.
+ * @param mode Must be {@link #FORWARD_WITH_MIN_SCOPE}.
+ * @param scope the minimum scope for this multicast routing config.
+ * @see Builder#Builder(int)
*/
- public static Builder newBuilderWithListeningAddresses() {
- return new Builder(FORWARD_SELECTED, MULTICAST_SCOPE_NONE);
+ public Builder(@MulticastForwardingMode final int mode, int scope) {
+ if (FORWARD_WITH_MIN_SCOPE != mode) {
+ throw new IllegalArgumentException("Forwarding with a min scope must "
+ + "use forward mode FORWARD_WITH_MIN_SCOPE");
+ }
+ mForwardingMode = mode;
+ mMinScope = scope;
+ mListeningAddresses = new ArraySet<>();
}
/**
@@ -221,6 +233,7 @@
* This is only meaningful (indeed, allowed) for configs in FORWARD_WITH_MIN_SCOPE mode.
* @return this builder
*/
+ @NonNull
public Builder setMinimumScope(final int scope) {
if (FORWARD_WITH_MIN_SCOPE != mForwardingMode) {
throw new IllegalArgumentException("Can't set the scope on a builder in mode "
@@ -237,6 +250,7 @@
* If this address was already added, this is a no-op.
* @return this builder
*/
+ @NonNull
public Builder addListeningAddress(@NonNull final Inet6Address address) {
if (FORWARD_SELECTED != mForwardingMode) {
throw new IllegalArgumentException("Can't add an address on a builder in mode "
@@ -254,7 +268,8 @@
* If this address was not added, or was already removed, this is a no-op.
* @return this builder
*/
- public Builder removeListeningAddress(@NonNull final Inet6Address address) {
+ @NonNull
+ public Builder clearListeningAddress(@NonNull final Inet6Address address) {
if (FORWARD_SELECTED != mForwardingMode) {
throw new IllegalArgumentException("Can't remove an address on a builder in mode "
+ modeToString(mForwardingMode));
@@ -266,6 +281,7 @@
/**
* Build the config.
*/
+ @NonNull
public MulticastRoutingConfig build() {
return new MulticastRoutingConfig(mForwardingMode, mMinScope, mListeningAddresses);
}
@@ -279,4 +295,41 @@
default: return "unknown multicast routing mode " + mode;
}
}
+
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ } else if (!(other instanceof MulticastRoutingConfig)) {
+ return false;
+ } else {
+ final MulticastRoutingConfig otherConfig = (MulticastRoutingConfig) other;
+ return mForwardingMode == otherConfig.mForwardingMode
+ && mMinScope == otherConfig.mMinScope
+ && mListeningAddresses.equals(otherConfig.mListeningAddresses);
+ }
+ }
+
+ public int hashCode() {
+ return Objects.hash(mForwardingMode, mMinScope, mListeningAddresses);
+ }
+
+ public String toString() {
+ final StringJoiner resultJoiner = new StringJoiner(" ", "{", "}");
+
+ resultJoiner.add("ForwardingMode:");
+ resultJoiner.add(modeToString(mForwardingMode));
+
+ if (mForwardingMode == FORWARD_WITH_MIN_SCOPE) {
+ resultJoiner.add("MinScope:");
+ resultJoiner.add(Integer.toString(mMinScope));
+ }
+
+ if (mForwardingMode == FORWARD_SELECTED && !mListeningAddresses.isEmpty()) {
+ resultJoiner.add("ListeningAddresses: [");
+ resultJoiner.add(TextUtils.join(",", mListeningAddresses));
+ resultJoiner.add("]");
+ }
+
+ return resultJoiner.toString();
+ }
}
diff --git a/framework/src/android/net/QosSession.java b/framework/src/android/net/QosSession.java
index 25f3965..d1edae9 100644
--- a/framework/src/android/net/QosSession.java
+++ b/framework/src/android/net/QosSession.java
@@ -22,6 +22,9 @@
import android.os.Parcel;
import android.os.Parcelable;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
/**
* Provides identifying information of a QoS session. Sent to an application through
* {@link QosCallback}.
@@ -107,6 +110,7 @@
TYPE_EPS_BEARER,
TYPE_NR_BEARER,
})
+ @Retention(RetentionPolicy.SOURCE)
@interface QosSessionType {}
private QosSession(final Parcel in) {
diff --git a/nearby/framework/java/android/nearby/BroadcastRequest.java b/nearby/framework/java/android/nearby/BroadcastRequest.java
index 90f4d0f..6d6357d 100644
--- a/nearby/framework/java/android/nearby/BroadcastRequest.java
+++ b/nearby/framework/java/android/nearby/BroadcastRequest.java
@@ -88,6 +88,7 @@
* @hide
*/
@IntDef({MEDIUM_BLE})
+ @Retention(RetentionPolicy.SOURCE)
public @interface Medium {}
/**
diff --git a/nearby/framework/java/android/nearby/NearbyDevice.java b/nearby/framework/java/android/nearby/NearbyDevice.java
index e8fcc28..e7db0c5 100644
--- a/nearby/framework/java/android/nearby/NearbyDevice.java
+++ b/nearby/framework/java/android/nearby/NearbyDevice.java
@@ -25,6 +25,8 @@
import com.android.internal.util.Preconditions;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.List;
import java.util.Objects;
import java.util.Set;
@@ -149,6 +151,7 @@
* @hide
*/
@IntDef({Medium.BLE, Medium.BLUETOOTH})
+ @Retention(RetentionPolicy.SOURCE)
public @interface Medium {
int BLE = 1;
int BLUETOOTH = 2;
diff --git a/nearby/framework/java/android/nearby/NearbyManager.java b/nearby/framework/java/android/nearby/NearbyManager.java
index a70b303..070a2b6 100644
--- a/nearby/framework/java/android/nearby/NearbyManager.java
+++ b/nearby/framework/java/android/nearby/NearbyManager.java
@@ -34,6 +34,8 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.Preconditions;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.lang.ref.WeakReference;
import java.util.Objects;
import java.util.WeakHashMap;
@@ -63,6 +65,7 @@
ScanStatus.SUCCESS,
ScanStatus.ERROR,
})
+ @Retention(RetentionPolicy.SOURCE)
public @interface ScanStatus {
// The undetermined status, some modules may be initializing. Retry is suggested.
int UNKNOWN = 0;
diff --git a/service-t/src/com/android/server/NsdService.java b/service-t/src/com/android/server/NsdService.java
index c74f229..ee5f25b 100644
--- a/service-t/src/com/android/server/NsdService.java
+++ b/service-t/src/com/android/server/NsdService.java
@@ -1645,12 +1645,14 @@
mContext, MdnsFeatureFlags.NSD_FORCE_DISABLE_MDNS_OFFLOAD))
.setIncludeInetAddressRecordsInProbing(mDeps.isFeatureEnabled(
mContext, MdnsFeatureFlags.INCLUDE_INET_ADDRESS_RECORDS_IN_PROBING))
- .setIsExpiredServicesRemovalEnabled(mDeps.isTrunkStableFeatureEnabled(
- MdnsFeatureFlags.NSD_EXPIRED_SERVICES_REMOVAL))
+ .setIsExpiredServicesRemovalEnabled(mDeps.isFeatureEnabled(
+ mContext, MdnsFeatureFlags.NSD_EXPIRED_SERVICES_REMOVAL))
+ .setIsLabelCountLimitEnabled(mDeps.isTetheringFeatureNotChickenedOut(
+ mContext, MdnsFeatureFlags.NSD_LIMIT_LABEL_COUNT))
.build();
mMdnsSocketClient =
new MdnsMultinetworkSocketClient(handler.getLooper(), mMdnsSocketProvider,
- LOGGER.forSubComponent("MdnsMultinetworkSocketClient"));
+ LOGGER.forSubComponent("MdnsMultinetworkSocketClient"), flags);
mMdnsDiscoveryManager = deps.makeMdnsDiscoveryManager(new ExecutorProvider(),
mMdnsSocketClient, LOGGER.forSubComponent("MdnsDiscoveryManager"), flags);
handler.post(() -> mMdnsSocketClient.setCallback(mMdnsDiscoveryManager));
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsFeatureFlags.java b/service-t/src/com/android/server/connectivity/mdns/MdnsFeatureFlags.java
index 6f7645e..0a6d8c1 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsFeatureFlags.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsFeatureFlags.java
@@ -36,6 +36,11 @@
public static final String NSD_EXPIRED_SERVICES_REMOVAL =
"nsd_expired_services_removal";
+ /**
+ * A feature flag to control whether the label count limit should be enabled.
+ */
+ public static final String NSD_LIMIT_LABEL_COUNT = "nsd_limit_label_count";
+
// Flag for offload feature
public final boolean mIsMdnsOffloadFeatureEnabled;
@@ -45,14 +50,20 @@
// Flag for expired services removal
public final boolean mIsExpiredServicesRemovalEnabled;
+ // Flag for label count limit
+ public final boolean mIsLabelCountLimitEnabled;
+
/**
* The constructor for {@link MdnsFeatureFlags}.
*/
public MdnsFeatureFlags(boolean isOffloadFeatureEnabled,
- boolean includeInetAddressRecordsInProbing, boolean isExpiredServicesRemovalEnabled) {
+ boolean includeInetAddressRecordsInProbing,
+ boolean isExpiredServicesRemovalEnabled,
+ boolean isLabelCountLimitEnabled) {
mIsMdnsOffloadFeatureEnabled = isOffloadFeatureEnabled;
mIncludeInetAddressRecordsInProbing = includeInetAddressRecordsInProbing;
mIsExpiredServicesRemovalEnabled = isExpiredServicesRemovalEnabled;
+ mIsLabelCountLimitEnabled = isLabelCountLimitEnabled;
}
@@ -67,6 +78,7 @@
private boolean mIsMdnsOffloadFeatureEnabled;
private boolean mIncludeInetAddressRecordsInProbing;
private boolean mIsExpiredServicesRemovalEnabled;
+ private boolean mIsLabelCountLimitEnabled;
/**
* The constructor for {@link Builder}.
@@ -74,7 +86,8 @@
public Builder() {
mIsMdnsOffloadFeatureEnabled = false;
mIncludeInetAddressRecordsInProbing = false;
- mIsExpiredServicesRemovalEnabled = true; // Default enabled.
+ mIsExpiredServicesRemovalEnabled = false;
+ mIsLabelCountLimitEnabled = true; // Default enabled.
}
/**
@@ -109,11 +122,23 @@
}
/**
+ * Set whether the label count limit is enabled.
+ *
+ * @see #NSD_LIMIT_LABEL_COUNT
+ */
+ public Builder setIsLabelCountLimitEnabled(boolean isLabelCountLimitEnabled) {
+ mIsLabelCountLimitEnabled = isLabelCountLimitEnabled;
+ return this;
+ }
+
+ /**
* Builds a {@link MdnsFeatureFlags} with the arguments supplied to this builder.
*/
public MdnsFeatureFlags build() {
return new MdnsFeatureFlags(mIsMdnsOffloadFeatureEnabled,
- mIncludeInetAddressRecordsInProbing, mIsExpiredServicesRemovalEnabled);
+ mIncludeInetAddressRecordsInProbing,
+ mIsExpiredServicesRemovalEnabled,
+ mIsLabelCountLimitEnabled);
}
}
}
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiser.java b/service-t/src/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiser.java
index 42a6b0d..62c37ad 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiser.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiser.java
@@ -65,11 +65,12 @@
private final MdnsProber mProber;
@NonNull
private final MdnsReplySender mReplySender;
-
@NonNull
private final SharedLog mSharedLog;
@NonNull
private final byte[] mPacketCreationBuffer;
+ @NonNull
+ private final MdnsFeatureFlags mMdnsFeatureFlags;
/**
* Callbacks called by {@link MdnsInterfaceAdvertiser} to report status updates.
@@ -213,6 +214,7 @@
mProber = deps.makeMdnsProber(sharedLog.getTag(), looper, mReplySender, mProbingCallback,
sharedLog);
mSharedLog = sharedLog;
+ mMdnsFeatureFlags = mdnsFeatureFlags;
}
/**
@@ -351,7 +353,7 @@
public void handlePacket(byte[] recvbuf, int length, InetSocketAddress src) {
final MdnsPacket packet;
try {
- packet = MdnsPacket.parse(new MdnsPacketReader(recvbuf, length));
+ packet = MdnsPacket.parse(new MdnsPacketReader(recvbuf, length, mMdnsFeatureFlags));
} catch (MdnsPacket.ParseException e) {
mSharedLog.e("Error parsing mDNS packet", e);
if (DBG) {
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsMultinetworkSocketClient.java b/service-t/src/com/android/server/connectivity/mdns/MdnsMultinetworkSocketClient.java
index 4ba6912..e7b0eaa 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsMultinetworkSocketClient.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsMultinetworkSocketClient.java
@@ -50,6 +50,7 @@
@NonNull private final Handler mHandler;
@NonNull private final MdnsSocketProvider mSocketProvider;
@NonNull private final SharedLog mSharedLog;
+ @NonNull private final MdnsFeatureFlags mMdnsFeatureFlags;
private final ArrayMap<MdnsServiceBrowserListener, InterfaceSocketCallback> mSocketRequests =
new ArrayMap<>();
@@ -58,11 +59,12 @@
private int mReceivedPacketNumber = 0;
public MdnsMultinetworkSocketClient(@NonNull Looper looper,
- @NonNull MdnsSocketProvider provider,
- @NonNull SharedLog sharedLog) {
+ @NonNull MdnsSocketProvider provider, @NonNull SharedLog sharedLog,
+ @NonNull MdnsFeatureFlags mdnsFeatureFlags) {
mHandler = new Handler(looper);
mSocketProvider = provider;
mSharedLog = sharedLog;
+ mMdnsFeatureFlags = mdnsFeatureFlags;
}
private class InterfaceSocketCallback implements MdnsSocketProvider.SocketCallback {
@@ -239,7 +241,7 @@
final MdnsPacket response;
try {
- response = MdnsResponseDecoder.parseResponse(recvbuf, length);
+ response = MdnsResponseDecoder.parseResponse(recvbuf, length, mMdnsFeatureFlags);
} catch (MdnsPacket.ParseException e) {
if (e.code != MdnsResponseErrorCode.ERROR_NOT_RESPONSE_MESSAGE) {
mSharedLog.e(e.getMessage(), e);
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsPacketReader.java b/service-t/src/com/android/server/connectivity/mdns/MdnsPacketReader.java
index aa38844..4917188 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsPacketReader.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsPacketReader.java
@@ -16,6 +16,7 @@
package com.android.server.connectivity.mdns;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.util.SparseArray;
@@ -33,21 +34,23 @@
private final byte[] buf;
private final int count;
private final SparseArray<LabelEntry> labelDictionary;
+ private final MdnsFeatureFlags mMdnsFeatureFlags;
private int pos;
private int limit;
/** Constructs a reader for the given packet. */
public MdnsPacketReader(DatagramPacket packet) {
- this(packet.getData(), packet.getLength());
+ this(packet.getData(), packet.getLength(), MdnsFeatureFlags.newBuilder().build());
}
/** Constructs a reader for the given packet. */
- public MdnsPacketReader(byte[] buffer, int length) {
+ public MdnsPacketReader(byte[] buffer, int length, @NonNull MdnsFeatureFlags mdnsFeatureFlags) {
buf = buffer;
count = length;
pos = 0;
limit = -1;
labelDictionary = new SparseArray<>(16);
+ mMdnsFeatureFlags = mdnsFeatureFlags;
}
/**
@@ -269,4 +272,4 @@
this.label = label;
}
}
-}
\ No newline at end of file
+}
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsRecordRepository.java b/service-t/src/com/android/server/connectivity/mdns/MdnsRecordRepository.java
index 73c1758..e34778f 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsRecordRepository.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsRecordRepository.java
@@ -133,11 +133,6 @@
public final boolean isSharedName;
/**
- * Whether probing is still in progress for the record.
- */
- public boolean isProbing;
-
- /**
* Last time (as per SystemClock.elapsedRealtime) when advertised via multicast, 0 if never
*/
public long lastAdvertisedTimeMs;
@@ -148,12 +143,10 @@
*/
public long lastSentTimeMs;
- RecordInfo(NsdServiceInfo serviceInfo, T record, boolean sharedName,
- boolean probing) {
+ RecordInfo(NsdServiceInfo serviceInfo, T record, boolean sharedName) {
this.serviceInfo = serviceInfo;
this.record = record;
this.isSharedName = sharedName;
- this.isProbing = probing;
}
}
@@ -187,6 +180,11 @@
public int sentPacketCount = NO_PACKET;
/**
+ * Whether probing is still in progress.
+ */
+ private boolean isProbing;
+
+ /**
* Create a ServiceRegistration for dns-sd service registration (RFC6763).
*
* @param deviceHostname Hostname of the device (for the interface used)
@@ -209,7 +207,7 @@
false /* cacheFlush */,
NON_NAME_RECORDS_TTL_MILLIS,
serviceName),
- true /* sharedName */, true /* probing */);
+ true /* sharedName */);
if (subtype == null) {
this.ptrRecords = Collections.singletonList(ptrRecord);
@@ -226,7 +224,7 @@
false /* cacheFlush */,
NON_NAME_RECORDS_TTL_MILLIS,
serviceName),
- true /* sharedName */, true /* probing */);
+ true /* sharedName */);
this.ptrRecords = List.of(ptrRecord, subtypeRecord);
}
@@ -239,7 +237,7 @@
NAME_RECORDS_TTL_MILLIS, 0 /* servicePriority */, 0 /* serviceWeight */,
serviceInfo.getPort(),
deviceHostname),
- false /* sharedName */, true /* probing */);
+ false /* sharedName */);
txtRecord = new RecordInfo<>(
serviceInfo,
@@ -248,7 +246,7 @@
true /* cacheFlush */, // Service name is verified unique after probing
NON_NAME_RECORDS_TTL_MILLIS,
attrsToTextEntries(serviceInfo.getAttributes())),
- false /* sharedName */, true /* probing */);
+ false /* sharedName */);
final ArrayList<RecordInfo<?>> allRecords = new ArrayList<>(5);
allRecords.addAll(ptrRecords);
@@ -263,18 +261,18 @@
false /* cacheFlush */,
NON_NAME_RECORDS_TTL_MILLIS,
serviceType),
- true /* sharedName */, true /* probing */));
+ true /* sharedName */));
this.allRecords = Collections.unmodifiableList(allRecords);
this.repliedServiceCount = repliedServiceCount;
this.sentPacketCount = sentPacketCount;
+ this.isProbing = true;
}
void setProbing(boolean probing) {
- for (RecordInfo<?> info : allRecords) {
- info.isProbing = probing;
- }
+ this.isProbing = probing;
}
+
}
/**
@@ -292,7 +290,7 @@
true /* cacheFlush */,
NAME_RECORDS_TTL_MILLIS,
mDeviceHostname),
- false /* sharedName */, false /* probing */));
+ false /* sharedName */));
mGeneralRecords.add(new RecordInfo<>(
null /* serviceInfo */,
@@ -302,7 +300,7 @@
true /* cacheFlush */,
NAME_RECORDS_TTL_MILLIS,
addr.getAddress()),
- false /* sharedName */, false /* probing */));
+ false /* sharedName */));
}
}
@@ -485,7 +483,7 @@
// Add answers from each service
for (int i = 0; i < mServices.size(); i++) {
final ServiceRegistration registration = mServices.valueAt(i);
- if (registration.exiting) continue;
+ if (registration.exiting || registration.isProbing) continue;
if (addReplyFromService(question, registration.allRecords, registration.ptrRecords,
registration.srvRecord, registration.txtRecord, replyUnicast, now,
answerInfo, additionalAnswerRecords)) {
@@ -558,7 +556,6 @@
final int answersStartIndex = answerInfo.size();
for (RecordInfo<?> info : serviceRecords) {
- if (info.isProbing) continue;
/* RFC6762 6.: the record name must match the question name, the record rrtype
must match the question qtype unless the qtype is "ANY" (255) or the rrtype is
@@ -870,7 +867,7 @@
final ServiceRegistration registration = mServices.get(serviceId);
if (registration == null) return false;
- return registration.srvRecord.isProbing;
+ return registration.isProbing;
}
/**
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsResponse.java b/service-t/src/com/android/server/connectivity/mdns/MdnsResponse.java
index e2288c1..05ad1be 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsResponse.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsResponse.java
@@ -33,6 +33,7 @@
/** An mDNS response. */
public class MdnsResponse {
+ public static final long EXPIRATION_NEVER = Long.MAX_VALUE;
private final List<MdnsRecord> records;
private final List<MdnsPointerRecord> pointerRecords;
private MdnsServiceRecord serviceRecord;
@@ -349,6 +350,21 @@
return serviceName;
}
+ /** Get the min remaining ttl time from received records */
+ public long getMinRemainingTtl(long now) {
+ long minRemainingTtl = EXPIRATION_NEVER;
+ // TODO: Check other records(A, AAAA, TXT) ttl time.
+ if (!hasServiceRecord()) {
+ return EXPIRATION_NEVER;
+ }
+ // Check ttl time.
+ long remainingTtl = serviceRecord.getRemainingTTL(now);
+ if (remainingTtl < minRemainingTtl) {
+ minRemainingTtl = remainingTtl;
+ }
+ return minRemainingTtl;
+ }
+
/**
* Tests if this response is a goodbye message. This will be true if a service record is present
* and any of the records have a TTL of 0.
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsResponseDecoder.java b/service-t/src/com/android/server/connectivity/mdns/MdnsResponseDecoder.java
index 050913f..b812bb4 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsResponseDecoder.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsResponseDecoder.java
@@ -84,9 +84,9 @@
* @throws MdnsPacket.ParseException if a response packet could not be parsed.
*/
@NonNull
- public static MdnsPacket parseResponse(@NonNull byte[] recvbuf, int length)
- throws MdnsPacket.ParseException {
- MdnsPacketReader reader = new MdnsPacketReader(recvbuf, length);
+ public static MdnsPacket parseResponse(@NonNull byte[] recvbuf, int length,
+ @NonNull MdnsFeatureFlags mdnsFeatureFlags) throws MdnsPacket.ParseException {
+ final MdnsPacketReader reader = new MdnsPacketReader(recvbuf, length, mdnsFeatureFlags);
final MdnsPacket mdnsPacket;
try {
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsServiceCache.java b/service-t/src/com/android/server/connectivity/mdns/MdnsServiceCache.java
index d3493c7..e9a41d1 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsServiceCache.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsServiceCache.java
@@ -16,16 +16,22 @@
package com.android.server.connectivity.mdns;
+import static com.android.server.connectivity.mdns.MdnsResponse.EXPIRATION_NEVER;
import static com.android.server.connectivity.mdns.util.MdnsUtils.ensureRunningOnHandlerThread;
import static com.android.server.connectivity.mdns.util.MdnsUtils.equalsIgnoreDnsCase;
import static com.android.server.connectivity.mdns.util.MdnsUtils.toDnsLowerCase;
+import static java.lang.Math.min;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Handler;
import android.os.Looper;
import android.util.ArrayMap;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.connectivity.mdns.util.MdnsUtils;
+
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
@@ -67,8 +73,11 @@
}
}
/**
- * A map of cached services. Key is composed of service name, type and socket. Value is the
- * service which use the service type to discover from each socket.
+ * A map of cached services. Key is composed of service type and socket. Value is the list of
+ * services which are discovered from the given CacheKey.
+ * When the MdnsFeatureFlags#NSD_EXPIRED_SERVICES_REMOVAL flag is enabled, the lists are sorted
+ * by expiration time, with the earliest entries appearing first. This sorting allows the
+ * removal process to progress through the expiration check efficiently.
*/
@NonNull
private final ArrayMap<CacheKey, List<MdnsResponse>> mCachedServices = new ArrayMap<>();
@@ -82,10 +91,20 @@
private final Handler mHandler;
@NonNull
private final MdnsFeatureFlags mMdnsFeatureFlags;
+ @NonNull
+ private final MdnsUtils.Clock mClock;
+ private long mNextExpirationTime = EXPIRATION_NEVER;
public MdnsServiceCache(@NonNull Looper looper, @NonNull MdnsFeatureFlags mdnsFeatureFlags) {
+ this(looper, mdnsFeatureFlags, new MdnsUtils.Clock());
+ }
+
+ @VisibleForTesting
+ MdnsServiceCache(@NonNull Looper looper, @NonNull MdnsFeatureFlags mdnsFeatureFlags,
+ @NonNull MdnsUtils.Clock clock) {
mHandler = new Handler(looper);
mMdnsFeatureFlags = mdnsFeatureFlags;
+ mClock = clock;
}
/**
@@ -97,6 +116,9 @@
@NonNull
public List<MdnsResponse> getCachedServices(@NonNull CacheKey cacheKey) {
ensureRunningOnHandlerThread(mHandler);
+ if (mMdnsFeatureFlags.mIsExpiredServicesRemovalEnabled) {
+ maybeRemoveExpiredServices(cacheKey, mClock.elapsedRealtime());
+ }
return mCachedServices.containsKey(cacheKey)
? Collections.unmodifiableList(new ArrayList<>(mCachedServices.get(cacheKey)))
: Collections.emptyList();
@@ -129,6 +151,9 @@
@Nullable
public MdnsResponse getCachedService(@NonNull String serviceName, @NonNull CacheKey cacheKey) {
ensureRunningOnHandlerThread(mHandler);
+ if (mMdnsFeatureFlags.mIsExpiredServicesRemovalEnabled) {
+ maybeRemoveExpiredServices(cacheKey, mClock.elapsedRealtime());
+ }
final List<MdnsResponse> responses = mCachedServices.get(cacheKey);
if (responses == null) {
return null;
@@ -137,6 +162,16 @@
return response != null ? new MdnsResponse(response) : null;
}
+ static void insertResponseAndSortList(
+ List<MdnsResponse> responses, MdnsResponse response, long now) {
+ // binarySearch returns "the index of the search key, if it is contained in the list;
+ // otherwise, (-(insertion point) - 1)"
+ final int searchRes = Collections.binarySearch(responses, response,
+ // Sort the list by ttl.
+ (o1, o2) -> Long.compare(o1.getMinRemainingTtl(now), o2.getMinRemainingTtl(now)));
+ responses.add(searchRes >= 0 ? searchRes : (-searchRes - 1), response);
+ }
+
/**
* Add or update a service.
*
@@ -151,7 +186,15 @@
final MdnsResponse existing =
findMatchedResponse(responses, response.getServiceInstanceName());
responses.remove(existing);
- responses.add(response);
+ if (mMdnsFeatureFlags.mIsExpiredServicesRemovalEnabled) {
+ final long now = mClock.elapsedRealtime();
+ // Insert and sort service
+ insertResponseAndSortList(responses, response, now);
+ // Update the next expiration check time when a new service is added.
+ mNextExpirationTime = getNextExpirationTime(now);
+ } else {
+ responses.add(response);
+ }
}
/**
@@ -168,14 +211,25 @@
return null;
}
final Iterator<MdnsResponse> iterator = responses.iterator();
+ MdnsResponse removedResponse = null;
while (iterator.hasNext()) {
final MdnsResponse response = iterator.next();
if (equalsIgnoreDnsCase(serviceName, response.getServiceInstanceName())) {
iterator.remove();
- return response;
+ removedResponse = response;
+ break;
}
}
- return null;
+
+ if (mMdnsFeatureFlags.mIsExpiredServicesRemovalEnabled) {
+ // Remove the serviceType if no response.
+ if (responses.isEmpty()) {
+ mCachedServices.remove(cacheKey);
+ }
+ // Update the next expiration check time when a service is removed.
+ mNextExpirationTime = getNextExpirationTime(mClock.elapsedRealtime());
+ }
+ return removedResponse;
}
/**
@@ -203,6 +257,87 @@
mCallbacks.remove(cacheKey);
}
+ private void notifyServiceExpired(@NonNull CacheKey cacheKey,
+ @NonNull MdnsResponse previousResponse, @Nullable MdnsResponse newResponse) {
+ final ServiceExpiredCallback callback = mCallbacks.get(cacheKey);
+ if (callback == null) {
+ // The cached service is no listener.
+ return;
+ }
+ mHandler.post(()-> callback.onServiceRecordExpired(previousResponse, newResponse));
+ }
+
+ static List<MdnsResponse> removeExpiredServices(@NonNull List<MdnsResponse> responses,
+ long now) {
+ final List<MdnsResponse> removedResponses = new ArrayList<>();
+ final Iterator<MdnsResponse> iterator = responses.iterator();
+ while (iterator.hasNext()) {
+ final MdnsResponse response = iterator.next();
+ // TODO: Check other records (A, AAAA, TXT) ttl time and remove the record if it's
+ // expired. Then send service update notification.
+ if (!response.hasServiceRecord() || response.getMinRemainingTtl(now) > 0) {
+ // The responses are sorted by the service record ttl time. Break out of loop
+ // early if service is not expired or no service record.
+ break;
+ }
+ // Remove the ttl expired service.
+ iterator.remove();
+ removedResponses.add(response);
+ }
+ return removedResponses;
+ }
+
+ private long getNextExpirationTime(long now) {
+ if (mCachedServices.isEmpty()) {
+ return EXPIRATION_NEVER;
+ }
+
+ long minRemainingTtl = EXPIRATION_NEVER;
+ for (int i = 0; i < mCachedServices.size(); i++) {
+ minRemainingTtl = min(minRemainingTtl,
+ // The empty lists are not kept in the map, so there's always at least one
+ // element in the list. Therefore, it's fine to get the first element without a
+ // null check.
+ mCachedServices.valueAt(i).get(0).getMinRemainingTtl(now));
+ }
+ return minRemainingTtl == EXPIRATION_NEVER ? EXPIRATION_NEVER : now + minRemainingTtl;
+ }
+
+ /**
+ * Check whether the ttl time is expired on each service and notify to the listeners
+ */
+ private void maybeRemoveExpiredServices(CacheKey cacheKey, long now) {
+ ensureRunningOnHandlerThread(mHandler);
+ if (now < mNextExpirationTime) {
+ // Skip the check if ttl time is not expired.
+ return;
+ }
+
+ final List<MdnsResponse> responses = mCachedServices.get(cacheKey);
+ if (responses == null) {
+ // No such services.
+ return;
+ }
+
+ final List<MdnsResponse> removedResponses = removeExpiredServices(responses, now);
+ if (removedResponses.isEmpty()) {
+ // No expired services.
+ return;
+ }
+
+ for (MdnsResponse previousResponse : removedResponses) {
+ notifyServiceExpired(cacheKey, previousResponse, null /* newResponse */);
+ }
+
+ // Remove the serviceType if no response.
+ if (responses.isEmpty()) {
+ mCachedServices.remove(cacheKey);
+ }
+
+ // Update next expiration time.
+ mNextExpirationTime = getNextExpirationTime(now);
+ }
+
/*** Callbacks for listening service expiration */
public interface ServiceExpiredCallback {
/*** Notify the service is expired */
@@ -210,5 +345,5 @@
@Nullable MdnsResponse newResponse);
}
- // TODO: check ttl expiration for each service and notify to the clients.
+ // TODO: Schedule a job to check ttl expiration for all services and notify to the clients.
}
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java b/service-t/src/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java
index 0a03186..32f604e 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java
@@ -312,8 +312,7 @@
this.searchOptions = searchOptions;
boolean hadReply = false;
if (listeners.put(listener, searchOptions) == null) {
- for (MdnsResponse existingResponse :
- serviceCache.getCachedServices(cacheKey)) {
+ for (MdnsResponse existingResponse : serviceCache.getCachedServices(cacheKey)) {
if (!responseMatchesOptions(existingResponse, searchOptions)) continue;
final MdnsServiceInfo info =
buildMdnsServiceInfoFromResponse(existingResponse, serviceTypeLabels);
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsSocketClient.java b/service-t/src/com/android/server/connectivity/mdns/MdnsSocketClient.java
index d18a19b..82c8c5b 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsSocketClient.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsSocketClient.java
@@ -105,9 +105,10 @@
private AtomicInteger packetsCount;
@Nullable private Timer checkMulticastResponseTimer;
private final SharedLog sharedLog;
+ @NonNull private final MdnsFeatureFlags mdnsFeatureFlags;
public MdnsSocketClient(@NonNull Context context, @NonNull MulticastLock multicastLock,
- SharedLog sharedLog) {
+ SharedLog sharedLog, @NonNull MdnsFeatureFlags mdnsFeatureFlags) {
this.sharedLog = sharedLog;
this.context = context;
this.multicastLock = multicastLock;
@@ -116,6 +117,7 @@
} else {
unicastReceiverBuffer = null;
}
+ this.mdnsFeatureFlags = mdnsFeatureFlags;
}
@Override
@@ -454,7 +456,8 @@
final MdnsPacket response;
try {
- response = MdnsResponseDecoder.parseResponse(packet.getData(), packet.getLength());
+ response = MdnsResponseDecoder.parseResponse(
+ packet.getData(), packet.getLength(), mdnsFeatureFlags);
} catch (MdnsPacket.ParseException e) {
sharedLog.w(String.format("Error while decoding %s packet (%d): %d",
responseType, packetNumber, e.code));
diff --git a/service/src/com/android/server/BpfNetMaps.java b/service/src/com/android/server/BpfNetMaps.java
index 086d276..ad9cfbe 100644
--- a/service/src/com/android/server/BpfNetMaps.java
+++ b/service/src/com/android/server/BpfNetMaps.java
@@ -45,8 +45,6 @@
import static android.system.OsConstants.ENODEV;
import static android.system.OsConstants.ENOENT;
import static android.system.OsConstants.EOPNOTSUPP;
-import static android.system.OsConstants.SOCK_RAW;
-import static android.system.OsConstants.SOCK_CLOEXEC;
import static com.android.server.ConnectivityStatsLog.NETWORK_BPF_MAP_INFO;
@@ -327,19 +325,9 @@
*/
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
public int synchronizeKernelRCU() {
- // See p/m/C's staticlibs/native/bpf_headers/include/bpf/BpfUtils.h
- // for equivalent C implementation of this function.
try {
- // When closing socket, kernel calls synchronize_rcu()
- // from pf_key's sock_release().
- // Constants from //bionic/libc/include/sys/socket.h: AF_KEY=15
- // and kernel's include/uapi/linux/pfkeyv2.h: PF_KEY_V2=2
- Os.close(Os.socket(15 /*PF_KEY*/, SOCK_RAW | SOCK_CLOEXEC, 2));
+ BpfMap.synchronizeKernelRCU();
} catch (ErrnoException e) {
- // socket() can only fail due to lack of privs (selinux) or OOM,
- // close() always succeeds, but may return a pending error,
- // however on a freshly opened socket that cannot happen.
- // As such this failing is basically a build configuration error.
return -e.errno;
}
return 0;
diff --git a/staticlibs/device/com/android/net/module/util/BpfMap.java b/staticlibs/device/com/android/net/module/util/BpfMap.java
index 595ac74..d622427 100644
--- a/staticlibs/device/com/android/net/module/util/BpfMap.java
+++ b/staticlibs/device/com/android/net/module/util/BpfMap.java
@@ -239,6 +239,11 @@
return Struct.parse(mValueClass, buffer);
}
+ /** Synchronize Kernel RCU */
+ public static void synchronizeKernelRCU() throws ErrnoException {
+ nativeSynchronizeKernelRCU();
+ }
+
private static native int nativeBpfFdGet(String path, int mode, int keySize, int valueSize)
throws ErrnoException, NullPointerException;
@@ -260,4 +265,6 @@
private native boolean nativeFindMapEntry(int fd, byte[] key, byte[] value)
throws ErrnoException;
+
+ private static native void nativeSynchronizeKernelRCU() throws ErrnoException;
}
diff --git a/staticlibs/framework/com/android/net/module/util/DnsSvcbPacket.java b/staticlibs/framework/com/android/net/module/util/DnsSvcbPacket.java
index c7ed3e6..d298599 100644
--- a/staticlibs/framework/com/android/net/module/util/DnsSvcbPacket.java
+++ b/staticlibs/framework/com/android/net/module/util/DnsSvcbPacket.java
@@ -21,7 +21,6 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.text.TextUtils;
import android.util.Log;
import java.net.InetAddress;
@@ -29,7 +28,6 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
-import java.util.StringJoiner;
/**
* A class for a DNS SVCB response packet.
@@ -159,16 +157,6 @@
return out;
}
- @Override
- public String toString() {
- final StringJoiner out = new StringJoiner(" ");
- out.add("QUERY: [" + TextUtils.join(", ", mRecords[QDSECTION]) + "]");
- out.add("ANSWER: [" + TextUtils.join(", ", mRecords[ANSECTION]) + "]");
- out.add("AUTHORITY: [" + TextUtils.join(", ", mRecords[NSSECTION]) + "]");
- out.add("ADDITIONAL: [" + TextUtils.join(", ", mRecords[ARSECTION]) + "]");
- return out.toString();
- }
-
/**
* Creates a DnsSvcbPacket object from the given wire-format DNS answer.
*/
diff --git a/staticlibs/framework/com/android/net/module/util/DnsSvcbRecord.java b/staticlibs/framework/com/android/net/module/util/DnsSvcbRecord.java
index 669725c..935cdf6 100644
--- a/staticlibs/framework/com/android/net/module/util/DnsSvcbRecord.java
+++ b/staticlibs/framework/com/android/net/module/util/DnsSvcbRecord.java
@@ -230,7 +230,7 @@
/**
* The base class for all SvcParam.
*/
- private abstract static class SvcParam {
+ private abstract static class SvcParam<T> {
private final int mKey;
SvcParam(int key) {
@@ -240,9 +240,11 @@
int getKey() {
return mKey;
}
+
+ abstract T getValue();
}
- private static class SvcParamMandatory extends SvcParam {
+ private static class SvcParamMandatory extends SvcParam<short[]> {
private final short[] mValue;
private SvcParamMandatory(@NonNull ByteBuffer buf) throws BufferUnderflowException,
@@ -258,6 +260,12 @@
}
@Override
+ short[] getValue() {
+ /* Not yet implemented */
+ return null;
+ }
+
+ @Override
public String toString() {
final StringJoiner valueJoiner = new StringJoiner(",");
for (short key : mValue) {
@@ -267,7 +275,7 @@
}
}
- private static class SvcParamAlpn extends SvcParam {
+ private static class SvcParamAlpn extends SvcParam<List<String>> {
private final List<String> mValue;
SvcParamAlpn(@NonNull ByteBuffer buf) throws BufferUnderflowException, ParseException {
@@ -281,6 +289,7 @@
}
}
+ @Override
List<String> getValue() {
return Collections.unmodifiableList(mValue);
}
@@ -291,7 +300,7 @@
}
}
- private static class SvcParamNoDefaultAlpn extends SvcParam {
+ private static class SvcParamNoDefaultAlpn extends SvcParam<Void> {
SvcParamNoDefaultAlpn(@NonNull ByteBuffer buf) throws BufferUnderflowException,
ParseException {
super(KEY_NO_DEFAULT_ALPN);
@@ -303,12 +312,17 @@
}
@Override
+ Void getValue() {
+ return null;
+ }
+
+ @Override
public String toString() {
return toKeyName(getKey());
}
}
- private static class SvcParamPort extends SvcParam {
+ private static class SvcParamPort extends SvcParam<Integer> {
private final int mValue;
SvcParamPort(@NonNull ByteBuffer buf) throws BufferUnderflowException, ParseException {
@@ -321,7 +335,8 @@
mValue = Short.toUnsignedInt(buf.getShort());
}
- int getValue() {
+ @Override
+ Integer getValue() {
return mValue;
}
@@ -331,7 +346,7 @@
}
}
- private static class SvcParamIpHint extends SvcParam {
+ private static class SvcParamIpHint extends SvcParam<List<InetAddress>> {
private final List<InetAddress> mValue;
private SvcParamIpHint(int key, @NonNull ByteBuffer buf, int addrLen) throws
@@ -346,6 +361,7 @@
}
}
+ @Override
List<InetAddress> getValue() {
return Collections.unmodifiableList(mValue);
}
@@ -378,7 +394,7 @@
}
}
- private static class SvcParamDohPath extends SvcParam {
+ private static class SvcParamDohPath extends SvcParam<String> {
private final String mValue;
SvcParamDohPath(@NonNull ByteBuffer buf) throws BufferUnderflowException, ParseException {
@@ -390,6 +406,7 @@
mValue = new String(value, StandardCharsets.UTF_8);
}
+ @Override
String getValue() {
return mValue;
}
@@ -401,7 +418,7 @@
}
// For other unrecognized and unimplemented SvcParams, they are stored as SvcParamGeneric.
- private static class SvcParamGeneric extends SvcParam {
+ private static class SvcParamGeneric extends SvcParam<byte[]> {
private final byte[] mValue;
SvcParamGeneric(int key, @NonNull ByteBuffer buf) throws BufferUnderflowException,
@@ -414,6 +431,12 @@
}
@Override
+ byte[] getValue() {
+ /* Not yet implemented */
+ return null;
+ }
+
+ @Override
public String toString() {
final StringBuilder out = new StringBuilder();
out.append(toKeyName(getKey()));
diff --git a/staticlibs/native/bpf_headers/BpfRingbufTest.cpp b/staticlibs/native/bpf_headers/BpfRingbufTest.cpp
index 6c0841c..e4de812 100644
--- a/staticlibs/native/bpf_headers/BpfRingbufTest.cpp
+++ b/staticlibs/native/bpf_headers/BpfRingbufTest.cpp
@@ -72,12 +72,15 @@
auto result = BpfRingbuf<uint64_t>::Create(mRingbufPath.c_str());
ASSERT_RESULT_OK(result);
+ EXPECT_TRUE(result.value()->isEmpty());
for (int i = 0; i < n; i++) {
RunProgram();
}
+ EXPECT_FALSE(result.value()->isEmpty());
EXPECT_THAT(result.value()->ConsumeAll(callback), HasValue(n));
+ EXPECT_TRUE(result.value()->isEmpty());
EXPECT_EQ(output, TEST_RINGBUF_MAGIC_NUM);
EXPECT_EQ(run_count, n);
}
diff --git a/staticlibs/native/bpf_headers/include/bpf/BpfRingbuf.h b/staticlibs/native/bpf_headers/include/bpf/BpfRingbuf.h
index dd1504c..9aff790 100644
--- a/staticlibs/native/bpf_headers/include/bpf/BpfRingbuf.h
+++ b/staticlibs/native/bpf_headers/include/bpf/BpfRingbuf.h
@@ -39,6 +39,8 @@
mProducerPos = nullptr;
}
+ bool isEmpty(void);
+
protected:
// Non-initializing constructor, used by Create.
BpfRingbufBase(size_t value_size) : mValueSize(value_size) {}
@@ -197,6 +199,13 @@
return {};
}
+inline bool BpfRingbufBase::isEmpty(void) {
+ uint32_t prod_pos = mProducerPos->load(std::memory_order_acquire);
+ // Only userspace writes to mConsumerPos, so no need to use std::memory_order_acquire
+ uint64_t cons_pos = mConsumerPos->load(std::memory_order_relaxed);
+ return (cons_pos & 0xFFFFFFFF) == prod_pos;
+}
+
inline base::Result<int> BpfRingbufBase::ConsumeAll(
const std::function<void(const void*)>& callback) {
int64_t count = 0;
diff --git a/staticlibs/native/bpfmapjni/com_android_net_module_util_BpfMap.cpp b/staticlibs/native/bpfmapjni/com_android_net_module_util_BpfMap.cpp
index f93d6e1..b92f107 100644
--- a/staticlibs/native/bpfmapjni/com_android_net_module_util_BpfMap.cpp
+++ b/staticlibs/native/bpfmapjni/com_android_net_module_util_BpfMap.cpp
@@ -15,6 +15,8 @@
*/
#include <errno.h>
+#include <linux/pfkeyv2.h>
+#include <sys/socket.h>
#include <jni.h>
#include <nativehelper/JNIHelp.h>
#include <nativehelper/ScopedLocalRef.h>
@@ -117,6 +119,22 @@
return throwIfNotEnoent(env, "nativeFindMapEntry", ret, errno);
}
+static void com_android_net_module_util_BpfMap_nativeSynchronizeKernelRCU(JNIEnv *env,
+ jclass clazz) {
+ const int pfSocket = socket(AF_KEY, SOCK_RAW | SOCK_CLOEXEC, PF_KEY_V2);
+
+ if (pfSocket < 0) {
+ jniThrowErrnoException(env, "nativeSynchronizeKernelRCU:socket", errno);
+ return;
+ }
+
+ if (close(pfSocket)) {
+ jniThrowErrnoException(env, "nativeSynchronizeKernelRCU:close", errno);
+ return;
+ }
+ return;
+}
+
/*
* JNI registration.
*/
@@ -132,6 +150,8 @@
(void*) com_android_net_module_util_BpfMap_nativeGetNextMapKey },
{ "nativeFindMapEntry", "(I[B[B)Z",
(void*) com_android_net_module_util_BpfMap_nativeFindMapEntry },
+ { "nativeSynchronizeKernelRCU", "()V",
+ (void*) com_android_net_module_util_BpfMap_nativeSynchronizeKernelRCU },
};
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/DnsSvcbPacketTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/DnsSvcbPacketTest.java
index 6778f8a..d59795f 100644
--- a/staticlibs/tests/unit/src/com/android/net/module/util/DnsSvcbPacketTest.java
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/DnsSvcbPacketTest.java
@@ -207,7 +207,7 @@
os.write(shortToByteArray((short) mRdataLen));
} else {
final byte[] targetNameLabels =
- DnsPacketUtils.DnsRecordParser.domainNameToLabels(mTargetName);
+ DnsPacketUtils.DnsRecordParser.domainNameToLabels(mTargetName);
mRdataLen += (Short.BYTES + targetNameLabels.length);
os.write(shortToByteArray((short) mRdataLen));
os.write(shortToByteArray(mSvcPriority));
@@ -251,7 +251,9 @@
// Check the content returned from toString() for now because the getter function for
// this SvcParam hasn't been implemented.
// TODO(b/240259333): Consider adding DnsSvcbRecord.isMandatory(String alpn) when needed.
- assertTrue(record.toString().contains("mandatory=ipv4hint,alpn,key333"));
+ assertTrue(record.toString().contains("ipv4hint"));
+ assertTrue(record.toString().contains("alpn"));
+ assertTrue(record.toString().contains("key333"));
}
@Test
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java
index bb32052..198b009 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java
@@ -62,6 +62,8 @@
import android.util.Log;
import android.util.Pair;
+import androidx.annotation.Nullable;
+
import com.android.compatibility.common.util.AmUtils;
import com.android.compatibility.common.util.BatteryUtils;
import com.android.compatibility.common.util.DeviceConfigStateHelper;
@@ -283,8 +285,30 @@
}
protected void assertBackgroundNetworkAccess(boolean expectAllowed) throws Exception {
+ assertBackgroundNetworkAccess(expectAllowed, null);
+ }
+
+ /**
+ * Asserts whether the active network is available or not for the background app. If the network
+ * is unavailable, also checks whether it is blocked by the expected error.
+ *
+ * @param expectAllowed expect background network access to be allowed or not.
+ * @param expectedUnavailableError the expected error when {@code expectAllowed} is false. It's
+ * meaningful only when the {@code expectAllowed} is 'false'.
+ * Throws an IllegalArgumentException when {@code expectAllowed}
+ * is true and this parameter is not null. When the
+ * {@code expectAllowed} is 'false' and this parameter is null,
+ * this function does not compare error type of the networking
+ * access failure.
+ */
+ protected void assertBackgroundNetworkAccess(boolean expectAllowed,
+ @Nullable final String expectedUnavailableError) throws Exception {
assertBackgroundState();
- assertNetworkAccess(expectAllowed /* expectAvailable */, false /* needScreenOn */);
+ if (expectAllowed && expectedUnavailableError != null) {
+ throw new IllegalArgumentException("expectedUnavailableError is not null");
+ }
+ assertNetworkAccess(expectAllowed /* expectAvailable */, false /* needScreenOn */,
+ expectedUnavailableError);
}
protected void assertForegroundNetworkAccess() throws Exception {
@@ -407,12 +431,17 @@
*/
private void assertNetworkAccess(boolean expectAvailable, boolean needScreenOn)
throws Exception {
+ assertNetworkAccess(expectAvailable, needScreenOn, null);
+ }
+
+ private void assertNetworkAccess(boolean expectAvailable, boolean needScreenOn,
+ @Nullable final String expectedUnavailableError) throws Exception {
final int maxTries = 5;
String error = null;
int timeoutMs = 500;
for (int i = 1; i <= maxTries; i++) {
- error = checkNetworkAccess(expectAvailable);
+ error = checkNetworkAccess(expectAvailable, expectedUnavailableError);
if (error == null) return;
@@ -479,12 +508,15 @@
*
* @return error message with the mismatch (or empty if assertion passed).
*/
- private String checkNetworkAccess(boolean expectAvailable) throws Exception {
+ private String checkNetworkAccess(boolean expectAvailable,
+ @Nullable final String expectedUnavailableError) throws Exception {
final String resultData = mServiceClient.checkNetworkStatus();
- return checkForAvailabilityInResultData(resultData, expectAvailable);
+ return checkForAvailabilityInResultData(resultData, expectAvailable,
+ expectedUnavailableError);
}
- private String checkForAvailabilityInResultData(String resultData, boolean expectAvailable) {
+ private String checkForAvailabilityInResultData(String resultData, boolean expectAvailable,
+ @Nullable final String expectedUnavailableError) {
if (resultData == null) {
assertNotNull("Network status from app2 is null", resultData);
}
@@ -516,6 +548,10 @@
if (expectedState != state || expectedDetailedState != detailedState) {
errors.append(String.format("Connection state mismatch: expected %s/%s, got %s/%s\n",
expectedState, expectedDetailedState, state, detailedState));
+ } else if (!expectAvailable && (expectedUnavailableError != null)
+ && !connectionCheckDetails.contains(expectedUnavailableError)) {
+ errors.append("Connection unavailable reason mismatch: expected "
+ + expectedUnavailableError + "\n");
}
if (errors.length() > 0) {
@@ -914,7 +950,7 @@
final String resultData = result.get(0).second;
if (resultCode == INetworkStateObserver.RESULT_SUCCESS_NETWORK_STATE_CHECKED) {
final String error = checkForAvailabilityInResultData(
- resultData, expectAvailable);
+ resultData, expectAvailable, null /* expectedUnavailableError */);
if (error != null) {
fail("Network is not available for activity in app2 (" + mUid + "): "
+ error);
@@ -949,7 +985,7 @@
final String resultData = result.get(0).second;
if (resultCode == INetworkStateObserver.RESULT_SUCCESS_NETWORK_STATE_CHECKED) {
final String error = checkForAvailabilityInResultData(
- resultData, expectAvailable);
+ resultData, expectAvailable, null /* expectedUnavailableError */);
if (error != null) {
Log.d(TAG, "Network state is unexpected, checking again. " + error);
// Right now we could end up in an unexpected state if expedited job
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkCallbackTest.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkCallbackTest.java
index ab3cf14..82f4a65 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkCallbackTest.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkCallbackTest.java
@@ -32,8 +32,11 @@
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkRequest;
+import android.net.cts.util.CtsNetUtils;
import android.util.Log;
+import com.android.modules.utils.build.SdkLevel;
+
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
@@ -46,6 +49,9 @@
public class NetworkCallbackTest extends AbstractRestrictBackgroundNetworkTestCase {
private Network mNetwork;
private final TestNetworkCallback mTestNetworkCallback = new TestNetworkCallback();
+ private CtsNetUtils mCtsNetUtils;
+ private static final String GOOGLE_PRIVATE_DNS_SERVER = "dns.google";
+
@Rule
public final MeterednessConfigurationRule mMeterednessConfiguration
= new MeterednessConfigurationRule();
@@ -218,6 +224,26 @@
mTestNetworkCallback.expectCapabilitiesCallbackEventually(mNetwork,
false /* hasCapability */, NET_CAPABILITY_NOT_METERED);
mTestNetworkCallback.expectBlockedStatusCallback(mNetwork, false);
+
+ // Before Android T, DNS queries over private DNS should be but are not restricted by Power
+ // Saver or Data Saver. The issue is fixed in mainline update and apps can no longer request
+ // DNS queries when its network is restricted by Power Saver. The fix takes effect backwards
+ // starting from Android T. But for Data Saver, the fix is not backward compatible since
+ // there are some platform changes involved. It is only available on devices that a specific
+ // trunk flag is enabled.
+ //
+ // This test can not only verify that the network traffic from apps is blocked at the right
+ // time, but also verify whether it is correctly blocked at the DNS stage, or at a later
+ // socket connection stage.
+ if (SdkLevel.isAtLeastT()) {
+ // Enable private DNS
+ mCtsNetUtils = new CtsNetUtils(mContext);
+ mCtsNetUtils.storePrivateDnsSetting();
+ mCtsNetUtils.setPrivateDnsStrictMode(GOOGLE_PRIVATE_DNS_SERVER);
+ mCtsNetUtils.awaitPrivateDnsSetting(
+ "NetworkCallbackTest wait private DNS setting timeout", mNetwork,
+ GOOGLE_PRIVATE_DNS_SERVER, true);
+ }
}
@After
@@ -227,6 +253,10 @@
setRestrictBackground(false);
setBatterySaverMode(false);
unregisterNetworkCallback();
+
+ if (SdkLevel.isAtLeastT() && (mCtsNetUtils != null)) {
+ mCtsNetUtils.restorePrivateDnsSetting();
+ }
}
@RequiredProperties({DATA_SAVER_MODE})
@@ -235,6 +265,8 @@
try {
// Enable restrict background
setRestrictBackground(true);
+ // TODO: Verify expectedUnavailableError when aconfig support mainline.
+ // (see go/aconfig-in-mainline-problems)
assertBackgroundNetworkAccess(false);
assertNetworkAccessBlockedByBpf(true, mUid, true /* metered */);
mTestNetworkCallback.expectBlockedStatusCallbackEventually(mNetwork, true);
@@ -247,6 +279,7 @@
// Remove from whitelist
removeRestrictBackgroundWhitelist(mUid);
+ // TODO: Verify expectedUnavailableError when aconfig support mainline.
assertBackgroundNetworkAccess(false);
assertNetworkAccessBlockedByBpf(true, mUid, true /* metered */);
mTestNetworkCallback.expectBlockedStatusCallbackEventually(mNetwork, true);
@@ -278,7 +311,11 @@
try {
// Enable Power Saver
setBatterySaverMode(true);
- assertBackgroundNetworkAccess(false);
+ if (SdkLevel.isAtLeastT()) {
+ assertBackgroundNetworkAccess(false, "java.net.UnknownHostException");
+ } else {
+ assertBackgroundNetworkAccess(false);
+ }
mTestNetworkCallback.expectBlockedStatusCallbackEventually(mNetwork, true);
assertNetworkAccessBlockedByBpf(true, mUid, true /* metered */);
@@ -298,7 +335,11 @@
try {
// Enable Power Saver
setBatterySaverMode(true);
- assertBackgroundNetworkAccess(false);
+ if (SdkLevel.isAtLeastT()) {
+ assertBackgroundNetworkAccess(false, "java.net.UnknownHostException");
+ } else {
+ assertBackgroundNetworkAccess(false);
+ }
mTestNetworkCallback.expectBlockedStatusCallbackEventually(mNetwork, true);
assertNetworkAccessBlockedByBpf(true, mUid, false /* metered */);
diff --git a/tests/native/utilities/firewall.cpp b/tests/native/utilities/firewall.cpp
index 22f83e8..669b76a 100644
--- a/tests/native/utilities/firewall.cpp
+++ b/tests/native/utilities/firewall.cpp
@@ -59,9 +59,11 @@
Result<void> Firewall::addRule(uint32_t uid, UidOwnerMatchType match, uint32_t iif) {
// iif should be non-zero if and only if match == MATCH_IIF
if (match == IIF_MATCH && iif == 0) {
- return Errorf("Interface match {} must have nonzero interface index", match);
+ return Errorf("Interface match {} must have nonzero interface index",
+ static_cast<int>(match));
} else if (match != IIF_MATCH && iif != 0) {
- return Errorf("Non-interface match {} must have zero interface index", match);
+ return Errorf("Non-interface match {} must have zero interface index",
+ static_cast<int>(match));
}
std::lock_guard guard(mMutex);
diff --git a/tests/unit/java/android/net/MulticastRoutingConfigTest.kt b/tests/unit/java/android/net/MulticastRoutingConfigTest.kt
new file mode 100644
index 0000000..f01057b
--- /dev/null
+++ b/tests/unit/java/android/net/MulticastRoutingConfigTest.kt
@@ -0,0 +1,99 @@
+/*
+ * 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.os.Build
+import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
+import com.android.testutils.DevSdkIgnoreRunner
+
+import org.junit.Test
+import org.junit.runner.RunWith
+import java.net.Inet6Address
+import kotlin.test.assertFalse
+import kotlin.test.assertTrue
+
+import android.net.MulticastRoutingConfig.Builder
+import android.net.MulticastRoutingConfig.FORWARD_NONE
+import android.net.MulticastRoutingConfig.FORWARD_SELECTED
+import android.net.MulticastRoutingConfig.FORWARD_WITH_MIN_SCOPE
+
+@RunWith(DevSdkIgnoreRunner::class)
+@IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+class MulticastRoutingConfigTest {
+
+ val address1 = Inet6Address.getByName("2000::8888") as Inet6Address
+ val address2 = Inet6Address.getByName("2000::9999") as Inet6Address
+
+ private fun configNone() = Builder(FORWARD_NONE).build()
+ private fun configMinScope(scope: Int) = Builder(FORWARD_WITH_MIN_SCOPE, scope).build()
+ private fun configSelected() = Builder(FORWARD_SELECTED).build()
+ private fun configSelectedWithAddress1AndAddress2() =
+ Builder(FORWARD_SELECTED).addListeningAddress(address1)
+ .addListeningAddress(address2).build()
+ private fun configSelectedWithAddress2AndAddress1() =
+ Builder(FORWARD_SELECTED).addListeningAddress(address2)
+ .addListeningAddress(address1).build()
+
+ @Test
+ fun equalityTests() {
+
+ assertTrue(configNone().equals(configNone()))
+
+ assertTrue(configSelected().equals(configSelected()))
+
+ assertTrue(configMinScope(4).equals(configMinScope(4)))
+
+ assertTrue(configSelectedWithAddress1AndAddress2()
+ .equals(configSelectedWithAddress2AndAddress1()))
+ }
+
+ @Test
+ fun inequalityTests() {
+
+ assertFalse(configNone().equals(configSelected()))
+
+ assertFalse(configNone().equals(configMinScope(4)))
+
+ assertFalse(configSelected().equals(configMinScope(4)))
+
+ assertFalse(configMinScope(4).equals(configMinScope(5)))
+
+ assertFalse(configSelected().equals(configSelectedWithAddress1AndAddress2()))
+ }
+
+ @Test
+ fun toString_equalObjects_returnsEqualStrings() {
+ val config1 = configSelectedWithAddress1AndAddress2()
+ val config2 = configSelectedWithAddress2AndAddress1()
+
+ val str1 = config1.toString()
+ val str2 = config2.toString()
+
+ assertTrue(str1.equals(str2))
+ }
+
+ @Test
+ fun toString_unequalObjects_returnsUnequalStrings() {
+ val config1 = configSelected()
+ val config2 = configSelectedWithAddress1AndAddress2()
+
+ val str1 = config1.toString()
+ val str2 = config2.toString()
+
+ assertFalse(str1.equals(str2))
+ }
+}
diff --git a/tests/unit/java/com/android/server/connectivity/VpnTest.java b/tests/unit/java/com/android/server/connectivity/VpnTest.java
index 48cfe77..ff801e5 100644
--- a/tests/unit/java/com/android/server/connectivity/VpnTest.java
+++ b/tests/unit/java/com/android/server/connectivity/VpnTest.java
@@ -56,11 +56,9 @@
import static com.android.server.connectivity.Vpn.PREFERRED_IKE_PROTOCOL_IPV4_UDP;
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;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
@@ -149,7 +147,6 @@
import android.net.wifi.WifiInfo;
import android.os.Build.VERSION_CODES;
import android.os.Bundle;
-import android.os.ConditionVariable;
import android.os.INetworkManagementService;
import android.os.ParcelFileDescriptor;
import android.os.PersistableBundle;
@@ -1983,22 +1980,6 @@
// a subsequent CL.
}
- @Test
- public void testStartLegacyVpnIpv6() throws Exception {
- setMockedUsers(PRIMARY_USER);
- final Vpn vpn = createVpn(PRIMARY_USER.id);
- final LinkProperties lp = new LinkProperties();
- lp.setInterfaceName(EGRESS_IFACE);
- lp.addLinkAddress(new LinkAddress("2001:db8::1/64"));
- final RouteInfo defaultRoute = new RouteInfo(
- new IpPrefix(Inet6Address.ANY, 0), null, EGRESS_IFACE);
- lp.addRoute(defaultRoute);
-
- // IllegalStateException thrown since legacy VPN only supports IPv4.
- assertThrows(IllegalStateException.class,
- () -> vpn.startLegacyVpn(mVpnProfile, EGRESS_NETWORK, lp));
- }
-
private Vpn startLegacyVpn(final Vpn vpn, final VpnProfile vpnProfile) throws Exception {
setMockedUsers(PRIMARY_USER);
@@ -3112,23 +3093,15 @@
}
@Test
- public void testStartRacoonNumericAddress() throws Exception {
- startRacoon("1.2.3.4", "1.2.3.4");
- }
+ public void testStartLegacyVpnType() throws Exception {
+ setMockedUsers(PRIMARY_USER);
+ final Vpn vpn = createVpn(PRIMARY_USER.id);
+ final VpnProfile profile = new VpnProfile("testProfile" /* key */);
- @Test
- public void testStartRacoonHostname() throws Exception {
- startRacoon("hostname", "5.6.7.8"); // address returned by deps.resolve
- }
-
- @Test
- public void testStartPptp() throws Exception {
- startPptp(true /* useMppe */);
- }
-
- @Test
- public void testStartPptp_NoMppe() throws Exception {
- startPptp(false /* useMppe */);
+ profile.type = VpnProfile.TYPE_PPTP;
+ assertThrows(UnsupportedOperationException.class, () -> startLegacyVpn(vpn, profile));
+ profile.type = VpnProfile.TYPE_L2TP_IPSEC_PSK;
+ assertThrows(UnsupportedOperationException.class, () -> startLegacyVpn(vpn, profile));
}
private void assertTransportInfoMatches(NetworkCapabilities nc, int type) {
@@ -3138,125 +3111,6 @@
assertEquals(type, ti.getType());
}
- private void startPptp(boolean useMppe) throws Exception {
- final VpnProfile profile = new VpnProfile("testProfile" /* key */);
- profile.type = VpnProfile.TYPE_PPTP;
- profile.name = "testProfileName";
- profile.username = "userName";
- profile.password = "thePassword";
- profile.server = "192.0.2.123";
- profile.mppe = useMppe;
-
- doReturn(new Network[] { new Network(101) }).when(mConnectivityManager).getAllNetworks();
- doReturn(new Network(102)).when(mConnectivityManager).registerNetworkAgent(
- any(), // INetworkAgent
- any(), // NetworkInfo
- any(), // LinkProperties
- any(), // NetworkCapabilities
- any(), // LocalNetworkConfig
- any(), // NetworkScore
- any(), // NetworkAgentConfig
- anyInt()); // provider ID
-
- final Vpn vpn = startLegacyVpn(createVpn(PRIMARY_USER.id), profile);
- final TestDeps deps = (TestDeps) vpn.mDeps;
-
- testAndCleanup(() -> {
- final String[] mtpdArgs = deps.mtpdArgs.get(10, TimeUnit.SECONDS);
- final String[] argsPrefix = new String[]{
- EGRESS_IFACE, "pptp", profile.server, "1723", "name", profile.username,
- "password", profile.password, "linkname", "vpn", "refuse-eap", "nodefaultroute",
- "usepeerdns", "idle", "1800", "mtu", "1270", "mru", "1270"
- };
- assertArrayEquals(argsPrefix, Arrays.copyOf(mtpdArgs, argsPrefix.length));
- if (useMppe) {
- assertEquals(argsPrefix.length + 2, mtpdArgs.length);
- assertEquals("+mppe", mtpdArgs[argsPrefix.length]);
- assertEquals("-pap", mtpdArgs[argsPrefix.length + 1]);
- } else {
- assertEquals(argsPrefix.length + 1, mtpdArgs.length);
- assertEquals("nomppe", mtpdArgs[argsPrefix.length]);
- }
-
- verify(mConnectivityManager, timeout(10_000)).registerNetworkAgent(
- any(), // INetworkAgent
- any(), // NetworkInfo
- any(), // LinkProperties
- any(), // NetworkCapabilities
- any(), // LocalNetworkConfig
- any(), // NetworkScore
- any(), // NetworkAgentConfig
- anyInt()); // provider ID
- }, () -> { // Cleanup
- vpn.mVpnRunner.exitVpnRunner();
- deps.getStateFile().delete(); // set to delete on exit, but this deletes it earlier
- vpn.mVpnRunner.join(10_000); // wait for up to 10s for the runner to die and cleanup
- });
- }
-
- public void startRacoon(final String serverAddr, final String expectedAddr)
- throws Exception {
- final ConditionVariable legacyRunnerReady = new ConditionVariable();
- final VpnProfile profile = new VpnProfile("testProfile" /* key */);
- profile.type = VpnProfile.TYPE_L2TP_IPSEC_PSK;
- profile.name = "testProfileName";
- profile.username = "userName";
- profile.password = "thePassword";
- profile.server = serverAddr;
- profile.ipsecIdentifier = "id";
- profile.ipsecSecret = "secret";
- profile.l2tpSecret = "l2tpsecret";
-
- when(mConnectivityManager.getAllNetworks())
- .thenReturn(new Network[] { new Network(101) });
-
- when(mConnectivityManager.registerNetworkAgent(any(), any(), any(), any(),
- any(), any(), any(), anyInt())).thenAnswer(invocation -> {
- // The runner has registered an agent and is now ready.
- legacyRunnerReady.open();
- return new Network(102);
- });
- final Vpn vpn = startLegacyVpn(createVpn(PRIMARY_USER.id), profile);
- final TestDeps deps = (TestDeps) vpn.mDeps;
- try {
- // udppsk and 1701 are the values for TYPE_L2TP_IPSEC_PSK
- assertArrayEquals(
- new String[] { EGRESS_IFACE, expectedAddr, "udppsk",
- profile.ipsecIdentifier, profile.ipsecSecret, "1701" },
- deps.racoonArgs.get(10, TimeUnit.SECONDS));
- // literal values are hardcoded in Vpn.java for mtpd args
- assertArrayEquals(
- new String[] { EGRESS_IFACE, "l2tp", expectedAddr, "1701", profile.l2tpSecret,
- "name", profile.username, "password", profile.password,
- "linkname", "vpn", "refuse-eap", "nodefaultroute", "usepeerdns",
- "idle", "1800", "mtu", "1270", "mru", "1270" },
- deps.mtpdArgs.get(10, TimeUnit.SECONDS));
-
- // Now wait for the runner to be ready before testing for the route.
- ArgumentCaptor<LinkProperties> lpCaptor = ArgumentCaptor.forClass(LinkProperties.class);
- ArgumentCaptor<NetworkCapabilities> ncCaptor =
- ArgumentCaptor.forClass(NetworkCapabilities.class);
- verify(mConnectivityManager, timeout(10_000)).registerNetworkAgent(any(), any(),
- lpCaptor.capture(), ncCaptor.capture(), any(), any(), any(), anyInt());
-
- // In this test the expected address is always v4 so /32.
- // Note that the interface needs to be specified because RouteInfo objects stored in
- // LinkProperties objects always acquire the LinkProperties' interface.
- final RouteInfo expectedRoute = new RouteInfo(new IpPrefix(expectedAddr + "/32"),
- null, EGRESS_IFACE, RouteInfo.RTN_THROW);
- final List<RouteInfo> actualRoutes = lpCaptor.getValue().getRoutes();
- assertTrue("Expected throw route (" + expectedRoute + ") not found in " + actualRoutes,
- actualRoutes.contains(expectedRoute));
-
- assertTransportInfoMatches(ncCaptor.getValue(), VpnManager.TYPE_VPN_LEGACY);
- } finally {
- // Now interrupt the thread, unblock the runner and clean up.
- vpn.mVpnRunner.exitVpnRunner();
- deps.getStateFile().delete(); // set to delete on exit, but this deletes it earlier
- vpn.mVpnRunner.join(10_000); // wait for up to 10s for the runner to die and cleanup
- }
- }
-
// Make it public and un-final so as to spy it
public class TestDeps extends Vpn.Dependencies {
public final CompletableFuture<String[]> racoonArgs = new CompletableFuture();
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsMultinetworkSocketClientTest.java b/tests/unit/java/com/android/server/connectivity/mdns/MdnsMultinetworkSocketClientTest.java
index 8917ed3..ad30ce0 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsMultinetworkSocketClientTest.java
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsMultinetworkSocketClientTest.java
@@ -82,8 +82,8 @@
mHandlerThread.start();
mHandler = new Handler(mHandlerThread.getLooper());
mSocketKey = new SocketKey(1000 /* interfaceIndex */);
- mSocketClient = new MdnsMultinetworkSocketClient(
- mHandlerThread.getLooper(), mProvider, mSharedLog);
+ mSocketClient = new MdnsMultinetworkSocketClient(mHandlerThread.getLooper(), mProvider,
+ mSharedLog, MdnsFeatureFlags.newBuilder().build());
mHandler.post(() -> mSocketClient.setCallback(mCallback));
}
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsPacketReaderTests.java b/tests/unit/java/com/android/server/connectivity/mdns/MdnsPacketReaderTests.java
index 19d8a00..37588b5 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsPacketReaderTests.java
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsPacketReaderTests.java
@@ -75,7 +75,7 @@
+ "the packet length");
} catch (IOException e) {
// Expected
- } catch (Exception e) {
+ } catch (RuntimeException e) {
fail(String.format(
Locale.ROOT,
"Should not have thrown any other exception except " + "for IOException: %s",
@@ -83,4 +83,4 @@
}
assertEquals(data.length, packetReader.getRemaining());
}
-}
\ No newline at end of file
+}
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsPacketTest.kt b/tests/unit/java/com/android/server/connectivity/mdns/MdnsPacketTest.kt
index 28ea4b6..0877b68 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsPacketTest.kt
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsPacketTest.kt
@@ -27,6 +27,9 @@
@RunWith(DevSdkIgnoreRunner::class)
class MdnsPacketTest {
+ private fun makeFlags(isLabelCountLimitEnabled: Boolean = false): MdnsFeatureFlags =
+ MdnsFeatureFlags.newBuilder()
+ .setIsLabelCountLimitEnabled(isLabelCountLimitEnabled).build()
@Test
fun testParseQuery() {
// Probe packet with 1 question for Android.local, and 4 additionalRecords with 4 addresses
@@ -38,7 +41,7 @@
"010db8000000000000000000000789"
val bytes = HexDump.hexStringToByteArray(packetHex)
- val reader = MdnsPacketReader(bytes, bytes.size)
+ val reader = MdnsPacketReader(bytes, bytes.size, makeFlags())
val packet = MdnsPacket.parse(reader)
assertEquals(123, packet.transactionId)
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsResponseDecoderTests.java b/tests/unit/java/com/android/server/connectivity/mdns/MdnsResponseDecoderTests.java
index 3fc656a..a22e8c6 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsResponseDecoderTests.java
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsResponseDecoderTests.java
@@ -17,8 +17,10 @@
package com.android.server.connectivity.mdns;
import static android.net.InetAddresses.parseNumericAddress;
+
import static com.android.server.connectivity.mdns.util.MdnsUtils.Clock;
import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
+
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -337,7 +339,8 @@
packet.setSocketAddress(
new InetSocketAddress(MdnsConstants.getMdnsIPv6Address(), MdnsConstants.MDNS_PORT));
- final MdnsPacket parsedPacket = MdnsResponseDecoder.parseResponse(data6, data6.length);
+ final MdnsPacket parsedPacket = MdnsResponseDecoder.parseResponse(
+ data6, data6.length, MdnsFeatureFlags.newBuilder().build());
assertNotNull(parsedPacket);
final Network network = mock(Network.class);
@@ -636,7 +639,8 @@
private ArraySet<MdnsResponse> decode(MdnsResponseDecoder decoder, byte[] data,
Collection<MdnsResponse> existingResponses) throws MdnsPacket.ParseException {
- final MdnsPacket parsedPacket = MdnsResponseDecoder.parseResponse(data, data.length);
+ final MdnsPacket parsedPacket = MdnsResponseDecoder.parseResponse(
+ data, data.length, MdnsFeatureFlags.newBuilder().build());
assertNotNull(parsedPacket);
return new ArraySet<>(decoder.augmentResponses(parsedPacket,
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceCacheTest.kt b/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceCacheTest.kt
index 2b3b834..3cea5cb 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceCacheTest.kt
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceCacheTest.kt
@@ -19,7 +19,10 @@
import android.os.Build
import android.os.Handler
import android.os.HandlerThread
+import com.android.net.module.util.ArrayTrackRecord
import com.android.server.connectivity.mdns.MdnsServiceCache.CacheKey
+import com.android.server.connectivity.mdns.MdnsServiceCacheTest.ExpiredRecord.ExpiredEvent.ServiceRecordExpired
+import com.android.server.connectivity.mdns.util.MdnsUtils
import com.android.testutils.DevSdkIgnoreRule
import com.android.testutils.DevSdkIgnoreRunner
import java.util.concurrent.CompletableFuture
@@ -32,13 +35,19 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.Mockito.doReturn
+import org.mockito.Mockito.mock
private const val SERVICE_NAME_1 = "service-instance-1"
private const val SERVICE_NAME_2 = "service-instance-2"
+private const val SERVICE_NAME_3 = "service-instance-3"
private const val SERVICE_TYPE_1 = "_test1._tcp.local"
private const val SERVICE_TYPE_2 = "_test2._tcp.local"
private const val INTERFACE_INDEX = 999
private const val DEFAULT_TIMEOUT_MS = 2000L
+private const val NO_CALLBACK_TIMEOUT_MS = 200L
+private const val TEST_ELAPSED_REALTIME_MS = 123L
+private const val DEFAULT_TTL_TIME_MS = 120000L
@RunWith(DevSdkIgnoreRunner::class)
@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S_V2)
@@ -47,10 +56,46 @@
private val cacheKey1 = CacheKey(SERVICE_TYPE_1, socketKey)
private val cacheKey2 = CacheKey(SERVICE_TYPE_2, socketKey)
private val thread = HandlerThread(MdnsServiceCacheTest::class.simpleName)
+ private val clock = mock(MdnsUtils.Clock::class.java)
private val handler by lazy {
Handler(thread.looper)
}
+ private class ExpiredRecord : MdnsServiceCache.ServiceExpiredCallback {
+ val history = ArrayTrackRecord<ExpiredEvent>().newReadHead()
+
+ sealed class ExpiredEvent {
+ abstract val previousResponse: MdnsResponse
+ abstract val newResponse: MdnsResponse?
+ data class ServiceRecordExpired(
+ override val previousResponse: MdnsResponse,
+ override val newResponse: MdnsResponse?
+ ) : ExpiredEvent()
+ }
+
+ override fun onServiceRecordExpired(
+ previousResponse: MdnsResponse,
+ newResponse: MdnsResponse?
+ ) {
+ history.add(ServiceRecordExpired(previousResponse, newResponse))
+ }
+
+ fun expectedServiceRecordExpired(
+ serviceName: String,
+ timeoutMs: Long = DEFAULT_TIMEOUT_MS
+ ) {
+ val event = history.poll(timeoutMs)
+ assertNotNull(event)
+ assertTrue(event is ServiceRecordExpired)
+ assertEquals(serviceName, event.previousResponse.serviceInstanceName)
+ }
+
+ fun assertNoCallback() {
+ val cb = history.poll(NO_CALLBACK_TIMEOUT_MS)
+ assertNull("Expected no callback but got $cb", cb)
+ }
+ }
+
@Before
fun setUp() {
thread.start()
@@ -89,19 +134,27 @@
private fun getService(
serviceCache: MdnsServiceCache,
serviceName: String,
- cacheKey: CacheKey,
+ cacheKey: CacheKey
): MdnsResponse? = runningOnHandlerAndReturn {
serviceCache.getCachedService(serviceName, cacheKey)
}
private fun getServices(
serviceCache: MdnsServiceCache,
- cacheKey: CacheKey,
+ cacheKey: CacheKey
): List<MdnsResponse> = runningOnHandlerAndReturn { serviceCache.getCachedServices(cacheKey) }
+ private fun registerServiceExpiredCallback(
+ serviceCache: MdnsServiceCache,
+ cacheKey: CacheKey,
+ callback: MdnsServiceCache.ServiceExpiredCallback
+ ) = runningOnHandlerAndReturn {
+ serviceCache.registerServiceExpiredCallback(cacheKey, callback)
+ }
+
@Test
fun testAddAndRemoveService() {
- val serviceCache = MdnsServiceCache(thread.looper, makeFlags())
+ val serviceCache = MdnsServiceCache(thread.looper, makeFlags(), clock)
addOrUpdateService(serviceCache, cacheKey1, createResponse(SERVICE_NAME_1, SERVICE_TYPE_1))
var response = getService(serviceCache, SERVICE_NAME_1, cacheKey1)
assertNotNull(response)
@@ -113,7 +166,7 @@
@Test
fun testGetCachedServices_multipleServiceTypes() {
- val serviceCache = MdnsServiceCache(thread.looper, makeFlags())
+ val serviceCache = MdnsServiceCache(thread.looper, makeFlags(), clock)
addOrUpdateService(serviceCache, cacheKey1, createResponse(SERVICE_NAME_1, SERVICE_TYPE_1))
addOrUpdateService(serviceCache, cacheKey1, createResponse(SERVICE_NAME_2, SERVICE_TYPE_1))
addOrUpdateService(serviceCache, cacheKey2, createResponse(SERVICE_NAME_2, SERVICE_TYPE_2))
@@ -145,7 +198,127 @@
})
}
- private fun createResponse(serviceInstanceName: String, serviceType: String) = MdnsResponse(
- 0 /* now */, "$serviceInstanceName.$serviceType".split(".").toTypedArray(),
- socketKey.interfaceIndex, socketKey.network)
+ @Test
+ fun testServiceExpiredAndSendCallbacks() {
+ val serviceCache = MdnsServiceCache(
+ thread.looper, makeFlags(isExpiredServicesRemovalEnabled = true), clock)
+ // Register service expired callbacks
+ val callback1 = ExpiredRecord()
+ val callback2 = ExpiredRecord()
+ registerServiceExpiredCallback(serviceCache, cacheKey1, callback1)
+ registerServiceExpiredCallback(serviceCache, cacheKey2, callback2)
+
+ doReturn(TEST_ELAPSED_REALTIME_MS).`when`(clock).elapsedRealtime()
+
+ // Add multiple services with different ttl time.
+ addOrUpdateService(serviceCache, cacheKey1, createResponse(SERVICE_NAME_1, SERVICE_TYPE_1,
+ DEFAULT_TTL_TIME_MS))
+ addOrUpdateService(serviceCache, cacheKey1, createResponse(SERVICE_NAME_2, SERVICE_TYPE_1,
+ DEFAULT_TTL_TIME_MS + 20L))
+ addOrUpdateService(serviceCache, cacheKey2, createResponse(SERVICE_NAME_3, SERVICE_TYPE_2,
+ DEFAULT_TTL_TIME_MS + 10L))
+
+ // Check the service expiration immediately. Should be no callback.
+ assertEquals(2, getServices(serviceCache, cacheKey1).size)
+ assertEquals(1, getServices(serviceCache, cacheKey2).size)
+ callback1.assertNoCallback()
+ callback2.assertNoCallback()
+
+ // Simulate the case where the response is after TTL then check expired services.
+ // Expect SERVICE_NAME_1 expired.
+ doReturn(TEST_ELAPSED_REALTIME_MS + DEFAULT_TTL_TIME_MS).`when`(clock).elapsedRealtime()
+ assertEquals(1, getServices(serviceCache, cacheKey1).size)
+ assertEquals(1, getServices(serviceCache, cacheKey2).size)
+ callback1.expectedServiceRecordExpired(SERVICE_NAME_1)
+ callback2.assertNoCallback()
+
+ // Simulate the case where the response is after TTL then check expired services.
+ // Expect SERVICE_NAME_3 expired.
+ doReturn(TEST_ELAPSED_REALTIME_MS + DEFAULT_TTL_TIME_MS + 11L)
+ .`when`(clock).elapsedRealtime()
+ assertEquals(1, getServices(serviceCache, cacheKey1).size)
+ assertEquals(0, getServices(serviceCache, cacheKey2).size)
+ callback1.assertNoCallback()
+ callback2.expectedServiceRecordExpired(SERVICE_NAME_3)
+ }
+
+ @Test
+ fun testRemoveExpiredServiceWhenGetting() {
+ val serviceCache = MdnsServiceCache(
+ thread.looper, makeFlags(isExpiredServicesRemovalEnabled = true), clock)
+
+ doReturn(TEST_ELAPSED_REALTIME_MS).`when`(clock).elapsedRealtime()
+ addOrUpdateService(serviceCache, cacheKey1,
+ createResponse(SERVICE_NAME_1, SERVICE_TYPE_1, 1L /* ttlTime */))
+ doReturn(TEST_ELAPSED_REALTIME_MS + 2L).`when`(clock).elapsedRealtime()
+ assertNull(getService(serviceCache, SERVICE_NAME_1, cacheKey1))
+
+ addOrUpdateService(serviceCache, cacheKey2,
+ createResponse(SERVICE_NAME_2, SERVICE_TYPE_2, 3L /* ttlTime */))
+ doReturn(TEST_ELAPSED_REALTIME_MS + 4L).`when`(clock).elapsedRealtime()
+ assertEquals(0, getServices(serviceCache, cacheKey2).size)
+ }
+
+ @Test
+ fun testInsertResponseAndSortList() {
+ val responses = ArrayList<MdnsResponse>()
+ val response1 = createResponse(SERVICE_NAME_1, SERVICE_TYPE_1, 100L /* ttlTime */)
+ MdnsServiceCache.insertResponseAndSortList(responses, response1, TEST_ELAPSED_REALTIME_MS)
+ assertEquals(1, responses.size)
+ assertEquals(response1, responses[0])
+
+ val response2 = createResponse(SERVICE_NAME_2, SERVICE_TYPE_1, 50L /* ttlTime */)
+ MdnsServiceCache.insertResponseAndSortList(responses, response2, TEST_ELAPSED_REALTIME_MS)
+ assertEquals(2, responses.size)
+ assertEquals(response2, responses[0])
+ assertEquals(response1, responses[1])
+
+ val response3 = createResponse(SERVICE_NAME_3, SERVICE_TYPE_1, 75L /* ttlTime */)
+ MdnsServiceCache.insertResponseAndSortList(responses, response3, TEST_ELAPSED_REALTIME_MS)
+ assertEquals(3, responses.size)
+ assertEquals(response2, responses[0])
+ assertEquals(response3, responses[1])
+ assertEquals(response1, responses[2])
+
+ val response4 = createResponse("service-instance-4", SERVICE_TYPE_1, 125L /* ttlTime */)
+ MdnsServiceCache.insertResponseAndSortList(responses, response4, TEST_ELAPSED_REALTIME_MS)
+ assertEquals(4, responses.size)
+ assertEquals(response2, responses[0])
+ assertEquals(response3, responses[1])
+ assertEquals(response1, responses[2])
+ assertEquals(response4, responses[3])
+ }
+
+ private fun createResponse(
+ serviceInstanceName: String,
+ serviceType: String,
+ ttlTime: Long = 120000L
+ ): MdnsResponse {
+ val serviceName = "$serviceInstanceName.$serviceType".split(".").toTypedArray()
+ val response = MdnsResponse(
+ 0 /* now */, "$serviceInstanceName.$serviceType".split(".").toTypedArray(),
+ socketKey.interfaceIndex, socketKey.network)
+
+ // Set PTR record
+ val pointerRecord = MdnsPointerRecord(
+ serviceType.split(".").toTypedArray(),
+ TEST_ELAPSED_REALTIME_MS /* receiptTimeMillis */,
+ false /* cacheFlush */,
+ ttlTime /* ttlMillis */,
+ serviceName)
+ response.addPointerRecord(pointerRecord)
+
+ // Set SRV record.
+ val serviceRecord = MdnsServiceRecord(
+ serviceName,
+ TEST_ELAPSED_REALTIME_MS /* receiptTimeMillis */,
+ false /* cacheFlush */,
+ ttlTime /* ttlMillis */,
+ 0 /* servicePriority */,
+ 0 /* serviceWeight */,
+ 12345 /* port */,
+ arrayOf("hostname"))
+ response.serviceRecord = serviceRecord
+ return response
+ }
}
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceTypeClientTests.java b/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceTypeClientTests.java
index ce154dd..26a3796 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceTypeClientTests.java
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceTypeClientTests.java
@@ -194,7 +194,9 @@
thread.start();
handler = new Handler(thread.getLooper());
serviceCache = new MdnsServiceCache(
- thread.getLooper(), MdnsFeatureFlags.newBuilder().build());
+ thread.getLooper(),
+ MdnsFeatureFlags.newBuilder().setIsExpiredServicesRemovalEnabled(false).build(),
+ mockDecoderClock);
doAnswer(inv -> {
latestDelayMs = 0;
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsSocketClientTests.java b/tests/unit/java/com/android/server/connectivity/mdns/MdnsSocketClientTests.java
index 74f1c37..8b7ab71 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsSocketClientTests.java
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsSocketClientTests.java
@@ -78,6 +78,7 @@
@Mock private SharedLog sharedLog;
private MdnsSocketClient mdnsClient;
+ private MdnsFeatureFlags flags = MdnsFeatureFlags.newBuilder().build();
@Before
public void setup() throws RuntimeException, IOException {
@@ -86,7 +87,7 @@
when(mockWifiManager.createMulticastLock(ArgumentMatchers.anyString()))
.thenReturn(mockMulticastLock);
- mdnsClient = new MdnsSocketClient(mContext, mockMulticastLock, sharedLog) {
+ mdnsClient = new MdnsSocketClient(mContext, mockMulticastLock, sharedLog, flags) {
@Override
MdnsSocket createMdnsSocket(int port, SharedLog sharedLog) throws IOException {
if (port == MdnsConstants.MDNS_PORT) {
@@ -515,7 +516,7 @@
//MdnsConfigsFlagsImpl.allowNetworkInterfaceIndexPropagation.override(true);
when(mockMulticastSocket.getInterfaceIndex()).thenReturn(21);
- mdnsClient = new MdnsSocketClient(mContext, mockMulticastLock, sharedLog) {
+ mdnsClient = new MdnsSocketClient(mContext, mockMulticastLock, sharedLog, flags) {
@Override
MdnsSocket createMdnsSocket(int port, SharedLog sharedLog) {
if (port == MdnsConstants.MDNS_PORT) {
@@ -538,7 +539,7 @@
//MdnsConfigsFlagsImpl.allowNetworkInterfaceIndexPropagation.override(false);
when(mockMulticastSocket.getInterfaceIndex()).thenReturn(21);
- mdnsClient = new MdnsSocketClient(mContext, mockMulticastLock, sharedLog) {
+ mdnsClient = new MdnsSocketClient(mContext, mockMulticastLock, sharedLog, flags) {
@Override
MdnsSocket createMdnsSocket(int port, SharedLog sharedLog) {
if (port == MdnsConstants.MDNS_PORT) {
diff --git a/thread/TEST_MAPPING b/thread/TEST_MAPPING
index 3eaebfa..30aeca5 100644
--- a/thread/TEST_MAPPING
+++ b/thread/TEST_MAPPING
@@ -3,5 +3,10 @@
{
"name": "CtsThreadNetworkTestCases"
}
+ ],
+ "postsubmit": [
+ {
+ "name": "ThreadNetworkUnitTests"
+ }
]
}
diff --git a/thread/framework/java/android/net/thread/PendingOperationalDataset.java b/thread/framework/java/android/net/thread/PendingOperationalDataset.java
index 9cfd0b8..c1351af 100644
--- a/thread/framework/java/android/net/thread/PendingOperationalDataset.java
+++ b/thread/framework/java/android/net/thread/PendingOperationalDataset.java
@@ -39,6 +39,7 @@
* a given delay. This is typically used to deploy new network parameters (e.g. Network Key or
* Channel) to all devices in the network.
*
+ * @see ThreadNetworkController#scheduleMigration
* @hide
*/
@FlaggedApi(ThreadNetworkFlags.FLAG_THREAD_ENABLED)
@@ -76,7 +77,8 @@
* @param pendingTimestamp the Pending Timestamp which represents the version of this Pending
* Dataset
* @param delayTimer the delay after when {@code activeOpDataset} will be committed on this
- * device
+ * device; use {@link Duration#ZERO} to tell the system to choose a reasonable value
+ * automatically
*/
public PendingOperationalDataset(
@NonNull ActiveOperationalDataset activeOpDataset,
diff --git a/thread/framework/java/android/net/thread/ThreadNetworkController.java b/thread/framework/java/android/net/thread/ThreadNetworkController.java
index ec39db4..5c5fda9 100644
--- a/thread/framework/java/android/net/thread/ThreadNetworkController.java
+++ b/thread/framework/java/android/net/thread/ThreadNetworkController.java
@@ -226,15 +226,17 @@
* @param callback the callback which has been registered with {@link #registerStateCallback}
* @throws IllegalArgumentException if {@code callback} hasn't been registered
*/
+ @RequiresPermission(permission.ACCESS_NETWORK_STATE)
public void unregisterStateCallback(@NonNull StateCallback callback) {
requireNonNull(callback, "callback cannot be null");
synchronized (mStateCallbackMapLock) {
- StateCallbackProxy callbackProxy = mStateCallbackMap.remove(callback);
+ StateCallbackProxy callbackProxy = mStateCallbackMap.get(callback);
if (callbackProxy == null) {
throw new IllegalArgumentException("callback hasn't been registered");
}
try {
mControllerService.unregisterStateCallback(callbackProxy);
+ mStateCallbackMap.remove(callback);
} catch (RemoteException e) {
e.rethrowFromSystemServer();
}
@@ -334,15 +336,21 @@
* #registerOperationalDatasetCallback}
* @throws IllegalArgumentException if {@code callback} hasn't been registered
*/
+ @RequiresPermission(
+ allOf = {
+ permission.ACCESS_NETWORK_STATE,
+ "android.permission.THREAD_NETWORK_PRIVILEGED"
+ })
public void unregisterOperationalDatasetCallback(@NonNull OperationalDatasetCallback callback) {
requireNonNull(callback, "callback cannot be null");
synchronized (mOpDatasetCallbackMapLock) {
- OperationalDatasetCallbackProxy callbackProxy = mOpDatasetCallbackMap.remove(callback);
+ OperationalDatasetCallbackProxy callbackProxy = mOpDatasetCallbackMap.get(callback);
if (callbackProxy == null) {
throw new IllegalArgumentException("callback hasn't been registered");
}
try {
mControllerService.unregisterOperationalDatasetCallback(callbackProxy);
+ mOpDatasetCallbackMap.remove(callback);
} catch (RemoteException e) {
e.rethrowFromSystemServer();
}
diff --git a/thread/service/java/com/android/server/thread/InfraInterfaceController.java b/thread/service/java/com/android/server/thread/InfraInterfaceController.java
new file mode 100644
index 0000000..d7c49a0
--- /dev/null
+++ b/thread/service/java/com/android/server/thread/InfraInterfaceController.java
@@ -0,0 +1,45 @@
+/*
+ * 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.thread;
+
+import android.os.ParcelFileDescriptor;
+
+import java.io.IOException;
+
+/** Controller for the infrastructure network interface. */
+public class InfraInterfaceController {
+ private static final String TAG = "InfraIfController";
+
+ static {
+ System.loadLibrary("service-thread-jni");
+ }
+
+ /**
+ * Creates a socket on the infrastructure network interface for sending/receiving ICMPv6
+ * Neighbor Discovery messages.
+ *
+ * @param infraInterfaceName the infrastructure network interface name.
+ * @return an ICMPv6 socket file descriptor on the Infrastructure network interface.
+ * @throws IOException when fails to create the socket.
+ */
+ public static ParcelFileDescriptor createIcmp6Socket(String infraInterfaceName)
+ throws IOException {
+ return ParcelFileDescriptor.adoptFd(nativeCreateIcmp6Socket(infraInterfaceName));
+ }
+
+ private static native int nativeCreateIcmp6Socket(String interfaceName) throws IOException;
+}
diff --git a/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java b/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java
index 6c9a775..33516aa 100644
--- a/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java
+++ b/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java
@@ -223,6 +223,7 @@
return mOtDaemon;
}
+ // TODO(b/309792480): restarts the OT daemon service
private void onOtDaemonDied() {
Log.w(TAG, "OT daemon became dead, clean up...");
OperationReceiverWrapper.onOtDaemonDied();
@@ -418,12 +419,12 @@
@Override
public void registerStateCallback(IStateCallback stateCallback) throws RemoteException {
enforceAllCallingPermissionsGranted(permission.ACCESS_NETWORK_STATE);
-
mHandler.post(() -> mOtDaemonCallbackProxy.registerStateCallback(stateCallback));
}
@Override
public void unregisterStateCallback(IStateCallback stateCallback) throws RemoteException {
+ enforceAllCallingPermissionsGranted(permission.ACCESS_NETWORK_STATE);
mHandler.post(() -> mOtDaemonCallbackProxy.unregisterStateCallback(stateCallback));
}
@@ -438,6 +439,8 @@
@Override
public void unregisterOperationalDatasetCallback(IOperationalDatasetCallback callback)
throws RemoteException {
+ enforceAllCallingPermissionsGranted(
+ permission.ACCESS_NETWORK_STATE, PERMISSION_THREAD_NETWORK_PRIVILEGED);
mHandler.post(() -> mOtDaemonCallbackProxy.unregisterDatasetCallback(callback));
}
@@ -566,9 +569,15 @@
private void handleDeviceRoleChanged(@DeviceRole int deviceRole) {
if (ThreadNetworkController.isAttached(deviceRole)) {
Log.d(TAG, "Attached to the Thread network");
+
+ // This is an idempotent method which can be called for multiple times when the device
+ // is already attached (e.g. going from Child to Router)
registerThreadNetwork();
} else {
Log.d(TAG, "Detached from the Thread network");
+
+ // This is an idempotent method which can be called for multiple times when the device
+ // is already detached or stopped
unregisterThreadNetwork();
}
}
@@ -625,7 +634,7 @@
public void registerStateCallback(IStateCallback callback) {
checkOnHandlerThread();
if (mStateCallbacks.containsKey(callback)) {
- return;
+ throw new IllegalStateException("Registering the same IStateCallback twice");
}
IBinder.DeathRecipient deathRecipient =
@@ -657,7 +666,8 @@
public void registerDatasetCallback(IOperationalDatasetCallback callback) {
checkOnHandlerThread();
if (mOpDatasetCallbacks.containsKey(callback)) {
- return;
+ throw new IllegalStateException(
+ "Registering the same IOperationalDatasetCallback twice");
}
IBinder.DeathRecipient deathRecipient =
@@ -732,7 +742,7 @@
mActiveDataset = newActiveDataset;
} catch (IllegalArgumentException e) {
// Is unlikely that OT will generate invalid Operational Dataset
- Log.w(TAG, "Ignoring invalid Active Operational Dataset changes", e);
+ Log.wtf(TAG, "Invalid Active Operational Dataset from OpenThread", e);
}
PendingOperationalDataset newPendingDataset;
@@ -746,7 +756,8 @@
onPendingOperationalDatasetChanged(newPendingDataset, listenerId);
mPendingDataset = newPendingDataset;
} catch (IllegalArgumentException e) {
- Log.w(TAG, "Ignoring invalid Pending Operational Dataset changes", e);
+ // Is unlikely that OT will generate invalid Operational Dataset
+ Log.wtf(TAG, "Invalid Pending Operational Dataset from OpenThread", e);
}
}
diff --git a/thread/service/java/com/android/server/thread/TunInterfaceController.java b/thread/service/java/com/android/server/thread/TunInterfaceController.java
index ac65b11..7223b2a 100644
--- a/thread/service/java/com/android/server/thread/TunInterfaceController.java
+++ b/thread/service/java/com/android/server/thread/TunInterfaceController.java
@@ -16,6 +16,7 @@
package com.android.server.thread;
+import android.annotation.Nullable;
import android.net.LinkAddress;
import android.net.util.SocketUtils;
import android.os.ParcelFileDescriptor;
@@ -34,6 +35,7 @@
/** Controller for virtual/tunnel network interfaces. */
public class TunInterfaceController {
private static final String TAG = "TunIfController";
+ private static final long INFINITE_LIFETIME = 0xffffffffL;
static final int MTU = 1280;
static {
@@ -76,6 +78,7 @@
}
/** Returns the FD of the tunnel interface. */
+ @Nullable
public ParcelFileDescriptor getTunFd() {
return mParcelTunFd;
}
@@ -98,7 +101,7 @@
if (address.getDeprecationTime() == LinkAddress.LIFETIME_PERMANENT
|| address.getDeprecationTime() == LinkAddress.LIFETIME_UNKNOWN) {
- validLifetimeSeconds = 0xffffffffL;
+ validLifetimeSeconds = INFINITE_LIFETIME;
} else {
validLifetimeSeconds =
Math.max(
@@ -108,7 +111,7 @@
if (address.getExpirationTime() == LinkAddress.LIFETIME_PERMANENT
|| address.getExpirationTime() == LinkAddress.LIFETIME_UNKNOWN) {
- preferredLifetimeSeconds = 0xffffffffL;
+ preferredLifetimeSeconds = INFINITE_LIFETIME;
} else {
preferredLifetimeSeconds =
Math.max(
diff --git a/thread/service/jni/com_android_server_thread_InfraInterfaceController.cpp b/thread/service/jni/com_android_server_thread_InfraInterfaceController.cpp
new file mode 100644
index 0000000..5d24eab
--- /dev/null
+++ b/thread/service/jni/com_android_server_thread_InfraInterfaceController.cpp
@@ -0,0 +1,141 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "jniThreadInfra"
+
+#include <arpa/inet.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <ifaddrs.h>
+#include <inttypes.h>
+#include <linux/if_arp.h>
+#include <linux/ioctl.h>
+#include <log/log.h>
+#include <net/if.h>
+#include <netdb.h>
+#include <netinet/icmp6.h>
+#include <netinet/in.h>
+#include <private/android_filesystem_config.h>
+#include <signal.h>
+#include <spawn.h>
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include "jni.h"
+#include "nativehelper/JNIHelp.h"
+#include "nativehelper/scoped_utf_chars.h"
+
+namespace android {
+static jint
+com_android_server_thread_InfraInterfaceController_createIcmp6Socket(JNIEnv *env, jobject clazz,
+ jstring interfaceName) {
+ ScopedUtfChars ifName(env, interfaceName);
+
+ struct icmp6_filter filter;
+ constexpr int kEnable = 1;
+ constexpr int kIpv6ChecksumOffset = 2;
+ constexpr int kHopLimit = 255;
+
+ // Initializes the ICMPv6 socket.
+ int sock = socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6);
+ if (sock == -1) {
+ jniThrowExceptionFmt(env, "java/io/IOException", "failed to create the socket (%s)",
+ strerror(errno));
+ return -1;
+ }
+
+ // Only accept Router Advertisements, Router Solicitations and Neighbor
+ // Advertisements.
+ ICMP6_FILTER_SETBLOCKALL(&filter);
+ ICMP6_FILTER_SETPASS(ND_ROUTER_SOLICIT, &filter);
+ ICMP6_FILTER_SETPASS(ND_ROUTER_ADVERT, &filter);
+ ICMP6_FILTER_SETPASS(ND_NEIGHBOR_ADVERT, &filter);
+
+ if (setsockopt(sock, IPPROTO_ICMPV6, ICMP6_FILTER, &filter, sizeof(filter)) != 0) {
+ jniThrowExceptionFmt(env, "java/io/IOException", "failed to setsockopt ICMP6_FILTER (%s)",
+ strerror(errno));
+ close(sock);
+ return -1;
+ }
+
+ // We want a source address and interface index.
+
+ if (setsockopt(sock, IPPROTO_IPV6, IPV6_RECVPKTINFO, &kEnable, sizeof(kEnable)) != 0) {
+ jniThrowExceptionFmt(env, "java/io/IOException", "failed to setsockopt IPV6_RECVPKTINFO (%s)",
+ strerror(errno));
+ close(sock);
+ return -1;
+ }
+
+ if (setsockopt(sock, IPPROTO_RAW, IPV6_CHECKSUM, &kIpv6ChecksumOffset,
+ sizeof(kIpv6ChecksumOffset)) != 0) {
+ jniThrowExceptionFmt(env, "java/io/IOException", "failed to setsockopt IPV6_CHECKSUM (%s)",
+ strerror(errno));
+ close(sock);
+ return -1;
+ }
+
+ // We need to be able to reject RAs arriving from off-link.
+ if (setsockopt(sock, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, &kEnable, sizeof(kEnable)) != 0) {
+ jniThrowExceptionFmt(env, "java/io/IOException", "failed to setsockopt IPV6_RECVHOPLIMIT (%s)",
+ strerror(errno));
+ close(sock);
+ return -1;
+ }
+
+ if (setsockopt(sock, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &kHopLimit, sizeof(kHopLimit)) != 0) {
+ jniThrowExceptionFmt(env, "java/io/IOException", "failed to setsockopt IPV6_UNICAST_HOPS (%s)",
+ strerror(errno));
+ close(sock);
+ return -1;
+ }
+
+ if (setsockopt(sock, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &kHopLimit, sizeof(kHopLimit)) != 0) {
+ jniThrowExceptionFmt(env, "java/io/IOException",
+ "failed to create the setsockopt IPV6_MULTICAST_HOPS (%s)",
+ strerror(errno));
+ close(sock);
+ return -1;
+ }
+
+ if (setsockopt(sock, SOL_SOCKET, SO_BINDTODEVICE, ifName.c_str(), strlen(ifName.c_str()))) {
+ jniThrowExceptionFmt(env, "java/io/IOException", "failed to setsockopt SO_BINDTODEVICE (%s)",
+ strerror(errno));
+ close(sock);
+ return -1;
+ }
+
+ return sock;
+}
+
+/*
+ * JNI registration.
+ */
+
+static const JNINativeMethod gMethods[] = {
+ /* name, signature, funcPtr */
+ {"nativeCreateIcmp6Socket", "(Ljava/lang/String;)I",
+ (void *)com_android_server_thread_InfraInterfaceController_createIcmp6Socket},
+};
+
+int register_com_android_server_thread_InfraInterfaceController(JNIEnv *env) {
+ return jniRegisterNativeMethods(env, "com/android/server/thread/InfraInterfaceController",
+ gMethods, NELEM(gMethods));
+}
+
+}; // namespace android
diff --git a/thread/service/jni/com_android_server_thread_TunInterfaceController.cpp b/thread/service/jni/com_android_server_thread_TunInterfaceController.cpp
index ed39fab..c56bc0b 100644
--- a/thread/service/jni/com_android_server_thread_TunInterfaceController.cpp
+++ b/thread/service/jni/com_android_server_thread_TunInterfaceController.cpp
@@ -53,25 +53,25 @@
strlcpy(ifr.ifr_name, ifName.c_str(), sizeof(ifr.ifr_name));
if (ioctl(fd, TUNSETIFF, &ifr, sizeof(ifr)) != 0) {
- close(fd);
jniThrowExceptionFmt(env, "java/io/IOException", "ioctl(TUNSETIFF) failed (%s)",
strerror(errno));
+ close(fd);
return -1;
}
int inet6 = socket(AF_INET6, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, IPPROTO_IP);
if (inet6 == -1) {
- close(fd);
jniThrowExceptionFmt(env, "java/io/IOException", "create inet6 socket failed (%s)",
strerror(errno));
+ close(fd);
return -1;
}
ifr.ifr_mtu = mtu;
if (ioctl(inet6, SIOCSIFMTU, &ifr) != 0) {
- close(fd);
- close(inet6);
jniThrowExceptionFmt(env, "java/io/IOException", "ioctl(SIOCSIFMTU) failed (%s)",
strerror(errno));
+ close(fd);
+ close(inet6);
return -1;
}
@@ -94,7 +94,6 @@
}
if (ioctl(inet6, SIOCSIFFLAGS, &ifr) != 0) {
- close(inet6);
jniThrowExceptionFmt(env, "java/io/IOException", "ioctl(SIOCSIFFLAGS) failed (%s)",
strerror(errno));
}
diff --git a/thread/service/jni/onload.cpp b/thread/service/jni/onload.cpp
index 5081664..66add74 100644
--- a/thread/service/jni/onload.cpp
+++ b/thread/service/jni/onload.cpp
@@ -19,6 +19,7 @@
namespace android {
int register_com_android_server_thread_TunInterfaceController(JNIEnv* env);
+int register_com_android_server_thread_InfraInterfaceController(JNIEnv* env);
}
using namespace android;
@@ -33,5 +34,6 @@
ALOG_ASSERT(env != NULL, "Could not retrieve the env!");
register_com_android_server_thread_TunInterfaceController(env);
+ register_com_android_server_thread_InfraInterfaceController(env);
return JNI_VERSION_1_4;
}
diff --git a/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkControllerTest.java b/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkControllerTest.java
index e17dd02..362ff39 100644
--- a/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkControllerTest.java
+++ b/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkControllerTest.java
@@ -101,7 +101,7 @@
public void tearDown() throws Exception {
if (mManager != null) {
leaveAndWait();
- dropPermissions();
+ dropAllPermissions();
}
}
@@ -128,7 +128,7 @@
getInstrumentation().getUiAutomation().adoptShellPermissionIdentity(allPermissions);
}
- private static void dropPermissions() {
+ private static void dropAllPermissions() {
getInstrumentation().getUiAutomation().dropShellPermissionIdentity();
}
@@ -217,16 +217,21 @@
for (ThreadNetworkController controller : getAllControllers()) {
SettableFuture<Integer> deviceRole = SettableFuture.create();
+ StateCallback callback = deviceRole::set;
- controller.registerStateCallback(mExecutor, role -> deviceRole.set(role));
+ try {
+ controller.registerStateCallback(mExecutor, callback);
- assertThat(deviceRole.get()).isEqualTo(DEVICE_ROLE_STOPPED);
+ assertThat(deviceRole.get()).isEqualTo(DEVICE_ROLE_STOPPED);
+ } finally {
+ controller.unregisterStateCallback(callback);
+ }
}
}
@Test
public void registerStateCallback_noPermissions_throwsSecurityException() throws Exception {
- dropPermissions();
+ dropAllPermissions();
for (ThreadNetworkController controller : getAllControllers()) {
assertThrows(
@@ -252,6 +257,26 @@
}
@Test
+ public void unregisterStateCallback_noPermissions_throwsSecurityException() throws Exception {
+ for (ThreadNetworkController controller : getAllControllers()) {
+ SettableFuture<Integer> deviceRole = SettableFuture.create();
+ StateCallback callback = role -> deviceRole.set(role);
+ grantPermissions(permission.ACCESS_NETWORK_STATE);
+ controller.registerStateCallback(mExecutor, callback);
+
+ try {
+ dropAllPermissions();
+ assertThrows(
+ SecurityException.class,
+ () -> controller.unregisterStateCallback(callback));
+ } finally {
+ grantPermissions(permission.ACCESS_NETWORK_STATE);
+ controller.unregisterStateCallback(callback);
+ }
+ }
+ }
+
+ @Test
public void unregisterStateCallback_callbackRegistered_success() throws Exception {
grantPermissions(permission.ACCESS_NETWORK_STATE);
for (ThreadNetworkController controller : getAllControllers()) {
@@ -282,7 +307,7 @@
grantPermissions(permission.ACCESS_NETWORK_STATE);
for (ThreadNetworkController controller : getAllControllers()) {
SettableFuture<Integer> deviceRole = SettableFuture.create();
- StateCallback callback = role -> deviceRole.set(role);
+ StateCallback callback = deviceRole::set;
controller.registerStateCallback(mExecutor, callback);
controller.unregisterStateCallback(callback);
@@ -300,12 +325,70 @@
for (ThreadNetworkController controller : getAllControllers()) {
SettableFuture<ActiveOperationalDataset> activeFuture = SettableFuture.create();
SettableFuture<PendingOperationalDataset> pendingFuture = SettableFuture.create();
+ var callback = newDatasetCallback(activeFuture, pendingFuture);
- controller.registerOperationalDatasetCallback(
- mExecutor, newDatasetCallback(activeFuture, pendingFuture));
+ try {
+ controller.registerOperationalDatasetCallback(mExecutor, callback);
- assertThat(activeFuture.get()).isNull();
- assertThat(pendingFuture.get()).isNull();
+ assertThat(activeFuture.get()).isNull();
+ assertThat(pendingFuture.get()).isNull();
+ } finally {
+ controller.unregisterOperationalDatasetCallback(callback);
+ }
+ }
+ }
+
+ @Test
+ public void registerOperationalDatasetCallback_noPermissions_throwsSecurityException()
+ throws Exception {
+ dropAllPermissions();
+
+ for (ThreadNetworkController controller : getAllControllers()) {
+ SettableFuture<ActiveOperationalDataset> activeFuture = SettableFuture.create();
+ SettableFuture<PendingOperationalDataset> pendingFuture = SettableFuture.create();
+ var callback = newDatasetCallback(activeFuture, pendingFuture);
+
+ assertThrows(
+ SecurityException.class,
+ () -> controller.registerOperationalDatasetCallback(mExecutor, callback));
+ }
+ }
+
+ @Test
+ public void unregisterOperationalDatasetCallback_callbackRegistered_success() throws Exception {
+ grantPermissions(permission.ACCESS_NETWORK_STATE, PERMISSION_THREAD_NETWORK_PRIVILEGED);
+ for (ThreadNetworkController controller : getAllControllers()) {
+ SettableFuture<ActiveOperationalDataset> activeFuture = SettableFuture.create();
+ SettableFuture<PendingOperationalDataset> pendingFuture = SettableFuture.create();
+ var callback = newDatasetCallback(activeFuture, pendingFuture);
+ controller.registerOperationalDatasetCallback(mExecutor, callback);
+
+ controller.unregisterOperationalDatasetCallback(callback);
+ }
+ }
+
+ @Test
+ public void unregisterOperationalDatasetCallback_noPermissions_throwsSecurityException()
+ throws Exception {
+ dropAllPermissions();
+
+ for (ThreadNetworkController controller : getAllControllers()) {
+ SettableFuture<ActiveOperationalDataset> activeFuture = SettableFuture.create();
+ SettableFuture<PendingOperationalDataset> pendingFuture = SettableFuture.create();
+ var callback = newDatasetCallback(activeFuture, pendingFuture);
+ grantPermissions(permission.ACCESS_NETWORK_STATE, PERMISSION_THREAD_NETWORK_PRIVILEGED);
+ controller.registerOperationalDatasetCallback(mExecutor, callback);
+
+ try {
+ dropAllPermissions();
+ assertThrows(
+ SecurityException.class,
+ () -> controller.unregisterOperationalDatasetCallback(callback));
+ } finally {
+ grantPermissions(
+ permission.ACCESS_NETWORK_STATE, PERMISSION_THREAD_NETWORK_PRIVILEGED);
+ controller.unregisterOperationalDatasetCallback(callback);
+ }
}
}
@@ -343,7 +426,7 @@
@Test
public void join_withoutPrivilegedPermission_throwsSecurityException() throws Exception {
- dropPermissions();
+ dropAllPermissions();
for (ThreadNetworkController controller : getAllControllers()) {
ActiveOperationalDataset activeDataset = newRandomizedDataset("TestNet", controller);
@@ -408,7 +491,7 @@
@Test
public void leave_withoutPrivilegedPermission_throwsSecurityException() {
- dropPermissions();
+ dropAllPermissions();
for (ThreadNetworkController controller : getAllControllers()) {
assertThrows(SecurityException.class, () -> controller.leave(mExecutor, v -> {}));