Snap for 7259092 from b2e801082912eb6dfbf814a40184796a5d94f48e to mainline-media-swcodec-release Change-Id: I8d74fd1e8978d48832f660c53a25bb28258fad16
diff --git a/TEST_MAPPING b/TEST_MAPPING index a2ed850..178026f 100644 --- a/TEST_MAPPING +++ b/TEST_MAPPING
@@ -15,6 +15,26 @@ "name": "NetworkStackIntegrationTests" } ], + "auto-postsubmit": [ + // Test tag for automotive targets. These are only running in postsubmit so as to harden the + // automotive targets to avoid introducing additional test flake and build time. The plan for + // presubmit testing for auto is to augment the existing tests to cover auto use cases as well. + // Additionally, this tag is used in targeted test suites to limit resource usage on the test + // infra during the hardening phase. + // TODO: this tag to be removed once the above is no longer an issue. + { + "name": "NetworkStackTests" + }, + { + "name": "NetworkStackNextTests" + }, + { + "name": "NetworkStackHostTests" + }, + { + "name": "NetworkStackIntegrationTests" + } + ], "mainline-presubmit": [ // These are unit tests only, so they don't actually require any modules to be installed. // We must specify at least one module here or the tests won't run. Use the same set as CTS
diff --git a/apishim/29/com/android/networkstack/apishim/api29/CaptivePortalDataShimImpl.java b/apishim/29/com/android/networkstack/apishim/api29/CaptivePortalDataShimImpl.java index 9e27666..8719e83 100644 --- a/apishim/29/com/android/networkstack/apishim/api29/CaptivePortalDataShimImpl.java +++ b/apishim/29/com/android/networkstack/apishim/api29/CaptivePortalDataShimImpl.java
@@ -51,7 +51,7 @@ } @Override - public String getVenueFriendlyName() { + public CharSequence getVenueFriendlyName() { // Not supported in API level 29 return null; }
diff --git a/apishim/29/com/android/networkstack/apishim/api29/ConnectivityManagerShimImpl.java b/apishim/29/com/android/networkstack/apishim/api29/ConnectivityManagerShimImpl.java index e504495..3c13a56 100644 --- a/apishim/29/com/android/networkstack/apishim/api29/ConnectivityManagerShimImpl.java +++ b/apishim/29/com/android/networkstack/apishim/api29/ConnectivityManagerShimImpl.java
@@ -22,7 +22,6 @@ import android.os.Handler; import androidx.annotation.NonNull; -import androidx.annotation.Nullable; import com.android.networkstack.apishim.common.ConnectivityManagerShim; import com.android.networkstack.apishim.common.UnsupportedApiLevelException; @@ -45,7 +44,7 @@ */ @Override public void requestBackgroundNetwork(@NonNull NetworkRequest request, - @Nullable Handler handler, @NonNull NetworkCallback networkCallback) + @NonNull Handler handler, @NonNull NetworkCallback networkCallback) throws UnsupportedApiLevelException { // Not supported for API 29. throw new UnsupportedApiLevelException("Not supported in API 29.");
diff --git a/apishim/29/com/android/networkstack/apishim/api29/NetworkRequestShimImpl.java b/apishim/29/com/android/networkstack/apishim/api29/NetworkRequestShimImpl.java new file mode 100644 index 0000000..0c1d837 --- /dev/null +++ b/apishim/29/com/android/networkstack/apishim/api29/NetworkRequestShimImpl.java
@@ -0,0 +1,49 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.networkstack.apishim.api29; + +import android.net.NetworkRequest; +import android.util.Range; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.android.networkstack.apishim.common.NetworkRequestShim; +import com.android.networkstack.apishim.common.UnsupportedApiLevelException; + +import java.util.Set; + +/** + * Implementation of {@link NetworkRequestShim} for API 29. + */ +public class NetworkRequestShimImpl implements NetworkRequestShim { + protected NetworkRequestShimImpl() {} + + /** + * Get a new instance of {@link NetworkRequestShim}. + */ + public static NetworkRequestShim newInstance() { + return new NetworkRequestShimImpl(); + } + + @Override + public void setUids(@NonNull NetworkRequest.Builder builder, + @Nullable Set<Range<Integer>> uids) throws UnsupportedApiLevelException { + // Not supported before API 31. + throw new UnsupportedApiLevelException("Not supported before API 31."); + } +}
diff --git a/apishim/30/com/android/networkstack/apishim/api30/ConnectivityManagerShimImpl.java b/apishim/30/com/android/networkstack/apishim/api30/ConnectivityManagerShimImpl.java index ac62522..c1e3561 100644 --- a/apishim/30/com/android/networkstack/apishim/api30/ConnectivityManagerShimImpl.java +++ b/apishim/30/com/android/networkstack/apishim/api30/ConnectivityManagerShimImpl.java
@@ -23,7 +23,6 @@ import android.os.Handler; import androidx.annotation.NonNull; -import androidx.annotation.Nullable; import com.android.networkstack.apishim.common.ConnectivityManagerShim; import com.android.networkstack.apishim.common.ShimUtils; @@ -55,7 +54,7 @@ */ @Override public void requestBackgroundNetwork(@NonNull NetworkRequest request, - @Nullable Handler handler, @NonNull NetworkCallback networkCallback) + @NonNull Handler handler, @NonNull NetworkCallback networkCallback) throws UnsupportedApiLevelException { // Not supported for API 30. throw new UnsupportedApiLevelException("Not supported in API 30.");
diff --git a/apishim/30/com/android/networkstack/apishim/api30/NetworkRequestShimImpl.java b/apishim/30/com/android/networkstack/apishim/api30/NetworkRequestShimImpl.java new file mode 100644 index 0000000..b65a556 --- /dev/null +++ b/apishim/30/com/android/networkstack/apishim/api30/NetworkRequestShimImpl.java
@@ -0,0 +1,43 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.networkstack.apishim.api30; + +import android.os.Build; + +import com.android.networkstack.apishim.common.NetworkRequestShim; +import com.android.networkstack.apishim.common.ShimUtils; + +/** + * Implementation of {@link NetworkRequestShim} for API 30. + */ +public class NetworkRequestShimImpl + extends com.android.networkstack.apishim.api29.NetworkRequestShimImpl { + protected NetworkRequestShimImpl() { + super(); + } + + /** + * Get a new instance of {@link NetworkRequestShim}. + */ + public static NetworkRequestShim newInstance() { + if (!ShimUtils.isReleaseOrDevelopmentApiAbove(Build.VERSION_CODES.Q)) { + return com.android.networkstack.apishim.api29.NetworkRequestShimImpl + .newInstance(); + } + return new NetworkRequestShimImpl(); + } +}
diff --git a/apishim/31/com/android/networkstack/apishim/CaptivePortalDataShimImpl.java b/apishim/31/com/android/networkstack/apishim/CaptivePortalDataShimImpl.java index 6e3eb19..5af7412 100644 --- a/apishim/31/com/android/networkstack/apishim/CaptivePortalDataShimImpl.java +++ b/apishim/31/com/android/networkstack/apishim/CaptivePortalDataShimImpl.java
@@ -33,7 +33,7 @@ } @Override - public String getVenueFriendlyName() { + public CharSequence getVenueFriendlyName() { return mData.getVenueFriendlyName(); }
diff --git a/apishim/31/com/android/networkstack/apishim/ConnectivityManagerShimImpl.java b/apishim/31/com/android/networkstack/apishim/ConnectivityManagerShimImpl.java index 0c3fa3e..a62a9db 100644 --- a/apishim/31/com/android/networkstack/apishim/ConnectivityManagerShimImpl.java +++ b/apishim/31/com/android/networkstack/apishim/ConnectivityManagerShimImpl.java
@@ -24,7 +24,6 @@ import android.os.Handler; import androidx.annotation.NonNull; -import androidx.annotation.Nullable; import com.android.networkstack.apishim.common.ConnectivityManagerShim; import com.android.networkstack.apishim.common.ShimUtils; @@ -57,7 +56,7 @@ */ @Override public void requestBackgroundNetwork(@NonNull NetworkRequest request, - @Nullable Handler handler, @NonNull NetworkCallback networkCallback) { + @NonNull Handler handler, @NonNull NetworkCallback networkCallback) { mCm.requestBackgroundNetwork(request, handler, networkCallback); }
diff --git a/apishim/31/com/android/networkstack/apishim/NetworkRequestShimImpl.java b/apishim/31/com/android/networkstack/apishim/NetworkRequestShimImpl.java new file mode 100644 index 0000000..b0d5827 --- /dev/null +++ b/apishim/31/com/android/networkstack/apishim/NetworkRequestShimImpl.java
@@ -0,0 +1,56 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.networkstack.apishim; + +import android.net.NetworkRequest; +import android.os.Build; +import android.util.Range; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.android.networkstack.apishim.common.NetworkRequestShim; +import com.android.networkstack.apishim.common.ShimUtils; + +import java.util.Set; + +/** + * Implementation of {@link NetworkRequestShim} for API 31. + */ +public class NetworkRequestShimImpl + extends com.android.networkstack.apishim.api30.NetworkRequestShimImpl { + protected NetworkRequestShimImpl() { + super(); + } + + /** + * Get a new instance of {@link NetworkRequestShim}. + */ + public static NetworkRequestShim newInstance() { + if (!ShimUtils.isReleaseOrDevelopmentApiAbove(Build.VERSION_CODES.R)) { + return com.android.networkstack.apishim.api30.NetworkRequestShimImpl + .newInstance(); + } + return new NetworkRequestShimImpl(); + } + + @Override + public void setUids(@NonNull NetworkRequest.Builder builder, + @Nullable Set<Range<Integer>> uids) { + builder.setUids(uids); + } +}
diff --git a/apishim/common/com/android/networkstack/apishim/common/CaptivePortalDataShim.java b/apishim/common/com/android/networkstack/apishim/common/CaptivePortalDataShim.java index 26ac9d4..13bf257 100644 --- a/apishim/common/com/android/networkstack/apishim/common/CaptivePortalDataShim.java +++ b/apishim/common/com/android/networkstack/apishim/common/CaptivePortalDataShim.java
@@ -54,7 +54,7 @@ /** * @see CaptivePortalData#getVenueFriendlyName() */ - String getVenueFriendlyName(); + CharSequence getVenueFriendlyName(); /** * @see CaptivePortalData#getUserPortalUrlSource()
diff --git a/apishim/common/com/android/networkstack/apishim/common/ConnectivityManagerShim.java b/apishim/common/com/android/networkstack/apishim/common/ConnectivityManagerShim.java index 2d60d97..beb5f1d 100644 --- a/apishim/common/com/android/networkstack/apishim/common/ConnectivityManagerShim.java +++ b/apishim/common/com/android/networkstack/apishim/common/ConnectivityManagerShim.java
@@ -21,8 +21,6 @@ import android.os.Handler; import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - /** * Interface used to access API methods in {@link android.net.ConnectivityManager}, with @@ -36,7 +34,7 @@ public interface ConnectivityManagerShim { /** See android.net.ConnectivityManager#requestBackgroundNetwork */ void requestBackgroundNetwork(@NonNull NetworkRequest request, - @Nullable Handler handler, @NonNull NetworkCallback networkCallback) + @NonNull Handler handler, @NonNull NetworkCallback networkCallback) throws UnsupportedApiLevelException; /** See android.net.ConnectivityManager#registerSystemDefaultNetworkCallback */
diff --git a/apishim/common/com/android/networkstack/apishim/common/NetworkRequestShim.java b/apishim/common/com/android/networkstack/apishim/common/NetworkRequestShim.java new file mode 100644 index 0000000..7b53efa --- /dev/null +++ b/apishim/common/com/android/networkstack/apishim/common/NetworkRequestShim.java
@@ -0,0 +1,38 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.networkstack.apishim.common; + +import android.net.NetworkRequest; +import android.util.Range; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import java.util.Set; + +/** + * Interface used to access API methods in {@link android.net.NetworkRequest}, with + * appropriate fallbacks if the methods are not yet part of the released API. + */ +public interface NetworkRequestShim { + /** + * See android.net.NetworkRequest.Builder#setUids. + * Set the {@code uids} into {@code builder}. + */ + void setUids(@NonNull NetworkRequest.Builder builder, + @Nullable Set<Range<Integer>> uids) throws UnsupportedApiLevelException; +}
diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml index 527d895..7b54302 100644 --- a/res/values-nl/strings.xml +++ b/res/values-nl/strings.xml
@@ -17,9 +17,9 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="notification_channel_name_connected" msgid="1795068343200033922">"Verificatie van captive portal"</string> - <string name="notification_channel_description_connected" msgid="7239184168268014518">"Er worden meldingen weergegeven als het apparaat is geverifieerd voor een captive portal-netwerk"</string> + <string name="notification_channel_description_connected" msgid="7239184168268014518">"Er worden meldingen getoond als het apparaat is geverifieerd voor een captive portal-netwerk"</string> <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"Netwerklocatie-informatie"</string> - <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"Er worden meldingen weergegeven om aan te geven dat het netwerk een locatie-informatiepagina heeft"</string> + <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"Er worden meldingen getoond om aan te geven dat het netwerk een locatie-informatiepagina heeft"</string> <string name="connected" msgid="4563643884927480998">"Verbonden"</string> <string name="tap_for_info" msgid="6849746325626883711">"Verbonden / Tik om de website te bekijken"</string> <string name="application_label" msgid="1322847171305285454">"Netwerkbeheer"</string>
diff --git a/src/android/net/dhcp/DhcpClient.java b/src/android/net/dhcp/DhcpClient.java index 5f77128..8e0e9d3 100644 --- a/src/android/net/dhcp/DhcpClient.java +++ b/src/android/net/dhcp/DhcpClient.java
@@ -97,9 +97,9 @@ import com.android.internal.util.MessageUtils; import com.android.internal.util.State; import com.android.internal.util.StateMachine; -import com.android.internal.util.TrafficStatsConstants; import com.android.internal.util.WakeupMessage; import com.android.net.module.util.DeviceConfigUtils; +import com.android.net.module.util.NetworkStackConstants; import com.android.net.module.util.PacketReader; import com.android.networkstack.R; import com.android.networkstack.apishim.CaptivePortalDataShimImpl; @@ -611,7 +611,7 @@ private boolean initUdpSocket() { final int oldTag = TrafficStats.getAndSetThreadStatsTag( - TrafficStatsConstants.TAG_SYSTEM_DHCP); + NetworkStackConstants.TAG_SYSTEM_DHCP); try { mUdpSock = Os.socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); SocketUtils.bindSocketToInterface(mUdpSock, mIfaceName);
diff --git a/src/android/net/dhcp/DhcpServer.java b/src/android/net/dhcp/DhcpServer.java index 3acd76e..3465e72 100644 --- a/src/android/net/dhcp/DhcpServer.java +++ b/src/android/net/dhcp/DhcpServer.java
@@ -33,12 +33,12 @@ import static android.system.OsConstants.SO_BROADCAST; import static android.system.OsConstants.SO_REUSEADDR; -import static com.android.internal.util.TrafficStatsConstants.TAG_SYSTEM_DHCP_SERVER; import static com.android.net.module.util.Inet4AddressUtils.getBroadcastAddress; import static com.android.net.module.util.Inet4AddressUtils.getPrefixMaskAsInet4Address; import static com.android.net.module.util.NetworkStackConstants.INFINITE_LEASE; import static com.android.net.module.util.NetworkStackConstants.IPV4_ADDR_ALL; import static com.android.net.module.util.NetworkStackConstants.IPV4_ADDR_ANY; +import static com.android.net.module.util.NetworkStackConstants.TAG_SYSTEM_DHCP_SERVER; import static com.android.server.util.PermissionUtil.enforceNetworkStackCallingPermission; import static java.lang.Integer.toUnsignedLong;
diff --git a/src/android/net/ip/IpClient.java b/src/android/net/ip/IpClient.java index a73e997..980c6d6 100644 --- a/src/android/net/ip/IpClient.java +++ b/src/android/net/ip/IpClient.java
@@ -18,8 +18,14 @@ import static android.net.RouteInfo.RTN_UNICAST; import static android.net.dhcp.DhcpResultsParcelableUtil.toStableParcelable; +import static android.net.util.NetworkStackUtils.IPCLIENT_GRATUITOUS_NA_VERSION; import static android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY; +import static android.system.OsConstants.AF_PACKET; +import static android.system.OsConstants.ETH_P_IPV6; +import static android.system.OsConstants.SOCK_NONBLOCK; +import static android.system.OsConstants.SOCK_RAW; +import static com.android.net.module.util.NetworkStackConstants.IPV6_ADDR_ALL_ROUTERS_MULTICAST; import static com.android.net.module.util.NetworkStackConstants.VENDOR_SPECIFIC_IE_ID; import static com.android.server.util.PermissionUtil.enforceNetworkStackCallingPermission; @@ -52,6 +58,7 @@ import android.net.shared.ProvisioningConfiguration.ScanResultInfo; import android.net.shared.ProvisioningConfiguration.ScanResultInfo.InformationElement; import android.net.util.InterfaceParams; +import android.net.util.NetworkStackUtils; import android.net.util.SharedLog; import android.os.Build; import android.os.ConditionVariable; @@ -61,6 +68,8 @@ import android.os.ServiceSpecificException; import android.os.SystemClock; import android.stats.connectivity.DisconnectCode; +import android.system.ErrnoException; +import android.system.Os; import android.text.TextUtils; import android.util.LocalLog; import android.util.Log; @@ -79,16 +88,21 @@ import com.android.internal.util.WakeupMessage; import com.android.net.module.util.DeviceConfigUtils; import com.android.networkstack.apishim.NetworkInformationShimImpl; +import com.android.networkstack.apishim.SocketUtilsShimImpl; import com.android.networkstack.apishim.common.NetworkInformationShim; import com.android.networkstack.apishim.common.ShimUtils; import com.android.networkstack.metrics.IpProvisioningMetrics; +import com.android.networkstack.packets.NeighborAdvertisement; import com.android.server.NetworkObserverRegistry; import com.android.server.NetworkStackService.NetworkStackServiceManager; import java.io.FileDescriptor; import java.io.PrintWriter; +import java.net.Inet6Address; import java.net.InetAddress; import java.net.MalformedURLException; +import java.net.SocketAddress; +import java.net.SocketException; import java.net.URL; import java.nio.BufferUnderflowException; import java.nio.ByteBuffer; @@ -479,6 +493,8 @@ private final MessageHandlingLogger mMsgStateLogger; private final IpConnectivityLog mMetricsLog; private final InterfaceController mInterfaceCtrl; + // Set of IPv6 addresses for which unsolicited gratuitous NA packets have been sent. + private final Set<Inet6Address> mGratuitousNaTargetAddresses = new HashSet<>(); // Ignore nonzero RDNSS option lifetimes below this value. 0 = disabled. private final int mMinRdnssLifetimeSec; @@ -564,6 +580,16 @@ public IpConnectivityLog getIpConnectivityLog() { return new IpConnectivityLog(); } + + /** + * Return whether a feature guarded by a feature flag is enabled. + * @see NetworkStackUtils#isFeatureEnabled(Context, String, String) + */ + public boolean isFeatureEnabled(final Context context, final String name, + boolean defaultEnabled) { + return DeviceConfigUtils.isFeatureEnabled(context, NAMESPACE_CONNECTIVITY, name, + defaultEnabled); + } } public IpClient(Context context, String ifName, IIpClientCallbacks callback, @@ -647,6 +673,21 @@ logMsg(msg); } + @Override + public void onInterfaceAddressRemoved(LinkAddress address, String iface) { + super.onInterfaceAddressRemoved(address, iface); + if (!mInterfaceName.equals(iface)) return; + if (!address.isIpv6()) return; + final Inet6Address targetIp = (Inet6Address) address.getAddress(); + if (mGratuitousNaTargetAddresses.contains(targetIp)) { + mGratuitousNaTargetAddresses.remove(targetIp); + + final String msg = "Global IPv6 address: " + targetIp + + " has removed from the set of gratuitous NA target address."; + logMsg(msg); + } + } + private void logMsg(String msg) { Log.d(mTag, msg); getHandler().post(() -> mLog.log("OBSERVED " + msg)); @@ -793,6 +834,11 @@ mLinkObserver.shutdown(); } + private boolean isGratuitousNaEnabled() { + return mDependencies.isFeatureEnabled(mContext, IPCLIENT_GRATUITOUS_NA_VERSION, + false /* defaultEnabled */); + } + @Override protected void onQuitting() { mCallback.onQuit(); @@ -1377,6 +1423,57 @@ } } + private void sendGratuitousNA(final Inet6Address srcIp, final Inet6Address target) { + final int flags = 0; // R=0, S=0, O=0 + final Inet6Address dstIp = IPV6_ADDR_ALL_ROUTERS_MULTICAST; + // Ethernet multicast destination address: 33:33:00:00:00:02. + final MacAddress dstMac = NetworkStackUtils.ipv6MulticastToEthernetMulticast(dstIp); + final ByteBuffer packet = NeighborAdvertisement.build(mInterfaceParams.macAddr, dstMac, + srcIp, dstIp, flags, target); + FileDescriptor sock = null; + try { + sock = Os.socket(AF_PACKET, SOCK_RAW | SOCK_NONBLOCK, 0 /* protocol */); + final SocketAddress sockAddress = + SocketUtilsShimImpl.newInstance().makePacketSocketAddress(ETH_P_IPV6, + mInterfaceParams.index, dstMac.toByteArray()); + Os.sendto(sock, packet.array(), 0 /* byteOffset */, packet.limit() /* byteCount */, + 0 /* flags */, sockAddress); + } catch (SocketException | ErrnoException e) { + logError("Fail to send Gratuitous Neighbor Advertisement", e); + } finally { + NetworkStackUtils.closeSocketQuietly(sock); + } + } + + private Inet6Address getIpv6LinkLocalAddress(final LinkProperties newLp) { + Inet6Address src = null; + for (LinkAddress la : newLp.getLinkAddresses()) { + if (!la.isIpv6()) continue; + final Inet6Address ip = (Inet6Address) la.getAddress(); + if (ip.isLinkLocalAddress()) return ip; + } + return null; + } + + private void maybeSendGratuitousNAs(final LinkProperties newLp) { + if (!newLp.hasGlobalIpv6Address() || !isGratuitousNaEnabled()) return; + + final Inet6Address src = getIpv6LinkLocalAddress(newLp); + if (src == null) return; + for (LinkAddress la : newLp.getLinkAddresses()) { + if (!la.isIpv6() || !la.isGlobalPreferred()) continue; + final Inet6Address targetIp = (Inet6Address) la.getAddress(); + // Already sent gratuitous NA with this target global IPv6 address. + if (mGratuitousNaTargetAddresses.contains(targetIp)) continue; + if (DBG) { + Log.d(mTag, "send Gratuitous NA from " + src + ", target Address is " + + targetIp); + } + sendGratuitousNA(src, targetIp); + mGratuitousNaTargetAddresses.add(targetIp); + } + } + // Returns false if we have lost provisioning, true otherwise. private boolean handleLinkPropertiesUpdate(boolean sendCallbacks) { final LinkProperties newLp = assembleLinkProperties(); @@ -1384,6 +1481,11 @@ return true; } + // Check if new assigned IPv6 GUA is available in the LinkProperties now. If so, initiate + // gratuitous multicast unsolicited Neighbor Advertisements as soon as possible to inform + // first-hop routers that the new GUA host is goning to use. + maybeSendGratuitousNAs(newLp); + // Either success IPv4 or IPv6 provisioning triggers new LinkProperties update, // wait for the provisioning completion and record the latency. mIpProvisioningMetrics.setIPv4ProvisionedLatencyOnFirstTime(newLp.isIpv4Provisioned()); @@ -1662,6 +1764,7 @@ public void enter() { stopAllIP(); mHasDisabledIPv6OnProvLoss = false; + mGratuitousNaTargetAddresses.clear(); mLinkObserver.clearInterfaceParams(); resetLinkProperties();
diff --git a/src/android/net/util/NetworkStackUtils.java b/src/android/net/util/NetworkStackUtils.java index 5c11733..6e32833 100755 --- a/src/android/net/util/NetworkStackUtils.java +++ b/src/android/net/util/NetworkStackUtils.java
@@ -17,12 +17,16 @@ package android.net.util; import android.content.Context; +import android.net.MacAddress; + +import androidx.annotation.NonNull; import com.android.net.module.util.DeviceConfigUtils; import java.io.FileDescriptor; import java.io.IOException; import java.net.Inet4Address; +import java.net.Inet6Address; import java.net.SocketException; /** @@ -232,6 +236,12 @@ */ public static final String VALIDATION_METRICS_VERSION = "validation_metrics_version"; + /** + * Experiment flag to enable sending gratuitous multicast unsolicited Neighbor Advertisements + * to propagate new assigned IPv6 GUA as quickly as possible. + */ + public static final String IPCLIENT_GRATUITOUS_NA_VERSION = "ipclient_gratuitous_na_version"; + static { System.loadLibrary("networkstackutilsjni"); } @@ -247,6 +257,21 @@ } /** + * Convert IPv6 multicast address to ethernet multicast address in network order. + */ + public static MacAddress ipv6MulticastToEthernetMulticast(@NonNull final Inet6Address addr) { + final byte[] etherMulticast = new byte[6]; + final byte[] ipv6Multicast = addr.getAddress(); + etherMulticast[0] = (byte) 0x33; + etherMulticast[1] = (byte) 0x33; + etherMulticast[2] = ipv6Multicast[12]; + etherMulticast[3] = ipv6Multicast[13]; + etherMulticast[4] = ipv6Multicast[14]; + etherMulticast[5] = ipv6Multicast[15]; + return MacAddress.fromBytes(etherMulticast); + } + + /** * Attaches a socket filter that accepts DHCP packets to the given socket. */ public static native void attachDhcpFilter(FileDescriptor fd) throws SocketException;
diff --git a/src/com/android/networkstack/NetworkStackNotifier.java b/src/com/android/networkstack/NetworkStackNotifier.java index 0558d3a..acf3c95 100644 --- a/src/com/android/networkstack/NetworkStackNotifier.java +++ b/src/com/android/networkstack/NetworkStackNotifier.java
@@ -237,8 +237,8 @@ // If the venue friendly name is available (in Passpoint use-case), display it. // Otherwise, display the SSID. - final String friendlyName = capportData.getVenueFriendlyName(); - final String venueDisplayName = TextUtils.isEmpty(friendlyName) + final CharSequence friendlyName = capportData.getVenueFriendlyName(); + final CharSequence venueDisplayName = TextUtils.isEmpty(friendlyName) ? getSsid(networkStatus) : friendlyName; builder = getNotificationBuilder(channel, networkStatus, res, venueDisplayName) @@ -284,9 +284,9 @@ private Notification.Builder getNotificationBuilder(@NonNull String channelId, @NonNull TrackedNetworkStatus networkStatus, @NonNull Resources res, - @NonNull String ssid) { + @NonNull CharSequence networkIdentifier) { return new Notification.Builder(mContext, channelId) - .setContentTitle(ssid) + .setContentTitle(networkIdentifier) .setSmallIcon(R.drawable.icon_wifi); }
diff --git a/src/com/android/networkstack/util/DnsUtils.java b/src/com/android/networkstack/util/DnsUtils.java index 83f2daf..622f56a 100644 --- a/src/com/android/networkstack/util/DnsUtils.java +++ b/src/com/android/networkstack/util/DnsUtils.java
@@ -29,7 +29,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import com.android.internal.util.TrafficStatsConstants; +import com.android.net.module.util.NetworkStackConstants; import com.android.server.connectivity.NetworkMonitor.DnsLogFunc; import java.net.InetAddress; @@ -126,7 +126,7 @@ // look at the tag at all. Given that this is a library, the tag should be passed in by the // caller. final int oldTag = TrafficStats.getAndSetThreadStatsTag( - TrafficStatsConstants.TAG_SYSTEM_PROBE); + NetworkStackConstants.TAG_SYSTEM_PROBE); if (type == TYPE_ADDRCONFIG) { dnsResolver.query(network, host, flag, r -> r.run(), null /* cancellationSignal */,
diff --git a/src/com/android/server/connectivity/NetworkMonitor.java b/src/com/android/server/connectivity/NetworkMonitor.java index b4a3ae9..94be13e 100755 --- a/src/com/android/server/connectivity/NetworkMonitor.java +++ b/src/com/android/server/connectivity/NetworkMonitor.java
@@ -161,8 +161,8 @@ import com.android.internal.util.RingBufferIndices; import com.android.internal.util.State; import com.android.internal.util.StateMachine; -import com.android.internal.util.TrafficStatsConstants; import com.android.net.module.util.DeviceConfigUtils; +import com.android.net.module.util.NetworkStackConstants; import com.android.networkstack.NetworkStackNotifier; import com.android.networkstack.R; import com.android.networkstack.apishim.CaptivePortalDataShimImpl; @@ -2463,7 +2463,7 @@ String redirectUrl = null; final Stopwatch probeTimer = new Stopwatch().start(); final int oldTag = TrafficStats.getAndSetThreadStatsTag( - TrafficStatsConstants.TAG_SYSTEM_PROBE); + NetworkStackConstants.TAG_SYSTEM_PROBE); try { // Follow redirects for PAC probes as such probes verify connectivity by fetching the // PAC proxy file, which may be configured behind a redirect.
diff --git a/tests/integration/src/android/net/ip/IpClientIntegrationTest.kt b/tests/integration/src/android/net/ip/IpClientIntegrationTest.kt index 9f29a2e..eb0a799 100644 --- a/tests/integration/src/android/net/ip/IpClientIntegrationTest.kt +++ b/tests/integration/src/android/net/ip/IpClientIntegrationTest.kt
@@ -17,6 +17,7 @@ package android.net.ip import android.net.ipmemorystore.NetworkAttributes +import android.util.ArrayMap import org.mockito.Mockito.any import org.mockito.ArgumentCaptor import org.mockito.Mockito.eq @@ -28,22 +29,20 @@ * Tests for IpClient, run with signature permissions. */ class IpClientIntegrationTest : IpClientIntegrationTestCommon() { + private val mEnabledFeatures = ArrayMap<String, Boolean>() + override fun makeIIpClient(ifaceName: String, cb: IIpClientCallbacks): IIpClient { return mIpc.makeConnector() } override fun useNetworkStackSignature() = true - override fun setDhcpFeatures( - isDhcpLeaseCacheEnabled: Boolean, - isRapidCommitEnabled: Boolean, - isDhcpIpConflictDetectEnabled: Boolean, - isIPv6OnlyPreferredEnabled: Boolean - ) { - mDependencies.setDhcpLeaseCacheEnabled(isDhcpLeaseCacheEnabled) - mDependencies.setDhcpRapidCommitEnabled(isRapidCommitEnabled) - mDependencies.setDhcpIpConflictDetectEnabled(isDhcpIpConflictDetectEnabled) - mDependencies.setIPv6OnlyPreferredEnabled(isIPv6OnlyPreferredEnabled) + override fun isFeatureEnabled(name: String, defaultEnabled: Boolean): Boolean { + return mEnabledFeatures.get(name) ?: defaultEnabled + } + + override fun setFeatureEnabled(name: String, enabled: Boolean) { + mEnabledFeatures.put(name, enabled) } override fun getStoredNetworkAttributes(l2Key: String, timeout: Long): NetworkAttributes {
diff --git a/tests/integration/src/android/net/ip/IpClientIntegrationTestCommon.java b/tests/integration/src/android/net/ip/IpClientIntegrationTestCommon.java index 451437e..ae68362 100644 --- a/tests/integration/src/android/net/ip/IpClientIntegrationTestCommon.java +++ b/tests/integration/src/android/net/ip/IpClientIntegrationTestCommon.java
@@ -39,11 +39,15 @@ import static com.android.net.module.util.NetworkStackConstants.ETHER_ADDR_LEN; import static com.android.net.module.util.NetworkStackConstants.ETHER_HEADER_LEN; import static com.android.net.module.util.NetworkStackConstants.ETHER_TYPE_OFFSET; +import static com.android.net.module.util.NetworkStackConstants.ICMPV6_NEIGHBOR_ADVERTISEMENT; import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ROUTER_SOLICITATION; import static com.android.net.module.util.NetworkStackConstants.IPV4_ADDR_ANY; import static com.android.net.module.util.NetworkStackConstants.IPV6_ADDR_ALL_NODES_MULTICAST; +import static com.android.net.module.util.NetworkStackConstants.IPV6_ADDR_ALL_ROUTERS_MULTICAST; import static com.android.net.module.util.NetworkStackConstants.IPV6_HEADER_LEN; import static com.android.net.module.util.NetworkStackConstants.IPV6_PROTOCOL_OFFSET; +import static com.android.net.module.util.NetworkStackConstants.PIO_FLAG_AUTONOMOUS; +import static com.android.net.module.util.NetworkStackConstants.PIO_FLAG_ON_LINK; import static junit.framework.Assert.fail; @@ -141,6 +145,7 @@ import com.android.networkstack.apishim.common.ShimUtils; import com.android.networkstack.arp.ArpPacket; import com.android.networkstack.metrics.IpProvisioningMetrics; +import com.android.networkstack.packets.NeighborAdvertisement; import com.android.server.NetworkObserver; import com.android.server.NetworkObserverRegistry; import com.android.server.NetworkStackService.NetworkStackServiceManager; @@ -343,32 +348,12 @@ }; protected class Dependencies extends IpClient.Dependencies { - private boolean mIsDhcpLeaseCacheEnabled; - private boolean mIsDhcpRapidCommitEnabled; - private boolean mIsDhcpIpConflictDetectEnabled; // Can't use SparseIntArray, it doesn't have an easy way to know if a key is not present. private HashMap<String, Integer> mIntConfigProperties = new HashMap<>(); private DhcpClient mDhcpClient; private boolean mIsHostnameConfigurationEnabled; private String mHostname; private boolean mIsInterfaceRecovered; - private boolean mIsIPv6OnlyPreferredEnabled; - - public void setDhcpLeaseCacheEnabled(final boolean enable) { - mIsDhcpLeaseCacheEnabled = enable; - } - - public void setDhcpRapidCommitEnabled(final boolean enable) { - mIsDhcpRapidCommitEnabled = enable; - } - - public void setDhcpIpConflictDetectEnabled(final boolean enable) { - mIsDhcpIpConflictDetectEnabled = enable; - } - - public void setIPv6OnlyPreferredEnabled(final boolean enable) { - mIsIPv6OnlyPreferredEnabled = enable; - } public void setHostnameConfiguration(final boolean enable, final String hostname) { mIsHostnameConfigurationEnabled = enable; @@ -411,25 +396,19 @@ } @Override + public boolean isFeatureEnabled(final Context context, final String name, + final boolean defaultEnabled) { + return IpClientIntegrationTestCommon.this.isFeatureEnabled(name, defaultEnabled); + } + + @Override public DhcpClient.Dependencies getDhcpClientDependencies( NetworkStackIpMemoryStore ipMemoryStore, IpProvisioningMetrics metrics) { return new DhcpClient.Dependencies(ipMemoryStore, metrics) { @Override public boolean isFeatureEnabled(final Context context, final String name, final boolean defaultEnabled) { - switch (name) { - case NetworkStackUtils.DHCP_RAPID_COMMIT_VERSION: - return mIsDhcpRapidCommitEnabled; - case NetworkStackUtils.DHCP_INIT_REBOOT_VERSION: - return mIsDhcpLeaseCacheEnabled; - case NetworkStackUtils.DHCP_IP_CONFLICT_DETECT_VERSION: - return mIsDhcpIpConflictDetectEnabled; - case NetworkStackUtils.DHCP_IPV6_ONLY_PREFERRED_VERSION: - return mIsIPv6OnlyPreferredEnabled; - default: - fail("Invalid experiment flag: " + name); - return false; - } + return Dependencies.this.isFeatureEnabled(context, name, defaultEnabled); } @Override @@ -473,9 +452,9 @@ protected abstract IIpClient makeIIpClient( @NonNull String ifaceName, @NonNull IIpClientCallbacks cb); - protected abstract void setDhcpFeatures(boolean isDhcpLeaseCacheEnabled, - boolean isRapidCommitEnabled, boolean isDhcpIpConflictDetectEnabled, - boolean isIPv6OnlyPreferredEnabled); + protected abstract void setFeatureEnabled(String name, boolean enabled); + + protected abstract boolean isFeatureEnabled(String name, boolean defaultEnabled); protected abstract boolean useNetworkStackSignature(); @@ -490,6 +469,17 @@ && (mIsSignatureRequiredTest || !TestNetworkStackServiceClient.isSupported()); } + protected void setDhcpFeatures(final boolean isDhcpLeaseCacheEnabled, + final boolean isRapidCommitEnabled, final boolean isDhcpIpConflictDetectEnabled, + final boolean isIPv6OnlyPreferredEnabled) { + setFeatureEnabled(NetworkStackUtils.DHCP_INIT_REBOOT_VERSION, isDhcpLeaseCacheEnabled); + setFeatureEnabled(NetworkStackUtils.DHCP_RAPID_COMMIT_VERSION, isRapidCommitEnabled); + setFeatureEnabled(NetworkStackUtils.DHCP_IP_CONFLICT_DETECT_VERSION, + isDhcpIpConflictDetectEnabled); + setFeatureEnabled(NetworkStackUtils.DHCP_IPV6_ONLY_PREFERRED_VERSION, + isIPv6OnlyPreferredEnabled); + } + @Before public void setUp() throws Exception { final Method testMethod = IpClientIntegrationTestCommon.class.getMethod( @@ -704,6 +694,14 @@ } } + private NeighborAdvertisement parseNeighborAdvertisementOrNull(final byte[] packet) { + try { + return NeighborAdvertisement.parse(packet, packet.length); + } catch (NeighborAdvertisement.ParseException e) { + return null; + } + } + private static ByteBuffer buildDhcpOfferPacket(final DhcpPacket packet, final Inet4Address clientAddress, final Integer leaseTimeSec, final short mtu, final String captivePortalUrl, final Integer ipv6OnlyWaitTime) { @@ -1434,6 +1432,24 @@ == (byte) ICMPV6_ROUTER_SOLICITATION; } + private boolean isNeighborAdvertisement(final byte[] packetBytes) { + ByteBuffer packet = ByteBuffer.wrap(packetBytes); + return packet.getShort(ETHER_TYPE_OFFSET) == (short) ETH_P_IPV6 + && packet.get(ETHER_HEADER_LEN + IPV6_PROTOCOL_OFFSET) == (byte) IPPROTO_ICMPV6 + && packet.get(ETHER_HEADER_LEN + IPV6_HEADER_LEN) + == (byte) ICMPV6_NEIGHBOR_ADVERTISEMENT; + } + + private NeighborAdvertisement getNextNeighborAdvertisement() throws ParseException { + final byte[] packet = mPacketReader.popPacket(PACKET_TIMEOUT_MS, + this::isNeighborAdvertisement); + if (packet == null) return null; + + final NeighborAdvertisement na = parseNeighborAdvertisementOrNull(packet); + assertNotNull("Invalid neighbour advertisement received", na); + return na; + } + private void waitForRouterSolicitation() throws ParseException { assertNotNull("No router solicitation received on interface within timeout", mPacketReader.popPacket(PACKET_TIMEOUT_MS, this::isRouterSolicitation)); @@ -1464,7 +1480,7 @@ private static ByteBuffer buildPioOption(int valid, int preferred, String prefixString) throws Exception { return PrefixInformationOption.build(new IpPrefix(prefixString), - (byte) 0b11000000 /* L = 1, A = 1 */, valid, preferred); + (byte) (PIO_FLAG_ON_LINK | PIO_FLAG_AUTONOMOUS), valid, preferred); } private static ByteBuffer buildRdnssOption(int lifetime, String... servers) throws Exception { @@ -2760,4 +2776,50 @@ assertArrayEquals(packet.mUserClass, TEST_OEM_USER_CLASS_INFO); assertFalse(packet.hasRequestedParam((byte) 42 /* NTP_SERVER */)); } + + private void assertGratuitousNa(final NeighborAdvertisement na) throws Exception { + final MacAddress etherMulticast = + NetworkStackUtils.ipv6MulticastToEthernetMulticast(IPV6_ADDR_ALL_ROUTERS_MULTICAST); + final LinkAddress target = new LinkAddress(na.naHdr.target, 64); + + assertEquals(etherMulticast, na.ethHdr.dstMac); + assertEquals(ETH_P_IPV6, na.ethHdr.etherType); + assertEquals(IPPROTO_ICMPV6, na.ipv6Hdr.nextHeader); + assertEquals(0xff, na.ipv6Hdr.hopLimit); + assertTrue(na.ipv6Hdr.srcIp.isLinkLocalAddress()); + assertEquals(IPV6_ADDR_ALL_ROUTERS_MULTICAST, na.ipv6Hdr.dstIp); + assertEquals(ICMPV6_NEIGHBOR_ADVERTISEMENT, na.icmpv6Hdr.type); + assertEquals(0, na.icmpv6Hdr.code); + assertEquals(0, na.naHdr.flags); + assertTrue(target.isGlobalPreferred()); + } + + @Test + public void testGratuitousNa() throws Exception { + final ProvisioningConfiguration config = new ProvisioningConfiguration.Builder() + .withoutIpReachabilityMonitor() + .withoutIPv4() + .build(); + + setFeatureEnabled(NetworkStackUtils.IPCLIENT_GRATUITOUS_NA_VERSION, + true /* isGratuitousNaEnabled */); + assertTrue(isFeatureEnabled(NetworkStackUtils.IPCLIENT_GRATUITOUS_NA_VERSION, false)); + startIpClientProvisioning(config); + + final InOrder inOrder = inOrder(mCb); + final String dnsServer = "2001:4860:4860::64"; + final ByteBuffer pio = buildPioOption(3600, 1800, "2001:db8:1::/64"); + final ByteBuffer rdnss = buildRdnssOption(3600, dnsServer); + final ByteBuffer ra = buildRaPacket(pio, rdnss); + + doIpv6OnlyProvisioning(inOrder, ra); + + final List<NeighborAdvertisement> naList = new ArrayList<>(); + NeighborAdvertisement packet; + while ((packet = getNextNeighborAdvertisement()) != null) { + assertGratuitousNa(packet); + naList.add(packet); + } + assertEquals(2, naList.size()); // privacy address and stable privacy address + } }
diff --git a/tests/integration/src/android/net/ip/IpClientRootTest.kt b/tests/integration/src/android/net/ip/IpClientRootTest.kt index ea2ec11..68d8aab 100644 --- a/tests/integration/src/android/net/ip/IpClientRootTest.kt +++ b/tests/integration/src/android/net/ip/IpClientRootTest.kt
@@ -26,12 +26,12 @@ import android.net.ipmemorystore.NetworkAttributes import android.net.ipmemorystore.Status import android.net.networkstack.TestNetworkStackServiceClient -import android.net.util.NetworkStackUtils import android.os.Process import android.provider.DeviceConfig import android.util.ArrayMap import android.util.Log import androidx.test.platform.app.InstrumentationRegistry +import com.android.net.module.util.DeviceConfigUtils import java.lang.System.currentTimeMillis import java.util.concurrent.CompletableFuture import java.util.concurrent.CountDownLatch @@ -194,37 +194,33 @@ return ipClientCaptor.value } - override fun setDhcpFeatures( - isDhcpLeaseCacheEnabled: Boolean, - isRapidCommitEnabled: Boolean, - isDhcpIpConflictDetectEnabled: Boolean, - isIPv6OnlyPreferredEnabled: Boolean - ) { + override fun setFeatureEnabled(feature: String, enabled: Boolean) { automation.adoptShellPermissionIdentity(READ_DEVICE_CONFIG, WRITE_DEVICE_CONFIG) try { - setFeatureEnabled(NetworkStackUtils.DHCP_INIT_REBOOT_VERSION, isDhcpLeaseCacheEnabled) - setFeatureEnabled(NetworkStackUtils.DHCP_RAPID_COMMIT_VERSION, isRapidCommitEnabled) - setFeatureEnabled(NetworkStackUtils.DHCP_IP_CONFLICT_DETECT_VERSION, - isDhcpIpConflictDetectEnabled) - setFeatureEnabled(NetworkStackUtils.DHCP_IPV6_ONLY_PREFERRED_VERSION, - isIPv6OnlyPreferredEnabled) + // Do not use computeIfAbsent as it would overwrite null values (flag originally unset) + if (!originalFlagValues.containsKey(feature)) { + originalFlagValues[feature] = + DeviceConfig.getProperty(DeviceConfig.NAMESPACE_CONNECTIVITY, feature) + } + // The feature is enabled if the flag is lower than the package version. + // Package versions follow a standard format with 9 digits. + // TODO: consider resetting flag values on reboot when set to special values like "1" or + // "999999999" + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_CONNECTIVITY, feature, + if (enabled) "1" else "999999999", false) } finally { automation.dropShellPermissionIdentity() } } - private fun setFeatureEnabled(feature: String, enabled: Boolean) { - // Do not use computeIfAbsent as it would overwrite null values (flag originally unset) - if (!originalFlagValues.containsKey(feature)) { - originalFlagValues[feature] = - DeviceConfig.getProperty(DeviceConfig.NAMESPACE_CONNECTIVITY, feature) + override fun isFeatureEnabled(name: String, defaultEnabled: Boolean): Boolean { + automation.adoptShellPermissionIdentity(READ_DEVICE_CONFIG, WRITE_DEVICE_CONFIG) + try { + return DeviceConfigUtils.isFeatureEnabled(mContext, DeviceConfig.NAMESPACE_CONNECTIVITY, + name, defaultEnabled) + } finally { + automation.dropShellPermissionIdentity() } - // The feature is enabled if the flag is lower than the package version. - // Package versions follow a standard format with 9 digits. - // TODO: consider resetting flag values on reboot when set to special values like "1" or - // "999999999" - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_CONNECTIVITY, feature, - if (enabled) "1" else "999999999", false) } private class TestAttributesRetrievedListener : OnNetworkAttributesRetrievedListener {
diff --git a/tests/unit/src/com/android/net/module/util/TrackRecordTest.kt b/tests/unit/src/com/android/net/module/util/TrackRecordTest.kt deleted file mode 100644 index 9fb4d8c..0000000 --- a/tests/unit/src/com/android/net/module/util/TrackRecordTest.kt +++ /dev/null
@@ -1,446 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.net.module.util - -import com.android.testutils.ConcurrentInterpreter -import com.android.testutils.InterpretException -import com.android.testutils.InterpretMatcher -import com.android.testutils.SyntaxException -import com.android.testutils.__FILE__ -import com.android.testutils.__LINE__ -import com.android.testutils.intArg -import com.android.testutils.strArg -import com.android.testutils.timeArg -import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.JUnit4 -import java.util.concurrent.CyclicBarrier -import java.util.concurrent.TimeUnit -import java.util.concurrent.atomic.AtomicInteger -import kotlin.system.measureTimeMillis -import kotlin.test.assertEquals -import kotlin.test.assertFailsWith -import kotlin.test.assertFalse -import kotlin.test.assertNotEquals -import kotlin.test.assertNull -import kotlin.test.assertTrue -import kotlin.test.fail - -val TEST_VALUES = listOf(4, 13, 52, 94, 41, 68, 11, 13, 51, 0, 91, 94, 33, 98, 14) -const val ABSENT_VALUE = 2 -// Caution in changing these : some tests rely on the fact that TEST_TIMEOUT > 2 * SHORT_TIMEOUT -// and LONG_TIMEOUT > 2 * TEST_TIMEOUT -const val SHORT_TIMEOUT = 40L // ms -const val TEST_TIMEOUT = 200L // ms -const val LONG_TIMEOUT = 5000L // ms - -@RunWith(JUnit4::class) -class TrackRecordTest { - @Test - fun testAddAndSizeAndGet() { - val repeats = 22 // arbitrary - val record = ArrayTrackRecord<Int>() - assertEquals(0, record.size) - repeat(repeats) { i -> record.add(i + 2) } - assertEquals(repeats, record.size) - record.add(2) - assertEquals(repeats + 1, record.size) - - assertEquals(11, record[9]) - assertEquals(11, record.getOrNull(9)) - assertEquals(2, record[record.size - 1]) - assertEquals(2, record.getOrNull(record.size - 1)) - - assertFailsWith<IndexOutOfBoundsException> { record[800] } - assertFailsWith<IndexOutOfBoundsException> { record[-1] } - assertFailsWith<IndexOutOfBoundsException> { record[repeats + 1] } - assertNull(record.getOrNull(800)) - assertNull(record.getOrNull(-1)) - assertNull(record.getOrNull(repeats + 1)) - assertNull(record.getOrNull(800) { true }) - assertNull(record.getOrNull(-1) { true }) - assertNull(record.getOrNull(repeats + 1) { true }) - } - - @Test - fun testIndexOf() { - val record = ArrayTrackRecord<Int>() - TEST_VALUES.forEach { record.add(it) } - with(record) { - assertEquals(9, indexOf(0)) - assertEquals(9, lastIndexOf(0)) - assertEquals(1, indexOf(13)) - assertEquals(7, lastIndexOf(13)) - assertEquals(3, indexOf(94)) - assertEquals(11, lastIndexOf(94)) - assertEquals(-1, indexOf(ABSENT_VALUE)) - assertEquals(-1, lastIndexOf(ABSENT_VALUE)) - } - } - - @Test - fun testContains() { - val record = ArrayTrackRecord<Int>() - TEST_VALUES.forEach { record.add(it) } - TEST_VALUES.forEach { assertTrue(record.contains(it)) } - assertFalse(record.contains(ABSENT_VALUE)) - assertTrue(record.containsAll(TEST_VALUES)) - assertTrue(record.containsAll(TEST_VALUES.sorted())) - assertTrue(record.containsAll(TEST_VALUES.sortedDescending())) - assertTrue(record.containsAll(TEST_VALUES.distinct())) - assertTrue(record.containsAll(TEST_VALUES.subList(0, TEST_VALUES.size / 2))) - assertTrue(record.containsAll(TEST_VALUES.subList(0, TEST_VALUES.size / 2).sorted())) - assertTrue(record.containsAll(listOf())) - assertFalse(record.containsAll(listOf(ABSENT_VALUE))) - assertFalse(record.containsAll(TEST_VALUES + listOf(ABSENT_VALUE))) - } - - @Test - fun testEmpty() { - val record = ArrayTrackRecord<Int>() - assertTrue(record.isEmpty()) - record.add(1) - assertFalse(record.isEmpty()) - } - - @Test - fun testIterate() { - val record = ArrayTrackRecord<Int>() - record.forEach { fail("Expected nothing to iterate") } - TEST_VALUES.forEach { record.add(it) } - // zip relies on the iterator (this calls extension function Iterable#zip(Iterable)) - record.zip(TEST_VALUES).forEach { assertEquals(it.first, it.second) } - // Also test reverse iteration (to test hasPrevious() and friends) - record.reversed().zip(TEST_VALUES.reversed()).forEach { assertEquals(it.first, it.second) } - } - - @Test - fun testIteratorIsSnapshot() { - val record = ArrayTrackRecord<Int>() - TEST_VALUES.forEach { record.add(it) } - val iterator = record.iterator() - val expectedSize = record.size - record.add(ABSENT_VALUE) - record.add(ABSENT_VALUE) - var measuredSize = 0 - iterator.forEach { - ++measuredSize - assertNotEquals(ABSENT_VALUE, it) - } - assertEquals(expectedSize, measuredSize) - } - - @Test - fun testSublist() { - val record = ArrayTrackRecord<Int>() - TEST_VALUES.forEach { record.add(it) } - assertEquals(record.subList(3, record.size - 3), - TEST_VALUES.subList(3, TEST_VALUES.size - 3)) - } - - fun testPollReturnsImmediately(record: TrackRecord<Int>) { - record.add(4) - val elapsed = measureTimeMillis { assertEquals(4, record.poll(LONG_TIMEOUT, 0)) } - // Should not have waited at all, in fact. - assertTrue(elapsed < LONG_TIMEOUT) - record.add(7) - record.add(9) - // Can poll multiple times for the same position, in whatever order - assertEquals(9, record.poll(0, 2)) - assertEquals(7, record.poll(Long.MAX_VALUE, 1)) - assertEquals(9, record.poll(0, 2)) - assertEquals(4, record.poll(0, 0)) - assertEquals(9, record.poll(0, 2) { it > 5 }) - assertEquals(7, record.poll(0, 0) { it > 5 }) - } - - @Test - fun testPollReturnsImmediately() { - testPollReturnsImmediately(ArrayTrackRecord()) - testPollReturnsImmediately(ArrayTrackRecord<Int>().newReadHead()) - } - - @Test - fun testPollTimesOut() { - val record = ArrayTrackRecord<Int>() - var delay = measureTimeMillis { assertNull(record.poll(SHORT_TIMEOUT, 0)) } - assertTrue(delay >= SHORT_TIMEOUT, "Delay $delay < $SHORT_TIMEOUT") - delay = measureTimeMillis { assertNull(record.poll(SHORT_TIMEOUT, 0) { it < 10 }) } - assertTrue(delay >= SHORT_TIMEOUT) - } - - @Test - fun testConcurrentPollDisallowed() { - val failures = AtomicInteger(0) - val readHead = ArrayTrackRecord<Int>().newReadHead() - val barrier = CyclicBarrier(2) - Thread { - barrier.await(LONG_TIMEOUT, TimeUnit.MILLISECONDS) // barrier 1 - try { - readHead.poll(LONG_TIMEOUT) - } catch (e: ConcurrentModificationException) { - failures.incrementAndGet() - // Unblock the other thread - readHead.add(0) - } - }.start() - barrier.await() // barrier 1 - try { - readHead.poll(LONG_TIMEOUT) - } catch (e: ConcurrentModificationException) { - failures.incrementAndGet() - // Unblock the other thread - readHead.add(0) - } - // One of the threads must have gotten an exception. - assertEquals(failures.get(), 1) - } - - @Test - fun testPollWakesUp() { - val record = ArrayTrackRecord<Int>() - val barrier = CyclicBarrier(2) - Thread { - barrier.await(LONG_TIMEOUT, TimeUnit.MILLISECONDS) // barrier 1 - barrier.await() // barrier 2 - Thread.sleep(SHORT_TIMEOUT * 2) - record.add(31) - }.start() - barrier.await() // barrier 1 - // Should find the element in more than SHORT_TIMEOUT but less than TEST_TIMEOUT - var delay = measureTimeMillis { - barrier.await() // barrier 2 - assertEquals(31, record.poll(TEST_TIMEOUT, 0)) - } - assertTrue(delay in SHORT_TIMEOUT..TEST_TIMEOUT) - // Polling for an element already added in anothe thread (pos 0) : should return immediately - delay = measureTimeMillis { assertEquals(31, record.poll(TEST_TIMEOUT, 0)) } - assertTrue(delay < TEST_TIMEOUT, "Delay $delay > $TEST_TIMEOUT") - // Waiting for an element that never comes - delay = measureTimeMillis { assertNull(record.poll(SHORT_TIMEOUT, 1)) } - assertTrue(delay >= SHORT_TIMEOUT, "Delay $delay < $SHORT_TIMEOUT") - // Polling for an element that doesn't match what is already there - delay = measureTimeMillis { assertNull(record.poll(SHORT_TIMEOUT, 0) { it < 10 }) } - assertTrue(delay >= SHORT_TIMEOUT) - } - - // Just make sure the interpreter actually throws an exception when the spec - // does not conform to the behavior. The interpreter is just a tool to test a - // tool used for a tool for test, let's not have hundreds of tests for it ; - // if it's broken one of the tests using it will break. - @Test - fun testInterpreter() { - val interpretLine = __LINE__ + 2 - try { - TRTInterpreter.interpretTestSpec(useReadHeads = true, spec = """ - add(4) | poll(1, 0) = 5 - """) - fail("This spec should have thrown") - } catch (e: InterpretException) { - assertTrue(e.cause is AssertionError) - assertEquals(interpretLine + 1, e.stackTrace[0].lineNumber) - assertTrue(e.stackTrace[0].fileName.contains(__FILE__)) - assertTrue(e.stackTrace[0].methodName.contains("testInterpreter")) - assertTrue(e.stackTrace[0].methodName.contains("thread1")) - } - } - - @Test - fun testMultipleAdds() { - TRTInterpreter.interpretTestSpec(useReadHeads = false, spec = """ - add(2) | | | - | add(4) | | - | | add(6) | - | | | add(8) - poll(0, 0) = 2 time 0..1 | poll(0, 0) = 2 | poll(0, 0) = 2 | poll(0, 0) = 2 - poll(0, 1) = 4 time 0..1 | poll(0, 1) = 4 | poll(0, 1) = 4 | poll(0, 1) = 4 - poll(0, 2) = 6 time 0..1 | poll(0, 2) = 6 | poll(0, 2) = 6 | poll(0, 2) = 6 - poll(0, 3) = 8 time 0..1 | poll(0, 3) = 8 | poll(0, 3) = 8 | poll(0, 3) = 8 - """) - } - - @Test - fun testConcurrentAdds() { - TRTInterpreter.interpretTestSpec(useReadHeads = false, spec = """ - add(2) | add(4) | add(6) | add(8) - add(1) | add(3) | add(5) | add(7) - poll(0, 1) is even | poll(0, 0) is even | poll(0, 3) is even | poll(0, 2) is even - poll(0, 5) is odd | poll(0, 4) is odd | poll(0, 7) is odd | poll(0, 6) is odd - """) - } - - @Test - fun testMultiplePoll() { - TRTInterpreter.interpretTestSpec(useReadHeads = false, spec = """ - add(4) | poll(1, 0) = 4 - | poll(0, 1) = null time 0..1 - | poll(1, 1) = null time 1..2 - sleep; add(7) | poll(2, 1) = 7 time 1..2 - sleep; add(18) | poll(2, 2) = 18 time 1..2 - """) - } - - @Test - fun testMultiplePollWithPredicate() { - TRTInterpreter.interpretTestSpec(useReadHeads = false, spec = """ - | poll(1, 0) = null | poll(1, 0) = null - add(6) | poll(1, 0) = 6 | - add(11) | poll(1, 0) { > 20 } = null | poll(1, 0) { = 11 } = 11 - | poll(1, 0) { > 8 } = 11 | - """) - } - - @Test - fun testMultipleReadHeads() { - TRTInterpreter.interpretTestSpec(useReadHeads = true, spec = """ - | poll() = null | poll() = null | poll() = null - add(5) | | poll() = 5 | - | poll() = 5 | | - add(8) | poll() = 8 | poll() = 8 | - | | | poll() = 5 - | | | poll() = 8 - | | | poll() = null - | | poll() = null | - """) - } - - @Test - fun testReadHeadPollWithPredicate() { - TRTInterpreter.interpretTestSpec(useReadHeads = true, spec = """ - add(5) | poll() { < 0 } = null - | poll() { > 5 } = null - add(10) | - | poll() { = 5 } = null // The "5" was skipped in the previous line - add(15) | poll() { > 8 } = 15 // The "10" was skipped in the previous line - | poll(1, 0) { > 8 } = 10 // 10 is the first element after pos 0 matching > 8 - """) - } - - @Test - fun testPollImmediatelyAdvancesReadhead() { - TRTInterpreter.interpretTestSpec(useReadHeads = true, spec = """ - add(1) | add(2) | add(3) | add(4) - mark = 0 | poll(0) { > 3 } = 4 | | - poll(0) { > 10 } = null | | | - mark = 4 | | | - poll() = null | | | - """) - } - - @Test - fun testParallelReadHeads() { - TRTInterpreter.interpretTestSpec(useReadHeads = true, spec = """ - mark = 0 | mark = 0 | mark = 0 | mark = 0 - add(2) | | | - | add(4) | | - | | add(6) | - | | | add(8) - poll() = 2 | poll() = 2 | poll() = 2 | poll() = 2 - poll() = 4 | poll() = 4 | poll() = 4 | poll() = 4 - poll() = 6 | poll() = 6 | poll() = 6 | mark = 2 - poll() = 8 | poll() = 8 | mark = 3 | poll() = 6 - mark = 4 | mark = 4 | poll() = 8 | poll() = 8 - """) - } - - @Test - fun testPeek() { - TRTInterpreter.interpretTestSpec(useReadHeads = true, spec = """ - add(2) | | | - | add(4) | | - | | add(6) | - | | | add(8) - peek() = 2 | poll() = 2 | poll() = 2 | peek() = 2 - peek() = 2 | peek() = 4 | poll() = 4 | peek() = 2 - peek() = 2 | peek() = 4 | peek() = 6 | poll() = 2 - peek() = 2 | mark = 1 | mark = 2 | poll() = 4 - mark = 0 | peek() = 4 | peek() = 6 | peek() = 6 - poll() = 2 | poll() = 4 | poll() = 6 | poll() = 6 - poll() = 4 | mark = 2 | poll() = 8 | peek() = 8 - peek() = 6 | peek() = 6 | peek() = null | mark = 3 - """) - } -} - -private object TRTInterpreter : ConcurrentInterpreter<TrackRecord<Int>>(interpretTable) { - fun interpretTestSpec(spec: String, useReadHeads: Boolean) = if (useReadHeads) { - interpretTestSpec(spec, initial = ArrayTrackRecord(), - threadTransform = { (it as ArrayTrackRecord).newReadHead() }) - } else { - interpretTestSpec(spec, ArrayTrackRecord()) - } -} - -/* - * Quick ref of supported expressions : - * sleep(x) : sleeps for x time units and returns Unit ; sleep alone means sleep(1) - * add(x) : calls and returns TrackRecord#add. - * poll(time, pos) [{ predicate }] : calls and returns TrackRecord#poll(x time units, pos). - * Optionally, a predicate may be specified. - * poll() [{ predicate }] : calls and returns ReadHead#poll(1 time unit). Optionally, a predicate - * may be specified. - * EXPR = VALUE : asserts that EXPR equals VALUE. EXPR is interpreted. VALUE can either be the - * string "null" or an int. Returns Unit. - * EXPR time x..y : measures the time taken by EXPR and asserts it took at least x and at most - * y time units. - * predicate must be one of "= x", "< x" or "> x". - */ -private val interpretTable = listOf<InterpretMatcher<TrackRecord<Int>>>( - // Interpret "XXX is odd" : run XXX and assert its return value is odd ("even" works too) - Regex("(.*)\\s+is\\s+(even|odd)") to { i, t, r -> - i.interpret(r.strArg(1), t).also { - assertEquals((it as Int) % 2, if ("even" == r.strArg(2)) 0 else 1) - } - }, - // Interpret "add(XXX)" as TrackRecord#add(int) - Regex("""add\((\d+)\)""") to { i, t, r -> - t.add(r.intArg(1)) - }, - // Interpret "poll(x, y)" as TrackRecord#poll(timeout = x * INTERPRET_TIME_UNIT, pos = y) - // Accepts an optional {} argument for the predicate (see makePredicate for syntax) - Regex("""poll\((\d+),\s*(\d+)\)\s*(\{.*\})?""") to { i, t, r -> - t.poll(r.timeArg(1), r.intArg(2), makePredicate(r.strArg(3))) - }, - // ReadHead#poll. If this throws in the cast, the code is malformed and has passed "poll()" - // in a test that takes a TrackRecord that is not a ReadHead. It's technically possible to get - // the test code to not compile instead of throw, but it's vastly more complex and this will - // fail 100% at runtime any test that would not have compiled. - Regex("""poll\((\d+)?\)\s*(\{.*\})?""") to { i, t, r -> - (if (r.strArg(1).isEmpty()) i.interpretTimeUnit else r.timeArg(1)).let { time -> - (t as ArrayTrackRecord<Int>.ReadHead).poll(time, makePredicate(r.strArg(2))) - } - }, - // ReadHead#mark. The same remarks apply as with ReadHead#poll. - Regex("mark") to { i, t, _ -> (t as ArrayTrackRecord<Int>.ReadHead).mark }, - // ReadHead#peek. The same remarks apply as with ReadHead#poll. - Regex("peek\\(\\)") to { i, t, _ -> (t as ArrayTrackRecord<Int>.ReadHead).peek() } -) - -// Parses a { = x } or { < x } or { > x } string and returns the corresponding predicate -// Returns an always-true predicate for empty and null arguments -private fun makePredicate(spec: String?): (Int) -> Boolean { - if (spec.isNullOrEmpty()) return { true } - val match = Regex("""\{\s*([<>=])\s*(\d+)\s*\}""").matchEntire(spec) - ?: throw SyntaxException("Predicate \"${spec}\"") - val arg = match.intArg(2) - return when (match.strArg(1)) { - ">" -> { i -> i > arg } - "<" -> { i -> i < arg } - "=" -> { i -> i == arg } - else -> throw RuntimeException("How did \"${spec}\" match this regexp ?") - } -}