Merge "[mdns] implement NsdPublisher AIDL service" into main
This commit is contained in:
commit
addabc4729
7 changed files with 922 additions and 11 deletions
335
thread/service/java/com/android/server/thread/NsdPublisher.java
Normal file
335
thread/service/java/com/android/server/thread/NsdPublisher.java
Normal file
|
|
@ -0,0 +1,335 @@
|
|||
/*
|
||||
* Copyright (C) 2024 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.server.thread;
|
||||
|
||||
import static android.net.nsd.NsdManager.PROTOCOL_DNS_SD;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.content.Context;
|
||||
import android.net.nsd.NsdManager;
|
||||
import android.net.nsd.NsdServiceInfo;
|
||||
import android.os.Handler;
|
||||
import android.os.RemoteException;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.util.SparseArray;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.server.thread.openthread.DnsTxtAttribute;
|
||||
import com.android.server.thread.openthread.INsdPublisher;
|
||||
import com.android.server.thread.openthread.INsdStatusReceiver;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Deque;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
/**
|
||||
* Implementation of {@link INsdPublisher}.
|
||||
*
|
||||
* <p>This class provides API for service registration and discovery over mDNS. This class is a
|
||||
* proxy between ot-daemon and NsdManager.
|
||||
*
|
||||
* <p>All the data members of this class MUST be accessed in the {@code mHandler}'s Thread except
|
||||
* {@code mHandler} itself.
|
||||
*
|
||||
* <p>TODO: b/323300118 - Remove the following mechanism when the race condition in NsdManager is
|
||||
* fixed.
|
||||
*
|
||||
* <p>There's always only one running registration job at any timepoint. All other pending jobs are
|
||||
* queued in {@code mRegistrationJobs}. When a registration job is complete (i.e. the according
|
||||
* method in {@link NsdManager.RegistrationListener} is called), it will start the next registration
|
||||
* job in the queue.
|
||||
*/
|
||||
public final class NsdPublisher extends INsdPublisher.Stub {
|
||||
// TODO: b/321883491 - specify network for mDNS operations
|
||||
private static final String TAG = NsdPublisher.class.getSimpleName();
|
||||
private final NsdManager mNsdManager;
|
||||
private final Handler mHandler;
|
||||
private final Executor mExecutor;
|
||||
private final SparseArray<RegistrationListener> mRegistrationListeners = new SparseArray<>(0);
|
||||
private final Deque<Runnable> mRegistrationJobs = new ArrayDeque<>();
|
||||
|
||||
@VisibleForTesting
|
||||
public NsdPublisher(NsdManager nsdManager, Handler handler) {
|
||||
mNsdManager = nsdManager;
|
||||
mHandler = handler;
|
||||
mExecutor = runnable -> mHandler.post(runnable);
|
||||
}
|
||||
|
||||
public static NsdPublisher newInstance(Context context, Handler handler) {
|
||||
return new NsdPublisher(context.getSystemService(NsdManager.class), handler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerService(
|
||||
String hostname,
|
||||
String name,
|
||||
String type,
|
||||
List<String> subTypeList,
|
||||
int port,
|
||||
List<DnsTxtAttribute> txt,
|
||||
INsdStatusReceiver receiver,
|
||||
int listenerId) {
|
||||
postRegistrationJob(
|
||||
() -> {
|
||||
NsdServiceInfo serviceInfo =
|
||||
buildServiceInfoForService(
|
||||
hostname, name, type, subTypeList, port, txt);
|
||||
registerInternal(serviceInfo, receiver, listenerId, "service");
|
||||
});
|
||||
}
|
||||
|
||||
private static NsdServiceInfo buildServiceInfoForService(
|
||||
String hostname,
|
||||
String name,
|
||||
String type,
|
||||
List<String> subTypeList,
|
||||
int port,
|
||||
List<DnsTxtAttribute> txt) {
|
||||
NsdServiceInfo serviceInfo = new NsdServiceInfo();
|
||||
|
||||
serviceInfo.setServiceName(name);
|
||||
if (!TextUtils.isEmpty(hostname)) {
|
||||
serviceInfo.setHostname(hostname);
|
||||
}
|
||||
serviceInfo.setServiceType(type);
|
||||
serviceInfo.setPort(port);
|
||||
serviceInfo.setSubtypes(new HashSet<>(subTypeList));
|
||||
for (DnsTxtAttribute attribute : txt) {
|
||||
serviceInfo.setAttribute(attribute.name, attribute.value);
|
||||
}
|
||||
|
||||
return serviceInfo;
|
||||
}
|
||||
|
||||
private void registerInternal(
|
||||
NsdServiceInfo serviceInfo,
|
||||
INsdStatusReceiver receiver,
|
||||
int listenerId,
|
||||
String registrationType) {
|
||||
checkOnHandlerThread();
|
||||
Log.i(
|
||||
TAG,
|
||||
"Registering "
|
||||
+ registrationType
|
||||
+ ". Listener ID: "
|
||||
+ listenerId
|
||||
+ ", serviceInfo: "
|
||||
+ serviceInfo);
|
||||
RegistrationListener listener = new RegistrationListener(serviceInfo, listenerId, receiver);
|
||||
mRegistrationListeners.append(listenerId, listener);
|
||||
try {
|
||||
mNsdManager.registerService(serviceInfo, PROTOCOL_DNS_SD, mExecutor, listener);
|
||||
} catch (IllegalArgumentException e) {
|
||||
Log.i(TAG, "Failed to register service. serviceInfo: " + serviceInfo, e);
|
||||
listener.onRegistrationFailed(serviceInfo, NsdManager.FAILURE_INTERNAL_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
public void unregister(INsdStatusReceiver receiver, int listenerId) {
|
||||
postRegistrationJob(() -> unregisterInternal(receiver, listenerId));
|
||||
}
|
||||
|
||||
public void unregisterInternal(INsdStatusReceiver receiver, int listenerId) {
|
||||
checkOnHandlerThread();
|
||||
RegistrationListener registrationListener = mRegistrationListeners.get(listenerId);
|
||||
if (registrationListener == null) {
|
||||
Log.w(
|
||||
TAG,
|
||||
"Failed to unregister service."
|
||||
+ " Listener ID: "
|
||||
+ listenerId
|
||||
+ " The registrationListener is empty.");
|
||||
|
||||
return;
|
||||
}
|
||||
Log.i(
|
||||
TAG,
|
||||
"Unregistering service."
|
||||
+ " Listener ID: "
|
||||
+ listenerId
|
||||
+ " serviceInfo: "
|
||||
+ registrationListener.mServiceInfo);
|
||||
registrationListener.addUnregistrationReceiver(receiver);
|
||||
mNsdManager.unregisterService(registrationListener);
|
||||
}
|
||||
|
||||
private void checkOnHandlerThread() {
|
||||
if (mHandler.getLooper().getThread() != Thread.currentThread()) {
|
||||
throw new IllegalStateException(
|
||||
"Not running on handler Thread: " + Thread.currentThread().getName());
|
||||
}
|
||||
}
|
||||
|
||||
/** On ot-daemon died, unregister all registrations. */
|
||||
public void onOtDaemonDied() {
|
||||
checkOnHandlerThread();
|
||||
for (int i = 0; i < mRegistrationListeners.size(); ++i) {
|
||||
try {
|
||||
mNsdManager.unregisterService(mRegistrationListeners.valueAt(i));
|
||||
} catch (IllegalArgumentException e) {
|
||||
Log.i(
|
||||
TAG,
|
||||
"Failed to unregister."
|
||||
+ " Listener ID: "
|
||||
+ mRegistrationListeners.keyAt(i)
|
||||
+ " serviceInfo: "
|
||||
+ mRegistrationListeners.valueAt(i).mServiceInfo,
|
||||
e);
|
||||
}
|
||||
}
|
||||
mRegistrationListeners.clear();
|
||||
}
|
||||
|
||||
// TODO: b/323300118 - Remove this mechanism when the race condition in NsdManager is fixed.
|
||||
/** Fetch the first job from the queue and run it. See the class doc for more details. */
|
||||
private void peekAndRun() {
|
||||
if (mRegistrationJobs.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
Runnable job = mRegistrationJobs.getFirst();
|
||||
job.run();
|
||||
}
|
||||
|
||||
// TODO: b/323300118 - Remove this mechanism when the race condition in NsdManager is fixed.
|
||||
/**
|
||||
* Pop the first job from the queue and run the next job. See the class doc for more details.
|
||||
*/
|
||||
private void popAndRunNext() {
|
||||
if (mRegistrationJobs.isEmpty()) {
|
||||
Log.i(TAG, "No registration jobs when trying to pop and run next.");
|
||||
return;
|
||||
}
|
||||
mRegistrationJobs.removeFirst();
|
||||
peekAndRun();
|
||||
}
|
||||
|
||||
private void postRegistrationJob(Runnable registrationJob) {
|
||||
mHandler.post(
|
||||
() -> {
|
||||
mRegistrationJobs.addLast(registrationJob);
|
||||
if (mRegistrationJobs.size() == 1) {
|
||||
peekAndRun();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private final class RegistrationListener implements NsdManager.RegistrationListener {
|
||||
private final NsdServiceInfo mServiceInfo;
|
||||
private final int mListenerId;
|
||||
private final INsdStatusReceiver mRegistrationReceiver;
|
||||
private final List<INsdStatusReceiver> mUnregistrationReceivers;
|
||||
|
||||
RegistrationListener(
|
||||
@NonNull NsdServiceInfo serviceInfo,
|
||||
int listenerId,
|
||||
@NonNull INsdStatusReceiver registrationReceiver) {
|
||||
mServiceInfo = serviceInfo;
|
||||
mListenerId = listenerId;
|
||||
mRegistrationReceiver = registrationReceiver;
|
||||
mUnregistrationReceivers = new ArrayList<>();
|
||||
}
|
||||
|
||||
void addUnregistrationReceiver(@NonNull INsdStatusReceiver unregistrationReceiver) {
|
||||
mUnregistrationReceivers.add(unregistrationReceiver);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRegistrationFailed(NsdServiceInfo serviceInfo, int errorCode) {
|
||||
checkOnHandlerThread();
|
||||
mRegistrationListeners.remove(mListenerId);
|
||||
Log.i(
|
||||
TAG,
|
||||
"Failed to register listener ID: "
|
||||
+ mListenerId
|
||||
+ " error code: "
|
||||
+ errorCode
|
||||
+ " serviceInfo: "
|
||||
+ serviceInfo);
|
||||
try {
|
||||
mRegistrationReceiver.onError(errorCode);
|
||||
} catch (RemoteException ignored) {
|
||||
// do nothing if the client is dead
|
||||
}
|
||||
popAndRunNext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUnregistrationFailed(NsdServiceInfo serviceInfo, int errorCode) {
|
||||
checkOnHandlerThread();
|
||||
for (INsdStatusReceiver receiver : mUnregistrationReceivers) {
|
||||
Log.i(
|
||||
TAG,
|
||||
"Failed to unregister."
|
||||
+ "Listener ID: "
|
||||
+ mListenerId
|
||||
+ ", error code: "
|
||||
+ errorCode
|
||||
+ ", serviceInfo: "
|
||||
+ serviceInfo);
|
||||
try {
|
||||
receiver.onError(errorCode);
|
||||
} catch (RemoteException ignored) {
|
||||
// do nothing if the client is dead
|
||||
}
|
||||
}
|
||||
popAndRunNext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServiceRegistered(NsdServiceInfo serviceInfo) {
|
||||
checkOnHandlerThread();
|
||||
Log.i(
|
||||
TAG,
|
||||
"Registered successfully. "
|
||||
+ "Listener ID: "
|
||||
+ mListenerId
|
||||
+ ", serviceInfo: "
|
||||
+ serviceInfo);
|
||||
try {
|
||||
mRegistrationReceiver.onSuccess();
|
||||
} catch (RemoteException ignored) {
|
||||
// do nothing if the client is dead
|
||||
}
|
||||
popAndRunNext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServiceUnregistered(NsdServiceInfo serviceInfo) {
|
||||
checkOnHandlerThread();
|
||||
for (INsdStatusReceiver receiver : mUnregistrationReceivers) {
|
||||
Log.i(
|
||||
TAG,
|
||||
"Unregistered successfully. "
|
||||
+ "Listener ID: "
|
||||
+ mListenerId
|
||||
+ ", serviceInfo: "
|
||||
+ serviceInfo);
|
||||
try {
|
||||
receiver.onSuccess();
|
||||
} catch (RemoteException ignored) {
|
||||
// do nothing if the client is dead
|
||||
}
|
||||
}
|
||||
mRegistrationListeners.remove(mListenerId);
|
||||
popAndRunNext();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -149,6 +149,7 @@ final class ThreadNetworkControllerService extends IThreadNetworkController.Stub
|
|||
private final ConnectivityManager mConnectivityManager;
|
||||
private final TunInterfaceController mTunIfController;
|
||||
private final InfraInterfaceController mInfraIfController;
|
||||
private final NsdPublisher mNsdPublisher;
|
||||
private final OtDaemonCallbackProxy mOtDaemonCallbackProxy = new OtDaemonCallbackProxy();
|
||||
|
||||
// TODO(b/308310823): read supported channel from Thread dameon
|
||||
|
|
@ -178,7 +179,8 @@ final class ThreadNetworkControllerService extends IThreadNetworkController.Stub
|
|||
ConnectivityManager connectivityManager,
|
||||
TunInterfaceController tunIfController,
|
||||
InfraInterfaceController infraIfController,
|
||||
ThreadPersistentSettings persistentSettings) {
|
||||
ThreadPersistentSettings persistentSettings,
|
||||
NsdPublisher nsdPublisher) {
|
||||
mContext = context;
|
||||
mHandler = handler;
|
||||
mNetworkProvider = networkProvider;
|
||||
|
|
@ -190,24 +192,27 @@ final class ThreadNetworkControllerService extends IThreadNetworkController.Stub
|
|||
mNetworkToInterface = new HashMap<Network, String>();
|
||||
mBorderRouterConfig = new BorderRouterConfigurationParcel();
|
||||
mPersistentSettings = persistentSettings;
|
||||
mNsdPublisher = nsdPublisher;
|
||||
}
|
||||
|
||||
public static ThreadNetworkControllerService newInstance(
|
||||
Context context, ThreadPersistentSettings persistentSettings) {
|
||||
HandlerThread handlerThread = new HandlerThread("ThreadHandlerThread");
|
||||
handlerThread.start();
|
||||
Handler handler = new Handler(handlerThread.getLooper());
|
||||
NetworkProvider networkProvider =
|
||||
new NetworkProvider(context, handlerThread.getLooper(), "ThreadNetworkProvider");
|
||||
|
||||
return new ThreadNetworkControllerService(
|
||||
context,
|
||||
new Handler(handlerThread.getLooper()),
|
||||
handler,
|
||||
networkProvider,
|
||||
() -> IOtDaemon.Stub.asInterface(ServiceManagerWrapper.waitForService("ot_daemon")),
|
||||
context.getSystemService(ConnectivityManager.class),
|
||||
new TunInterfaceController(TUN_IF_NAME),
|
||||
new InfraInterfaceController(),
|
||||
persistentSettings);
|
||||
persistentSettings,
|
||||
NsdPublisher.newInstance(context, handler));
|
||||
}
|
||||
|
||||
private static Inet6Address bytesToInet6Address(byte[] addressBytes) {
|
||||
|
|
@ -285,7 +290,8 @@ final class ThreadNetworkControllerService extends IThreadNetworkController.Stub
|
|||
}
|
||||
otDaemon.initialize(
|
||||
mTunIfController.getTunFd(),
|
||||
mPersistentSettings.get(ThreadPersistentSettings.THREAD_ENABLED));
|
||||
mPersistentSettings.get(ThreadPersistentSettings.THREAD_ENABLED),
|
||||
mNsdPublisher);
|
||||
otDaemon.registerStateCallback(mOtDaemonCallbackProxy, -1);
|
||||
otDaemon.asBinder().linkToDeath(() -> mHandler.post(this::onOtDaemonDied), 0);
|
||||
mOtDaemon = otDaemon;
|
||||
|
|
@ -299,7 +305,7 @@ final class ThreadNetworkControllerService extends IThreadNetworkController.Stub
|
|||
OperationReceiverWrapper.onOtDaemonDied();
|
||||
mOtDaemonCallbackProxy.onOtDaemonDied();
|
||||
mTunIfController.onOtDaemonDied();
|
||||
|
||||
mNsdPublisher.onOtDaemonDied();
|
||||
mOtDaemon = null;
|
||||
initializeOtDaemon();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -51,4 +51,5 @@ android_test {
|
|||
// Test coverage system runs on different devices. Need to
|
||||
// compile for all architectures.
|
||||
compile_multilib: "both",
|
||||
platform_apis: true,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,9 @@
|
|||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="android.net.thread.cts">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE"/>
|
||||
|
||||
<application android:debuggable="true">
|
||||
<uses-library android:name="android.test.runner" />
|
||||
</application>
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
package android.net.thread.cts;
|
||||
|
||||
import static android.Manifest.permission.ACCESS_NETWORK_STATE;
|
||||
import static android.Manifest.permission.MANAGE_TEST_NETWORKS;
|
||||
import static android.net.thread.ThreadNetworkController.DEVICE_ROLE_CHILD;
|
||||
import static android.net.thread.ThreadNetworkController.DEVICE_ROLE_LEADER;
|
||||
import static android.net.thread.ThreadNetworkController.DEVICE_ROLE_ROUTER;
|
||||
|
|
@ -32,6 +33,7 @@ import static android.net.thread.ThreadNetworkException.ERROR_THREAD_DISABLED;
|
|||
|
||||
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
|
||||
|
||||
import static com.android.testutils.TestNetworkTrackerKt.initTestNetwork;
|
||||
import static com.android.testutils.TestPermissionUtil.runAsShell;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
|
@ -46,9 +48,12 @@ import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
|||
|
||||
import android.content.Context;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.LinkAddress;
|
||||
import android.net.Network;
|
||||
import android.net.NetworkCapabilities;
|
||||
import android.net.NetworkRequest;
|
||||
import android.net.nsd.NsdManager;
|
||||
import android.net.nsd.NsdServiceInfo;
|
||||
import android.net.thread.ActiveOperationalDataset;
|
||||
import android.net.thread.OperationalDatasetTimestamp;
|
||||
import android.net.thread.PendingOperationalDataset;
|
||||
|
|
@ -60,6 +65,7 @@ import android.net.thread.ThreadNetworkManager;
|
|||
import android.os.Build;
|
||||
import android.os.OutcomeReceiver;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
import androidx.test.filters.LargeTest;
|
||||
|
||||
|
|
@ -68,6 +74,7 @@ import com.android.testutils.DevSdkIgnoreRule;
|
|||
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
|
||||
import com.android.testutils.DevSdkIgnoreRunner;
|
||||
import com.android.testutils.FunctionalUtils.ThrowingRunnable;
|
||||
import com.android.testutils.TestNetworkTracker;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
|
|
@ -75,16 +82,22 @@ import org.junit.Rule;
|
|||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/** CTS tests for {@link ThreadNetworkController}. */
|
||||
@LargeTest
|
||||
|
|
@ -97,6 +110,8 @@ public class ThreadNetworkControllerTest {
|
|||
private static final int NETWORK_CALLBACK_TIMEOUT_MILLIS = 10 * 1000;
|
||||
private static final int CALLBACK_TIMEOUT_MILLIS = 1_000;
|
||||
private static final int ENABLED_TIMEOUT_MILLIS = 2_000;
|
||||
private static final int SERVICE_DISCOVERY_TIMEOUT_MILLIS = 10 * 1000;
|
||||
private static final String MESHCOP_SERVICE_TYPE = "_meshcop._udp";
|
||||
private static final String THREAD_NETWORK_PRIVILEGED =
|
||||
"android.permission.THREAD_NETWORK_PRIVILEGED";
|
||||
|
||||
|
|
@ -105,6 +120,7 @@ public class ThreadNetworkControllerTest {
|
|||
private final Context mContext = ApplicationProvider.getApplicationContext();
|
||||
private ExecutorService mExecutor;
|
||||
private ThreadNetworkController mController;
|
||||
private NsdManager mNsdManager;
|
||||
|
||||
private Set<String> mGrantedPermissions;
|
||||
|
||||
|
|
@ -123,6 +139,8 @@ public class ThreadNetworkControllerTest {
|
|||
assumeNotNull(mController);
|
||||
|
||||
setEnabledAndWait(mController, true);
|
||||
|
||||
mNsdManager = mContext.getSystemService(NsdManager.class);
|
||||
}
|
||||
|
||||
@After
|
||||
|
|
@ -809,6 +827,74 @@ public class ThreadNetworkControllerTest {
|
|||
getInstrumentation().getUiAutomation().adoptShellPermissionIdentity(allPermissions);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void meshcopService_threadEnabledButNotJoined_discoveredButNoNetwork() throws Exception {
|
||||
TestNetworkTracker testNetwork = setUpTestNetwork();
|
||||
|
||||
setEnabledAndWait(mController, true);
|
||||
leaveAndWait(mController);
|
||||
|
||||
NsdServiceInfo serviceInfo =
|
||||
expectServiceResolved(
|
||||
MESHCOP_SERVICE_TYPE,
|
||||
SERVICE_DISCOVERY_TIMEOUT_MILLIS,
|
||||
s -> s.getAttributes().get("at") == null);
|
||||
|
||||
Map<String, byte[]> txtMap = serviceInfo.getAttributes();
|
||||
|
||||
assertThat(txtMap.get("rv")).isNotNull();
|
||||
assertThat(txtMap.get("tv")).isNotNull();
|
||||
assertThat(txtMap.get("sb")).isNotNull();
|
||||
|
||||
tearDownTestNetwork(testNetwork);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void meshcopService_joinedNetwork_discoveredHasNetwork() throws Exception {
|
||||
TestNetworkTracker testNetwork = setUpTestNetwork();
|
||||
|
||||
String networkName = "TestNet" + new Random().nextInt(10_000);
|
||||
joinRandomizedDatasetAndWait(mController, networkName);
|
||||
|
||||
Predicate<NsdServiceInfo> predicate =
|
||||
serviceInfo ->
|
||||
serviceInfo.getAttributes().get("at") != null
|
||||
&& Arrays.equals(
|
||||
serviceInfo.getAttributes().get("nn"),
|
||||
networkName.getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
NsdServiceInfo resolvedService =
|
||||
expectServiceResolved(
|
||||
MESHCOP_SERVICE_TYPE, SERVICE_DISCOVERY_TIMEOUT_MILLIS, predicate);
|
||||
|
||||
Map<String, byte[]> txtMap = resolvedService.getAttributes();
|
||||
assertThat(txtMap.get("rv")).isNotNull();
|
||||
assertThat(txtMap.get("tv")).isNotNull();
|
||||
assertThat(txtMap.get("sb")).isNotNull();
|
||||
assertThat(txtMap.get("id").length).isEqualTo(16);
|
||||
|
||||
tearDownTestNetwork(testNetwork);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void meshcopService_threadDisabled_notDiscovered() throws Exception {
|
||||
TestNetworkTracker testNetwork = setUpTestNetwork();
|
||||
|
||||
CompletableFuture<NsdServiceInfo> serviceLostFuture = new CompletableFuture<>();
|
||||
NsdManager.DiscoveryListener listener =
|
||||
discoverForServiceLost(MESHCOP_SERVICE_TYPE, serviceLostFuture);
|
||||
setEnabledAndWait(mController, false);
|
||||
|
||||
try {
|
||||
serviceLostFuture.get(10_000, MILLISECONDS);
|
||||
} finally {
|
||||
mNsdManager.stopServiceDiscovery(listener);
|
||||
}
|
||||
assertThrows(TimeoutException.class, () -> discoverService(MESHCOP_SERVICE_TYPE));
|
||||
|
||||
tearDownTestNetwork(testNetwork);
|
||||
}
|
||||
|
||||
private static void dropAllPermissions() {
|
||||
getInstrumentation().getUiAutomation().dropShellPermissionIdentity();
|
||||
}
|
||||
|
|
@ -888,6 +974,12 @@ public class ThreadNetworkControllerTest {
|
|||
runAsShell(THREAD_NETWORK_PRIVILEGED, () -> controller.leave(mExecutor, receiver));
|
||||
}
|
||||
|
||||
private void leaveAndWait(ThreadNetworkController controller) throws Exception {
|
||||
CompletableFuture<Void> future = new CompletableFuture<>();
|
||||
leave(controller, future::complete);
|
||||
future.get(LEAVE_TIMEOUT_MILLIS, MILLISECONDS);
|
||||
}
|
||||
|
||||
private void scheduleMigration(
|
||||
ThreadNetworkController controller,
|
||||
PendingOperationalDataset pendingDataset,
|
||||
|
|
@ -942,9 +1034,9 @@ public class ThreadNetworkControllerTest {
|
|||
waitForEnabledState(controller, booleanToEnabledState(enabled));
|
||||
}
|
||||
|
||||
private CompletableFuture joinRandomizedDataset(ThreadNetworkController controller)
|
||||
throws Exception {
|
||||
ActiveOperationalDataset activeDataset = newRandomizedDataset("TestNet", controller);
|
||||
private CompletableFuture joinRandomizedDataset(
|
||||
ThreadNetworkController controller, String networkName) throws Exception {
|
||||
ActiveOperationalDataset activeDataset = newRandomizedDataset(networkName, controller);
|
||||
CompletableFuture<Void> joinFuture = new CompletableFuture<>();
|
||||
runAsShell(
|
||||
THREAD_NETWORK_PRIVILEGED,
|
||||
|
|
@ -953,7 +1045,12 @@ public class ThreadNetworkControllerTest {
|
|||
}
|
||||
|
||||
private void joinRandomizedDatasetAndWait(ThreadNetworkController controller) throws Exception {
|
||||
CompletableFuture<Void> joinFuture = joinRandomizedDataset(controller);
|
||||
joinRandomizedDatasetAndWait(controller, "TestNet");
|
||||
}
|
||||
|
||||
private void joinRandomizedDatasetAndWait(
|
||||
ThreadNetworkController controller, String networkName) throws Exception {
|
||||
CompletableFuture<Void> joinFuture = joinRandomizedDataset(controller, networkName);
|
||||
joinFuture.get(JOIN_TIMEOUT_MILLIS, MILLISECONDS);
|
||||
assertThat(isAttached(controller)).isTrue();
|
||||
}
|
||||
|
|
@ -1010,4 +1107,103 @@ public class ThreadNetworkControllerTest {
|
|||
fail("Should not have thrown " + e);
|
||||
}
|
||||
}
|
||||
|
||||
// Return the first discovered service instance.
|
||||
private NsdServiceInfo discoverService(String serviceType) throws Exception {
|
||||
CompletableFuture<NsdServiceInfo> serviceInfoFuture = new CompletableFuture<>();
|
||||
NsdManager.DiscoveryListener listener =
|
||||
new DefaultDiscoveryListener() {
|
||||
@Override
|
||||
public void onServiceFound(NsdServiceInfo serviceInfo) {
|
||||
serviceInfoFuture.complete(serviceInfo);
|
||||
}
|
||||
};
|
||||
mNsdManager.discoverServices(serviceType, NsdManager.PROTOCOL_DNS_SD, listener);
|
||||
try {
|
||||
serviceInfoFuture.get(SERVICE_DISCOVERY_TIMEOUT_MILLIS, MILLISECONDS);
|
||||
} finally {
|
||||
mNsdManager.stopServiceDiscovery(listener);
|
||||
}
|
||||
|
||||
return serviceInfoFuture.get();
|
||||
}
|
||||
|
||||
private NsdManager.DiscoveryListener discoverForServiceLost(
|
||||
String serviceType, CompletableFuture<NsdServiceInfo> serviceInfoFuture) {
|
||||
NsdManager.DiscoveryListener listener =
|
||||
new DefaultDiscoveryListener() {
|
||||
@Override
|
||||
public void onServiceLost(NsdServiceInfo serviceInfo) {
|
||||
serviceInfoFuture.complete(serviceInfo);
|
||||
}
|
||||
};
|
||||
mNsdManager.discoverServices(serviceType, NsdManager.PROTOCOL_DNS_SD, listener);
|
||||
return listener;
|
||||
}
|
||||
|
||||
private NsdServiceInfo expectServiceResolved(
|
||||
String serviceType, int timeoutMilliseconds, Predicate<NsdServiceInfo> predicate)
|
||||
throws Exception {
|
||||
NsdServiceInfo discoveredServiceInfo = discoverService(serviceType);
|
||||
CompletableFuture<NsdServiceInfo> future = new CompletableFuture<>();
|
||||
NsdManager.ServiceInfoCallback callback =
|
||||
new DefaultServiceInfoCallback() {
|
||||
@Override
|
||||
public void onServiceUpdated(@NonNull NsdServiceInfo serviceInfo) {
|
||||
if (predicate.test(serviceInfo)) {
|
||||
future.complete(serviceInfo);
|
||||
}
|
||||
}
|
||||
};
|
||||
mNsdManager.registerServiceInfoCallback(discoveredServiceInfo, mExecutor, callback);
|
||||
try {
|
||||
return future.get(timeoutMilliseconds, MILLISECONDS);
|
||||
} finally {
|
||||
mNsdManager.unregisterServiceInfoCallback(callback);
|
||||
}
|
||||
}
|
||||
|
||||
TestNetworkTracker setUpTestNetwork() {
|
||||
return runAsShell(
|
||||
MANAGE_TEST_NETWORKS,
|
||||
() -> initTestNetwork(mContext, new LinkAddress("2001:db8:123::/64"), 10_000));
|
||||
}
|
||||
|
||||
void tearDownTestNetwork(TestNetworkTracker testNetwork) {
|
||||
runAsShell(MANAGE_TEST_NETWORKS, () -> testNetwork.teardown());
|
||||
}
|
||||
|
||||
private static class DefaultDiscoveryListener implements NsdManager.DiscoveryListener {
|
||||
@Override
|
||||
public void onStartDiscoveryFailed(String serviceType, int errorCode) {}
|
||||
|
||||
@Override
|
||||
public void onStopDiscoveryFailed(String serviceType, int errorCode) {}
|
||||
|
||||
@Override
|
||||
public void onDiscoveryStarted(String serviceType) {}
|
||||
|
||||
@Override
|
||||
public void onDiscoveryStopped(String serviceType) {}
|
||||
|
||||
@Override
|
||||
public void onServiceFound(NsdServiceInfo serviceInfo) {}
|
||||
|
||||
@Override
|
||||
public void onServiceLost(NsdServiceInfo serviceInfo) {}
|
||||
}
|
||||
|
||||
private static class DefaultServiceInfoCallback implements NsdManager.ServiceInfoCallback {
|
||||
@Override
|
||||
public void onServiceInfoCallbackRegistrationFailed(int errorCode) {}
|
||||
|
||||
@Override
|
||||
public void onServiceUpdated(@NonNull NsdServiceInfo serviceInfo) {}
|
||||
|
||||
@Override
|
||||
public void onServiceLost() {}
|
||||
|
||||
@Override
|
||||
public void onServiceInfoCallbackUnregistered() {}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,367 @@
|
|||
/*
|
||||
* Copyright (C) 2024 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.server.thread;
|
||||
|
||||
import static android.net.nsd.NsdManager.FAILURE_INTERNAL_ERROR;
|
||||
import static android.net.nsd.NsdManager.PROTOCOL_DNS_SD;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.doThrow;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
import android.net.nsd.NsdManager;
|
||||
import android.net.nsd.NsdServiceInfo;
|
||||
import android.os.Handler;
|
||||
import android.os.test.TestLooper;
|
||||
|
||||
import com.android.server.thread.openthread.DnsTxtAttribute;
|
||||
import com.android.server.thread.openthread.INsdStatusReceiver;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
/** Unit tests for {@link NsdPublisher}. */
|
||||
public final class NsdPublisherTest {
|
||||
@Mock private NsdManager mMockNsdManager;
|
||||
|
||||
@Mock private INsdStatusReceiver mRegistrationReceiver;
|
||||
@Mock private INsdStatusReceiver mUnregistrationReceiver;
|
||||
|
||||
private TestLooper mTestLooper;
|
||||
private NsdPublisher mNsdPublisher;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void registerService_nsdManagerSucceeds_serviceRegistrationSucceeds() throws Exception {
|
||||
prepareTest();
|
||||
|
||||
DnsTxtAttribute txt1 = makeTxtAttribute("key1", List.of(0x01, 0x02));
|
||||
DnsTxtAttribute txt2 = makeTxtAttribute("key2", List.of(0x03));
|
||||
|
||||
mNsdPublisher.registerService(
|
||||
null,
|
||||
"MyService",
|
||||
"_test._tcp",
|
||||
List.of("_subtype1", "_subtype2"),
|
||||
12345,
|
||||
List.of(txt1, txt2),
|
||||
mRegistrationReceiver,
|
||||
16 /* listenerId */);
|
||||
|
||||
mTestLooper.dispatchAll();
|
||||
|
||||
ArgumentCaptor<NsdServiceInfo> actualServiceInfoCaptor =
|
||||
ArgumentCaptor.forClass(NsdServiceInfo.class);
|
||||
ArgumentCaptor<NsdManager.RegistrationListener> actualRegistrationListenerCaptor =
|
||||
ArgumentCaptor.forClass(NsdManager.RegistrationListener.class);
|
||||
|
||||
verify(mMockNsdManager, times(1))
|
||||
.registerService(
|
||||
actualServiceInfoCaptor.capture(),
|
||||
eq(PROTOCOL_DNS_SD),
|
||||
any(),
|
||||
actualRegistrationListenerCaptor.capture());
|
||||
|
||||
NsdServiceInfo actualServiceInfo = actualServiceInfoCaptor.getValue();
|
||||
NsdManager.RegistrationListener actualRegistrationListener =
|
||||
actualRegistrationListenerCaptor.getValue();
|
||||
|
||||
actualRegistrationListener.onServiceRegistered(actualServiceInfo);
|
||||
mTestLooper.dispatchAll();
|
||||
|
||||
assertThat(actualServiceInfo.getServiceName()).isEqualTo("MyService");
|
||||
assertThat(actualServiceInfo.getServiceType()).isEqualTo("_test._tcp");
|
||||
assertThat(actualServiceInfo.getSubtypes()).isEqualTo(Set.of("_subtype1", "_subtype2"));
|
||||
assertThat(actualServiceInfo.getPort()).isEqualTo(12345);
|
||||
assertThat(actualServiceInfo.getAttributes().size()).isEqualTo(2);
|
||||
assertThat(actualServiceInfo.getAttributes().get("key1"))
|
||||
.isEqualTo(new byte[] {(byte) 0x01, (byte) 0x02});
|
||||
assertThat(actualServiceInfo.getAttributes().get("key2"))
|
||||
.isEqualTo(new byte[] {(byte) 0x03});
|
||||
|
||||
verify(mRegistrationReceiver, times(1)).onSuccess();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void registerService_nsdManagerFails_serviceRegistrationFails() throws Exception {
|
||||
prepareTest();
|
||||
|
||||
DnsTxtAttribute txt1 = makeTxtAttribute("key1", List.of(0x01, 0x02));
|
||||
DnsTxtAttribute txt2 = makeTxtAttribute("key2", List.of(0x03));
|
||||
|
||||
mNsdPublisher.registerService(
|
||||
null,
|
||||
"MyService",
|
||||
"_test._tcp",
|
||||
List.of("_subtype1", "_subtype2"),
|
||||
12345,
|
||||
List.of(txt1, txt2),
|
||||
mRegistrationReceiver,
|
||||
16 /* listenerId */);
|
||||
|
||||
mTestLooper.dispatchAll();
|
||||
|
||||
ArgumentCaptor<NsdServiceInfo> actualServiceInfoCaptor =
|
||||
ArgumentCaptor.forClass(NsdServiceInfo.class);
|
||||
ArgumentCaptor<NsdManager.RegistrationListener> actualRegistrationListenerCaptor =
|
||||
ArgumentCaptor.forClass(NsdManager.RegistrationListener.class);
|
||||
|
||||
verify(mMockNsdManager, times(1))
|
||||
.registerService(
|
||||
actualServiceInfoCaptor.capture(),
|
||||
eq(PROTOCOL_DNS_SD),
|
||||
any(Executor.class),
|
||||
actualRegistrationListenerCaptor.capture());
|
||||
|
||||
NsdServiceInfo actualServiceInfo = actualServiceInfoCaptor.getValue();
|
||||
NsdManager.RegistrationListener actualRegistrationListener =
|
||||
actualRegistrationListenerCaptor.getValue();
|
||||
|
||||
actualRegistrationListener.onRegistrationFailed(actualServiceInfo, FAILURE_INTERNAL_ERROR);
|
||||
mTestLooper.dispatchAll();
|
||||
|
||||
assertThat(actualServiceInfo.getServiceName()).isEqualTo("MyService");
|
||||
assertThat(actualServiceInfo.getServiceType()).isEqualTo("_test._tcp");
|
||||
assertThat(actualServiceInfo.getSubtypes()).isEqualTo(Set.of("_subtype1", "_subtype2"));
|
||||
assertThat(actualServiceInfo.getPort()).isEqualTo(12345);
|
||||
assertThat(actualServiceInfo.getAttributes().size()).isEqualTo(2);
|
||||
assertThat(actualServiceInfo.getAttributes().get("key1"))
|
||||
.isEqualTo(new byte[] {(byte) 0x01, (byte) 0x02});
|
||||
assertThat(actualServiceInfo.getAttributes().get("key2"))
|
||||
.isEqualTo(new byte[] {(byte) 0x03});
|
||||
|
||||
verify(mRegistrationReceiver, times(1)).onError(FAILURE_INTERNAL_ERROR);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void registerService_nsdManagerThrows_serviceRegistrationFails() throws Exception {
|
||||
prepareTest();
|
||||
|
||||
DnsTxtAttribute txt1 = makeTxtAttribute("key1", List.of(0x01, 0x02));
|
||||
DnsTxtAttribute txt2 = makeTxtAttribute("key2", List.of(0x03));
|
||||
|
||||
doThrow(new IllegalArgumentException("NsdManager fails"))
|
||||
.when(mMockNsdManager)
|
||||
.registerService(any(), anyInt(), any(Executor.class), any());
|
||||
|
||||
mNsdPublisher.registerService(
|
||||
null,
|
||||
"MyService",
|
||||
"_test._tcp",
|
||||
List.of("_subtype1", "_subtype2"),
|
||||
12345,
|
||||
List.of(txt1, txt2),
|
||||
mRegistrationReceiver,
|
||||
16 /* listenerId */);
|
||||
mTestLooper.dispatchAll();
|
||||
|
||||
verify(mRegistrationReceiver, times(1)).onError(FAILURE_INTERNAL_ERROR);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void unregisterService_nsdManagerSucceeds_serviceUnregistrationSucceeds()
|
||||
throws Exception {
|
||||
prepareTest();
|
||||
|
||||
DnsTxtAttribute txt1 = makeTxtAttribute("key1", List.of(0x01, 0x02));
|
||||
DnsTxtAttribute txt2 = makeTxtAttribute("key2", List.of(0x03));
|
||||
|
||||
mNsdPublisher.registerService(
|
||||
null,
|
||||
"MyService",
|
||||
"_test._tcp",
|
||||
List.of("_subtype1", "_subtype2"),
|
||||
12345,
|
||||
List.of(txt1, txt2),
|
||||
mRegistrationReceiver,
|
||||
16 /* listenerId */);
|
||||
|
||||
mTestLooper.dispatchAll();
|
||||
|
||||
ArgumentCaptor<NsdServiceInfo> actualServiceInfoCaptor =
|
||||
ArgumentCaptor.forClass(NsdServiceInfo.class);
|
||||
ArgumentCaptor<NsdManager.RegistrationListener> actualRegistrationListenerCaptor =
|
||||
ArgumentCaptor.forClass(NsdManager.RegistrationListener.class);
|
||||
|
||||
verify(mMockNsdManager, times(1))
|
||||
.registerService(
|
||||
actualServiceInfoCaptor.capture(),
|
||||
eq(PROTOCOL_DNS_SD),
|
||||
any(Executor.class),
|
||||
actualRegistrationListenerCaptor.capture());
|
||||
|
||||
NsdServiceInfo actualServiceInfo = actualServiceInfoCaptor.getValue();
|
||||
NsdManager.RegistrationListener actualRegistrationListener =
|
||||
actualRegistrationListenerCaptor.getValue();
|
||||
|
||||
actualRegistrationListener.onServiceRegistered(actualServiceInfo);
|
||||
mNsdPublisher.unregister(mUnregistrationReceiver, 16 /* listenerId */);
|
||||
mTestLooper.dispatchAll();
|
||||
verify(mMockNsdManager, times(1)).unregisterService(actualRegistrationListener);
|
||||
|
||||
actualRegistrationListener.onServiceUnregistered(actualServiceInfo);
|
||||
mTestLooper.dispatchAll();
|
||||
verify(mUnregistrationReceiver, times(1)).onSuccess();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void unregisterService_nsdManagerFails_serviceUnregistrationFails() throws Exception {
|
||||
prepareTest();
|
||||
|
||||
DnsTxtAttribute txt1 = makeTxtAttribute("key1", List.of(0x01, 0x02));
|
||||
DnsTxtAttribute txt2 = makeTxtAttribute("key2", List.of(0x03));
|
||||
|
||||
mNsdPublisher.registerService(
|
||||
null,
|
||||
"MyService",
|
||||
"_test._tcp",
|
||||
List.of("_subtype1", "_subtype2"),
|
||||
12345,
|
||||
List.of(txt1, txt2),
|
||||
mRegistrationReceiver,
|
||||
16 /* listenerId */);
|
||||
|
||||
mTestLooper.dispatchAll();
|
||||
|
||||
ArgumentCaptor<NsdServiceInfo> actualServiceInfoCaptor =
|
||||
ArgumentCaptor.forClass(NsdServiceInfo.class);
|
||||
ArgumentCaptor<NsdManager.RegistrationListener> actualRegistrationListenerCaptor =
|
||||
ArgumentCaptor.forClass(NsdManager.RegistrationListener.class);
|
||||
|
||||
verify(mMockNsdManager, times(1))
|
||||
.registerService(
|
||||
actualServiceInfoCaptor.capture(),
|
||||
eq(PROTOCOL_DNS_SD),
|
||||
any(Executor.class),
|
||||
actualRegistrationListenerCaptor.capture());
|
||||
|
||||
NsdServiceInfo actualServiceInfo = actualServiceInfoCaptor.getValue();
|
||||
NsdManager.RegistrationListener actualRegistrationListener =
|
||||
actualRegistrationListenerCaptor.getValue();
|
||||
|
||||
actualRegistrationListener.onServiceRegistered(actualServiceInfo);
|
||||
mNsdPublisher.unregister(mUnregistrationReceiver, 16 /* listenerId */);
|
||||
mTestLooper.dispatchAll();
|
||||
verify(mMockNsdManager, times(1)).unregisterService(actualRegistrationListener);
|
||||
|
||||
actualRegistrationListener.onUnregistrationFailed(
|
||||
actualServiceInfo, FAILURE_INTERNAL_ERROR);
|
||||
mTestLooper.dispatchAll();
|
||||
verify(mUnregistrationReceiver, times(1)).onError(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onOtDaemonDied_unregisterAll() {
|
||||
prepareTest();
|
||||
|
||||
DnsTxtAttribute txt1 = makeTxtAttribute("key1", List.of(0x01, 0x02));
|
||||
DnsTxtAttribute txt2 = makeTxtAttribute("key2", List.of(0x03));
|
||||
|
||||
ArgumentCaptor<NsdServiceInfo> actualServiceInfoCaptor =
|
||||
ArgumentCaptor.forClass(NsdServiceInfo.class);
|
||||
ArgumentCaptor<NsdManager.RegistrationListener> actualRegistrationListenerCaptor =
|
||||
ArgumentCaptor.forClass(NsdManager.RegistrationListener.class);
|
||||
|
||||
mNsdPublisher.registerService(
|
||||
null,
|
||||
"MyService",
|
||||
"_test._tcp",
|
||||
List.of("_subtype1", "_subtype2"),
|
||||
12345,
|
||||
List.of(txt1, txt2),
|
||||
mRegistrationReceiver,
|
||||
16 /* listenerId */);
|
||||
mTestLooper.dispatchAll();
|
||||
|
||||
verify(mMockNsdManager, times(1))
|
||||
.registerService(
|
||||
actualServiceInfoCaptor.capture(),
|
||||
eq(PROTOCOL_DNS_SD),
|
||||
any(Executor.class),
|
||||
actualRegistrationListenerCaptor.capture());
|
||||
NsdManager.RegistrationListener actualListener1 =
|
||||
actualRegistrationListenerCaptor.getValue();
|
||||
actualListener1.onServiceRegistered(actualServiceInfoCaptor.getValue());
|
||||
|
||||
mNsdPublisher.registerService(
|
||||
null,
|
||||
"MyService2",
|
||||
"_test._udp",
|
||||
Collections.emptyList(),
|
||||
11111,
|
||||
Collections.emptyList(),
|
||||
mRegistrationReceiver,
|
||||
17 /* listenerId */);
|
||||
|
||||
mTestLooper.dispatchAll();
|
||||
|
||||
verify(mMockNsdManager, times(2))
|
||||
.registerService(
|
||||
actualServiceInfoCaptor.capture(),
|
||||
eq(PROTOCOL_DNS_SD),
|
||||
any(Executor.class),
|
||||
actualRegistrationListenerCaptor.capture());
|
||||
NsdManager.RegistrationListener actualListener2 =
|
||||
actualRegistrationListenerCaptor.getAllValues().get(1);
|
||||
actualListener2.onServiceRegistered(actualServiceInfoCaptor.getValue());
|
||||
|
||||
mNsdPublisher.onOtDaemonDied();
|
||||
mTestLooper.dispatchAll();
|
||||
|
||||
verify(mMockNsdManager, times(1)).unregisterService(actualListener1);
|
||||
verify(mMockNsdManager, times(1)).unregisterService(actualListener2);
|
||||
}
|
||||
|
||||
private static DnsTxtAttribute makeTxtAttribute(String name, List<Integer> value) {
|
||||
DnsTxtAttribute txtAttribute = new DnsTxtAttribute();
|
||||
|
||||
txtAttribute.name = name;
|
||||
txtAttribute.value = new byte[value.size()];
|
||||
|
||||
for (int i = 0; i < value.size(); ++i) {
|
||||
txtAttribute.value[i] = value.get(i).byteValue();
|
||||
}
|
||||
|
||||
return txtAttribute;
|
||||
}
|
||||
|
||||
// @Before and @Test run in different threads. NsdPublisher requires the jobs are run on the
|
||||
// thread looper, so TestLooper needs to be created inside each test case to install the
|
||||
// correct looper.
|
||||
private void prepareTest() {
|
||||
mTestLooper = new TestLooper();
|
||||
Handler handler = new Handler(mTestLooper.getLooper());
|
||||
mNsdPublisher = new NsdPublisher(mMockNsdManager, handler);
|
||||
}
|
||||
}
|
||||
|
|
@ -87,6 +87,7 @@ public final class ThreadNetworkControllerServiceTest {
|
|||
@Mock private ParcelFileDescriptor mMockTunFd;
|
||||
@Mock private InfraInterfaceController mMockInfraIfController;
|
||||
@Mock private ThreadPersistentSettings mMockPersistentSettings;
|
||||
@Mock private NsdPublisher mMockNsdPublisher;
|
||||
private Context mContext;
|
||||
private TestLooper mTestLooper;
|
||||
private FakeOtDaemon mFakeOtDaemon;
|
||||
|
|
@ -117,12 +118,13 @@ public final class ThreadNetworkControllerServiceTest {
|
|||
mMockConnectivityManager,
|
||||
mMockTunIfController,
|
||||
mMockInfraIfController,
|
||||
mMockPersistentSettings);
|
||||
mMockPersistentSettings,
|
||||
mMockNsdPublisher);
|
||||
mService.setTestNetworkAgent(mMockNetworkAgent);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void initialize_tunInterfaceSetToOtDaemon() throws Exception {
|
||||
public void initialize_tunInterfaceAndNsdPublisherSetToOtDaemon() throws Exception {
|
||||
when(mMockTunIfController.getTunFd()).thenReturn(mMockTunFd);
|
||||
|
||||
mService.initialize();
|
||||
|
|
@ -130,6 +132,7 @@ public final class ThreadNetworkControllerServiceTest {
|
|||
|
||||
verify(mMockTunIfController, times(1)).createTunInterface();
|
||||
assertThat(mFakeOtDaemon.getTunFd()).isEqualTo(mMockTunFd);
|
||||
assertThat(mFakeOtDaemon.getNsdPublisher()).isEqualTo(mMockNsdPublisher);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
|||
Loading…
Reference in a new issue