summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2023-09-05 04:43:42 +0000
committerAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2023-09-05 04:43:42 +0000
commit63a232365575574479c35efd59ef954eb9e343ea (patch)
treeefaf9e838b13caea604ccea565056c8a4f7f3630
parent22971b2332e35ac9ba78dd3a6b33c71a83e454f1 (diff)
parentc28fc7974331b64aa2d4b96ce9871a7bb6b24f7f (diff)
downloadnet-63a232365575574479c35efd59ef954eb9e343ea.tar.gz
Snap for 10760240 from c28fc7974331b64aa2d4b96ce9871a7bb6b24f7f to mainline-appsearch-release
Change-Id: I52d3ac9f4ca5dbfe70d63c98b7969417c002e4a4
-rw-r--r--OWNERS2
-rw-r--r--common/Android.bp4
-rw-r--r--common/device/com/android/net/module/util/BpfMap.java30
-rw-r--r--common/device/com/android/net/module/util/DeviceConfigUtils.java136
-rw-r--r--common/device/com/android/net/module/util/DomainUtils.java143
-rw-r--r--common/device/com/android/net/module/util/FeatureVersions.java45
-rw-r--r--common/device/com/android/net/module/util/SharedLog.java37
-rw-r--r--common/device/com/android/net/module/util/arp/ArpPacket.java171
-rw-r--r--common/device/com/android/net/module/util/netlink/NetlinkUtils.java16
-rw-r--r--common/device/com/android/net/module/util/netlink/StructNdaCacheInfo.java12
-rw-r--r--common/device/com/android/net/module/util/structs/Ipv6PktInfo.java42
-rw-r--r--common/device/com/android/net/module/util/structs/RouteInformationOption.java97
-rw-r--r--common/framework/com/android/net/module/util/DnsPacketUtils.java19
-rw-r--r--common/framework/com/android/net/module/util/NetworkStackConstants.java15
-rw-r--r--common/native/bpf_headers/include/bpf/BpfClassic.h14
-rw-r--r--common/native/bpf_headers/include/bpf/BpfMap.h118
-rw-r--r--common/netd/libnetdutils/Android.bp5
-rw-r--r--common/tests/unit/Android.bp1
-rw-r--r--common/tests/unit/src/com/android/net/module/util/ArpPacketTest.java201
-rw-r--r--common/tests/unit/src/com/android/net/module/util/CollectionUtilsTest.kt3
-rw-r--r--common/tests/unit/src/com/android/net/module/util/DeviceConfigUtilsTest.java121
-rw-r--r--common/tests/unit/src/com/android/net/module/util/DomainUtilsTest.java210
-rw-r--r--common/tests/unit/src/com/android/net/module/util/StructTest.java144
-rw-r--r--common/tests/unit/src/com/android/net/module/util/netlink/NetlinkUtilsTest.java6
-rw-r--r--common/testutils/Android.bp1
-rw-r--r--common/testutils/app/connectivitychecker/src/com/android/testutils/connectivitypreparer/ConnectivityCheckTest.kt2
-rw-r--r--common/testutils/devicetests/com/android/testutils/DevSdkIgnoreRule.kt20
-rw-r--r--common/testutils/devicetests/com/android/testutils/FakeDns.kt4
-rw-r--r--common/testutils/devicetests/com/android/testutils/NonNullTestUtils.java33
-rw-r--r--common/testutils/devicetests/com/android/testutils/PacketBridge.kt4
-rw-r--r--common/testutils/devicetests/com/android/testutils/TestNetworkTracker.kt4
-rw-r--r--common/testutils/devicetests/com/android/testutils/TestableNetworkCallback.kt9
-rw-r--r--common/testutils/hostdevice/com/android/net/module/util/TrackRecord.kt24
-rw-r--r--common/testutils/hostdevice/com/android/testutils/DnsResolverModuleTest.kt22
-rw-r--r--common/testutils/hostdevice/com/android/testutils/SkipMainlinePresubmit.kt25
35 files changed, 1530 insertions, 210 deletions
diff --git a/OWNERS b/OWNERS
index 70357ac6..5392cc10 100644
--- a/OWNERS
+++ b/OWNERS
@@ -1,4 +1,4 @@
# Bug template url: http://b/new?component=31808
# TODO: move bug template config to common owners file once b/226427845 is resolved
set noparent
-file:platform/packages/modules/Connectivity:master:/OWNERS_core_networking
+file:platform/packages/modules/Connectivity:main:/OWNERS_core_networking
diff --git a/common/Android.bp b/common/Android.bp
index ff65228a..d8d0accb 100644
--- a/common/Android.bp
+++ b/common/Android.bp
@@ -34,12 +34,15 @@ package {
java_library {
name: "net-utils-device-common",
srcs: [
+ "device/com/android/net/module/util/arp/ArpPacket.java",
"device/com/android/net/module/util/DeviceConfigUtils.java",
+ "device/com/android/net/module/util/DomainUtils.java",
"device/com/android/net/module/util/FdEventsReader.java",
"device/com/android/net/module/util/NetworkMonitorUtils.java",
"device/com/android/net/module/util/PacketReader.java",
"device/com/android/net/module/util/SharedLog.java",
"device/com/android/net/module/util/SocketUtils.java",
+ "device/com/android/net/module/util/FeatureVersions.java",
// This library is used by system modules, for which the system health impact of Kotlin
// has not yet been evaluated. Annotations may need jarjar'ing.
// "src_devicecommon/**/*.kt",
@@ -243,6 +246,7 @@ java_library {
"framework-annotations-lib",
"framework-connectivity.stubs.module_lib",
"framework-connectivity-t.stubs.module_lib",
+ "framework-location.stubs.module_lib",
],
jarjar_rules: "jarjar-rules-shared.txt",
visibility: [
diff --git a/common/device/com/android/net/module/util/BpfMap.java b/common/device/com/android/net/module/util/BpfMap.java
index 9df2b035..d45caceb 100644
--- a/common/device/com/android/net/module/util/BpfMap.java
+++ b/common/device/com/android/net/module/util/BpfMap.java
@@ -194,9 +194,11 @@ public class BpfMap<K extends Struct, V extends Struct> implements IBpfMap<K, V>
}
private K getNextKeyInternal(@Nullable K key) throws ErrnoException {
- final byte[] rawKey = getNextRawKey(
- key == null ? null : key.writeToBytes());
- if (rawKey == null) return null;
+ byte[] rawKey = new byte[mKeySize];
+
+ if (!nativeGetNextMapKey(mMapFd.getFd(),
+ key == null ? null : key.writeToBytes(),
+ rawKey)) return null;
final ByteBuffer buffer = ByteBuffer.wrap(rawKey);
buffer.order(ByteOrder.nativeOrder());
@@ -215,13 +217,6 @@ public class BpfMap<K extends Struct, V extends Struct> implements IBpfMap<K, V>
return getNextKeyInternal(key);
}
- private byte[] getNextRawKey(@Nullable final byte[] key) throws ErrnoException {
- byte[] nextKey = new byte[mKeySize];
- if (nativeGetNextMapKey(mMapFd.getFd(), key, nextKey)) return nextKey;
-
- return null;
- }
-
/** Get the first key of eBpf map. */
@Override
public K getFirstKey() throws ErrnoException {
@@ -233,30 +228,23 @@ public class BpfMap<K extends Struct, V extends Struct> implements IBpfMap<K, V>
public boolean containsKey(@NonNull K key) throws ErrnoException {
Objects.requireNonNull(key);
- final byte[] rawValue = getRawValue(key.writeToBytes());
- return rawValue != null;
+ byte[] rawValue = new byte[mValueSize];
+ return nativeFindMapEntry(mMapFd.getFd(), key.writeToBytes(), rawValue);
}
/** Retrieve a value from the map. Return null if there is no such key. */
@Override
public V getValue(@NonNull K key) throws ErrnoException {
Objects.requireNonNull(key);
- final byte[] rawValue = getRawValue(key.writeToBytes());
- if (rawValue == null) return null;
+ byte[] rawValue = new byte[mValueSize];
+ if (!nativeFindMapEntry(mMapFd.getFd(), key.writeToBytes(), rawValue)) return null;
final ByteBuffer buffer = ByteBuffer.wrap(rawValue);
buffer.order(ByteOrder.nativeOrder());
return Struct.parse(mValueClass, buffer);
}
- private byte[] getRawValue(final byte[] key) throws ErrnoException {
- byte[] value = new byte[mValueSize];
- if (nativeFindMapEntry(mMapFd.getFd(), key, value)) return value;
-
- return null;
- }
-
/**
* Iterate through the map and handle each key -> value retrieved base on the given BiConsumer.
* The given BiConsumer may to delete the passed-in entry, but is not allowed to perform any
diff --git a/common/device/com/android/net/module/util/DeviceConfigUtils.java b/common/device/com/android/net/module/util/DeviceConfigUtils.java
index 138c1e54..fb130f67 100644
--- a/common/device/com/android/net/module/util/DeviceConfigUtils.java
+++ b/common/device/com/android/net/module/util/DeviceConfigUtils.java
@@ -17,6 +17,13 @@
package com.android.net.module.util;
import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
+import static android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY;
+import static android.provider.DeviceConfig.NAMESPACE_TETHERING;
+
+import static com.android.net.module.util.FeatureVersions.CONNECTIVITY_MODULE_ID;
+import static com.android.net.module.util.FeatureVersions.MODULE_MASK;
+import static com.android.net.module.util.FeatureVersions.NETWORK_STACK_MODULE_ID;
+import static com.android.net.module.util.FeatureVersions.VERSION_MASK;
import android.content.Context;
import android.content.Intent;
@@ -53,12 +60,14 @@ public final class DeviceConfigUtils {
"com.android.server.connectivity.intent.action.SERVICE_CONNECTIVITY_RESOURCES_APK";
private static final String CONNECTIVITY_RES_PKG_DIR = "/apex/" + TETHERING_MODULE_NAME + "/";
- private static final long DEFAULT_PACKAGE_VERSION = 1000;
+ @VisibleForTesting
+ public static final long DEFAULT_PACKAGE_VERSION = 1000;
@VisibleForTesting
public static void resetPackageVersionCacheForTest() {
sPackageVersion = -1;
sModuleVersion = -1;
+ sNetworkStackModuleVersion = -1;
}
private static volatile long sPackageVersion = -1;
@@ -199,8 +208,9 @@ public final class DeviceConfigUtils {
* null.
* @return true if this feature is enabled, or false if disabled.
*/
- public static boolean isFeatureEnabled(@NonNull Context context, @NonNull String namespace,
- @NonNull String name, @NonNull String moduleName, boolean defaultEnabled) {
+ public static boolean isTetheringFeatureEnabled(@NonNull Context context,
+ @NonNull String namespace, @NonNull String name, @NonNull String moduleName,
+ boolean defaultEnabled) {
// TODO: migrate callers to a non-generic isTetheringFeatureEnabled method.
if (!TETHERING_MODULE_NAME.equals(moduleName)) {
throw new IllegalArgumentException(
@@ -224,14 +234,7 @@ public final class DeviceConfigUtils {
// If that fails retry by appending "go.tethering" instead
private static long resolveTetheringModuleVersion(@NonNull Context context)
throws PackageManager.NameNotFoundException {
- final String connResourcesPackage = getConnectivityResourcesPackageName(context);
- final int pkgPrefixLen = connResourcesPackage.indexOf("connectivity");
- if (pkgPrefixLen < 0) {
- throw new IllegalStateException(
- "Invalid connectivity resources package: " + connResourcesPackage);
- }
-
- final String pkgPrefix = connResourcesPackage.substring(0, pkgPrefixLen);
+ final String pkgPrefix = resolvePkgPrefix(context);
final PackageManager packageManager = context.getPackageManager();
try {
return packageManager.getPackageInfo(pkgPrefix + "tethering",
@@ -245,6 +248,17 @@ public final class DeviceConfigUtils {
PackageManager.MATCH_APEX).getLongVersionCode();
}
+ private static String resolvePkgPrefix(Context context) {
+ final String connResourcesPackage = getConnectivityResourcesPackageName(context);
+ final int pkgPrefixLen = connResourcesPackage.indexOf("connectivity");
+ if (pkgPrefixLen < 0) {
+ throw new IllegalStateException(
+ "Invalid connectivity resources package: " + connResourcesPackage);
+ }
+
+ return connResourcesPackage.substring(0, pkgPrefixLen);
+ }
+
private static volatile long sModuleVersion = -1;
private static long getTetheringModuleVersion(@NonNull Context context) {
if (sModuleVersion >= 0) return sModuleVersion;
@@ -260,6 +274,106 @@ public final class DeviceConfigUtils {
return sModuleVersion;
}
+ private static volatile long sNetworkStackModuleVersion = -1;
+
+ /**
+ * Get networkstack module version.
+ */
+ @VisibleForTesting
+ static long getNetworkStackModuleVersion(@NonNull Context context) {
+ if (sNetworkStackModuleVersion >= 0) return sNetworkStackModuleVersion;
+
+ try {
+ sNetworkStackModuleVersion = resolveNetworkStackModuleVersion(context);
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.wtf(TAG, "Failed to resolve networkstack module version: " + e);
+ return DEFAULT_PACKAGE_VERSION;
+ }
+ return sNetworkStackModuleVersion;
+ }
+
+ private static long resolveNetworkStackModuleVersion(@NonNull Context context)
+ throws PackageManager.NameNotFoundException {
+ // TODO(b/293975546): Strictly speaking this is the prefix for connectivity and not
+ // network stack. In practice, it's the same. Read the prefix from network stack instead.
+ final String pkgPrefix = resolvePkgPrefix(context);
+ final PackageManager packageManager = context.getPackageManager();
+ try {
+ return packageManager.getPackageInfo(pkgPrefix + "networkstack",
+ PackageManager.MATCH_SYSTEM_ONLY).getLongVersionCode();
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.d(TAG, "Device is using go or non-mainline modules");
+ // fall through
+ }
+
+ return packageManager.getPackageInfo(pkgPrefix + "go.networkstack",
+ PackageManager.MATCH_ALL).getLongVersionCode();
+ }
+
+ /**
+ * Check whether one specific feature is supported from the feature Id. The feature Id is
+ * composed by a module package Id and version Id from {@link FeatureVersions}.
+ *
+ * This is useful when a feature required minimal module version supported and cannot function
+ * well with a standalone newer module.
+ * @param context The global context information about an app environment.
+ * @param featureId The feature id that contains required module id and minimal module version
+ * @return true if this feature is supported, or false if not supported.
+ **/
+ public static boolean isFeatureSupported(@NonNull Context context, long featureId) {
+ final long moduleVersion;
+ final long moduleId = featureId & MODULE_MASK;
+ if (moduleId == CONNECTIVITY_MODULE_ID) {
+ moduleVersion = getTetheringModuleVersion(context);
+ } else if (moduleId == NETWORK_STACK_MODULE_ID) {
+ moduleVersion = getNetworkStackModuleVersion(context);
+ } else {
+ throw new IllegalArgumentException("Unknown module " + moduleId);
+ }
+ // Support by default if no module version is available.
+ return moduleVersion == DEFAULT_PACKAGE_VERSION
+ || moduleVersion >= (featureId & VERSION_MASK);
+ }
+
+ /**
+ * Check whether one specific experimental feature in specific namespace from
+ * {@link DeviceConfig} is not disabled. Feature can be disabled by setting a non-zero
+ * value in the property. If the feature is enabled by default and disabled by flag push
+ * (kill switch), this method should be used. If the feature is disabled by default and
+ * enabled by flag push, {@link #isFeatureEnabled} should be used.
+ *
+ * @param namespace The namespace containing the property to look up.
+ * @param name The name of the property to look up.
+ * @return true if this feature is enabled, or false if disabled.
+ */
+ private static boolean isFeatureNotChickenedOut(String namespace, String name) {
+ final int propertyVersion = getDeviceConfigPropertyInt(namespace, name,
+ 0 /* default value */);
+ return propertyVersion == 0;
+ }
+
+ /**
+ * Check whether one specific experimental feature in Tethering module from {@link DeviceConfig}
+ * is not disabled.
+ *
+ * @param name The name of the property in tethering module to look up.
+ * @return true if this feature is enabled, or false if disabled.
+ */
+ public static boolean isTetheringFeatureNotChickenedOut(String name) {
+ return isFeatureNotChickenedOut(NAMESPACE_TETHERING, name);
+ }
+
+ /**
+ * Check whether one specific experimental feature in NetworkStack module from
+ * {@link DeviceConfig} is not disabled.
+ *
+ * @param name The name of the property in NetworkStack module to look up.
+ * @return true if this feature is enabled, or false if disabled.
+ */
+ public static boolean isNetworkStackFeatureNotChickenedOut(String name) {
+ return isFeatureNotChickenedOut(NAMESPACE_CONNECTIVITY, name);
+ }
+
/**
* Gets boolean config from resources.
*/
diff --git a/common/device/com/android/net/module/util/DomainUtils.java b/common/device/com/android/net/module/util/DomainUtils.java
new file mode 100644
index 00000000..b327fd08
--- /dev/null
+++ b/common/device/com/android/net/module/util/DomainUtils.java
@@ -0,0 +1,143 @@
+/*
+ * 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.net.module.util;
+
+import android.util.ArrayMap;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.net.module.util.DnsPacketUtils.DnsRecordParser;
+
+import java.nio.BufferOverflowException;
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+
+/**
+ * Utilities for encoding/decoding the domain name or domain search list.
+ *
+ * @hide
+ */
+public final class DomainUtils {
+ private static final String TAG = "DomainUtils";
+ private static final int MAX_OPTION_LEN = 255;
+
+ @NonNull
+ private static String getSubstring(@NonNull final String string, @NonNull final String[] labels,
+ int index) {
+ int beginIndex = 0;
+ for (int i = 0; i < index; i++) {
+ beginIndex += labels[i].length() + 1; // include the dot
+ }
+ return string.substring(beginIndex);
+ }
+
+ /**
+ * Encode the given single domain name to byte array, comply with RFC1035 section-3.1.
+ *
+ * @return null if the given domain string is invalid, otherwise, return a byte array
+ * wrapping the encoded domain, not including any padded octets, caller should
+ * pad zero octets at the end if needed.
+ */
+ @Nullable
+ public static byte[] encode(@NonNull final String domain) {
+ if (!DnsRecordParser.isHostName(domain)) return null;
+ return encode(new String[]{ domain }, false /* compression */);
+ }
+
+ /**
+ * Encode the given multiple domain names to byte array, comply with RFC1035 section-3.1
+ * and section 4.1.4 (message compression) if enabled.
+ *
+ * @return Null if encode fails due to BufferOverflowException, otherwise, return a byte
+ * array wrapping the encoded domains, not including any padded octets, caller
+ * should pad zero octets at the end if needed. The byte array may be empty if
+ * the given domain strings are invalid.
+ */
+ @Nullable
+ public static byte[] encode(@NonNull final String[] domains, boolean compression) {
+ try {
+ final ByteBuffer buffer = ByteBuffer.allocate(MAX_OPTION_LEN);
+ final ArrayMap<String, Integer> offsetMap = new ArrayMap<>();
+ for (int i = 0; i < domains.length; i++) {
+ if (!DnsRecordParser.isHostName(domains[i])) {
+ Log.e(TAG, "Skip invalid domain name " + domains[i]);
+ continue;
+ }
+ final String[] labels = domains[i].split("\\.");
+ for (int j = 0; j < labels.length; j++) {
+ if (compression) {
+ final String suffix = getSubstring(domains[i], labels, j);
+ if (offsetMap.containsKey(suffix)) {
+ int offsetOfSuffix = offsetMap.get(suffix);
+ offsetOfSuffix |= 0xC000;
+ buffer.putShort((short) offsetOfSuffix);
+ break; // unnecessary to put the compressed string into map
+ } else {
+ offsetMap.put(suffix, buffer.position());
+ }
+ }
+ // encode the domain name string without compression when:
+ // - compression feature isn't enabled,
+ // - suffix does not match any string in the map.
+ final byte[] labelBytes = labels[j].getBytes(StandardCharsets.UTF_8);
+ buffer.put((byte) labelBytes.length);
+ buffer.put(labelBytes);
+ if (j == labels.length - 1) {
+ // Pad terminate label at the end of last label.
+ buffer.put((byte) 0);
+ }
+ }
+ }
+ buffer.flip();
+ final byte[] out = new byte[buffer.limit()];
+ buffer.get(out);
+ return out;
+ } catch (BufferOverflowException e) {
+ Log.e(TAG, "Fail to encode domain name and stop encoding", e);
+ return null;
+ }
+ }
+
+ /**
+ * Decode domain name(s) from the given byteBuffer. Decode follows RFC1035 section 3.1 and
+ * section 4.1.4(message compression).
+ *
+ * @return domain name(s) string array with space separated, or empty string if decode fails.
+ */
+ @NonNull
+ public static ArrayList<String> decode(@NonNull final ByteBuffer buffer, boolean compression) {
+ final ArrayList<String> domainList = new ArrayList<>();
+ while (buffer.remaining() > 0) {
+ try {
+ // TODO: replace the recursion with loop in parseName and don't need to pass in the
+ // maxLabelCount parameter to prevent recursion from overflowing stack.
+ final String domain = DnsRecordParser.parseName(buffer, 0 /* depth */,
+ 15 /* maxLabelCount */, compression);
+ if (!DnsRecordParser.isHostName(domain)) continue;
+ domainList.add(domain);
+ } catch (BufferUnderflowException | DnsPacket.ParseException e) {
+ Log.e(TAG, "Fail to parse domain name and stop parsing", e);
+ break;
+ }
+ }
+ return domainList;
+ }
+}
diff --git a/common/device/com/android/net/module/util/FeatureVersions.java b/common/device/com/android/net/module/util/FeatureVersions.java
new file mode 100644
index 00000000..149756c3
--- /dev/null
+++ b/common/device/com/android/net/module/util/FeatureVersions.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.net.module.util;
+
+/**
+ * Class to centralize feature version control that requires a specific module or a specific
+ * module version.
+ * @hide
+ */
+public class FeatureVersions {
+ /**
+ * This constant is used to do bitwise shift operation to create module ids.
+ * The module version is composed with 9 digits which is placed in the lower 36 bits.
+ */
+ private static final int MODULE_SHIFT = 36;
+ /**
+ * The bitmask to do bitwise-and(i.e. {@code &}) operation to get the module id.
+ */
+ public static final long MODULE_MASK = 0xFF0_0000_0000L;
+ /**
+ * The bitmask to do bitwise-and(i.e. {@code &}) operation to get the module version.
+ */
+ public static final long VERSION_MASK = 0x00F_FFFF_FFFFL;
+ public static final long CONNECTIVITY_MODULE_ID = 0x01L << MODULE_SHIFT;
+ public static final long NETWORK_STACK_MODULE_ID = 0x02L << MODULE_SHIFT;
+ // CLAT_ADDRESS_TRANSLATE is a feature of the network stack, which doesn't throw when system
+ // try to add a NAT-T keepalive packet filter with v6 address, introduced in version
+ // M-2023-Sept on July 3rd, 2023.
+ public static final long FEATURE_CLAT_ADDRESS_TRANSLATE =
+ NETWORK_STACK_MODULE_ID + 34_09_00_000L;
+}
diff --git a/common/device/com/android/net/module/util/SharedLog.java b/common/device/com/android/net/module/util/SharedLog.java
index 17b061e6..6b12c80d 100644
--- a/common/device/com/android/net/module/util/SharedLog.java
+++ b/common/device/com/android/net/module/util/SharedLog.java
@@ -46,6 +46,8 @@ public class SharedLog {
ERROR,
MARK,
WARN,
+ VERBOSE,
+ TERRIBLE,
}
private final LocalLog mLocalLog;
@@ -159,6 +161,41 @@ public class SharedLog {
Log.w(mTag, record(Category.WARN, msg));
}
+ /**
+ * Log a verbose message.
+ *
+ * <p>The log entry will be also added to the system log.
+ */
+ public void v(String msg) {
+ Log.v(mTag, record(Category.VERBOSE, msg));
+ }
+
+ /**
+ * Log a terrible failure message.
+ *
+ * <p>The log entry will be also added to the system log and will trigger system reporting
+ * for terrible failures.
+ */
+ public void wtf(String msg) {
+ Log.wtf(mTag, record(Category.TERRIBLE, msg));
+ }
+
+ /**
+ * Log a terrible failure due to an exception, with the exception stacktrace if provided.
+ *
+ * <p>The error and exception message appear in the shared log, but the stacktrace is only
+ * logged in general log output (logcat). The log entry will be also added to the system log
+ * and will trigger system reporting for terrible failures.
+ */
+ public void wtf(@NonNull String msg, @Nullable Throwable exception) {
+ if (exception == null) {
+ e(msg);
+ return;
+ }
+ Log.wtf(mTag, record(Category.TERRIBLE, msg + ": " + exception.getMessage()), exception);
+ }
+
+
//////
// Methods that only log an entry (and do NOT emit to the system log).
//////
diff --git a/common/device/com/android/net/module/util/arp/ArpPacket.java b/common/device/com/android/net/module/util/arp/ArpPacket.java
new file mode 100644
index 00000000..dab9694b
--- /dev/null
+++ b/common/device/com/android/net/module/util/arp/ArpPacket.java
@@ -0,0 +1,171 @@
+/*
+ * 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.arp;
+
+import static android.system.OsConstants.ETH_P_ARP;
+import static android.system.OsConstants.ETH_P_IP;
+
+import static com.android.net.module.util.NetworkStackConstants.ARP_ETHER_IPV4_LEN;
+import static com.android.net.module.util.NetworkStackConstants.ARP_HWTYPE_ETHER;
+import static com.android.net.module.util.NetworkStackConstants.ARP_REPLY;
+import static com.android.net.module.util.NetworkStackConstants.ARP_REQUEST;
+import static com.android.net.module.util.NetworkStackConstants.ETHER_ADDR_LEN;
+import static com.android.net.module.util.NetworkStackConstants.IPV4_ADDR_LEN;
+
+import android.net.MacAddress;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.net.Inet4Address;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+
+/**
+ * Defines basic data and operations needed to build and parse packets for the
+ * ARP protocol.
+ *
+ * @hide
+ */
+public class ArpPacket {
+ private static final String TAG = "ArpPacket";
+
+ public final short opCode;
+ public final Inet4Address senderIp;
+ public final Inet4Address targetIp;
+ public final MacAddress senderHwAddress;
+ public final MacAddress targetHwAddress;
+
+ ArpPacket(short opCode, MacAddress senderHwAddress, Inet4Address senderIp,
+ MacAddress targetHwAddress, Inet4Address targetIp) {
+ this.opCode = opCode;
+ this.senderHwAddress = senderHwAddress;
+ this.senderIp = senderIp;
+ this.targetHwAddress = targetHwAddress;
+ this.targetIp = targetIp;
+ }
+
+ /**
+ * Build an ARP packet from the required specified parameters.
+ */
+ @VisibleForTesting
+ public static ByteBuffer buildArpPacket(final byte[] dstMac, final byte[] srcMac,
+ final byte[] targetIp, final byte[] targetHwAddress, byte[] senderIp,
+ final short opCode) {
+ final ByteBuffer buf = ByteBuffer.allocate(ARP_ETHER_IPV4_LEN);
+
+ // Ether header
+ buf.put(dstMac);
+ buf.put(srcMac);
+ buf.putShort((short) ETH_P_ARP);
+
+ // ARP header
+ buf.putShort((short) ARP_HWTYPE_ETHER); // hrd
+ buf.putShort((short) ETH_P_IP); // pro
+ buf.put((byte) ETHER_ADDR_LEN); // hln
+ buf.put((byte) IPV4_ADDR_LEN); // pln
+ buf.putShort(opCode); // op
+ buf.put(srcMac); // sha
+ buf.put(senderIp); // spa
+ buf.put(targetHwAddress); // tha
+ buf.put(targetIp); // tpa
+ buf.flip();
+ return buf;
+ }
+
+ /**
+ * Parse an ARP packet from a ByteBuffer object.
+ */
+ @VisibleForTesting
+ public static ArpPacket parseArpPacket(final byte[] recvbuf, final int length)
+ throws ParseException {
+ try {
+ if (length < ARP_ETHER_IPV4_LEN || recvbuf.length < length) {
+ throw new ParseException("Invalid packet length: " + length);
+ }
+
+ final ByteBuffer buffer = ByteBuffer.wrap(recvbuf, 0, length);
+ byte[] l2dst = new byte[ETHER_ADDR_LEN];
+ byte[] l2src = new byte[ETHER_ADDR_LEN];
+ buffer.get(l2dst);
+ buffer.get(l2src);
+
+ final short etherType = buffer.getShort();
+ if (etherType != ETH_P_ARP) {
+ throw new ParseException("Incorrect Ether Type: " + etherType);
+ }
+
+ final short hwType = buffer.getShort();
+ if (hwType != ARP_HWTYPE_ETHER) {
+ throw new ParseException("Incorrect HW Type: " + hwType);
+ }
+
+ final short protoType = buffer.getShort();
+ if (protoType != ETH_P_IP) {
+ throw new ParseException("Incorrect Protocol Type: " + protoType);
+ }
+
+ final byte hwAddrLength = buffer.get();
+ if (hwAddrLength != ETHER_ADDR_LEN) {
+ throw new ParseException("Incorrect HW address length: " + hwAddrLength);
+ }
+
+ final byte ipAddrLength = buffer.get();
+ if (ipAddrLength != IPV4_ADDR_LEN) {
+ throw new ParseException("Incorrect Protocol address length: " + ipAddrLength);
+ }
+
+ final short opCode = buffer.getShort();
+ if (opCode != ARP_REQUEST && opCode != ARP_REPLY) {
+ throw new ParseException("Incorrect opCode: " + opCode);
+ }
+
+ byte[] senderHwAddress = new byte[ETHER_ADDR_LEN];
+ byte[] senderIp = new byte[IPV4_ADDR_LEN];
+ buffer.get(senderHwAddress);
+ buffer.get(senderIp);
+
+ byte[] targetHwAddress = new byte[ETHER_ADDR_LEN];
+ byte[] targetIp = new byte[IPV4_ADDR_LEN];
+ buffer.get(targetHwAddress);
+ buffer.get(targetIp);
+
+ return new ArpPacket(opCode, MacAddress.fromBytes(senderHwAddress),
+ (Inet4Address) InetAddress.getByAddress(senderIp),
+ MacAddress.fromBytes(targetHwAddress),
+ (Inet4Address) InetAddress.getByAddress(targetIp));
+ } catch (IndexOutOfBoundsException e) {
+ throw new ParseException("Invalid index when wrapping a byte array into a buffer");
+ } catch (BufferUnderflowException e) {
+ throw new ParseException("Invalid buffer position");
+ } catch (IllegalArgumentException e) {
+ throw new ParseException("Invalid MAC address representation");
+ } catch (UnknownHostException e) {
+ throw new ParseException("Invalid IP address of Host");
+ }
+ }
+
+ /**
+ * Thrown when parsing ARP packet failed.
+ */
+ public static class ParseException extends Exception {
+ ParseException(String message) {
+ super(message);
+ }
+ }
+}
diff --git a/common/device/com/android/net/module/util/netlink/NetlinkUtils.java b/common/device/com/android/net/module/util/netlink/NetlinkUtils.java
index 73c88c77..33bd36d8 100644
--- a/common/device/com/android/net/module/util/netlink/NetlinkUtils.java
+++ b/common/device/com/android/net/module/util/netlink/NetlinkUtils.java
@@ -33,6 +33,7 @@ import static android.system.OsConstants.SO_SNDTIMEO;
import android.net.util.SocketUtils;
import android.system.ErrnoException;
import android.system.Os;
+import android.system.OsConstants;
import android.system.StructTimeval;
import android.util.Log;
@@ -291,5 +292,20 @@ public class NetlinkUtils {
return Os.write(fd, bytes, offset, count);
}
+ private static final long CLOCK_TICKS_PER_SECOND = Os.sysconf(OsConstants._SC_CLK_TCK);
+
+ /**
+ * Convert the system time in clock ticks(clock_t type in times(), not in clock()) to
+ * milliseconds. times() clock_t ticks at the kernel's USER_HZ (100) while clock() clock_t
+ * ticks at CLOCKS_PER_SEC (1000000).
+ *
+ * See the NOTES on https://man7.org/linux/man-pages/man2/times.2.html for the difference
+ * of clock_t used in clock() and times().
+ */
+ public static long ticksToMilliSeconds(int intClockTicks) {
+ final long longClockTicks = intClockTicks & 0xffffffffL;
+ return (longClockTicks * 1000) / CLOCK_TICKS_PER_SECOND;
+ }
+
private NetlinkUtils() {}
}
diff --git a/common/device/com/android/net/module/util/netlink/StructNdaCacheInfo.java b/common/device/com/android/net/module/util/netlink/StructNdaCacheInfo.java
index 79d5ff4a..1f9bb7ec 100644
--- a/common/device/com/android/net/module/util/netlink/StructNdaCacheInfo.java
+++ b/common/device/com/android/net/module/util/netlink/StructNdaCacheInfo.java
@@ -16,8 +16,7 @@
package com.android.net.module.util.netlink;
-import android.system.Os;
-import android.system.OsConstants;
+import static com.android.net.module.util.netlink.NetlinkUtils.ticksToMilliSeconds;
import java.nio.ByteBuffer;
@@ -57,15 +56,6 @@ public class StructNdaCacheInfo {
return struct;
}
- // TODO: investigate whether this can change during device runtime and
- // decide what (if anything) should be done about that.
- private static final long CLOCK_TICKS_PER_SECOND = Os.sysconf(OsConstants._SC_CLK_TCK);
-
- private static long ticksToMilliSeconds(int intClockTicks) {
- final long longClockTicks = (long) intClockTicks & 0xffffffff;
- return (longClockTicks * 1000) / CLOCK_TICKS_PER_SECOND;
- }
-
/**
* Explanatory notes, for reference.
*
diff --git a/common/device/com/android/net/module/util/structs/Ipv6PktInfo.java b/common/device/com/android/net/module/util/structs/Ipv6PktInfo.java
new file mode 100644
index 00000000..0dccb727
--- /dev/null
+++ b/common/device/com/android/net/module/util/structs/Ipv6PktInfo.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.net.module.util.structs;
+
+import com.android.net.module.util.Struct;
+import com.android.net.module.util.Struct.Field;
+import com.android.net.module.util.Struct.Type;
+
+import java.net.Inet6Address;
+
+/**
+ * structure in6_pktinfo
+ *
+ * see also:
+ *
+ * include/uapi/linux/ipv6.h
+ */
+public class Ipv6PktInfo extends Struct {
+ @Field(order = 0, type = Type.Ipv6Address)
+ public final Inet6Address addr; // IPv6 source or destination address
+ @Field(order = 1, type = Type.S32)
+ public final int ifindex; // interface index
+
+ public Ipv6PktInfo(final Inet6Address addr, final int ifindex) {
+ this.addr = addr;
+ this.ifindex = ifindex;
+ }
+}
diff --git a/common/device/com/android/net/module/util/structs/RouteInformationOption.java b/common/device/com/android/net/module/util/structs/RouteInformationOption.java
new file mode 100644
index 00000000..49bafedb
--- /dev/null
+++ b/common/device/com/android/net/module/util/structs/RouteInformationOption.java
@@ -0,0 +1,97 @@
+/*
+ * 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.net.module.util.structs;
+
+import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ND_OPTION_RIO;
+
+import android.net.IpPrefix;
+
+import androidx.annotation.NonNull;
+
+import com.android.net.module.util.Struct;
+import com.android.net.module.util.Struct.Field;
+import com.android.net.module.util.Struct.Type;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+/**
+ * ICMPv6 route information option, as per https://tools.ietf.org/html/rfc4191.
+ *
+ * 0 1 2 3
+ * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Type | Length | Prefix Length |Resvd|Prf|Resvd|
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Route Lifetime |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Prefix (Variable Length) |
+ * . .
+ * . .
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ */
+public class RouteInformationOption extends Struct {
+ public enum Preference {
+ HIGH((byte) 0x1),
+ MEDIUM((byte) 0x0),
+ LOW((byte) 0x3),
+ RESERVED((byte) 0x2);
+
+ final byte mValue;
+ Preference(byte value) {
+ this.mValue = value;
+ }
+ }
+
+ @Field(order = 0, type = Type.S8)
+ public final byte type;
+ @Field(order = 1, type = Type.S8)
+ public final byte length; // Length in 8-byte octets
+ @Field(order = 2, type = Type.U8)
+ public final short prefixLen;
+ @Field(order = 3, type = Type.S8)
+ public final byte prf;
+ @Field(order = 4, type = Type.U32)
+ public final long routeLifetime;
+ @Field(order = 5, type = Type.ByteArray, arraysize = 16)
+ public final byte[] prefix;
+
+ RouteInformationOption(final byte type, final byte length, final short prefixLen,
+ final byte prf, final long routeLifetime, @NonNull final byte[] prefix) {
+ this.type = type;
+ this.length = length;
+ this.prefixLen = prefixLen;
+ this.prf = prf;
+ this.routeLifetime = routeLifetime;
+ this.prefix = prefix;
+ }
+
+ /**
+ * Build a Route Information option from the required specified parameters.
+ */
+ public static ByteBuffer build(final IpPrefix prefix, final Preference prf,
+ final long routeLifetime) {
+ // The prefix field is always assumed to have 16 bytes, but the number of leading
+ // bits in this prefix depends on IpPrefix#prefixLength, then we can simply set the
+ // option length to 3.
+ final RouteInformationOption option = new RouteInformationOption(
+ (byte) ICMPV6_ND_OPTION_RIO, (byte) 3 /* option length */,
+ (short) prefix.getPrefixLength(), (byte) (prf.mValue << 3), routeLifetime,
+ prefix.getRawAddress());
+ return ByteBuffer.wrap(option.writeToBytes(ByteOrder.BIG_ENDIAN));
+ }
+}
diff --git a/common/framework/com/android/net/module/util/DnsPacketUtils.java b/common/framework/com/android/net/module/util/DnsPacketUtils.java
index c47bfa07..105d7837 100644
--- a/common/framework/com/android/net/module/util/DnsPacketUtils.java
+++ b/common/framework/com/android/net/module/util/DnsPacketUtils.java
@@ -130,16 +130,25 @@ public final class DnsPacketUtils {
/**
* Parses the domain / target name of a DNS record.
+ */
+ public static String parseName(final ByteBuffer buf, int depth,
+ boolean isNameCompressionSupported) throws
+ BufferUnderflowException, DnsPacket.ParseException {
+ return parseName(buf, depth, MAXLABELCOUNT, isNameCompressionSupported);
+ }
+
+ /**
+ * Parses the domain / target name of a DNS record.
*
* As described in RFC 1035 Section 4.1.3, the NAME field of a DNS Resource Record always
* supports Name Compression, whereas domain names contained in the RDATA payload of a DNS
* record may or may not support Name Compression, depending on the record TYPE. Moreover,
* even if Name Compression is supported, its usage is left to the implementation.
*/
- public static String parseName(ByteBuffer buf, int depth,
+ public static String parseName(final ByteBuffer buf, int depth, int maxLabelCount,
boolean isNameCompressionSupported) throws
BufferUnderflowException, DnsPacket.ParseException {
- if (depth > MAXLABELCOUNT) {
+ if (depth > maxLabelCount) {
throw new DnsPacket.ParseException("Failed to parse name, too many labels");
}
final int len = Byte.toUnsignedInt(buf.get());
@@ -158,7 +167,8 @@ public final class DnsPacketUtils {
"Parse compression name fail, invalid compression");
}
buf.position(offset);
- final String pointed = parseName(buf, depth + 1, isNameCompressionSupported);
+ final String pointed = parseName(buf, depth + 1, maxLabelCount,
+ isNameCompressionSupported);
buf.position(oldPos);
return pointed;
} else {
@@ -168,7 +178,8 @@ public final class DnsPacketUtils {
if (head.length() > MAXLABELSIZE) {
throw new DnsPacket.ParseException("Parse name fail, invalid label length");
}
- final String tail = parseName(buf, depth + 1, isNameCompressionSupported);
+ final String tail = parseName(buf, depth + 1, maxLabelCount,
+ isNameCompressionSupported);
return TextUtils.isEmpty(tail) ? head : head + "." + tail;
}
}
diff --git a/common/framework/com/android/net/module/util/NetworkStackConstants.java b/common/framework/com/android/net/module/util/NetworkStackConstants.java
index 6a6f5e10..91491605 100644
--- a/common/framework/com/android/net/module/util/NetworkStackConstants.java
+++ b/common/framework/com/android/net/module/util/NetworkStackConstants.java
@@ -17,6 +17,7 @@
package com.android.net.module.util;
import android.net.InetAddresses;
+import android.net.IpPrefix;
import java.net.Inet4Address;
import java.net.Inet6Address;
@@ -99,6 +100,8 @@ public final class NetworkStackConstants {
public static final int IPV4_ADDR_LEN = 4;
public static final int IPV4_FLAG_MF = 0x2000;
public static final int IPV4_FLAG_DF = 0x4000;
+ // getSockOpt() for v4 MTU
+ public static final int IP_MTU = 14;
public static final Inet4Address IPV4_ADDR_ALL = makeInet4Address(
(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff);
public static final Inet4Address IPV4_ADDR_ANY = makeInet4Address(
@@ -108,6 +111,12 @@ public final class NetworkStackConstants {
(byte) 0, (byte) 0, (byte) 0, (byte) 0,
(byte) 0, (byte) 0, (byte) 0, (byte) 0,
(byte) 0, (byte) 0, (byte) 0, (byte) 0 });
+
+ /**
+ * CLAT constants
+ */
+ public static final IpPrefix CLAT_PREFIX = new IpPrefix("192.0.0.0/29");
+
/**
* IPv6 constants.
*
@@ -121,6 +130,10 @@ public final class NetworkStackConstants {
public static final int IPV6_SRC_ADDR_OFFSET = 8;
public static final int IPV6_DST_ADDR_OFFSET = 24;
public static final int IPV6_MIN_MTU = 1280;
+ public static final int IPV6_FRAGMENT_HEADER_LEN = 8;
+ public static final int RFC7421_PREFIX_LENGTH = 64;
+ // getSockOpt() for v6 MTU
+ public static final int IPV6_MTU = 24;
public static final Inet6Address IPV6_ADDR_ALL_NODES_MULTICAST =
(Inet6Address) InetAddresses.parseNumericAddress("ff02::1");
public static final Inet6Address IPV6_ADDR_ALL_ROUTERS_MULTICAST =
@@ -140,6 +153,7 @@ public final class NetworkStackConstants {
* ICMPv6 constants.
*
* See also:
+ * - https://tools.ietf.org/html/rfc4191
* - https://tools.ietf.org/html/rfc4443
* - https://tools.ietf.org/html/rfc4861
*/
@@ -157,6 +171,7 @@ public final class NetworkStackConstants {
public static final int ICMPV6_ND_OPTION_TLLA = 2;
public static final int ICMPV6_ND_OPTION_PIO = 3;
public static final int ICMPV6_ND_OPTION_MTU = 5;
+ public static final int ICMPV6_ND_OPTION_RIO = 24;
public static final int ICMPV6_ND_OPTION_RDNSS = 25;
public static final int ICMPV6_ND_OPTION_PREF64 = 38;
diff --git a/common/native/bpf_headers/include/bpf/BpfClassic.h b/common/native/bpf_headers/include/bpf/BpfClassic.h
index 9b38deec..dd0804c3 100644
--- a/common/native/bpf_headers/include/bpf/BpfClassic.h
+++ b/common/native/bpf_headers/include/bpf/BpfClassic.h
@@ -22,11 +22,25 @@
// Reject the packet
#define BPF_REJECT BPF_STMT(BPF_RET | BPF_K, 0)
+// *TWO* instructions: compare and if not equal jump over the accept statement
+#define BPF2_ACCEPT_IF_EQUAL(v) \
+ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, (v), 0, 1), \
+ BPF_ACCEPT
+
// *TWO* instructions: compare and if equal jump over the reject statement
#define BPF2_REJECT_IF_NOT_EQUAL(v) \
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, (v), 1, 0), \
BPF_REJECT
+// *TWO* instructions: compare and if none of the bits are set jump over the reject statement
+#define BPF2_REJECT_IF_ANY_BITS_SET(v) \
+ BPF_JUMP(BPF_JMP | BPF_JSET | BPF_K, (v), 0, 1), \
+ BPF_REJECT
+
+// loads skb->protocol
+#define BPF_LOAD_SKB_PROTOCOL \
+ BPF_STMT(BPF_LD | BPF_H | BPF_ABS, (__u32)SKF_AD_OFF + SKF_AD_PROTOCOL)
+
// 8-bit load relative to start of link layer (mac/ethernet) header.
#define BPF_LOAD_MAC_RELATIVE_U8(ofs) \
BPF_STMT(BPF_LD | BPF_B | BPF_ABS, (__u32)SKF_LL_OFF + (ofs))
diff --git a/common/native/bpf_headers/include/bpf/BpfMap.h b/common/native/bpf_headers/include/bpf/BpfMap.h
index 51e6d162..847083e5 100644
--- a/common/native/bpf_headers/include/bpf/BpfMap.h
+++ b/common/native/bpf_headers/include/bpf/BpfMap.h
@@ -29,6 +29,10 @@
namespace android {
namespace bpf {
+using base::Result;
+using base::unique_fd;
+using std::function;
+
// This is a class wrapper for eBPF maps. The eBPF map is a special in-kernel
// data structure that stores data in <Key, Value> pairs. It can be read/write
// from userspace by passing syscalls with the map file descriptor. This class
@@ -80,7 +84,7 @@ class BpfMap {
}
#endif
- base::Result<Key> getFirstKey() const {
+ Result<Key> getFirstKey() const {
Key firstKey;
if (getFirstMapKey(mMapFd, &firstKey)) {
return ErrnoErrorf("Get firstKey map {} failed", mMapFd.get());
@@ -88,7 +92,7 @@ class BpfMap {
return firstKey;
}
- base::Result<Key> getNextKey(const Key& key) const {
+ Result<Key> getNextKey(const Key& key) const {
Key nextKey;
if (getNextMapKey(mMapFd, &key, &nextKey)) {
return ErrnoErrorf("Get next key of map {} failed", mMapFd.get());
@@ -96,14 +100,14 @@ class BpfMap {
return nextKey;
}
- base::Result<void> writeValue(const Key& key, const Value& value, uint64_t flags) {
+ Result<void> writeValue(const Key& key, const Value& value, uint64_t flags) {
if (writeToMapEntry(mMapFd, &key, &value, flags)) {
return ErrnoErrorf("Write to map {} failed", mMapFd.get());
}
return {};
}
- base::Result<Value> readValue(const Key key) const {
+ Result<Value> readValue(const Key key) const {
Value value;
if (findMapEntry(mMapFd, &key, &value)) {
return ErrnoErrorf("Read value of map {} failed", mMapFd.get());
@@ -111,7 +115,7 @@ class BpfMap {
return value;
}
- base::Result<void> deleteValue(const Key& key) {
+ Result<void> deleteValue(const Key& key) {
if (deleteMapEntry(mMapFd, &key)) {
return ErrnoErrorf("Delete entry from map {} failed", mMapFd.get());
}
@@ -119,7 +123,7 @@ class BpfMap {
}
protected:
- [[clang::reinitializes]] base::Result<void> init(const char* path, int fd) {
+ [[clang::reinitializes]] Result<void> init(const char* path, int fd) {
mMapFd.reset(fd);
if (!mMapFd.ok()) {
return ErrnoErrorf("Pinned map not accessible or does not exist: ({})", path);
@@ -134,7 +138,7 @@ class BpfMap {
public:
// Function that tries to get map from a pinned path.
- [[clang::reinitializes]] base::Result<void> init(const char* path) {
+ [[clang::reinitializes]] Result<void> init(const char* path) {
return init(path, mapRetrieveRW(path));
}
@@ -144,7 +148,7 @@ class BpfMap {
// this should only ever be used by test code, it is equivalent to:
// .reset(createMap(type, keysize, valuesize, max_entries, map_flags)
// TODO: derive map_flags from BpfMap vs BpfMapRO
- [[clang::reinitializes]] base::Result<void> resetMap(bpf_map_type map_type,
+ [[clang::reinitializes]] Result<void> resetMap(bpf_map_type map_type,
uint32_t max_entries,
uint32_t map_flags = 0) {
mMapFd.reset(createMap(map_type, sizeof(Key), sizeof(Value), max_entries, map_flags));
@@ -155,30 +159,30 @@ class BpfMap {
// Iterate through the map and handle each key retrieved based on the filter
// without modification of map content.
- base::Result<void> iterate(
- const std::function<base::Result<void>(const Key& key, const BpfMap<Key, Value>& map)>&
- filter) const;
+ Result<void> iterate(
+ const function<Result<void>(const Key& key,
+ const BpfMap<Key, Value>& map)>& filter) const;
// Iterate through the map and get each <key, value> pair, handle each <key,
// value> pair based on the filter without modification of map content.
- base::Result<void> iterateWithValue(
- const std::function<base::Result<void>(const Key& key, const Value& value,
- const BpfMap<Key, Value>& map)>& filter) const;
+ Result<void> iterateWithValue(
+ const function<Result<void>(const Key& key, const Value& value,
+ const BpfMap<Key, Value>& map)>& filter) const;
// Iterate through the map and handle each key retrieved based on the filter
- base::Result<void> iterate(
- const std::function<base::Result<void>(const Key& key, BpfMap<Key, Value>& map)>&
- filter);
+ Result<void> iterate(
+ const function<Result<void>(const Key& key,
+ BpfMap<Key, Value>& map)>& filter);
// Iterate through the map and get each <key, value> pair, handle each <key,
// value> pair based on the filter.
- base::Result<void> iterateWithValue(
- const std::function<base::Result<void>(const Key& key, const Value& value,
- BpfMap<Key, Value>& map)>& filter);
-
- const base::unique_fd& getMap() const { return mMapFd; };
+ Result<void> iterateWithValue(
+ const function<Result<void>(const Key& key, const Value& value,
+ BpfMap<Key, Value>& map)>& filter);
#ifdef BPF_MAP_MAKE_VISIBLE_FOR_TESTING
+ const unique_fd& getMap() const { return mMapFd; };
+
// Copy assignment operator - due to need for fd duping, should not be used in non-test code.
BpfMap<Key, Value>& operator=(const BpfMap<Key, Value>& other) {
if (this != &other) mMapFd.reset(fcntl(other.mMapFd.get(), F_DUPFD_CLOEXEC, 0));
@@ -197,7 +201,7 @@ class BpfMap {
return *this;
}
- void reset(base::unique_fd fd) = delete;
+ void reset(unique_fd fd) = delete;
#ifdef BPF_MAP_MAKE_VISIBLE_FOR_TESTING
// Note that unique_fd.reset() carefully saves and restores the errno,
@@ -216,7 +220,7 @@ class BpfMap {
bool isValid() const { return mMapFd.ok(); }
- base::Result<void> clear() {
+ Result<void> clear() {
while (true) {
auto key = getFirstKey();
if (!key.ok()) {
@@ -233,28 +237,25 @@ class BpfMap {
}
}
- base::Result<bool> isEmpty() const {
+ Result<bool> isEmpty() const {
auto key = getFirstKey();
- if (!key.ok()) {
- // Return error code ENOENT means the map is empty
- if (key.error().code() == ENOENT) return true;
- return key.error();
- }
- return false;
+ if (key.ok()) return false;
+ if (key.error().code() == ENOENT) return true;
+ return key.error();
}
private:
- base::unique_fd mMapFd;
+ unique_fd mMapFd;
};
template <class Key, class Value>
-base::Result<void> BpfMap<Key, Value>::iterate(
- const std::function<base::Result<void>(const Key& key, const BpfMap<Key, Value>& map)>&
- filter) const {
- base::Result<Key> curKey = getFirstKey();
+Result<void> BpfMap<Key, Value>::iterate(
+ const function<Result<void>(const Key& key,
+ const BpfMap<Key, Value>& map)>& filter) const {
+ Result<Key> curKey = getFirstKey();
while (curKey.ok()) {
- const base::Result<Key>& nextKey = getNextKey(curKey.value());
- base::Result<void> status = filter(curKey.value(), *this);
+ const Result<Key>& nextKey = getNextKey(curKey.value());
+ Result<void> status = filter(curKey.value(), *this);
if (!status.ok()) return status;
curKey = nextKey;
}
@@ -263,15 +264,15 @@ base::Result<void> BpfMap<Key, Value>::iterate(
}
template <class Key, class Value>
-base::Result<void> BpfMap<Key, Value>::iterateWithValue(
- const std::function<base::Result<void>(const Key& key, const Value& value,
- const BpfMap<Key, Value>& map)>& filter) const {
- base::Result<Key> curKey = getFirstKey();
+Result<void> BpfMap<Key, Value>::iterateWithValue(
+ const function<Result<void>(const Key& key, const Value& value,
+ const BpfMap<Key, Value>& map)>& filter) const {
+ Result<Key> curKey = getFirstKey();
while (curKey.ok()) {
- const base::Result<Key>& nextKey = getNextKey(curKey.value());
- base::Result<Value> curValue = readValue(curKey.value());
+ const Result<Key>& nextKey = getNextKey(curKey.value());
+ Result<Value> curValue = readValue(curKey.value());
if (!curValue.ok()) return curValue.error();
- base::Result<void> status = filter(curKey.value(), curValue.value(), *this);
+ Result<void> status = filter(curKey.value(), curValue.value(), *this);
if (!status.ok()) return status;
curKey = nextKey;
}
@@ -280,12 +281,13 @@ base::Result<void> BpfMap<Key, Value>::iterateWithValue(
}
template <class Key, class Value>
-base::Result<void> BpfMap<Key, Value>::iterate(
- const std::function<base::Result<void>(const Key& key, BpfMap<Key, Value>& map)>& filter) {
- base::Result<Key> curKey = getFirstKey();
+Result<void> BpfMap<Key, Value>::iterate(
+ const function<Result<void>(const Key& key,
+ BpfMap<Key, Value>& map)>& filter) {
+ Result<Key> curKey = getFirstKey();
while (curKey.ok()) {
- const base::Result<Key>& nextKey = getNextKey(curKey.value());
- base::Result<void> status = filter(curKey.value(), *this);
+ const Result<Key>& nextKey = getNextKey(curKey.value());
+ Result<void> status = filter(curKey.value(), *this);
if (!status.ok()) return status;
curKey = nextKey;
}
@@ -294,15 +296,15 @@ base::Result<void> BpfMap<Key, Value>::iterate(
}
template <class Key, class Value>
-base::Result<void> BpfMap<Key, Value>::iterateWithValue(
- const std::function<base::Result<void>(const Key& key, const Value& value,
- BpfMap<Key, Value>& map)>& filter) {
- base::Result<Key> curKey = getFirstKey();
+Result<void> BpfMap<Key, Value>::iterateWithValue(
+ const function<Result<void>(const Key& key, const Value& value,
+ BpfMap<Key, Value>& map)>& filter) {
+ Result<Key> curKey = getFirstKey();
while (curKey.ok()) {
- const base::Result<Key>& nextKey = getNextKey(curKey.value());
- base::Result<Value> curValue = readValue(curKey.value());
+ const Result<Key>& nextKey = getNextKey(curKey.value());
+ Result<Value> curValue = readValue(curKey.value());
if (!curValue.ok()) return curValue.error();
- base::Result<void> status = filter(curKey.value(), curValue.value(), *this);
+ Result<void> status = filter(curKey.value(), curValue.value(), *this);
if (!status.ok()) return status;
curKey = nextKey;
}
@@ -319,7 +321,7 @@ class BpfMapRO : public BpfMap<Key, Value> {
: BpfMap<Key, Value>(pathname, BPF_F_RDONLY) {}
// Function that tries to get map from a pinned path.
- [[clang::reinitializes]] base::Result<void> init(const char* path) {
+ [[clang::reinitializes]] Result<void> init(const char* path) {
return BpfMap<Key,Value>::init(path, mapRetrieveRO(path));
}
};
diff --git a/common/netd/libnetdutils/Android.bp b/common/netd/libnetdutils/Android.bp
index 08d54121..31690331 100644
--- a/common/netd/libnetdutils/Android.bp
+++ b/common/netd/libnetdutils/Android.bp
@@ -66,3 +66,8 @@ cc_test {
"libbase",
],
}
+
+cc_library_headers {
+ name: "libnetd_utils_headers",
+ export_include_dirs: ["include"],
+}
diff --git a/common/tests/unit/Android.bp b/common/tests/unit/Android.bp
index 8c49954e..40371e63 100644
--- a/common/tests/unit/Android.bp
+++ b/common/tests/unit/Android.bp
@@ -22,7 +22,6 @@ android_library {
"net-utils-device-common-bpf",
"net-utils-device-common-ip",
"net-utils-device-common-wear",
- "truth",
],
libs: [
"android.test.runner",
diff --git a/common/tests/unit/src/com/android/net/module/util/ArpPacketTest.java b/common/tests/unit/src/com/android/net/module/util/ArpPacketTest.java
new file mode 100644
index 00000000..e25d5548
--- /dev/null
+++ b/common/tests/unit/src/com/android/net/module/util/ArpPacketTest.java
@@ -0,0 +1,201 @@
+/*
+ * 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 static com.android.net.module.util.NetworkStackConstants.ARP_REQUEST;
+import static com.android.net.module.util.NetworkStackConstants.ETHER_ADDR_LEN;
+import static com.android.net.module.util.NetworkStackConstants.ETHER_BROADCAST;
+import static com.android.testutils.MiscAsserts.assertThrows;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+
+import android.net.InetAddresses;
+import android.net.MacAddress;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.net.module.util.arp.ArpPacket;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.net.Inet4Address;
+import java.nio.ByteBuffer;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public final class ArpPacketTest {
+
+ private static final Inet4Address TEST_IPV4_ADDR =
+ (Inet4Address) InetAddresses.parseNumericAddress("192.168.1.2");
+ private static final Inet4Address INADDR_ANY =
+ (Inet4Address) InetAddresses.parseNumericAddress("0.0.0.0");
+ private static final byte[] TEST_SENDER_MAC_ADDR = new byte[] {
+ 0x00, 0x1a, 0x11, 0x22, 0x33, 0x33 };
+ private static final byte[] TEST_TARGET_MAC_ADDR = new byte[] {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
+ private static final byte[] TEST_ARP_PROBE = new byte[] {
+ // dst mac address
+ (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+ // src mac address
+ (byte) 0x00, (byte) 0x1a, (byte) 0x11, (byte) 0x22, (byte) 0x33, (byte) 0x33,
+ // ether type
+ (byte) 0x08, (byte) 0x06,
+ // hardware type
+ (byte) 0x00, (byte) 0x01,
+ // protocol type
+ (byte) 0x08, (byte) 0x00,
+ // hardware address size
+ (byte) 0x06,
+ // protocol address size
+ (byte) 0x04,
+ // opcode
+ (byte) 0x00, (byte) 0x01,
+ // sender mac address
+ (byte) 0x00, (byte) 0x1a, (byte) 0x11, (byte) 0x22, (byte) 0x33, (byte) 0x33,
+ // sender IP address
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ // target mac address
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ // target IP address
+ (byte) 0xc0, (byte) 0xa8, (byte) 0x01, (byte) 0x02,
+ };
+
+ private static final byte[] TEST_ARP_ANNOUNCE = new byte[] {
+ // dst mac address
+ (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+ // src mac address
+ (byte) 0x00, (byte) 0x1a, (byte) 0x11, (byte) 0x22, (byte) 0x33, (byte) 0x33,
+ // ether type
+ (byte) 0x08, (byte) 0x06,
+ // hardware type
+ (byte) 0x00, (byte) 0x01,
+ // protocol type
+ (byte) 0x08, (byte) 0x00,
+ // hardware address size
+ (byte) 0x06,
+ // protocol address size
+ (byte) 0x04,
+ // opcode
+ (byte) 0x00, (byte) 0x01,
+ // sender mac address
+ (byte) 0x00, (byte) 0x1a, (byte) 0x11, (byte) 0x22, (byte) 0x33, (byte) 0x33,
+ // sender IP address
+ (byte) 0xc0, (byte) 0xa8, (byte) 0x01, (byte) 0x02,
+ // target mac address
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ // target IP address
+ (byte) 0xc0, (byte) 0xa8, (byte) 0x01, (byte) 0x02,
+ };
+
+ private static final byte[] TEST_ARP_PROBE_TRUNCATED = new byte[] {
+ // dst mac address
+ (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+ // src mac address
+ (byte) 0x00, (byte) 0x1a, (byte) 0x11, (byte) 0x22, (byte) 0x33, (byte) 0x33,
+ // ether type
+ (byte) 0x08, (byte) 0x06,
+ // hardware type
+ (byte) 0x00, (byte) 0x01,
+ // protocol type
+ (byte) 0x08, (byte) 0x00,
+ // hardware address size
+ (byte) 0x06,
+ // protocol address size
+ (byte) 0x04,
+ // opcode
+ (byte) 0x00,
+ };
+
+ private static final byte[] TEST_ARP_PROBE_TRUNCATED_MAC = new byte[] {
+ // dst mac address
+ (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+ // src mac address
+ (byte) 0x00, (byte) 0x1a, (byte) 0x11, (byte) 0x22, (byte) 0x33, (byte) 0x33,
+ // ether type
+ (byte) 0x08, (byte) 0x06,
+ // hardware type
+ (byte) 0x00, (byte) 0x01,
+ // protocol type
+ (byte) 0x08, (byte) 0x00,
+ // hardware address size
+ (byte) 0x06,
+ // protocol address size
+ (byte) 0x04,
+ // opcode
+ (byte) 0x00, (byte) 0x01,
+ // sender mac address
+ (byte) 0x00, (byte) 0x1a, (byte) 0x11, (byte) 0x22, (byte) 0x33,
+ };
+
+ @Test
+ public void testBuildArpProbePacket() throws Exception {
+ final ByteBuffer arpProbe = ArpPacket.buildArpPacket(ETHER_BROADCAST,
+ TEST_SENDER_MAC_ADDR, TEST_IPV4_ADDR.getAddress(), new byte[ETHER_ADDR_LEN],
+ INADDR_ANY.getAddress(), (short) ARP_REQUEST);
+ assertArrayEquals(arpProbe.array(), TEST_ARP_PROBE);
+ }
+
+ @Test
+ public void testBuildArpAnnouncePacket() throws Exception {
+ final ByteBuffer arpAnnounce = ArpPacket.buildArpPacket(ETHER_BROADCAST,
+ TEST_SENDER_MAC_ADDR, TEST_IPV4_ADDR.getAddress(), new byte[ETHER_ADDR_LEN],
+ TEST_IPV4_ADDR.getAddress(), (short) ARP_REQUEST);
+ assertArrayEquals(arpAnnounce.array(), TEST_ARP_ANNOUNCE);
+ }
+
+ @Test
+ public void testParseArpProbePacket() throws Exception {
+ final ArpPacket packet = ArpPacket.parseArpPacket(TEST_ARP_PROBE, TEST_ARP_PROBE.length);
+ assertEquals(packet.opCode, ARP_REQUEST);
+ assertEquals(packet.senderHwAddress, MacAddress.fromBytes(TEST_SENDER_MAC_ADDR));
+ assertEquals(packet.targetHwAddress, MacAddress.fromBytes(TEST_TARGET_MAC_ADDR));
+ assertEquals(packet.senderIp, INADDR_ANY);
+ assertEquals(packet.targetIp, TEST_IPV4_ADDR);
+ }
+
+ @Test
+ public void testParseArpAnnouncePacket() throws Exception {
+ final ArpPacket packet = ArpPacket.parseArpPacket(TEST_ARP_ANNOUNCE,
+ TEST_ARP_ANNOUNCE.length);
+ assertEquals(packet.opCode, ARP_REQUEST);
+ assertEquals(packet.senderHwAddress, MacAddress.fromBytes(TEST_SENDER_MAC_ADDR));
+ assertEquals(packet.targetHwAddress, MacAddress.fromBytes(TEST_TARGET_MAC_ADDR));
+ assertEquals(packet.senderIp, TEST_IPV4_ADDR);
+ assertEquals(packet.targetIp, TEST_IPV4_ADDR);
+ }
+
+ @Test
+ public void testParseArpPacket_invalidByteBufferParameters() throws Exception {
+ assertThrows(ArpPacket.ParseException.class, () -> ArpPacket.parseArpPacket(
+ TEST_ARP_PROBE, 0));
+ }
+
+ @Test
+ public void testParseArpPacket_truncatedPacket() throws Exception {
+ assertThrows(ArpPacket.ParseException.class, () -> ArpPacket.parseArpPacket(
+ TEST_ARP_PROBE_TRUNCATED, TEST_ARP_PROBE_TRUNCATED.length));
+ }
+
+ @Test
+ public void testParseArpPacket_truncatedMacAddress() throws Exception {
+ assertThrows(ArpPacket.ParseException.class, () -> ArpPacket.parseArpPacket(
+ TEST_ARP_PROBE_TRUNCATED_MAC, TEST_ARP_PROBE_TRUNCATED.length));
+ }
+}
diff --git a/common/tests/unit/src/com/android/net/module/util/CollectionUtilsTest.kt b/common/tests/unit/src/com/android/net/module/util/CollectionUtilsTest.kt
index 9fb025b3..e23f9991 100644
--- a/common/tests/unit/src/com/android/net/module/util/CollectionUtilsTest.kt
+++ b/common/tests/unit/src/com/android/net/module/util/CollectionUtilsTest.kt
@@ -63,9 +63,6 @@ class CollectionUtilsTest {
assertEquals(0, CollectionUtils.indexOfSubArray(haystack, byteArrayOf()))
assertEquals(-1, CollectionUtils.indexOfSubArray(byteArrayOf(), byteArrayOf(3, 2)))
assertEquals(0, CollectionUtils.indexOfSubArray(byteArrayOf(), byteArrayOf()))
- assertThrows(NullPointerException::class.java) {
- CollectionUtils.indexOfSubArray(haystack, null)
- }
}
@Test
diff --git a/common/tests/unit/src/com/android/net/module/util/DeviceConfigUtilsTest.java b/common/tests/unit/src/com/android/net/module/util/DeviceConfigUtilsTest.java
index 328c39a4..f259e687 100644
--- a/common/tests/unit/src/com/android/net/module/util/DeviceConfigUtilsTest.java
+++ b/common/tests/unit/src/com/android/net/module/util/DeviceConfigUtilsTest.java
@@ -17,12 +17,17 @@
package com.android.net.module.util;
import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
+import static android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY;
+import static android.provider.DeviceConfig.NAMESPACE_TETHERING;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+import static com.android.net.module.util.FeatureVersions.CONNECTIVITY_MODULE_ID;
+import static com.android.net.module.util.FeatureVersions.NETWORK_STACK_MODULE_ID;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.Mockito.anyInt;
@@ -84,6 +89,8 @@ public class DeviceConfigUtilsTest {
private static final String TEST_GO_APEX_PACKAGE_NAME = "com.prefix.android.go.tethering";
private static final String TEST_CONNRES_PACKAGE_NAME =
"com.prefix.android.connectivity.resources";
+ private static final String TEST_NETWORKSTACK_NAME = "com.prefix.android.networkstack";
+ private static final String TEST_GO_NETWORKSTACK_NAME = "com.prefix.android.go.networkstack";
private final PackageInfo mPackageInfo = new PackageInfo();
private final PackageInfo mApexPackageInfo = new PackageInfo();
private MockitoSession mSession;
@@ -227,7 +234,7 @@ public class DeviceConfigUtilsTest {
eq(TEST_EXPERIMENT_FLAG)));
assertTrue(DeviceConfigUtils.isFeatureEnabled(mContext, TEST_NAME_SPACE,
TEST_EXPERIMENT_FLAG));
- assertTrue(DeviceConfigUtils.isFeatureEnabled(mContext, TEST_NAME_SPACE,
+ assertTrue(DeviceConfigUtils.isTetheringFeatureEnabled(mContext, TEST_NAME_SPACE,
TEST_EXPERIMENT_FLAG, TEST_APEX_NAME, false /* defaultEnabled */));
}
@@ -237,9 +244,9 @@ public class DeviceConfigUtilsTest {
eq(TEST_EXPERIMENT_FLAG)));
assertFalse(DeviceConfigUtils.isFeatureEnabled(mContext, TEST_NAME_SPACE,
TEST_EXPERIMENT_FLAG));
- assertFalse(DeviceConfigUtils.isFeatureEnabled(mContext, TEST_NAME_SPACE,
+ assertFalse(DeviceConfigUtils.isTetheringFeatureEnabled(mContext, TEST_NAME_SPACE,
TEST_EXPERIMENT_FLAG, TEST_APEX_NAME, false /* defaultEnabled */));
- assertTrue(DeviceConfigUtils.isFeatureEnabled(mContext, TEST_NAME_SPACE,
+ assertTrue(DeviceConfigUtils.isTetheringFeatureEnabled(mContext, TEST_NAME_SPACE,
TEST_EXPERIMENT_FLAG, TEST_APEX_NAME, true /* defaultEnabled */));
}
@@ -252,7 +259,7 @@ public class DeviceConfigUtilsTest {
eq(TEST_EXPERIMENT_FLAG)));
assertTrue(DeviceConfigUtils.isFeatureEnabled(mContext, TEST_NAME_SPACE,
TEST_EXPERIMENT_FLAG));
- assertTrue(DeviceConfigUtils.isFeatureEnabled(mContext, TEST_NAME_SPACE,
+ assertTrue(DeviceConfigUtils.isTetheringFeatureEnabled(mContext, TEST_NAME_SPACE,
TEST_EXPERIMENT_FLAG, TEST_APEX_NAME, false /* defaultEnabled */));
// Feature should be disabled by flag value "999999999".
@@ -260,7 +267,7 @@ public class DeviceConfigUtilsTest {
eq(TEST_EXPERIMENT_FLAG)));
assertFalse(DeviceConfigUtils.isFeatureEnabled(mContext, TEST_NAME_SPACE,
TEST_EXPERIMENT_FLAG));
- assertFalse(DeviceConfigUtils.isFeatureEnabled(mContext, TEST_NAME_SPACE,
+ assertFalse(DeviceConfigUtils.isTetheringFeatureEnabled(mContext, TEST_NAME_SPACE,
TEST_EXPERIMENT_FLAG, TEST_APEX_NAME, false /* defaultEnabled */));
// Follow defaultEnabled if the flag is not set
@@ -270,9 +277,9 @@ public class DeviceConfigUtilsTest {
TEST_EXPERIMENT_FLAG, false /* defaultEnabled */));
assertTrue(DeviceConfigUtils.isFeatureEnabled(mContext, TEST_NAME_SPACE,
TEST_EXPERIMENT_FLAG, true /* defaultEnabled */));
- assertFalse(DeviceConfigUtils.isFeatureEnabled(mContext, TEST_NAME_SPACE,
+ assertFalse(DeviceConfigUtils.isTetheringFeatureEnabled(mContext, TEST_NAME_SPACE,
TEST_EXPERIMENT_FLAG, TEST_APEX_NAME, false /* defaultEnabled */));
- assertTrue(DeviceConfigUtils.isFeatureEnabled(mContext, TEST_NAME_SPACE,
+ assertTrue(DeviceConfigUtils.isTetheringFeatureEnabled(mContext, TEST_NAME_SPACE,
TEST_EXPERIMENT_FLAG, TEST_APEX_NAME, true /* defaultEnabled */));
}
@@ -287,14 +294,14 @@ public class DeviceConfigUtilsTest {
assertFalse(DeviceConfigUtils.isFeatureEnabled(mContext, TEST_NAME_SPACE,
TEST_EXPERIMENT_FLAG));
- assertFalse(DeviceConfigUtils.isFeatureEnabled(mContext, TEST_NAME_SPACE,
+ assertFalse(DeviceConfigUtils.isTetheringFeatureEnabled(mContext, TEST_NAME_SPACE,
TEST_EXPERIMENT_FLAG, TEST_APEX_NAME, false /* defaultEnabled */));
- assertTrue(DeviceConfigUtils.isFeatureEnabled(mContext, TEST_NAME_SPACE,
+ assertTrue(DeviceConfigUtils.isTetheringFeatureEnabled(mContext, TEST_NAME_SPACE,
TEST_EXPERIMENT_FLAG, TEST_APEX_NAME, true /* defaultEnabled */));
doReturn(TEST_FLAG_VALUE_STRING).when(() -> DeviceConfig.getProperty(eq(TEST_NAME_SPACE),
eq(TEST_EXPERIMENT_FLAG)));
- assertTrue(DeviceConfigUtils.isFeatureEnabled(mContext, TEST_NAME_SPACE,
+ assertTrue(DeviceConfigUtils.isTetheringFeatureEnabled(mContext, TEST_NAME_SPACE,
TEST_EXPERIMENT_FLAG, TEST_APEX_NAME, false /* defaultEnabled */));
}
@@ -317,9 +324,9 @@ public class DeviceConfigUtilsTest {
public void testFeatureIsEnabledCaching_APEX() throws Exception {
doReturn(TEST_FLAG_VALUE_STRING).when(() -> DeviceConfig.getProperty(eq(TEST_NAME_SPACE),
eq(TEST_EXPERIMENT_FLAG)));
- assertTrue(DeviceConfigUtils.isFeatureEnabled(mContext, TEST_NAME_SPACE,
+ assertTrue(DeviceConfigUtils.isTetheringFeatureEnabled(mContext, TEST_NAME_SPACE,
TEST_EXPERIMENT_FLAG, TEST_APEX_NAME, false /* defaultEnabled */));
- assertTrue(DeviceConfigUtils.isFeatureEnabled(mContext, TEST_NAME_SPACE,
+ assertTrue(DeviceConfigUtils.isTetheringFeatureEnabled(mContext, TEST_NAME_SPACE,
TEST_EXPERIMENT_FLAG, TEST_APEX_NAME, false /* defaultEnabled */));
// Package info is only queried once
@@ -346,4 +353,94 @@ public class DeviceConfigUtilsTest {
doThrow(new Resources.NotFoundException()).when(mResources).getInteger(someResId);
assertEquals(2098, DeviceConfigUtils.getResIntegerConfig(mContext, someResId, 2098));
}
+
+ @Test
+ public void testGetNetworkStackModuleVersionCaching() throws Exception {
+ final PackageInfo networkStackPackageInfo = new PackageInfo();
+ networkStackPackageInfo.setLongVersionCode(TEST_PACKAGE_VERSION);
+ doReturn(networkStackPackageInfo).when(mPm).getPackageInfo(
+ eq(TEST_NETWORKSTACK_NAME), anyInt());
+ assertEquals(TEST_PACKAGE_VERSION,
+ DeviceConfigUtils.getNetworkStackModuleVersion(mContext));
+
+ assertEquals(TEST_PACKAGE_VERSION,
+ DeviceConfigUtils.getNetworkStackModuleVersion(mContext));
+ // Package info is only queried once
+ verify(mPm, times(1)).getPackageInfo(anyString(), anyInt());
+ verify(mContext, never()).getPackageName();
+ }
+
+ @Test
+ public void testGetNetworkStackModuleVersionOnNonMainline() {
+ assertEquals(DeviceConfigUtils.DEFAULT_PACKAGE_VERSION,
+ DeviceConfigUtils.getNetworkStackModuleVersion(mContext));
+ }
+
+ @Test
+ public void testGetNetworkStackModuleVersion() throws Exception {
+ final PackageInfo networkStackPackageInfo = new PackageInfo();
+ final PackageInfo goNetworkStackPackageInfo = new PackageInfo();
+ networkStackPackageInfo.setLongVersionCode(TEST_PACKAGE_VERSION);
+ goNetworkStackPackageInfo.setLongVersionCode(TEST_PACKAGE_VERSION + 1);
+ doReturn(goNetworkStackPackageInfo).when(mPm).getPackageInfo(
+ eq(TEST_NETWORKSTACK_NAME), anyInt());
+ // Verify the returned value is go module version.
+ assertEquals(TEST_PACKAGE_VERSION + 1,
+ DeviceConfigUtils.getNetworkStackModuleVersion(mContext));
+ }
+
+ @Test
+ public void testIsFeatureSupported_networkStackFeature() throws Exception {
+ // Supported for DEFAULT_PACKAGE_VERSION
+ assertTrue(DeviceConfigUtils.isFeatureSupported(
+ mContext, TEST_PACKAGE_VERSION + NETWORK_STACK_MODULE_ID));
+
+ final PackageInfo networkStackPackageInfo = new PackageInfo();
+ networkStackPackageInfo.setLongVersionCode(TEST_PACKAGE_VERSION);
+ doReturn(networkStackPackageInfo).when(mPm).getPackageInfo(
+ eq(TEST_NETWORKSTACK_NAME), anyInt());
+
+ assertTrue(DeviceConfigUtils.isFeatureSupported(
+ mContext, TEST_PACKAGE_VERSION + NETWORK_STACK_MODULE_ID));
+ assertFalse(DeviceConfigUtils.isFeatureSupported(
+ mContext, TEST_PACKAGE_VERSION + NETWORK_STACK_MODULE_ID + 1));
+ }
+
+ @Test
+ public void testIsFeatureSupported_tetheringFeature() throws Exception {
+ assertTrue(DeviceConfigUtils.isFeatureSupported(
+ mContext, TEST_PACKAGE_VERSION + CONNECTIVITY_MODULE_ID));
+ // Return false because feature requires a future version.
+ assertFalse(DeviceConfigUtils.isFeatureSupported(
+ mContext, 889900000L + CONNECTIVITY_MODULE_ID));
+ }
+
+ @Test
+ public void testIsFeatureSupported_illegalModule() throws Exception {
+ assertThrows(IllegalArgumentException.class,
+ () -> DeviceConfigUtils.isFeatureSupported(mContext, TEST_PACKAGE_VERSION));
+ }
+
+ @Test
+ public void testIsTetheringFeatureNotChickenedOut() throws Exception {
+ doReturn("0").when(() -> DeviceConfig.getProperty(
+ eq(NAMESPACE_TETHERING), eq(TEST_EXPERIMENT_FLAG)));
+ assertTrue(DeviceConfigUtils.isTetheringFeatureNotChickenedOut(TEST_EXPERIMENT_FLAG));
+
+ doReturn(TEST_FLAG_VALUE_STRING).when(
+ () -> DeviceConfig.getProperty(eq(NAMESPACE_TETHERING), eq(TEST_EXPERIMENT_FLAG)));
+ assertFalse(DeviceConfigUtils.isTetheringFeatureNotChickenedOut(TEST_EXPERIMENT_FLAG));
+ }
+
+ @Test
+ public void testIsNetworkStackFeatureNotChickenedOut() throws Exception {
+ doReturn("0").when(() -> DeviceConfig.getProperty(
+ eq(NAMESPACE_CONNECTIVITY), eq(TEST_EXPERIMENT_FLAG)));
+ assertTrue(DeviceConfigUtils.isNetworkStackFeatureNotChickenedOut(TEST_EXPERIMENT_FLAG));
+
+ doReturn(TEST_FLAG_VALUE_STRING).when(
+ () -> DeviceConfig.getProperty(eq(NAMESPACE_CONNECTIVITY),
+ eq(TEST_EXPERIMENT_FLAG)));
+ assertFalse(DeviceConfigUtils.isNetworkStackFeatureNotChickenedOut(TEST_EXPERIMENT_FLAG));
+ }
}
diff --git a/common/tests/unit/src/com/android/net/module/util/DomainUtilsTest.java b/common/tests/unit/src/com/android/net/module/util/DomainUtilsTest.java
new file mode 100644
index 00000000..5eaf2add
--- /dev/null
+++ b/common/tests/unit/src/com/android/net/module/util/DomainUtilsTest.java
@@ -0,0 +1,210 @@
+/*
+ * 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.net.module.util;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import libcore.util.HexEncoding;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.ArrayList;
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class DomainUtilsTest {
+ @Test
+ public void testEncodeInvalidDomain() {
+ byte[] buffer = DomainUtils.encode(".google.com");
+ assertNull(buffer);
+
+ buffer = DomainUtils.encode("google.com.");
+ assertNull(buffer);
+
+ buffer = DomainUtils.encode("-google.com");
+ assertNull(buffer);
+
+ buffer = DomainUtils.encode("google.com-");
+ assertNull(buffer);
+
+ buffer = DomainUtils.encode("google..com");
+ assertNull(buffer);
+
+ buffer = DomainUtils.encode("google!.com");
+ assertNull(buffer);
+
+ buffer = DomainUtils.encode("google.o");
+ assertNull(buffer);
+
+ buffer = DomainUtils.encode("google,com");
+ assertNull(buffer);
+ }
+
+ @Test
+ public void testEncodeValidDomainNamesWithoutCompression() {
+ // Single domain: "google.com"
+ String suffix = "06676F6F676C6503636F6D00";
+ byte[] buffer = DomainUtils.encode("google.com");
+ //assertNotNull(buffer);
+ assertEquals(suffix, HexEncoding.encodeToString(buffer));
+
+ // Single domain: "google-guest.com"
+ suffix = "0C676F6F676C652D677565737403636F6D00";
+ buffer = DomainUtils.encode("google-guest.com");
+ assertNotNull(buffer);
+ assertEquals(suffix, HexEncoding.encodeToString(buffer));
+
+ // domain search list: "example.corp.google.com", "corp.google.com", "google.com"
+ suffix = "076578616D706C6504636F727006676F6F676C6503636F6D00" // example.corp.google.com
+ + "04636F727006676F6F676C6503636F6D00" // corp.google.com
+ + "06676F6F676C6503636F6D00"; // google.com
+ buffer = DomainUtils.encode(new String[] {
+ "example.corp.google.com", "corp.google.com", "google.com"},
+ false /* compression */);
+ assertNotNull(buffer);
+ assertEquals(suffix, HexEncoding.encodeToString(buffer));
+
+
+ // domain search list: "example.corp.google.com", "corp..google.com"(invalid domain),
+ // "google.com"
+ suffix = "076578616D706C6504636F727006676F6F676C6503636F6D00" // example.corp.google.com
+ + "06676F6F676C6503636F6D00"; // google.com
+ buffer = DomainUtils.encode(new String[] {
+ "example.corp.google.com", "corp..google.com", "google.com"},
+ false /* compression */);
+ assertNotNull(buffer);
+ assertEquals(suffix, HexEncoding.encodeToString(buffer));
+
+ // Invalid domain search list: "corp..google.com", "..google.com"
+ buffer = DomainUtils.encode(new String[] {"corp..google.com", "..google.com"},
+ false /* compression */);
+ assertEquals(0, buffer.length);
+ }
+
+ @Test
+ public void testEncodeValidDomainNamesWithCompression() {
+ // domain search list: "example.corp.google.com", "corp.google.com", "google.com"
+ String suffix =
+ "076578616D706C6504636F727006676F6F676C6503636F6D00" // example.corp.google.com
+ + "C008" // corp.google.com
+ + "C00D"; // google.com
+ byte[] buffer = DomainUtils.encode(new String[] {
+ "example.corp.google.com", "corp.google.com", "google.com"}, true);
+ assertNotNull(buffer);
+ assertEquals(suffix, HexEncoding.encodeToString(buffer));
+
+ // domain search list: "example.corp.google.com", "a.example.corp.google.com", "google.com"
+ suffix = "076578616D706C6504636F727006676F6F676C6503636F6D00" // example.corp.google.com
+ + "0161C000" // a.example.corp.google.com
+ + "C00D"; // google.com
+ buffer = DomainUtils.encode(new String[] {
+ "example.corp.google.com", "a.example.corp.google.com", "google.com"}, true);
+ assertNotNull(buffer);
+ assertEquals(suffix, HexEncoding.encodeToString(buffer));
+
+ // domain search list: "example.corp.google.com", "google.com", "gle.com"
+ suffix = "076578616D706C6504636F727006676F6F676C6503636F6D00" // example.corp.google.com
+ + "C00D" // google.com
+ + "03676C65C014"; // gle.com
+ buffer = DomainUtils.encode(new String[] {
+ "example.corp.google.com", "google.com", "gle.com"}, true);
+ assertNotNull(buffer);
+ assertEquals(suffix, HexEncoding.encodeToString(buffer));
+
+ // domain search list: "example.corp.google.com", "google.com", "google"
+ suffix = "076578616D706C6504636F727006676F6F676C6503636F6D00" // example.corp.google.com
+ + "C00D"; // google.com
+ buffer = DomainUtils.encode(new String[] {
+ "example.corp.google.com", "google.com", "google"}, true);
+ assertNotNull(buffer);
+ assertEquals(suffix, HexEncoding.encodeToString(buffer));
+
+ // domain search list: "example.corp.google.com", "..google.com"(invalid domain), "google"
+ suffix = "076578616D706C6504636F727006676F6F676C6503636F6D00"; // example.corp.google.com
+ buffer = DomainUtils.encode(new String[] {
+ "example.corp.google.com", "..google.com", "google"}, true);
+ assertNotNull(buffer);
+ assertEquals(suffix, HexEncoding.encodeToString(buffer));
+
+ // domain search list: "example.corp.google.com", "suffix.example.edu.cn", "edu.cn"
+ suffix = "076578616D706C6504636F727006676F6F676C6503636F6D00" // example.corp.google.com
+ + "06737566666978076578616D706C650365647502636E00" // suffix.example.edu.cn
+ + "C028"; // edu.cn
+ buffer = DomainUtils.encode(new String[] {
+ "example.corp.google.com", "suffix.example.edu.cn", "edu.cn"}, true);
+ assertNotNull(buffer);
+ assertEquals(suffix, HexEncoding.encodeToString(buffer));
+
+ // domain search list: "google.com", "example.com", "sub.example.com"
+ suffix = "06676F6F676C6503636F6D00" // google.com
+ + "076578616D706C65C007" // example.com
+ + "03737562C00C"; // sub.example.com
+ buffer = DomainUtils.encode(new String[] {
+ "google.com", "example.com", "sub.example.com"}, true);
+ assertNotNull(buffer);
+ assertEquals(suffix, HexEncoding.encodeToString(buffer));
+ }
+
+ @Test
+ public void testDecodeDomainNames() {
+ ArrayList<String> suffixStringList;
+ String suffixes = "06676F6F676C6503636F6D00" // google.com
+ + "076578616D706C6503636F6D00" // example.com
+ + "06676F6F676C6500"; // google
+ List<String> expected = Arrays.asList("google.com", "example.com");
+ ByteBuffer buffer = ByteBuffer.wrap(HexEncoding.decode(suffixes));
+ suffixStringList = DomainUtils.decode(buffer, false /* compression */);
+ assertEquals(expected, suffixStringList);
+
+ // include suffix with invalid length: 64
+ suffixes = "06676F6F676C6503636F6D00" // google.com
+ + "406578616D706C6503636F6D00" // example.com(length=64)
+ + "06676F6F676C6500"; // google
+ expected = Arrays.asList("google.com");
+ buffer = ByteBuffer.wrap(HexEncoding.decode(suffixes));
+ suffixStringList = DomainUtils.decode(buffer, false /* compression */);
+ assertEquals(expected, suffixStringList);
+
+ // include suffix with invalid length: 0
+ suffixes = "06676F6F676C6503636F6D00" // google.com
+ + "076578616D706C6503636F6D00" // example.com
+ + "00676F6F676C6500"; // google(length=0)
+ expected = Arrays.asList("google.com", "example.com");
+ buffer = ByteBuffer.wrap(HexEncoding.decode(suffixes));
+ suffixStringList = DomainUtils.decode(buffer, false /* compression */);
+ assertEquals(expected, suffixStringList);
+
+ suffixes =
+ "076578616D706C6504636F727006676F6F676C6503636F6D00" // example.corp.google.com
+ + "C008" // corp.google.com
+ + "C00D"; // google.com
+ expected = Arrays.asList("example.corp.google.com", "corp.google.com", "google.com");
+ buffer = ByteBuffer.wrap(HexEncoding.decode(suffixes));
+ suffixStringList = DomainUtils.decode(buffer, true /* compression */);
+ assertEquals(expected, suffixStringList);
+ }
+}
diff --git a/common/tests/unit/src/com/android/net/module/util/StructTest.java b/common/tests/unit/src/com/android/net/module/util/StructTest.java
index 4e462106..b4da0439 100644
--- a/common/tests/unit/src/com/android/net/module/util/StructTest.java
+++ b/common/tests/unit/src/com/android/net/module/util/StructTest.java
@@ -89,20 +89,20 @@ public class StructTest {
return Struct.parse(clazz, buf);
}
- static class HeaderMsgWithConstructor extends Struct {
+ public static class HeaderMsgWithConstructor extends Struct {
static int sType;
static int sLength;
@Field(order = 0, type = Type.U8, padding = 1)
- final short mFamily;
+ public final short mFamily;
@Field(order = 1, type = Type.U16)
- final int mLen;
+ public final int mLen;
@Field(order = 2, type = Type.S32)
- final int mIfindex;
+ public final int mIfindex;
@Field(order = 3, type = Type.U8)
- final short mIcmpType;
+ public final short mIcmpType;
@Field(order = 4, type = Type.U8, padding = 6)
- final short mIcmpCode;
+ public final short mIcmpCode;
HeaderMsgWithConstructor(final short family, final int len, final int ifindex,
final short type, final short code) {
@@ -133,20 +133,20 @@ public class StructTest {
verifyHeaderParsing(msg);
}
- static class HeaderMsgWithoutConstructor extends Struct {
+ public static class HeaderMsgWithoutConstructor extends Struct {
static int sType;
static int sLength;
@Field(order = 0, type = Type.U8, padding = 1)
- short mFamily;
+ public short mFamily;
@Field(order = 1, type = Type.U16)
- int mLen;
+ public int mLen;
@Field(order = 2, type = Type.S32)
- int mIfindex;
+ public int mIfindex;
@Field(order = 3, type = Type.U8)
- short mIcmpType;
+ public short mIcmpType;
@Field(order = 4, type = Type.U8, padding = 6)
- short mIcmpCode;
+ public short mIcmpCode;
}
@Test
@@ -164,7 +164,7 @@ public class StructTest {
msg.writeToBytes(ByteOrder.LITTLE_ENDIAN));
}
- static class HeaderMessage {
+ public static class HeaderMessage {
@Field(order = 0, type = Type.U8, padding = 1)
short mFamily;
@Field(order = 1, type = Type.U16)
@@ -183,7 +183,7 @@ public class StructTest {
assertThrows(IllegalArgumentException.class, () -> Struct.parse(HeaderMessage.class, buf));
}
- static class HeaderMessageMissingAnnotation extends Struct {
+ public static class HeaderMessageMissingAnnotation extends Struct {
@Field(order = 0, type = Type.U8, padding = 1)
short mFamily;
@Field(order = 1, type = Type.U16)
@@ -202,15 +202,15 @@ public class StructTest {
() -> Struct.parse(HeaderMessageMissingAnnotation.class, buf));
}
- static class NetworkOrderMessage extends Struct {
+ public static class NetworkOrderMessage extends Struct {
@Field(order = 0, type = Type.UBE16)
- final int mUBE16;
+ public final int mUBE16;
@Field(order = 1, type = Type.UBE32)
- final long mUBE32;
+ public final long mUBE32;
@Field(order = 2, type = Type.UBE64)
- final BigInteger mUBE64;
+ public final BigInteger mUBE64;
@Field(order = 3, type = Type.UBE63)
- final long mUBE63;
+ public final long mUBE63;
NetworkOrderMessage(final int be16, final long be32, final BigInteger be64,
final long be63) {
@@ -235,19 +235,19 @@ public class StructTest {
msg.writeToBytes(ByteOrder.LITTLE_ENDIAN));
}
- static class UnsignedDataMessage extends Struct {
+ public static class UnsignedDataMessage extends Struct {
@Field(order = 0, type = Type.U8)
- final short mU8;
+ public final short mU8;
@Field(order = 1, type = Type.U16)
- final int mU16;
+ public final int mU16;
@Field(order = 2, type = Type.U32)
- final long mU32;
+ public final long mU32;
@Field(order = 3, type = Type.U64)
- final BigInteger mU64;
+ public final BigInteger mU64;
@Field(order = 4, type = Type.U63)
- final long mU63;
+ public final long mU63;
@Field(order = 5, type = Type.U63)
- final long mLU64; // represent U64 data with U63 type
+ public final long mLU64; // represent U64 data with U63 type
UnsignedDataMessage(final short u8, final int u16, final long u32, final BigInteger u64,
final long u63, final long lu64) {
@@ -276,7 +276,7 @@ public class StructTest {
msg.writeToBytes(ByteOrder.LITTLE_ENDIAN));
}
- static class U64DataMessage extends Struct {
+ public static class U64DataMessage extends Struct {
@Field(order = 0, type = Type.U64) long mU64;
}
@@ -290,10 +290,10 @@ public class StructTest {
private static final String SMALL_VALUE_BIGINTEGER = "3412000000000000" + "0000000000001234"
+ "0000000000000000";
- static class SmallValueBigInteger extends Struct {
- @Field(order = 0, type = Type.U64) final BigInteger mSmallValue;
- @Field(order = 1, type = Type.UBE64) final BigInteger mBSmallValue;
- @Field(order = 2, type = Type.U64) final BigInteger mZero;
+ public static class SmallValueBigInteger extends Struct {
+ @Field(order = 0, type = Type.U64) public final BigInteger mSmallValue;
+ @Field(order = 1, type = Type.UBE64) public final BigInteger mBSmallValue;
+ @Field(order = 2, type = Type.U64) public final BigInteger mZero;
SmallValueBigInteger(final BigInteger smallValue, final BigInteger bSmallValue,
final BigInteger zero) {
@@ -316,15 +316,15 @@ public class StructTest {
msg.writeToBytes(ByteOrder.LITTLE_ENDIAN));
}
- static class SignedDataMessage extends Struct {
+ public static class SignedDataMessage extends Struct {
@Field(order = 0, type = Type.S8)
- final byte mS8;
+ public final byte mS8;
@Field(order = 1, type = Type.S16)
- final short mS16;
+ public final short mS16;
@Field(order = 2, type = Type.S32)
- final int mS32;
+ public final int mS32;
@Field(order = 3, type = Type.S64)
- final long mS64;
+ public final long mS64;
SignedDataMessage(final byte s8, final short s16, final int s32, final long s64) {
mS8 = s8;
@@ -362,7 +362,7 @@ public class StructTest {
msg.writeToBytes(ByteOrder.LITTLE_ENDIAN));
}
- static class HeaderMessageWithDuplicateOrder extends Struct {
+ public static class HeaderMessageWithDuplicateOrder extends Struct {
@Field(order = 0, type = Type.U8, padding = 1)
short mFamily;
@Field(order = 1, type = Type.U16)
@@ -382,7 +382,7 @@ public class StructTest {
() -> Struct.parse(HeaderMessageWithDuplicateOrder.class, buf));
}
- static class HeaderMessageWithNegativeOrder extends Struct {
+ public static class HeaderMessageWithNegativeOrder extends Struct {
@Field(order = 0, type = Type.U8, padding = 1)
short mFamily;
@Field(order = 1, type = Type.U16)
@@ -402,7 +402,7 @@ public class StructTest {
() -> Struct.parse(HeaderMessageWithNegativeOrder.class, buf));
}
- static class HeaderMessageOutOfIndexBounds extends Struct {
+ public static class HeaderMessageOutOfIndexBounds extends Struct {
@Field(order = 0, type = Type.U8, padding = 1)
short mFamily;
@Field(order = 1, type = Type.U16)
@@ -422,7 +422,7 @@ public class StructTest {
() -> Struct.parse(HeaderMessageOutOfIndexBounds.class, buf));
}
- static class HeaderMessageMismatchedPrimitiveType extends Struct {
+ public static class HeaderMessageMismatchedPrimitiveType extends Struct {
@Field(order = 0, type = Type.U8, padding = 1)
short mFamily;
@Field(order = 1, type = Type.U16)
@@ -442,11 +442,11 @@ public class StructTest {
() -> Struct.parse(HeaderMessageMismatchedPrimitiveType.class, buf));
}
- static class PrefixMessage extends Struct {
+ public static class PrefixMessage extends Struct {
@Field(order = 0, type = Type.UBE16)
- final int mLifetime;
+ public final int mLifetime;
@Field(order = 1, type = Type.ByteArray, arraysize = 12)
- final byte[] mPrefix;
+ public final byte[] mPrefix;
PrefixMessage(final int lifetime, final byte[] prefix) {
mLifetime = lifetime;
@@ -469,7 +469,7 @@ public class StructTest {
msg.writeToBytes(ByteOrder.LITTLE_ENDIAN));
}
- static class PrefixMessageWithZeroLengthArray extends Struct {
+ public static class PrefixMessageWithZeroLengthArray extends Struct {
@Field(order = 0, type = Type.UBE16)
final int mLifetime;
@Field(order = 1, type = Type.ByteArray, arraysize = 0)
@@ -495,7 +495,7 @@ public class StructTest {
verifyPrefixByteArrayParsing(msg);
}
- static class HeaderMessageWithMutableField extends Struct {
+ public static class HeaderMessageWithMutableField extends Struct {
@Field(order = 0, type = Type.U8, padding = 1)
final short mFamily;
@Field(order = 1, type = Type.U16)
@@ -521,20 +521,20 @@ public class StructTest {
() -> Struct.parse(HeaderMessageWithMutableField.class, buf));
}
- static class HeaderMsgWithStaticConstant extends Struct {
+ public static class HeaderMsgWithStaticConstant extends Struct {
private static final String TAG = "HeaderMessage";
private static final int FIELD_COUNT = 5;
@Field(order = 0, type = Type.U8, padding = 1)
- final short mFamily;
+ public final short mFamily;
@Field(order = 1, type = Type.U16)
- final int mLen;
+ public final int mLen;
@Field(order = 2, type = Type.S32)
- final int mIfindex;
+ public final int mIfindex;
@Field(order = 3, type = Type.U8)
- final short mIcmpType;
+ public final short mIcmpType;
@Field(order = 4, type = Type.U8, padding = 6)
- final short mIcmpCode;
+ public final short mIcmpCode;
HeaderMsgWithStaticConstant(final short family, final int len, final int ifindex,
final short type, final short code) {
@@ -561,7 +561,7 @@ public class StructTest {
msg.writeToBytes(ByteOrder.LITTLE_ENDIAN));
}
- static class MismatchedConstructor extends Struct {
+ public static class MismatchedConstructor extends Struct {
@Field(order = 0, type = Type.U16) final int mInt1;
@Field(order = 1, type = Type.U16) final int mInt2;
MismatchedConstructor(String int1, String int2) {
@@ -577,9 +577,9 @@ public class StructTest {
() -> Struct.parse(MismatchedConstructor.class, buf));
}
- static class ClassWithTwoConstructors extends Struct {
- @Field(order = 0, type = Type.U16) final int mInt1;
- @Field(order = 1, type = Type.U16) final int mInt2;
+ public static class ClassWithTwoConstructors extends Struct {
+ @Field(order = 0, type = Type.U16) public final int mInt1;
+ @Field(order = 1, type = Type.U16) public final int mInt2;
ClassWithTwoConstructors(String int1, String int2) {
mInt1 = Integer.valueOf(int1);
mInt2 = Integer.valueOf(int2);
@@ -645,14 +645,14 @@ public class StructTest {
}
}
- static class BigEndianDataMessage extends Struct {
- @Field(order = 0, type = Type.S32) int mInt1;
- @Field(order = 1, type = Type.S32) int mInt2;
- @Field(order = 2, type = Type.UBE16) int mInt3;
- @Field(order = 3, type = Type.U16) int mInt4;
- @Field(order = 4, type = Type.U64) BigInteger mBigInteger1;
- @Field(order = 5, type = Type.UBE64) BigInteger mBigInteger2;
- @Field(order = 6, type = Type.S64) long mLong;
+ public static class BigEndianDataMessage extends Struct {
+ @Field(order = 0, type = Type.S32) public int mInt1;
+ @Field(order = 1, type = Type.S32) public int mInt2;
+ @Field(order = 2, type = Type.UBE16) public int mInt3;
+ @Field(order = 3, type = Type.U16) public int mInt4;
+ @Field(order = 4, type = Type.U64) public BigInteger mBigInteger1;
+ @Field(order = 5, type = Type.UBE64) public BigInteger mBigInteger2;
+ @Field(order = 6, type = Type.S64) public long mLong;
}
private static final String BIG_ENDIAN_DATA = "00000001" + "fffffffe" + "fffe" + "fffe"
@@ -680,9 +680,9 @@ public class StructTest {
return ByteBuffer.wrap(HexDump.hexStringToByteArray(hexString));
}
- static class MacAddressMessage extends Struct {
- @Field(order = 0, type = Type.EUI48) final MacAddress mMac1;
- @Field(order = 1, type = Type.EUI48) final MacAddress mMac2;
+ public static class MacAddressMessage extends Struct {
+ public @Field(order = 0, type = Type.EUI48) final MacAddress mMac1;
+ public @Field(order = 1, type = Type.EUI48) final MacAddress mMac2;
MacAddressMessage(final MacAddress mac1, final MacAddress mac2) {
this.mMac1 = mac1;
@@ -703,7 +703,7 @@ public class StructTest {
msg.writeToBytes(ByteOrder.BIG_ENDIAN));
}
- static class BadMacAddressType extends Struct {
+ public static class BadMacAddressType extends Struct {
@Field(order = 0, type = Type.EUI48) byte[] mMac;
}
@@ -742,7 +742,7 @@ public class StructTest {
assertArrayEquals(toByteBuffer(hexString).array(), msg.writeToBytes());
}
- static class IpAddressMessage extends Struct {
+ public static class IpAddressMessage extends Struct {
@Field(order = 0, type = Type.Ipv4Address) public final Inet4Address ipv4Address;
@Field(order = 1, type = Type.Ipv6Address) public final Inet6Address ipv6Address;
@@ -765,7 +765,7 @@ public class StructTest {
msg.writeToBytes(ByteOrder.BIG_ENDIAN));
}
- static class WrongIpAddressType extends Struct {
+ public static class WrongIpAddressType extends Struct {
@Field(order = 0, type = Type.Ipv4Address) public byte[] ipv4Address;
@Field(order = 1, type = Type.Ipv6Address) public byte[] ipv6Address;
}
@@ -777,7 +777,7 @@ public class StructTest {
toByteBuffer("c0a86401" + "20010db8000300040005000600070008")));
}
- static class FullTypeMessage extends Struct {
+ public static class FullTypeMessage extends Struct {
@Field(order = 0, type = Type.U8) public final short u8;
@Field(order = 1, type = Type.U16) public final int u16;
@Field(order = 2, type = Type.U32) public final long u32;
@@ -886,7 +886,7 @@ public class StructTest {
assertTrue(msg.equals(msg1));
}
- static class FullTypeMessageWithDupType extends Struct {
+ public static class FullTypeMessageWithDupType extends Struct {
@Field(order = 0, type = Type.U8) public final short u8;
@Field(order = 1, type = Type.U16) public final int u16;
@Field(order = 2, type = Type.U32) public final long u32;
@@ -1029,7 +1029,7 @@ public class StructTest {
assertEquals(msg.hashCode(), msg1.hashCode());
}
- static class InvalidByteArray extends Struct {
+ public static class InvalidByteArray extends Struct {
@Field(order = 0, type = Type.ByteArray, arraysize = 12) public byte[] bytes;
}
diff --git a/common/tests/unit/src/com/android/net/module/util/netlink/NetlinkUtilsTest.java b/common/tests/unit/src/com/android/net/module/util/netlink/NetlinkUtilsTest.java
index dac5911a..3a72dd1b 100644
--- a/common/tests/unit/src/com/android/net/module/util/netlink/NetlinkUtilsTest.java
+++ b/common/tests/unit/src/com/android/net/module/util/netlink/NetlinkUtilsTest.java
@@ -26,8 +26,6 @@ import static com.android.net.module.util.netlink.NetlinkUtils.DEFAULT_RECV_BUFS
import static com.android.net.module.util.netlink.StructNlMsgHdr.NLM_F_DUMP;
import static com.android.net.module.util.netlink.StructNlMsgHdr.NLM_F_REQUEST;
-import static com.google.common.truth.Truth.assertThat;
-
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
@@ -193,7 +191,9 @@ public class NetlinkUtilsTest {
// and IPv6 loopback address: 127.0.0.1 and ::1.
final StructIfaddrMsg ifaMsg = ((RtNetlinkAddressMessage) msg).getIfaddrHeader();
final InetAddress ipAddress = ((RtNetlinkAddressMessage) msg).getIpAddress();
- assertThat((int) ifaMsg.family).isAnyOf(AF_INET, AF_INET6);
+ assertTrue(
+ "Non-IP address family: " + ifaMsg.family,
+ ifaMsg.family == AF_INET || ifaMsg.family == AF_INET6);
assertTrue(ipAddress.isLoopbackAddress());
}
diff --git a/common/testutils/Android.bp b/common/testutils/Android.bp
index be4ccfed..d88cb0b4 100644
--- a/common/testutils/Android.bp
+++ b/common/testutils/Android.bp
@@ -57,6 +57,7 @@ java_library {
visibility: [
"//frameworks/libs/net/common/tests:__subpackages__",
"//frameworks/libs/net/client-libs/tests:__subpackages__",
+ "//packages/modules/Connectivity/tests/cts/hostside",
],
// There are downstream branches using an old version of Kotlin
// that used to reserve the right to make breaking changes to the
diff --git a/common/testutils/app/connectivitychecker/src/com/android/testutils/connectivitypreparer/ConnectivityCheckTest.kt b/common/testutils/app/connectivitychecker/src/com/android/testutils/connectivitypreparer/ConnectivityCheckTest.kt
index 6bcb8fc1..f1f09754 100644
--- a/common/testutils/app/connectivitychecker/src/com/android/testutils/connectivitypreparer/ConnectivityCheckTest.kt
+++ b/common/testutils/app/connectivitychecker/src/com/android/testutils/connectivitypreparer/ConnectivityCheckTest.kt
@@ -72,7 +72,7 @@ class ConnectivityCheckTest {
val cb = TestableNetworkCallback()
val cm = context.getSystemService(ConnectivityManager::class.java)
?: fail("Could not get ConnectivityManager")
- cm.registerNetworkCallback(
+ cm.requestNetwork(
NetworkRequest.Builder()
.addTransportType(TRANSPORT_CELLULAR)
.addCapability(NET_CAPABILITY_INTERNET).build(), cb)
diff --git a/common/testutils/devicetests/com/android/testutils/DevSdkIgnoreRule.kt b/common/testutils/devicetests/com/android/testutils/DevSdkIgnoreRule.kt
index 21e84daf..35f22b9e 100644
--- a/common/testutils/devicetests/com/android/testutils/DevSdkIgnoreRule.kt
+++ b/common/testutils/devicetests/com/android/testutils/DevSdkIgnoreRule.kt
@@ -19,11 +19,11 @@ package com.android.testutils
import android.os.Build
import androidx.test.InstrumentationRegistry
import com.android.modules.utils.build.UnboundedSdkLevel
+import java.util.regex.Pattern
import org.junit.Assume.assumeTrue
import org.junit.rules.TestRule
import org.junit.runner.Description
import org.junit.runners.model.Statement
-import java.util.regex.Pattern
@Deprecated("Use Build.VERSION_CODES", ReplaceWith("Build.VERSION_CODES.S_V2"))
const val SC_V2 = Build.VERSION_CODES.S_V2
@@ -32,8 +32,22 @@ private val MAX_TARGET_SDK_ANNOTATION_RE = Pattern.compile("MaxTargetSdk([0-9]+)
private val targetSdk = InstrumentationRegistry.getContext().applicationInfo.targetSdkVersion
private fun isDevSdkInRange(minExclusive: String?, maxInclusive: String?): Boolean {
- return (minExclusive == null || !UnboundedSdkLevel.isAtMost(minExclusive)) &&
- (maxInclusive == null || UnboundedSdkLevel.isAtMost(maxInclusive))
+ return (minExclusive == null || !isAtMost(minExclusive)) &&
+ (maxInclusive == null || isAtMost(maxInclusive))
+}
+
+private fun isAtMost(sdkVersionOrCodename: String): Boolean {
+ // UnboundedSdkLevel does not support builds < Q, and may stop supporting Q as well since it
+ // is intended for mainline modules that are now R+.
+ if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q) {
+ // Assume that any codename passed as argument from current code is a more recent build than
+ // Q: this util did not exist before Q, and codenames are only used before the corresponding
+ // build is finalized. This util could list 28 older codenames to check against (as per
+ // ro.build.version.known_codenames in more recent builds), but this does not seem valuable.
+ val intVersion = sdkVersionOrCodename.toIntOrNull() ?: return true
+ return Build.VERSION.SDK_INT <= intVersion
+ }
+ return UnboundedSdkLevel.isAtMost(sdkVersionOrCodename)
}
/**
diff --git a/common/testutils/devicetests/com/android/testutils/FakeDns.kt b/common/testutils/devicetests/com/android/testutils/FakeDns.kt
index 825d7487..1f82a35d 100644
--- a/common/testutils/devicetests/com/android/testutils/FakeDns.kt
+++ b/common/testutils/devicetests/com/android/testutils/FakeDns.kt
@@ -81,9 +81,9 @@ class FakeDns(val mockResolver: DnsResolver) {
var type = if (posType != -1) it.arguments[posType] as Int else TYPE_UNSPECIFIED
val answer = getAnswer(hostname, type)
- if (!answer?.addresses.isNullOrEmpty()) {
+ if (answer != null && !answer.addresses.isNullOrEmpty()) {
Handler(Looper.getMainLooper()).post({ executor.execute({
- callback.onAnswer(answer?.addresses, 0); }) })
+ callback.onAnswer(answer.addresses, 0); }) })
}
}
diff --git a/common/testutils/devicetests/com/android/testutils/NonNullTestUtils.java b/common/testutils/devicetests/com/android/testutils/NonNullTestUtils.java
new file mode 100644
index 00000000..463c4706
--- /dev/null
+++ b/common/testutils/devicetests/com/android/testutils/NonNullTestUtils.java
@@ -0,0 +1,33 @@
+/*
+ * 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.testutils;
+
+import android.annotation.NonNull;
+
+/**
+ * Utilities to help Kotlin test to verify java @NonNull code
+ */
+public class NonNullTestUtils {
+ /**
+ * This method allows Kotlin to pass nullable to @NonNull java code for testing.
+ * For Foo(@NonNull arg) java method, Kotlin can pass nullable variable by
+ * Foo(NonNullTestUtils.nullUnsafe(nullableVar)).
+ */
+ @NonNull public static <T> T nullUnsafe(T v) {
+ return v;
+ }
+}
diff --git a/common/testutils/devicetests/com/android/testutils/PacketBridge.kt b/common/testutils/devicetests/com/android/testutils/PacketBridge.kt
index da3508de..d50f78a1 100644
--- a/common/testutils/devicetests/com/android/testutils/PacketBridge.kt
+++ b/common/testutils/devicetests/com/android/testutils/PacketBridge.kt
@@ -48,8 +48,8 @@ class PacketBridge(
private val natMap = NatMap()
private val binder = Binder()
- private val cm = context.getSystemService(ConnectivityManager::class.java)
- private val tnm = context.getSystemService(TestNetworkManager::class.java)
+ private val cm = context.getSystemService(ConnectivityManager::class.java)!!
+ private val tnm = context.getSystemService(TestNetworkManager::class.java)!!
// Create test networks.
private val internalIface = tnm.createTunInterface(listOf(internalAddr))
diff --git a/common/testutils/devicetests/com/android/testutils/TestNetworkTracker.kt b/common/testutils/devicetests/com/android/testutils/TestNetworkTracker.kt
index b743b6c9..84fb47bc 100644
--- a/common/testutils/devicetests/com/android/testutils/TestNetworkTracker.kt
+++ b/common/testutils/devicetests/com/android/testutils/TestNetworkTracker.kt
@@ -91,7 +91,7 @@ private fun initTestNetwork(
lp: LinkProperties?,
setupTimeoutMs: Long = 10_000L
): TestNetworkTracker {
- val tnm = context.getSystemService(TestNetworkManager::class.java)
+ val tnm = context.getSystemService(TestNetworkManager::class.java)!!
val iface = if (isAtLeastS()) tnm.createTunInterface(linkAddrs)
else tnm.createTunInterface(linkAddrs.toTypedArray())
val lpWithIface = if (lp == null) null else LinkProperties(lp).apply {
@@ -112,7 +112,7 @@ class TestNetworkTracker internal constructor(
val lp: LinkProperties?,
setupTimeoutMs: Long
) : TestableNetworkCallback.HasNetwork {
- private val cm = context.getSystemService(ConnectivityManager::class.java)
+ private val cm = context.getSystemService(ConnectivityManager::class.java)!!
private val binder = Binder()
private val networkCallback: NetworkCallback
diff --git a/common/testutils/devicetests/com/android/testutils/TestableNetworkCallback.kt b/common/testutils/devicetests/com/android/testutils/TestableNetworkCallback.kt
index 4b6aea2d..df9c61ae 100644
--- a/common/testutils/devicetests/com/android/testutils/TestableNetworkCallback.kt
+++ b/common/testutils/devicetests/com/android/testutils/TestableNetworkCallback.kt
@@ -389,7 +389,8 @@ open class TestableNetworkCallback private constructor(
from: Int = mark,
crossinline predicate: (T) -> Boolean = { true }
): T = history.poll(timeoutMs, from) { it is T && predicate(it) }.also {
- assertNotNull(it, "Callback ${T::class} not received within ${timeoutMs}ms")
+ assertNotNull(it, "Callback ${T::class} not received within ${timeoutMs}ms. " +
+ "Got ${history.backtrace()}")
} as T
@JvmOverloads
@@ -398,7 +399,8 @@ open class TestableNetworkCallback private constructor(
timeoutMs: Long = defaultTimeoutMs,
predicate: (cb: T) -> Boolean = { true }
) = history.poll(timeoutMs) { type.java.isInstance(it) && predicate(it as T) }.also {
- assertNotNull(it, "Callback ${type.java} not received within ${timeoutMs}ms")
+ assertNotNull(it, "Callback ${type.java} not received within ${timeoutMs}ms. " +
+ "Got ${history.backtrace()}")
} as T
fun <T : CallbackEntry> eventuallyExpect(
@@ -407,7 +409,8 @@ open class TestableNetworkCallback private constructor(
from: Int = mark,
predicate: (cb: T) -> Boolean = { true }
) = history.poll(timeoutMs, from) { type.java.isInstance(it) && predicate(it as T) }.also {
- assertNotNull(it, "Callback ${type.java} not received within ${timeoutMs}ms")
+ assertNotNull(it, "Callback ${type.java} not received within ${timeoutMs}ms. " +
+ "Got ${history.backtrace()}")
} as T
// Expects onAvailable and the callbacks that follow it. These are:
diff --git a/common/testutils/hostdevice/com/android/net/module/util/TrackRecord.kt b/common/testutils/hostdevice/com/android/net/module/util/TrackRecord.kt
index efd77d1b..f24e4f18 100644
--- a/common/testutils/hostdevice/com/android/net/module/util/TrackRecord.kt
+++ b/common/testutils/hostdevice/com/android/net/module/util/TrackRecord.kt
@@ -213,6 +213,9 @@ class ArrayTrackRecord<E> : TrackRecord<E> {
private val slock = StampedLock()
private var readHead = 0
+ // A special mark used to track the start of the last poll() operation.
+ private var pollMark = 0
+
/**
* @return the current value of the mark.
*/
@@ -223,6 +226,7 @@ class ArrayTrackRecord<E> : TrackRecord<E> {
val stamp = slock.tryWriteLock()
if (0L == stamp) concurrentAccessDetected()
readHead = v
+ pollMark = v
slock.unlockWrite(stamp)
}
@@ -261,6 +265,7 @@ class ArrayTrackRecord<E> : TrackRecord<E> {
fun poll(timeoutMs: Long, predicate: (E) -> Boolean = { true }): E? {
val stamp = slock.tryWriteLock()
if (0L == stamp) concurrentAccessDetected()
+ pollMark = readHead
try {
lock.withLock {
val index = pollForIndexReadLocked(timeoutMs, readHead, predicate)
@@ -273,6 +278,25 @@ class ArrayTrackRecord<E> : TrackRecord<E> {
}
/**
+ * Returns a list of events that were observed since the last time poll() was called on this
+ * ReadHead.
+ *
+ * @return list of events since poll() was called.
+ */
+ fun backtrace(): List<E> {
+ val stamp = slock.tryReadLock()
+ if (0L == stamp) concurrentAccessDetected()
+
+ try {
+ lock.withLock {
+ return ArrayList(subList(pollMark, mark))
+ }
+ } finally {
+ slock.unlockRead(stamp)
+ }
+ }
+
+ /**
* Returns the first element after the mark or null. This never blocks.
*
* This method is subject to threading restrictions. It can be used concurrently on
diff --git a/common/testutils/hostdevice/com/android/testutils/DnsResolverModuleTest.kt b/common/testutils/hostdevice/com/android/testutils/DnsResolverModuleTest.kt
new file mode 100644
index 00000000..9e97d51e
--- /dev/null
+++ b/common/testutils/hostdevice/com/android/testutils/DnsResolverModuleTest.kt
@@ -0,0 +1,22 @@
+/*
+ * 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.testutils
+
+/**
+ * Indicates that the test covers functionality that was rolled out in a resolv module update.
+ */
+annotation class DnsResolverModuleTest
diff --git a/common/testutils/hostdevice/com/android/testutils/SkipMainlinePresubmit.kt b/common/testutils/hostdevice/com/android/testutils/SkipMainlinePresubmit.kt
new file mode 100644
index 00000000..59523651
--- /dev/null
+++ b/common/testutils/hostdevice/com/android/testutils/SkipMainlinePresubmit.kt
@@ -0,0 +1,25 @@
+/*
+ * 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.testutils
+
+/**
+ * Skip the test in presubmit runs for the reason specified in [reason].
+ *
+ * This annotation is typically used to document limitations that prevent a test from being
+ * executed in presubmit on older builds.
+ */
+annotation class SkipMainlinePresubmit(val reason: String)