Merge "Let IpServer use RoutingCoordinator in R" into main
diff --git a/Tethering/common/TetheringLib/src/android/net/TetheringManager.java b/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
index 8b3102a..ae0161d 100644
--- a/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
+++ b/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
@@ -1379,6 +1379,9 @@
@RequiresPermission(Manifest.permission.ACCESS_NETWORK_STATE)
public void registerTetheringEventCallback(@NonNull Executor executor,
@NonNull TetheringEventCallback callback) {
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(callback);
+
final String callerPkg = mContext.getOpPackageName();
Log.i(TAG, "registerTetheringEventCallback caller:" + callerPkg);
@@ -1533,6 +1536,8 @@
Manifest.permission.ACCESS_NETWORK_STATE
})
public void unregisterTetheringEventCallback(@NonNull final TetheringEventCallback callback) {
+ Objects.requireNonNull(callback);
+
final String callerPkg = mContext.getOpPackageName();
Log.i(TAG, "unregisterTetheringEventCallback caller:" + callerPkg);
diff --git a/Tethering/src/com/android/networkstack/tethering/Tethering.java b/Tethering/src/com/android/networkstack/tethering/Tethering.java
index 556b413..d62f18f 100644
--- a/Tethering/src/com/android/networkstack/tethering/Tethering.java
+++ b/Tethering/src/com/android/networkstack/tethering/Tethering.java
@@ -294,7 +294,7 @@
mRoutingCoordinator = mDeps.getRoutingCoordinator(mContext, mLog);
mLooper = mDeps.makeTetheringLooper();
mNotificationUpdater = mDeps.makeNotificationUpdater(mContext, mLooper);
- mTetheringMetrics = mDeps.makeTetheringMetrics();
+ mTetheringMetrics = mDeps.makeTetheringMetrics(mContext);
// This is intended to ensrure that if something calls startTethering(bluetooth) just after
// bluetooth is enabled. Before onServiceConnected is called, store the calls into this
@@ -2393,9 +2393,6 @@
hasCallingPermission(NETWORK_SETTINGS)
|| hasCallingPermission(PERMISSION_MAINLINE_NETWORK_STACK)
|| hasCallingPermission(NETWORK_STACK);
- if (callback == null) {
- throw new NullPointerException();
- }
mHandler.post(() -> {
mTetheringEventCallbacks.register(callback, new CallbackCookie(hasListPermission));
final TetheringCallbackStartedParcel parcel = new TetheringCallbackStartedParcel();
@@ -2433,9 +2430,6 @@
/** Unregister tethering event callback */
void unregisterTetheringEventCallback(ITetheringEventCallback callback) {
- if (callback == null) {
- throw new NullPointerException();
- }
mHandler.post(() -> {
mTetheringEventCallbacks.unregister(callback);
});
diff --git a/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java b/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java
index ed2daca..5d9d349 100644
--- a/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java
+++ b/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java
@@ -193,8 +193,8 @@
/**
* Make the TetheringMetrics to be used by tethering.
*/
- public TetheringMetrics makeTetheringMetrics() {
- return new TetheringMetrics();
+ public TetheringMetrics makeTetheringMetrics(Context ctx) {
+ return new TetheringMetrics(ctx);
}
/**
diff --git a/Tethering/src/com/android/networkstack/tethering/TetheringService.java b/Tethering/src/com/android/networkstack/tethering/TetheringService.java
index a147a4a..454cbf1 100644
--- a/Tethering/src/com/android/networkstack/tethering/TetheringService.java
+++ b/Tethering/src/com/android/networkstack/tethering/TetheringService.java
@@ -164,6 +164,8 @@
@Override
public void registerTetheringEventCallback(ITetheringEventCallback callback,
String callerPkg) {
+ // Silently ignore call if the callback is null. This can only happen via reflection.
+ if (callback == null) return;
try {
if (!hasTetherAccessPermission()) {
callback.onCallbackStopped(TETHER_ERROR_NO_ACCESS_TETHERING_PERMISSION);
@@ -176,6 +178,8 @@
@Override
public void unregisterTetheringEventCallback(ITetheringEventCallback callback,
String callerPkg) {
+ // Silently ignore call if the callback is null. This can only happen via reflection.
+ if (callback == null) return;
try {
if (!hasTetherAccessPermission()) {
callback.onCallbackStopped(TETHER_ERROR_NO_ACCESS_TETHERING_PERMISSION);
diff --git a/Tethering/src/com/android/networkstack/tethering/metrics/TetheringMetrics.java b/Tethering/src/com/android/networkstack/tethering/metrics/TetheringMetrics.java
index 814afcd..4a116ec 100644
--- a/Tethering/src/com/android/networkstack/tethering/metrics/TetheringMetrics.java
+++ b/Tethering/src/com/android/networkstack/tethering/metrics/TetheringMetrics.java
@@ -46,6 +46,7 @@
import static android.net.TetheringManager.TETHER_ERROR_UNTETHER_IFACE_ERROR;
import android.annotation.Nullable;
+import android.content.Context;
import android.net.NetworkCapabilities;
import android.stats.connectivity.DownstreamType;
import android.stats.connectivity.ErrorCode;
@@ -81,16 +82,50 @@
private final SparseArray<NetworkTetheringReported.Builder> mBuilderMap = new SparseArray<>();
private final SparseArray<Long> mDownstreamStartTime = new SparseArray<Long>();
private final ArrayList<RecordUpstreamEvent> mUpstreamEventList = new ArrayList<>();
+ private final Context mContext;
+ private final Dependencies mDependencies;
private UpstreamType mCurrentUpstream = null;
private Long mCurrentUpStreamStartTime = 0L;
+ /**
+ * Dependencies of TetheringMetrics, for injection in tests.
+ */
+ @VisibleForTesting
+ public static class Dependencies {
+ /**
+ * @see TetheringStatsLog
+ */
+ public void write(NetworkTetheringReported reported) {
+ TetheringStatsLog.write(
+ TetheringStatsLog.NETWORK_TETHERING_REPORTED,
+ reported.getErrorCode().getNumber(),
+ reported.getDownstreamType().getNumber(),
+ reported.getUpstreamType().getNumber(),
+ reported.getUserType().getNumber(),
+ reported.getUpstreamEvents().toByteArray(),
+ reported.getDurationMillis());
+ }
+
+ /**
+ * @see System#currentTimeMillis()
+ */
+ public long timeNow() {
+ return System.currentTimeMillis();
+ }
+ }
/**
- * Return the current system time in milliseconds.
- * @return the current system time in milliseconds.
+ * Constructor for the TetheringMetrics class.
+ *
+ * @param context The Context object used to access system services.
*/
- public long timeNow() {
- return System.currentTimeMillis();
+ public TetheringMetrics(Context context) {
+ this(context, new Dependencies());
+ }
+
+ TetheringMetrics(Context context, Dependencies dependencies) {
+ mContext = context;
+ mDependencies = dependencies;
}
private static class RecordUpstreamEvent {
@@ -123,7 +158,7 @@
.setUpstreamEvents(UpstreamEvents.newBuilder())
.setDurationMillis(0);
mBuilderMap.put(downstreamType, statsBuilder);
- mDownstreamStartTime.put(downstreamType, timeNow());
+ mDownstreamStartTime.put(downstreamType, mDependencies.timeNow());
}
/**
@@ -149,7 +184,7 @@
UpstreamType upstream = transportTypeToUpstreamTypeEnum(ns);
if (upstream.equals(mCurrentUpstream)) return;
- final long newTime = timeNow();
+ final long newTime = mDependencies.timeNow();
if (mCurrentUpstream != null) {
mUpstreamEventList.add(new RecordUpstreamEvent(mCurrentUpStreamStartTime, newTime,
mCurrentUpstream));
@@ -206,7 +241,7 @@
event.mUpstreamType, 0L /* txBytes */, 0L /* rxBytes */);
}
final long startTime = Math.max(downstreamStartTime, mCurrentUpStreamStartTime);
- final long stopTime = timeNow();
+ final long stopTime = mDependencies.timeNow();
// Handle the last upstream event.
addUpstreamEvent(upstreamEventsBuilder, startTime, stopTime, mCurrentUpstream,
0L /* txBytes */, 0L /* rxBytes */);
@@ -248,15 +283,7 @@
@VisibleForTesting
public void write(@NonNull final NetworkTetheringReported reported) {
final byte[] upstreamEvents = reported.getUpstreamEvents().toByteArray();
-
- TetheringStatsLog.write(
- TetheringStatsLog.NETWORK_TETHERING_REPORTED,
- reported.getErrorCode().getNumber(),
- reported.getDownstreamType().getNumber(),
- reported.getUpstreamType().getNumber(),
- reported.getUserType().getNumber(),
- upstreamEvents,
- reported.getDurationMillis());
+ mDependencies.write(reported);
if (DBG) {
Log.d(
TAG,
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
index 950c381..6ba5d48 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
@@ -528,7 +528,7 @@
}
@Override
- public TetheringMetrics makeTetheringMetrics() {
+ public TetheringMetrics makeTetheringMetrics(Context ctx) {
return mTetheringMetrics;
}
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/metrics/TetheringMetricsTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/metrics/TetheringMetricsTest.java
index e2c924c..0046ce1 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/metrics/TetheringMetricsTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/metrics/TetheringMetricsTest.java
@@ -46,10 +46,10 @@
import static android.net.TetheringManager.TETHER_ERROR_UNSUPPORTED;
import static android.net.TetheringManager.TETHER_ERROR_UNTETHER_IFACE_ERROR;
-import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.verify;
+import android.content.Context;
import android.net.NetworkCapabilities;
import android.stats.connectivity.DownstreamType;
import android.stats.connectivity.ErrorCode;
@@ -60,10 +60,12 @@
import androidx.test.runner.AndroidJUnit4;
import com.android.networkstack.tethering.UpstreamNetworkState;
+import com.android.networkstack.tethering.metrics.TetheringMetrics.Dependencies;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@RunWith(AndroidJUnit4.class)
@@ -76,28 +78,23 @@
private static final long TEST_START_TIME = 1670395936033L;
private static final long SECOND_IN_MILLIS = 1_000L;
+ @Mock private Context mContext;
+ @Mock private Dependencies mDeps;
+
private TetheringMetrics mTetheringMetrics;
private final NetworkTetheringReported.Builder mStatsBuilder =
NetworkTetheringReported.newBuilder();
private long mElapsedRealtime;
- private class MockTetheringMetrics extends TetheringMetrics {
- @Override
- public void write(final NetworkTetheringReported reported) {}
- @Override
- public long timeNow() {
- return currentTimeMillis();
- }
- }
-
private long currentTimeMillis() {
return TEST_START_TIME + mElapsedRealtime;
}
private void incrementCurrentTime(final long duration) {
mElapsedRealtime += duration;
- mTetheringMetrics.timeNow();
+ final long currentTimeMillis = currentTimeMillis();
+ doReturn(currentTimeMillis).when(mDeps).timeNow();
}
private long getElapsedRealtime() {
@@ -106,12 +103,14 @@
private void clearElapsedRealtime() {
mElapsedRealtime = 0;
+ doReturn(TEST_START_TIME).when(mDeps).timeNow();
}
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
- mTetheringMetrics = spy(new MockTetheringMetrics());
+ doReturn(TEST_START_TIME).when(mDeps).timeNow();
+ mTetheringMetrics = new TetheringMetrics(mContext, mDeps);
mElapsedRealtime = 0L;
}
@@ -126,7 +125,7 @@
.setUpstreamEvents(upstreamEvents)
.setDurationMillis(duration)
.build();
- verify(mTetheringMetrics).write(expectedReport);
+ verify(mDeps).write(expectedReport);
}
private void updateErrorAndSendReport(final int downstream, final int error) {
@@ -162,6 +161,7 @@
private void runDownstreamTypesTest(final int type, final DownstreamType expectedResult)
throws Exception {
+ mTetheringMetrics = new TetheringMetrics(mContext, mDeps);
mTetheringMetrics.createBuilder(type, TEST_CALLER_PKG);
final long duration = 2 * SECOND_IN_MILLIS;
incrementCurrentTime(duration);
@@ -172,9 +172,7 @@
verifyReport(expectedResult, ErrorCode.EC_NO_ERROR, UserType.USER_UNKNOWN,
upstreamEvents, getElapsedRealtime());
- reset(mTetheringMetrics);
clearElapsedRealtime();
- mTetheringMetrics.cleanup();
}
@Test
@@ -189,6 +187,7 @@
private void runErrorCodesTest(final int errorCode, final ErrorCode expectedResult)
throws Exception {
+ mTetheringMetrics = new TetheringMetrics(mContext, mDeps);
mTetheringMetrics.createBuilder(TETHERING_WIFI, TEST_CALLER_PKG);
mTetheringMetrics.maybeUpdateUpstreamType(buildUpstreamState(TRANSPORT_WIFI));
final long duration = 2 * SECOND_IN_MILLIS;
@@ -199,9 +198,7 @@
addUpstreamEvent(upstreamEvents, UpstreamType.UT_WIFI, duration, 0L, 0L);
verifyReport(DownstreamType.DS_TETHERING_WIFI, expectedResult, UserType.USER_UNKNOWN,
upstreamEvents, getElapsedRealtime());
- reset(mTetheringMetrics);
clearElapsedRealtime();
- mTetheringMetrics.cleanup();
}
@Test
@@ -231,6 +228,7 @@
private void runUserTypesTest(final String callerPkg, final UserType expectedResult)
throws Exception {
+ mTetheringMetrics = new TetheringMetrics(mContext, mDeps);
mTetheringMetrics.createBuilder(TETHERING_WIFI, callerPkg);
final long duration = 1 * SECOND_IN_MILLIS;
incrementCurrentTime(duration);
@@ -241,9 +239,7 @@
addUpstreamEvent(upstreamEvents, UpstreamType.UT_NO_NETWORK, duration, 0L, 0L);
verifyReport(DownstreamType.DS_TETHERING_WIFI, ErrorCode.EC_NO_ERROR, expectedResult,
upstreamEvents, getElapsedRealtime());
- reset(mTetheringMetrics);
clearElapsedRealtime();
- mTetheringMetrics.cleanup();
}
@Test
@@ -256,6 +252,7 @@
private void runUpstreamTypesTest(final UpstreamNetworkState ns,
final UpstreamType expectedResult) throws Exception {
+ mTetheringMetrics = new TetheringMetrics(mContext, mDeps);
mTetheringMetrics.createBuilder(TETHERING_WIFI, TEST_CALLER_PKG);
mTetheringMetrics.maybeUpdateUpstreamType(ns);
final long duration = 2 * SECOND_IN_MILLIS;
@@ -266,9 +263,7 @@
addUpstreamEvent(upstreamEvents, expectedResult, duration, 0L, 0L);
verifyReport(DownstreamType.DS_TETHERING_WIFI, ErrorCode.EC_NO_ERROR,
UserType.USER_UNKNOWN, upstreamEvents, getElapsedRealtime());
- reset(mTetheringMetrics);
clearElapsedRealtime();
- mTetheringMetrics.cleanup();
}
@Test
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsMultinetworkSocketClient.java b/service-t/src/com/android/server/connectivity/mdns/MdnsMultinetworkSocketClient.java
index fcfb15f..c575d40 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsMultinetworkSocketClient.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsMultinetworkSocketClient.java
@@ -29,7 +29,9 @@
import android.util.ArrayMap;
import android.util.Log;
+import com.android.net.module.util.CollectionUtils;
import com.android.net.module.util.SharedLog;
+import com.android.server.connectivity.mdns.util.MdnsUtils;
import java.io.IOException;
import java.net.DatagramPacket;
@@ -225,6 +227,12 @@
Log.wtf(TAG, "No mDns packets to send");
return;
}
+ // Check all packets with the same address
+ if (!MdnsUtils.checkAllPacketsWithSameAddress(packets)) {
+ Log.wtf(TAG, "Some mDNS packets have a different target address. addresses="
+ + CollectionUtils.map(packets, DatagramPacket::getSocketAddress));
+ return;
+ }
final boolean isIpv6 = ((InetSocketAddress) packets.get(0).getSocketAddress())
.getAddress() instanceof Inet6Address;
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java b/service-t/src/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java
index b3bdbe0..643430a 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java
@@ -303,8 +303,8 @@
serviceCache.unregisterServiceExpiredCallback(cacheKey);
}
- private static MdnsServiceInfo buildMdnsServiceInfoFromResponse(
- @NonNull MdnsResponse response, @NonNull String[] serviceTypeLabels) {
+ private static MdnsServiceInfo buildMdnsServiceInfoFromResponse(@NonNull MdnsResponse response,
+ @NonNull String[] serviceTypeLabels, long elapsedRealtimeMillis) {
String[] hostName = null;
int port = 0;
if (response.hasServiceRecord()) {
@@ -351,7 +351,7 @@
textEntries,
response.getInterfaceIndex(),
response.getNetwork(),
- now.plusMillis(response.getMinRemainingTtl(now.toEpochMilli())));
+ now.plusMillis(response.getMinRemainingTtl(elapsedRealtimeMillis)));
}
private List<MdnsResponse> getExistingServices() {
@@ -380,8 +380,8 @@
if (existingInfo == null) {
for (MdnsResponse existingResponse : serviceCache.getCachedServices(cacheKey)) {
if (!responseMatchesOptions(existingResponse, searchOptions)) continue;
- final MdnsServiceInfo info =
- buildMdnsServiceInfoFromResponse(existingResponse, serviceTypeLabels);
+ final MdnsServiceInfo info = buildMdnsServiceInfoFromResponse(
+ existingResponse, serviceTypeLabels, clock.elapsedRealtime());
listener.onServiceNameDiscovered(info, true /* isServiceFromCache */);
listenerInfo.setServiceDiscovered(info.getServiceInstanceName());
if (existingResponse.isComplete()) {
@@ -561,7 +561,7 @@
if (response.getServiceInstanceName() != null) {
listeners.valueAt(i).unsetServiceDiscovered(response.getServiceInstanceName());
final MdnsServiceInfo serviceInfo = buildMdnsServiceInfoFromResponse(
- response, serviceTypeLabels);
+ response, serviceTypeLabels, clock.elapsedRealtime());
if (response.isComplete()) {
sharedLog.log(message + ". onServiceRemoved: " + serviceInfo);
listener.onServiceRemoved(serviceInfo);
@@ -605,8 +605,8 @@
+ " %b, responseIsComplete: %b",
serviceInstanceName, newInCache, serviceBecomesComplete,
response.isComplete()));
- MdnsServiceInfo serviceInfo =
- buildMdnsServiceInfoFromResponse(response, serviceTypeLabels);
+ final MdnsServiceInfo serviceInfo = buildMdnsServiceInfoFromResponse(
+ response, serviceTypeLabels, clock.elapsedRealtime());
for (int i = 0; i < listeners.size(); i++) {
// If a service stops matching the options (currently can only happen if it loses a
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsSocketClient.java b/service-t/src/com/android/server/connectivity/mdns/MdnsSocketClient.java
index 9cfcba1..17e5b31 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsSocketClient.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsSocketClient.java
@@ -28,7 +28,9 @@
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.net.module.util.CollectionUtils;
import com.android.net.module.util.SharedLog;
+import com.android.server.connectivity.mdns.util.MdnsUtils;
import java.io.IOException;
import java.net.DatagramPacket;
@@ -249,6 +251,12 @@
Log.wtf(TAG, "No mDns packets to send");
return;
}
+ // Check all packets with the same address
+ if (!MdnsUtils.checkAllPacketsWithSameAddress(packets)) {
+ Log.wtf(TAG, "Some mDNS packets have a different target address. addresses="
+ + CollectionUtils.map(packets, DatagramPacket::getSocketAddress));
+ return;
+ }
final boolean isIpv4 = ((InetSocketAddress) packets.get(0).getSocketAddress())
.getAddress() instanceof Inet4Address;
diff --git a/service-t/src/com/android/server/connectivity/mdns/util/MdnsUtils.java b/service-t/src/com/android/server/connectivity/mdns/util/MdnsUtils.java
index 3c11a24..226867f 100644
--- a/service-t/src/com/android/server/connectivity/mdns/util/MdnsUtils.java
+++ b/service-t/src/com/android/server/connectivity/mdns/util/MdnsUtils.java
@@ -34,6 +34,7 @@
import java.io.IOException;
import java.net.DatagramPacket;
+import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
@@ -361,4 +362,23 @@
return SystemClock.elapsedRealtime();
}
}
+
+ /**
+ * Check all DatagramPackets with the same destination address.
+ */
+ public static boolean checkAllPacketsWithSameAddress(List<DatagramPacket> packets) {
+ // No packet for address check
+ if (packets.isEmpty()) {
+ return true;
+ }
+
+ final InetAddress address =
+ ((InetSocketAddress) packets.get(0).getSocketAddress()).getAddress();
+ for (DatagramPacket packet : packets) {
+ if (!address.equals(((InetSocketAddress) packet.getSocketAddress()).getAddress())) {
+ return false;
+ }
+ }
+ return true;
+ }
}
\ No newline at end of file
diff --git a/service/ServiceConnectivityResources/res/values/config_thread.xml b/service/ServiceConnectivityResources/res/values/config_thread.xml
index 02a9ce6..4027038 100644
--- a/service/ServiceConnectivityResources/res/values/config_thread.xml
+++ b/service/ServiceConnectivityResources/res/values/config_thread.xml
@@ -54,9 +54,21 @@
-->
<string translatable="false" name="config_thread_model_name">Thread Border Router</string>
- <!-- Whether the Thread network will be managed by the Google Home ecosystem. When this value
- is set, a TXT entry "vgh=0" or "vgh=1" will be added to the "_mehscop._udp" mDNS service
- respectively (The TXT value is a string).
+ <!-- Specifies vendor-specific mDNS TXT entries which will be included in the "_meshcop._udp"
+ service. The TXT entries list MUST conform to the format requirement in RFC 6763 section 6. For
+ example, the key and value of each TXT entry MUST be separated with "=". If the value length is
+ 0, the trailing "=" may be omitted. Additionally, the TXT keys MUST start with "v" and be at
+ least 2 characters.
+
+ Note, do not include credentials in any of the TXT entries - they will be advertised on Wi-Fi
+ or Ethernet link.
+
+ An example config can be:
+ <string-array name="config_thread_mdns_vendor_specific_txts">
+ <item>vab=123</item>
+ <item>vcd</item>
+ </string-array>
-->
- <bool name="config_thread_managed_by_google_home">false</bool>
+ <string-array name="config_thread_mdns_vendor_specific_txts">
+ </string-array>
</resources>
diff --git a/service/ServiceConnectivityResources/res/values/overlayable.xml b/service/ServiceConnectivityResources/res/values/overlayable.xml
index 158b0c8..fbaae05 100644
--- a/service/ServiceConnectivityResources/res/values/overlayable.xml
+++ b/service/ServiceConnectivityResources/res/values/overlayable.xml
@@ -51,6 +51,7 @@
<item type="string" name="config_thread_vendor_name" />
<item type="string" name="config_thread_vendor_oui" />
<item type="string" name="config_thread_model_name" />
+ <item type="array" name="config_thread_mdns_vendor_specific_txts" />
</policy>
</overlayable>
</resources>
diff --git a/service/src/com/android/server/connectivity/ConnectivityNativeService.java b/service/src/com/android/server/connectivity/ConnectivityNativeService.java
index cf6127f..917ad4d 100644
--- a/service/src/com/android/server/connectivity/ConnectivityNativeService.java
+++ b/service/src/com/android/server/connectivity/ConnectivityNativeService.java
@@ -23,6 +23,7 @@
import android.os.Binder;
import android.os.Process;
import android.os.ServiceSpecificException;
+import android.os.UserHandle;
import android.system.ErrnoException;
import android.util.Log;
@@ -67,8 +68,8 @@
}
private void enforceBlockPortPermission() {
- final int uid = Binder.getCallingUid();
- if (uid == Process.ROOT_UID || uid == Process.PHONE_UID) return;
+ final int appId = UserHandle.getAppId(Binder.getCallingUid());
+ if (appId == Process.ROOT_UID || appId == Process.PHONE_UID) return;
PermissionUtils.enforceNetworkStackPermission(mContext);
}
diff --git a/staticlibs/device/com/android/net/module/util/SingleWriterBpfMap.java b/staticlibs/device/com/android/net/module/util/SingleWriterBpfMap.java
index cd6bfec..a638cc4 100644
--- a/staticlibs/device/com/android/net/module/util/SingleWriterBpfMap.java
+++ b/staticlibs/device/com/android/net/module/util/SingleWriterBpfMap.java
@@ -19,6 +19,7 @@
import android.system.ErrnoException;
import android.util.Pair;
+import androidx.annotation.GuardedBy;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
@@ -60,6 +61,7 @@
public class SingleWriterBpfMap<K extends Struct, V extends Struct> extends BpfMap<K, V> {
// HashMap instead of ArrayMap because it performs better on larger maps, and many maps used in
// our code can contain hundreds of items.
+ @GuardedBy("this")
private final HashMap<K, V> mCache = new HashMap<>();
// This should only ever be called (hence private) once for a given 'path'.
@@ -72,10 +74,12 @@
super(path, BPF_F_RDWR_EXCLUSIVE, key, value);
// Populate cache with the current map contents.
- K currentKey = super.getFirstKey();
- while (currentKey != null) {
- mCache.put(currentKey, super.getValue(currentKey));
- currentKey = super.getNextKey(currentKey);
+ synchronized (this) {
+ K currentKey = super.getFirstKey();
+ while (currentKey != null) {
+ mCache.put(currentKey, super.getValue(currentKey));
+ currentKey = super.getNextKey(currentKey);
+ }
}
}
diff --git a/staticlibs/netd/libnetdutils/include/netdutils/Log.h b/staticlibs/netd/libnetdutils/include/netdutils/Log.h
index d266cbc..2de5ed7 100644
--- a/staticlibs/netd/libnetdutils/include/netdutils/Log.h
+++ b/staticlibs/netd/libnetdutils/include/netdutils/Log.h
@@ -203,7 +203,7 @@
void record(Level lvl, const std::string& entry);
mutable std::shared_mutex mLock;
- std::deque<const std::string> mEntries; // GUARDED_BY(mLock), when supported
+ std::deque<std::string> mEntries; // GUARDED_BY(mLock), when supported
};
} // namespace netdutils
diff --git a/staticlibs/tests/unit/host/python/apf_utils_test.py b/staticlibs/tests/unit/host/python/apf_utils_test.py
index 8b390e3..caaf959 100644
--- a/staticlibs/tests/unit/host/python/apf_utils_test.py
+++ b/staticlibs/tests/unit/host/python/apf_utils_test.py
@@ -13,13 +13,16 @@
# limitations under the License.
from unittest.mock import MagicMock, patch
+from absl.testing import parameterized
from mobly import asserts
from mobly import base_test
from mobly import config_parser
from mobly.controllers.android_device_lib.adb import AdbError
from net_tests_utils.host.python.apf_utils import (
+ ApfCapabilities,
PatternNotFoundException,
UnsupportedOperationException,
+ get_apf_capabilities,
get_apf_counter,
get_apf_counters_from_dumpsys,
get_hardware_address,
@@ -29,7 +32,7 @@
from net_tests_utils.host.python.assert_utils import UnexpectedBehaviorError
-class TestApfUtils(base_test.BaseTestClass):
+class TestApfUtils(base_test.BaseTestClass, parameterized.TestCase):
def __init__(self, configs: config_parser.TestRunConfig):
super().__init__(configs)
@@ -150,3 +153,23 @@
)
with asserts.assert_raises(UnsupportedOperationException):
send_raw_packet_downstream(self.mock_ad, "eth0", "AABBCCDDEEFF")
+
+ @parameterized.parameters(
+ ("2,2048,1", ApfCapabilities(2, 2048, 1)), # Valid input
+ ("3,1024,0", ApfCapabilities(3, 1024, 0)), # Valid input
+ ("invalid,output", ApfCapabilities(0, 0, 0)), # Invalid input
+ ("", ApfCapabilities(0, 0, 0)), # Empty input
+ )
+ @patch("net_tests_utils.host.python.adb_utils.adb_shell")
+ def test_get_apf_capabilities(
+ self, mock_output, expected_result, mock_adb_shell
+ ):
+ """Tests the get_apf_capabilities function with various inputs and expected results."""
+ # Configure the mock adb_shell to return the specified output
+ mock_adb_shell.return_value = mock_output
+
+ # Call the function under test
+ result = get_apf_capabilities(self.mock_ad, "wlan0")
+
+ # Assert that the result matches the expected result
+ asserts.assert_equal(result, expected_result)
diff --git a/staticlibs/testutils/host/python/apf_utils.py b/staticlibs/testutils/host/python/apf_utils.py
index f71464c..415799c 100644
--- a/staticlibs/testutils/host/python/apf_utils.py
+++ b/staticlibs/testutils/host/python/apf_utils.py
@@ -12,7 +12,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+from dataclasses import dataclass
import re
+from mobly import asserts
from mobly.controllers import android_device
from mobly.controllers.android_device_lib.adb import AdbError
from net_tests_utils.host.python import adb_utils, assert_utils
@@ -190,3 +192,65 @@
raise assert_utils.UnexpectedBehaviorError(
f"Got unexpected output: {output} for command: {cmd}."
)
+
+
+@dataclass
+class ApfCapabilities:
+ """APF program support capabilities.
+
+ See android.net.apf.ApfCapabilities.
+
+ Attributes:
+ apf_version_supported (int): Version of APF instruction set supported for
+ packet filtering. 0 indicates no support for packet filtering using APF
+ programs.
+ apf_ram_size (int): Size of APF ram.
+ apf_packet_format (int): Format of packets passed to APF filter. Should be
+ one of ARPHRD_*
+ """
+
+ apf_version_supported: int
+ apf_ram_size: int
+ apf_packet_format: int
+
+ def __init__(
+ self,
+ apf_version_supported: int,
+ apf_ram_size: int,
+ apf_packet_format: int,
+ ):
+ self.apf_version_supported = apf_version_supported
+ self.apf_ram_size = apf_ram_size
+ self.apf_packet_format = apf_packet_format
+
+ def __str__(self):
+ """Returns a user-friendly string representation of the APF capabilities."""
+ return (
+ f"APF Version: {self.apf_version_supported}\n"
+ f"Ram Size: {self.apf_ram_size} bytes\n"
+ f"Packet Format: {self.apf_packet_format}"
+ )
+
+
+def get_apf_capabilities(
+ ad: android_device.AndroidDevice, iface_name: str
+) -> ApfCapabilities:
+ output = adb_utils.adb_shell(
+ ad, f"cmd network_stack apf {iface_name} capabilities"
+ )
+ try:
+ values = [int(value_str) for value_str in output.split(",")]
+ except ValueError:
+ return ApfCapabilities(0, 0, 0) # Conversion to integer failed
+ return ApfCapabilities(values[0], values[1], values[2])
+
+
+def assume_apf_version_support_at_least(
+ ad: android_device.AndroidDevice, iface_name: str, expected_version: int
+) -> None:
+ caps = get_apf_capabilities(ad, iface_name)
+ asserts.skip_if(
+ caps.apf_version_supported < expected_version,
+ f"Supported apf version {caps.apf_version_supported} < expected version"
+ f" {expected_version}",
+ )
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsMultinetworkSocketClientTest.java b/tests/unit/java/com/android/server/connectivity/mdns/MdnsMultinetworkSocketClientTest.java
index fb3d183..4c71991 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsMultinetworkSocketClientTest.java
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsMultinetworkSocketClientTest.java
@@ -18,8 +18,10 @@
import static com.android.server.connectivity.mdns.MdnsSocketProvider.SocketCallback;
import static com.android.server.connectivity.mdns.MulticastPacketReader.PacketHandler;
+import static com.android.testutils.Cleanup.testAndCleanup;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.eq;
@@ -35,6 +37,7 @@
import android.os.Build;
import android.os.Handler;
import android.os.HandlerThread;
+import android.util.Log;
import com.android.net.module.util.HexDump;
import com.android.net.module.util.SharedLog;
@@ -59,6 +62,7 @@
import java.net.SocketException;
import java.util.ArrayList;
import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
@RunWith(DevSdkIgnoreRunner.class)
@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S_V2)
@@ -437,4 +441,34 @@
inOrder.verify(mSocket).send(packets.get(i));
}
}
+
+ @Test
+ public void testSendPacketWithMultiplePacketsWithDifferentAddresses() throws IOException {
+ final SocketCallback callback = expectSocketCallback();
+ final DatagramPacket ipv4Packet = new DatagramPacket(BUFFER, 0 /* offset */, BUFFER.length,
+ InetAddresses.parseNumericAddress("192.0.2.1"), 0 /* port */);
+ final DatagramPacket ipv6Packet = new DatagramPacket(BUFFER, 0 /* offset */, BUFFER.length,
+ InetAddresses.parseNumericAddress("2001:db8::"), 0 /* port */);
+ doReturn(true).when(mSocket).hasJoinedIpv4();
+ doReturn(true).when(mSocket).hasJoinedIpv6();
+ doReturn(createEmptyNetworkInterface()).when(mSocket).getInterface();
+
+ // Notify socket created
+ callback.onSocketCreated(mSocketKey, mSocket, List.of());
+ verify(mSocketCreationCallback).onSocketCreated(mSocketKey);
+
+ // Send packets with IPv4 and IPv6 then verify wtf logs and sending has never been called.
+ // Override the default TerribleFailureHandler, as that handler might terminate the process
+ // (if we're on an eng build).
+ final AtomicBoolean hasFailed = new AtomicBoolean(false);
+ final Log.TerribleFailureHandler originalHandler =
+ Log.setWtfHandler((tag, what, system) -> hasFailed.set(true));
+ testAndCleanup(() -> {
+ mSocketClient.sendPacketRequestingMulticastResponse(List.of(ipv4Packet, ipv6Packet),
+ mSocketKey, false /* onlyUseIpv6OnIpv6OnlyNetworks */);
+ HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
+ assertTrue(hasFailed.get());
+ verify(mSocket, never()).send(any());
+ }, () -> Log.setWtfHandler(originalHandler));
+ }
}
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsSocketClientTests.java b/tests/unit/java/com/android/server/connectivity/mdns/MdnsSocketClientTests.java
index 1989ed3..ab70e38 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsSocketClientTests.java
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsSocketClientTests.java
@@ -16,6 +16,7 @@
package com.android.server.connectivity.mdns;
+import static com.android.testutils.Cleanup.testAndCleanup;
import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
import static org.junit.Assert.assertFalse;
@@ -38,9 +39,11 @@
import android.annotation.RequiresPermission;
import android.content.Context;
import android.net.ConnectivityManager;
+import android.net.InetAddresses;
import android.net.wifi.WifiManager;
import android.net.wifi.WifiManager.MulticastLock;
import android.text.format.DateUtils;
+import android.util.Log;
import com.android.net.module.util.HexDump;
import com.android.net.module.util.SharedLog;
@@ -594,6 +597,29 @@
}
}
+ @Test
+ public void testSendPacketWithMultiplePacketsWithDifferentAddresses() throws IOException {
+ mdnsClient.startDiscovery();
+ final byte[] buffer = new byte[10];
+ final DatagramPacket ipv4Packet = new DatagramPacket(buffer, 0 /* offset */, buffer.length,
+ InetAddresses.parseNumericAddress("192.0.2.1"), 0 /* port */);
+ final DatagramPacket ipv6Packet = new DatagramPacket(buffer, 0 /* offset */, buffer.length,
+ InetAddresses.parseNumericAddress("2001:db8::"), 0 /* port */);
+
+ // Send packets with IPv4 and IPv6 then verify wtf logs and sending has never been called.
+ // Override the default TerribleFailureHandler, as that handler might terminate the process
+ // (if we're on an eng build).
+ final AtomicBoolean hasFailed = new AtomicBoolean(false);
+ final Log.TerribleFailureHandler originalHandler =
+ Log.setWtfHandler((tag, what, system) -> hasFailed.set(true));
+ testAndCleanup(() -> {
+ mdnsClient.sendPacketRequestingMulticastResponse(List.of(ipv4Packet, ipv6Packet),
+ false /* onlyUseIpv6OnIpv6OnlyNetworks */);
+ assertTrue(hasFailed.get());
+ verify(mockMulticastSocket, never()).send(any());
+ }, () -> Log.setWtfHandler(originalHandler));
+ }
+
private DatagramPacket getTestDatagramPacket() {
return new DatagramPacket(buf, 0, 5,
new InetSocketAddress(MdnsConstants.getMdnsIPv4Address(), 5353 /* port */));
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/util/MdnsUtilsTest.kt b/tests/unit/java/com/android/server/connectivity/mdns/util/MdnsUtilsTest.kt
index 009205e..cf88d05 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/util/MdnsUtilsTest.kt
+++ b/tests/unit/java/com/android/server/connectivity/mdns/util/MdnsUtilsTest.kt
@@ -16,9 +16,12 @@
package com.android.server.connectivity.mdns.util
+import android.net.InetAddresses
import android.os.Build
import com.android.server.connectivity.mdns.MdnsConstants
import com.android.server.connectivity.mdns.MdnsConstants.FLAG_TRUNCATED
+import com.android.server.connectivity.mdns.MdnsConstants.IPV4_SOCKET_ADDR
+import com.android.server.connectivity.mdns.MdnsConstants.IPV6_SOCKET_ADDR
import com.android.server.connectivity.mdns.MdnsPacket
import com.android.server.connectivity.mdns.MdnsPacketReader
import com.android.server.connectivity.mdns.MdnsPointerRecord
@@ -193,4 +196,31 @@
}
return MdnsPacket(flags, questions, answers, emptyList(), emptyList())
}
+
+ @Test
+ fun testCheckAllPacketsWithSameAddress() {
+ val buffer = ByteArray(10)
+ val v4Packet = DatagramPacket(buffer, buffer.size, IPV4_SOCKET_ADDR)
+ val otherV4Packet = DatagramPacket(
+ buffer,
+ buffer.size,
+ InetAddresses.parseNumericAddress("192.0.2.1"),
+ 1234
+ )
+ val v6Packet = DatagramPacket(ByteArray(10), 10, IPV6_SOCKET_ADDR)
+ val otherV6Packet = DatagramPacket(
+ buffer,
+ buffer.size,
+ InetAddresses.parseNumericAddress("2001:db8::"),
+ 1234
+ )
+ assertTrue(MdnsUtils.checkAllPacketsWithSameAddress(listOf()))
+ assertTrue(MdnsUtils.checkAllPacketsWithSameAddress(listOf(v4Packet)))
+ assertTrue(MdnsUtils.checkAllPacketsWithSameAddress(listOf(v4Packet, v4Packet)))
+ assertFalse(MdnsUtils.checkAllPacketsWithSameAddress(listOf(v4Packet, otherV4Packet)))
+ assertTrue(MdnsUtils.checkAllPacketsWithSameAddress(listOf(v6Packet)))
+ assertTrue(MdnsUtils.checkAllPacketsWithSameAddress(listOf(v6Packet, v6Packet)))
+ assertFalse(MdnsUtils.checkAllPacketsWithSameAddress(listOf(v6Packet, otherV6Packet)))
+ assertFalse(MdnsUtils.checkAllPacketsWithSameAddress(listOf(v4Packet, v6Packet)))
+ }
}
diff --git a/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java b/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java
index 2f60d9a..bf3cedb 100644
--- a/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java
+++ b/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java
@@ -139,6 +139,7 @@
import java.time.Clock;
import java.time.DateTimeException;
import java.time.Instant;
+import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -344,8 +345,8 @@
final String modelName = resources.getString(R.string.config_thread_model_name);
final String vendorName = resources.getString(R.string.config_thread_vendor_name);
final String vendorOui = resources.getString(R.string.config_thread_vendor_oui);
- final boolean managedByGoogle =
- resources.getBoolean(R.bool.config_thread_managed_by_google_home);
+ final String[] vendorSpecificTxts =
+ resources.getStringArray(R.array.config_thread_mdns_vendor_specific_txts);
if (!modelName.isEmpty()) {
if (modelName.getBytes(UTF_8).length > MAX_MODEL_NAME_UTF8_BYTES) {
@@ -375,19 +376,44 @@
meshcopTxts.modelName = modelName;
meshcopTxts.vendorName = vendorName;
meshcopTxts.vendorOui = HexEncoding.decode(vendorOui.replace("-", "").replace(":", ""));
- meshcopTxts.nonStandardTxtEntries = List.of(makeManagedByGoogleTxtAttr(managedByGoogle));
+ meshcopTxts.nonStandardTxtEntries = makeVendorSpecificTxtAttrs(vendorSpecificTxts);
return meshcopTxts;
}
/**
- * Creates a DNS-SD TXT entry for indicating whether Thread on this device is managed by Google.
+ * Parses vendor-specific TXT entries from "=" separated strings into list of {@link
+ * DnsTxtAttribute}.
*
- * @return TXT entry "vgh=1" if {@code managedByGoogle} is {@code true}; otherwise, "vgh=0"
+ * @throws IllegalArgumentsException if invalid TXT entries are found in {@code vendorTxts}
*/
- private static DnsTxtAttribute makeManagedByGoogleTxtAttr(boolean managedByGoogle) {
- final byte[] value = (managedByGoogle ? "1" : "0").getBytes(UTF_8);
- return new DnsTxtAttribute("vgh", value);
+ @VisibleForTesting
+ static List<DnsTxtAttribute> makeVendorSpecificTxtAttrs(String[] vendorTxts) {
+ List<DnsTxtAttribute> txts = new ArrayList<>();
+ for (String txt : vendorTxts) {
+ String[] kv = txt.split("=", 2 /* limit */); // Split with only the first '='
+ if (kv.length < 1) {
+ throw new IllegalArgumentException(
+ "Invalid vendor-specific TXT is found in resources: " + txt);
+ }
+
+ if (kv[0].length() < 2) {
+ throw new IllegalArgumentException(
+ "Invalid vendor-specific TXT key \""
+ + kv[0]
+ + "\": it must contain at least 2 characters");
+ }
+
+ if (!kv[0].startsWith("v")) {
+ throw new IllegalArgumentException(
+ "Invalid vendor-specific TXT key \""
+ + kv[0]
+ + "\": it doesn't start with \"v\"");
+ }
+
+ txts.add(new DnsTxtAttribute(kv[0], (kv.length >= 2 ? kv[1] : "").getBytes(UTF_8)));
+ }
+ return txts;
}
private void onOtDaemonDied() {
@@ -598,10 +624,12 @@
new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
- mHandler.post(() -> onUserRestrictionsChanged(isThreadUserRestricted()));
+ onUserRestrictionsChanged(isThreadUserRestricted());
}
},
- new IntentFilter(UserManager.ACTION_USER_RESTRICTIONS_CHANGED));
+ new IntentFilter(UserManager.ACTION_USER_RESTRICTIONS_CHANGED),
+ null /* broadcastPermission */,
+ mHandler);
}
private void onUserRestrictionsChanged(boolean newUserRestrictedState) {
@@ -653,10 +681,12 @@
new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
- mHandler.post(() -> onAirplaneModeChanged(isAirplaneModeOn()));
+ onAirplaneModeChanged(isAirplaneModeOn());
}
},
- new IntentFilter(Intent.ACTION_AIRPLANE_MODE_CHANGED));
+ new IntentFilter(Intent.ACTION_AIRPLANE_MODE_CHANGED),
+ null /* broadcastPermission */,
+ mHandler);
}
private void onAirplaneModeChanged(boolean newAirplaneModeOn) {
diff --git a/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkControllerTest.java b/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkControllerTest.java
index 41f34ff..22e7a98 100644
--- a/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkControllerTest.java
+++ b/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkControllerTest.java
@@ -45,7 +45,6 @@
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.fail;
-
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import android.content.Context;
@@ -859,7 +858,6 @@
assertThat(txtMap.get("rv")).isNotNull();
assertThat(txtMap.get("tv")).isNotNull();
assertThat(txtMap.get("sb")).isNotNull();
- assertThat(new String(txtMap.get("vgh"))).isIn(List.of("0", "1"));
}
@Test
@@ -886,7 +884,6 @@
assertThat(txtMap.get("tv")).isNotNull();
assertThat(txtMap.get("sb")).isNotNull();
assertThat(txtMap.get("id").length).isEqualTo(16);
- assertThat(new String(txtMap.get("vgh"))).isIn(List.of("0", "1"));
}
@Test
diff --git a/thread/tests/unit/src/com/android/server/thread/ThreadNetworkControllerServiceTest.java b/thread/tests/unit/src/com/android/server/thread/ThreadNetworkControllerServiceTest.java
index 6e2369f..bf61c68 100644
--- a/thread/tests/unit/src/com/android/server/thread/ThreadNetworkControllerServiceTest.java
+++ b/thread/tests/unit/src/com/android/server/thread/ThreadNetworkControllerServiceTest.java
@@ -49,6 +49,8 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -96,11 +98,11 @@
import org.mockito.MockitoAnnotations;
import org.mockito.MockitoSession;
-import java.nio.charset.StandardCharsets;
import java.time.Clock;
import java.time.DateTimeException;
import java.time.Instant;
import java.time.ZoneId;
+import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.AtomicReference;
@@ -150,7 +152,6 @@
private static final byte[] TEST_VENDOR_OUI_BYTES = new byte[] {(byte) 0xAC, (byte) 0xDE, 0x48};
private static final String TEST_VENDOR_NAME = "test vendor";
private static final String TEST_MODEL_NAME = "test model";
- private static final boolean TEST_VGH_VALUE = false;
@Mock private ConnectivityManager mMockConnectivityManager;
@Mock private NetworkAgent mMockNetworkAgent;
@@ -203,8 +204,8 @@
.thenReturn(TEST_VENDOR_OUI);
when(mResources.getString(eq(R.string.config_thread_model_name)))
.thenReturn(TEST_MODEL_NAME);
- when(mResources.getBoolean(eq(R.bool.config_thread_managed_by_google_home)))
- .thenReturn(TEST_VGH_VALUE);
+ when(mResources.getStringArray(eq(R.array.config_thread_mdns_vendor_specific_txts)))
+ .thenReturn(new String[] {});
final AtomicFile storageFile = new AtomicFile(tempFolder.newFile("thread_settings.xml"));
mPersistentSettings = new ThreadPersistentSettings(storageFile, mConnectivityResources);
@@ -247,8 +248,8 @@
.thenReturn(TEST_VENDOR_OUI);
when(mResources.getString(eq(R.string.config_thread_model_name)))
.thenReturn(TEST_MODEL_NAME);
- when(mResources.getBoolean(eq(R.bool.config_thread_managed_by_google_home)))
- .thenReturn(true);
+ when(mResources.getStringArray(eq(R.array.config_thread_mdns_vendor_specific_txts)))
+ .thenReturn(new String[] {"vt=test"});
mService.initialize();
mTestLooper.dispatchAll();
@@ -258,19 +259,7 @@
assertThat(meshcopTxts.vendorOui).isEqualTo(TEST_VENDOR_OUI_BYTES);
assertThat(meshcopTxts.modelName).isEqualTo(TEST_MODEL_NAME);
assertThat(meshcopTxts.nonStandardTxtEntries)
- .containsExactly(new DnsTxtAttribute("vgh", "1".getBytes(StandardCharsets.UTF_8)));
- }
-
- @Test
- public void getMeshcopTxtAttributes_managedByGoogleIsFalse_vghIsZero() {
- when(mResources.getBoolean(eq(R.bool.config_thread_managed_by_google_home)))
- .thenReturn(false);
-
- MeshcopTxtAttributes meshcopTxts =
- ThreadNetworkControllerService.getMeshcopTxtAttributes(mResources);
-
- assertThat(meshcopTxts.nonStandardTxtEntries)
- .containsExactly(new DnsTxtAttribute("vgh", "0".getBytes(StandardCharsets.UTF_8)));
+ .containsExactly(new DnsTxtAttribute("vt", "test".getBytes(UTF_8)));
}
@Test
@@ -343,6 +332,61 @@
}
@Test
+ public void makeVendorSpecificTxtAttrs_validTxts_returnsParsedTxtAttrs() {
+ String[] txts = new String[] {"va=123", "vb=", "vc"};
+
+ List<DnsTxtAttribute> attrs = mService.makeVendorSpecificTxtAttrs(txts);
+
+ assertThat(attrs)
+ .containsExactly(
+ new DnsTxtAttribute("va", "123".getBytes(UTF_8)),
+ new DnsTxtAttribute("vb", new byte[] {}),
+ new DnsTxtAttribute("vc", new byte[] {}));
+ }
+
+ @Test
+ public void makeVendorSpecificTxtAttrs_txtKeyNotStartWithV_throwsIllegalArgument() {
+ String[] txts = new String[] {"abc=123"};
+
+ assertThrows(
+ IllegalArgumentException.class, () -> mService.makeVendorSpecificTxtAttrs(txts));
+ }
+
+ @Test
+ public void makeVendorSpecificTxtAttrs_txtIsTooShort_throwsIllegalArgument() {
+ String[] txtEmptyKey = new String[] {"=123"};
+ String[] txtSingleCharKey = new String[] {"v=456"};
+
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> mService.makeVendorSpecificTxtAttrs(txtEmptyKey));
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> mService.makeVendorSpecificTxtAttrs(txtSingleCharKey));
+ }
+
+ @Test
+ public void makeVendorSpecificTxtAttrs_txtValueIsEmpty_parseSuccess() {
+ String[] txts = new String[] {"va=", "vb"};
+
+ List<DnsTxtAttribute> attrs = mService.makeVendorSpecificTxtAttrs(txts);
+
+ assertThat(attrs)
+ .containsExactly(
+ new DnsTxtAttribute("va", new byte[] {}),
+ new DnsTxtAttribute("vb", new byte[] {}));
+ }
+
+ @Test
+ public void makeVendorSpecificTxtAttrs_multipleEquals_splittedByTheFirstEqual() {
+ String[] txts = new String[] {"va=abc=def=123"};
+
+ List<DnsTxtAttribute> attrs = mService.makeVendorSpecificTxtAttrs(txts);
+
+ assertThat(attrs).containsExactly(new DnsTxtAttribute("va", "abc=def=123".getBytes(UTF_8)));
+ }
+
+ @Test
public void join_otDaemonRemoteFailure_returnsInternalError() throws Exception {
mService.initialize();
final IOperationReceiver mockReceiver = mock(IOperationReceiver.class);
@@ -542,7 +586,9 @@
.when(mContext)
.registerReceiver(
any(BroadcastReceiver.class),
- argThat(actualIntentFilter -> actualIntentFilter.hasAction(action)));
+ argThat(actualIntentFilter -> actualIntentFilter.hasAction(action)),
+ any(),
+ any());
return receiverRef;
}