Merge changes Iad0b0d52,Iee727308 am: 6eb0654f9b am: 27f85f972f
Original change: https://android-review.googlesource.com/c/platform/packages/modules/Connectivity/+/1793508 Change-Id: I61ed3eabd942af24f499605cfe5c5b9c883bfe7a
This commit is contained in:
@@ -1,594 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2012 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package android.net.cts;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.net.nsd.NsdManager;
|
|
||||||
import android.net.nsd.NsdServiceInfo;
|
|
||||||
import android.platform.test.annotations.AppModeFull;
|
|
||||||
import android.test.AndroidTestCase;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.net.ServerSocket;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Random;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
|
|
||||||
@AppModeFull(reason = "Socket cannot bind in instant app mode")
|
|
||||||
public class NsdManagerTest extends AndroidTestCase {
|
|
||||||
|
|
||||||
private static final String TAG = "NsdManagerTest";
|
|
||||||
private static final String SERVICE_TYPE = "_nmt._tcp";
|
|
||||||
private static final int TIMEOUT = 2000;
|
|
||||||
|
|
||||||
private static final boolean DBG = false;
|
|
||||||
|
|
||||||
NsdManager mNsdManager;
|
|
||||||
|
|
||||||
NsdManager.RegistrationListener mRegistrationListener;
|
|
||||||
NsdManager.DiscoveryListener mDiscoveryListener;
|
|
||||||
NsdManager.ResolveListener mResolveListener;
|
|
||||||
private NsdServiceInfo mResolvedService;
|
|
||||||
|
|
||||||
public NsdManagerTest() {
|
|
||||||
initRegistrationListener();
|
|
||||||
initDiscoveryListener();
|
|
||||||
initResolveListener();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initRegistrationListener() {
|
|
||||||
mRegistrationListener = new NsdManager.RegistrationListener() {
|
|
||||||
@Override
|
|
||||||
public void onRegistrationFailed(NsdServiceInfo serviceInfo, int errorCode) {
|
|
||||||
setEvent("onRegistrationFailed", errorCode);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onUnregistrationFailed(NsdServiceInfo serviceInfo, int errorCode) {
|
|
||||||
setEvent("onUnregistrationFailed", errorCode);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onServiceRegistered(NsdServiceInfo serviceInfo) {
|
|
||||||
setEvent("onServiceRegistered", serviceInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onServiceUnregistered(NsdServiceInfo serviceInfo) {
|
|
||||||
setEvent("onServiceUnregistered", serviceInfo);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initDiscoveryListener() {
|
|
||||||
mDiscoveryListener = new NsdManager.DiscoveryListener() {
|
|
||||||
@Override
|
|
||||||
public void onStartDiscoveryFailed(String serviceType, int errorCode) {
|
|
||||||
setEvent("onStartDiscoveryFailed", errorCode);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onStopDiscoveryFailed(String serviceType, int errorCode) {
|
|
||||||
setEvent("onStopDiscoveryFailed", errorCode);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDiscoveryStarted(String serviceType) {
|
|
||||||
NsdServiceInfo info = new NsdServiceInfo();
|
|
||||||
info.setServiceType(serviceType);
|
|
||||||
setEvent("onDiscoveryStarted", info);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDiscoveryStopped(String serviceType) {
|
|
||||||
NsdServiceInfo info = new NsdServiceInfo();
|
|
||||||
info.setServiceType(serviceType);
|
|
||||||
setEvent("onDiscoveryStopped", info);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onServiceFound(NsdServiceInfo serviceInfo) {
|
|
||||||
setEvent("onServiceFound", serviceInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onServiceLost(NsdServiceInfo serviceInfo) {
|
|
||||||
setEvent("onServiceLost", serviceInfo);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initResolveListener() {
|
|
||||||
mResolveListener = new NsdManager.ResolveListener() {
|
|
||||||
@Override
|
|
||||||
public void onResolveFailed(NsdServiceInfo serviceInfo, int errorCode) {
|
|
||||||
setEvent("onResolveFailed", errorCode);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onServiceResolved(NsdServiceInfo serviceInfo) {
|
|
||||||
mResolvedService = serviceInfo;
|
|
||||||
setEvent("onServiceResolved", serviceInfo);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private final class EventData {
|
|
||||||
EventData(String callbackName, NsdServiceInfo info) {
|
|
||||||
mCallbackName = callbackName;
|
|
||||||
mSucceeded = true;
|
|
||||||
mErrorCode = 0;
|
|
||||||
mInfo = info;
|
|
||||||
}
|
|
||||||
EventData(String callbackName, int errorCode) {
|
|
||||||
mCallbackName = callbackName;
|
|
||||||
mSucceeded = false;
|
|
||||||
mErrorCode = errorCode;
|
|
||||||
mInfo = null;
|
|
||||||
}
|
|
||||||
private final String mCallbackName;
|
|
||||||
private final boolean mSucceeded;
|
|
||||||
private final int mErrorCode;
|
|
||||||
private final NsdServiceInfo mInfo;
|
|
||||||
}
|
|
||||||
|
|
||||||
private final List<EventData> mEventCache = new ArrayList<EventData>();
|
|
||||||
|
|
||||||
private void setEvent(String callbackName, int errorCode) {
|
|
||||||
if (DBG) Log.d(TAG, callbackName + " failed with " + String.valueOf(errorCode));
|
|
||||||
EventData eventData = new EventData(callbackName, errorCode);
|
|
||||||
synchronized (mEventCache) {
|
|
||||||
mEventCache.add(eventData);
|
|
||||||
mEventCache.notify();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setEvent(String callbackName, NsdServiceInfo info) {
|
|
||||||
if (DBG) Log.d(TAG, "Received event " + callbackName + " for " + info.getServiceName());
|
|
||||||
EventData eventData = new EventData(callbackName, info);
|
|
||||||
synchronized (mEventCache) {
|
|
||||||
mEventCache.add(eventData);
|
|
||||||
mEventCache.notify();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void clearEventCache() {
|
|
||||||
synchronized(mEventCache) {
|
|
||||||
mEventCache.clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int eventCacheSize() {
|
|
||||||
synchronized(mEventCache) {
|
|
||||||
return mEventCache.size();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private int mWaitId = 0;
|
|
||||||
private EventData waitForCallback(String callbackName) {
|
|
||||||
|
|
||||||
synchronized(mEventCache) {
|
|
||||||
|
|
||||||
mWaitId ++;
|
|
||||||
if (DBG) Log.d(TAG, "Waiting for " + callbackName + ", id=" + String.valueOf(mWaitId));
|
|
||||||
|
|
||||||
try {
|
|
||||||
long startTime = android.os.SystemClock.uptimeMillis();
|
|
||||||
long elapsedTime = 0;
|
|
||||||
int index = 0;
|
|
||||||
while (elapsedTime < TIMEOUT ) {
|
|
||||||
// first check if we've received that event
|
|
||||||
for (; index < mEventCache.size(); index++) {
|
|
||||||
EventData e = mEventCache.get(index);
|
|
||||||
if (e.mCallbackName.equals(callbackName)) {
|
|
||||||
if (DBG) Log.d(TAG, "exiting wait id=" + String.valueOf(mWaitId));
|
|
||||||
return e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Not yet received, just wait
|
|
||||||
mEventCache.wait(TIMEOUT - elapsedTime);
|
|
||||||
elapsedTime = android.os.SystemClock.uptimeMillis() - startTime;
|
|
||||||
}
|
|
||||||
// we exited the loop because of TIMEOUT; fail the call
|
|
||||||
if (DBG) Log.d(TAG, "timed out waiting id=" + String.valueOf(mWaitId));
|
|
||||||
return null;
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
return null; // wait timed out!
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private EventData waitForNewEvents() throws InterruptedException {
|
|
||||||
if (DBG) Log.d(TAG, "Waiting for a bit, id=" + String.valueOf(mWaitId));
|
|
||||||
|
|
||||||
long startTime = android.os.SystemClock.uptimeMillis();
|
|
||||||
long elapsedTime = 0;
|
|
||||||
synchronized (mEventCache) {
|
|
||||||
int index = mEventCache.size();
|
|
||||||
while (elapsedTime < TIMEOUT ) {
|
|
||||||
// first check if we've received that event
|
|
||||||
for (; index < mEventCache.size(); index++) {
|
|
||||||
EventData e = mEventCache.get(index);
|
|
||||||
return e;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Not yet received, just wait
|
|
||||||
mEventCache.wait(TIMEOUT - elapsedTime);
|
|
||||||
elapsedTime = android.os.SystemClock.uptimeMillis() - startTime;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private String mServiceName;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setUp() throws Exception {
|
|
||||||
super.setUp();
|
|
||||||
if (DBG) Log.d(TAG, "Setup test ...");
|
|
||||||
mNsdManager = (NsdManager) getContext().getSystemService(Context.NSD_SERVICE);
|
|
||||||
|
|
||||||
Random rand = new Random();
|
|
||||||
mServiceName = new String("NsdTest");
|
|
||||||
for (int i = 0; i < 4; i++) {
|
|
||||||
mServiceName = mServiceName + String.valueOf(rand.nextInt(10));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void tearDown() throws Exception {
|
|
||||||
if (DBG) Log.d(TAG, "Tear down test ...");
|
|
||||||
super.tearDown();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testNDSManager() throws Exception {
|
|
||||||
EventData lastEvent = null;
|
|
||||||
|
|
||||||
if (DBG) Log.d(TAG, "Starting test ...");
|
|
||||||
|
|
||||||
NsdServiceInfo si = new NsdServiceInfo();
|
|
||||||
si.setServiceType(SERVICE_TYPE);
|
|
||||||
si.setServiceName(mServiceName);
|
|
||||||
|
|
||||||
byte testByteArray[] = new byte[] {-128, 127, 2, 1, 0, 1, 2};
|
|
||||||
String String256 = "1_________2_________3_________4_________5_________6_________" +
|
|
||||||
"7_________8_________9_________10________11________12________13________" +
|
|
||||||
"14________15________16________17________18________19________20________" +
|
|
||||||
"21________22________23________24________25________123456";
|
|
||||||
|
|
||||||
// Illegal attributes
|
|
||||||
try {
|
|
||||||
si.setAttribute(null, (String) null);
|
|
||||||
fail("Could set null key");
|
|
||||||
} catch (IllegalArgumentException e) {
|
|
||||||
// expected
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
si.setAttribute("", (String) null);
|
|
||||||
fail("Could set empty key");
|
|
||||||
} catch (IllegalArgumentException e) {
|
|
||||||
// expected
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
si.setAttribute(String256, (String) null);
|
|
||||||
fail("Could set key with 255 characters");
|
|
||||||
} catch (IllegalArgumentException e) {
|
|
||||||
// expected
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
si.setAttribute("key", String256.substring(3));
|
|
||||||
fail("Could set key+value combination with more than 255 characters");
|
|
||||||
} catch (IllegalArgumentException e) {
|
|
||||||
// expected
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
si.setAttribute("key", String256.substring(4));
|
|
||||||
fail("Could set key+value combination with 255 characters");
|
|
||||||
} catch (IllegalArgumentException e) {
|
|
||||||
// expected
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
si.setAttribute(new String(new byte[]{0x19}), (String) null);
|
|
||||||
fail("Could set key with invalid character");
|
|
||||||
} catch (IllegalArgumentException e) {
|
|
||||||
// expected
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
si.setAttribute("=", (String) null);
|
|
||||||
fail("Could set key with invalid character");
|
|
||||||
} catch (IllegalArgumentException e) {
|
|
||||||
// expected
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
si.setAttribute(new String(new byte[]{0x7F}), (String) null);
|
|
||||||
fail("Could set key with invalid character");
|
|
||||||
} catch (IllegalArgumentException e) {
|
|
||||||
// expected
|
|
||||||
}
|
|
||||||
|
|
||||||
// Allowed attributes
|
|
||||||
si.setAttribute("booleanAttr", (String) null);
|
|
||||||
si.setAttribute("keyValueAttr", "value");
|
|
||||||
si.setAttribute("keyEqualsAttr", "=");
|
|
||||||
si.setAttribute(" whiteSpaceKeyValueAttr ", " value ");
|
|
||||||
si.setAttribute("binaryDataAttr", testByteArray);
|
|
||||||
si.setAttribute("nullBinaryDataAttr", (byte[]) null);
|
|
||||||
si.setAttribute("emptyBinaryDataAttr", new byte[]{});
|
|
||||||
si.setAttribute("longkey", String256.substring(9));
|
|
||||||
|
|
||||||
ServerSocket socket;
|
|
||||||
int localPort;
|
|
||||||
|
|
||||||
try {
|
|
||||||
socket = new ServerSocket(0);
|
|
||||||
localPort = socket.getLocalPort();
|
|
||||||
si.setPort(localPort);
|
|
||||||
} catch (IOException e) {
|
|
||||||
if (DBG) Log.d(TAG, "Could not open a local socket");
|
|
||||||
assertTrue(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (DBG) Log.d(TAG, "Port = " + String.valueOf(localPort));
|
|
||||||
|
|
||||||
clearEventCache();
|
|
||||||
|
|
||||||
mNsdManager.registerService(si, NsdManager.PROTOCOL_DNS_SD, mRegistrationListener);
|
|
||||||
lastEvent = waitForCallback("onServiceRegistered"); // id = 1
|
|
||||||
assertTrue(lastEvent != null);
|
|
||||||
assertTrue(lastEvent.mSucceeded);
|
|
||||||
assertTrue(eventCacheSize() == 1);
|
|
||||||
|
|
||||||
// We may not always get the name that we tried to register;
|
|
||||||
// This events tells us the name that was registered.
|
|
||||||
String registeredName = lastEvent.mInfo.getServiceName();
|
|
||||||
si.setServiceName(registeredName);
|
|
||||||
|
|
||||||
clearEventCache();
|
|
||||||
|
|
||||||
mNsdManager.discoverServices(SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD,
|
|
||||||
mDiscoveryListener);
|
|
||||||
|
|
||||||
// Expect discovery started
|
|
||||||
lastEvent = waitForCallback("onDiscoveryStarted"); // id = 2
|
|
||||||
|
|
||||||
assertTrue(lastEvent != null);
|
|
||||||
assertTrue(lastEvent.mSucceeded);
|
|
||||||
|
|
||||||
// Remove this event, so accounting becomes easier later
|
|
||||||
synchronized (mEventCache) {
|
|
||||||
mEventCache.remove(lastEvent);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Expect a service record to be discovered (and filter the ones
|
|
||||||
// that are unrelated to this test)
|
|
||||||
boolean found = false;
|
|
||||||
for (int i = 0; i < 32; i++) {
|
|
||||||
|
|
||||||
lastEvent = waitForCallback("onServiceFound"); // id = 3
|
|
||||||
if (lastEvent == null) {
|
|
||||||
// no more onServiceFound events are being reported!
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
assertTrue(lastEvent.mSucceeded);
|
|
||||||
|
|
||||||
if (DBG) Log.d(TAG, "id = " + String.valueOf(mWaitId) + ": ServiceName = " +
|
|
||||||
lastEvent.mInfo.getServiceName());
|
|
||||||
|
|
||||||
if (lastEvent.mInfo.getServiceName().equals(registeredName)) {
|
|
||||||
// Save it, as it will get overwritten with new serviceFound events
|
|
||||||
si = lastEvent.mInfo;
|
|
||||||
found = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove this event from the event cache, so it won't be found by subsequent
|
|
||||||
// calls to waitForCallback
|
|
||||||
synchronized (mEventCache) {
|
|
||||||
mEventCache.remove(lastEvent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
assertTrue(found);
|
|
||||||
|
|
||||||
// We've removed all serviceFound events, and we've removed the discoveryStarted
|
|
||||||
// event as well, so now the event cache should be empty!
|
|
||||||
assertTrue(eventCacheSize() == 0);
|
|
||||||
|
|
||||||
// Resolve the service
|
|
||||||
clearEventCache();
|
|
||||||
mNsdManager.resolveService(si, mResolveListener);
|
|
||||||
lastEvent = waitForCallback("onServiceResolved"); // id = 4
|
|
||||||
|
|
||||||
assertNotNull(mResolvedService);
|
|
||||||
|
|
||||||
// Check Txt attributes
|
|
||||||
assertEquals(8, mResolvedService.getAttributes().size());
|
|
||||||
assertTrue(mResolvedService.getAttributes().containsKey("booleanAttr"));
|
|
||||||
assertNull(mResolvedService.getAttributes().get("booleanAttr"));
|
|
||||||
assertEquals("value", new String(mResolvedService.getAttributes().get("keyValueAttr")));
|
|
||||||
assertEquals("=", new String(mResolvedService.getAttributes().get("keyEqualsAttr")));
|
|
||||||
assertEquals(" value ", new String(mResolvedService.getAttributes()
|
|
||||||
.get(" whiteSpaceKeyValueAttr ")));
|
|
||||||
assertEquals(String256.substring(9), new String(mResolvedService.getAttributes()
|
|
||||||
.get("longkey")));
|
|
||||||
assertTrue(Arrays.equals(testByteArray,
|
|
||||||
mResolvedService.getAttributes().get("binaryDataAttr")));
|
|
||||||
assertTrue(mResolvedService.getAttributes().containsKey("nullBinaryDataAttr"));
|
|
||||||
assertNull(mResolvedService.getAttributes().get("nullBinaryDataAttr"));
|
|
||||||
assertTrue(mResolvedService.getAttributes().containsKey("emptyBinaryDataAttr"));
|
|
||||||
assertNull(mResolvedService.getAttributes().get("emptyBinaryDataAttr"));
|
|
||||||
|
|
||||||
assertTrue(lastEvent != null);
|
|
||||||
assertTrue(lastEvent.mSucceeded);
|
|
||||||
|
|
||||||
if (DBG) Log.d(TAG, "id = " + String.valueOf(mWaitId) + ": Port = " +
|
|
||||||
String.valueOf(lastEvent.mInfo.getPort()));
|
|
||||||
|
|
||||||
assertTrue(lastEvent.mInfo.getPort() == localPort);
|
|
||||||
assertTrue(eventCacheSize() == 1);
|
|
||||||
|
|
||||||
checkForAdditionalEvents();
|
|
||||||
clearEventCache();
|
|
||||||
|
|
||||||
// Unregister the service
|
|
||||||
mNsdManager.unregisterService(mRegistrationListener);
|
|
||||||
lastEvent = waitForCallback("onServiceUnregistered"); // id = 5
|
|
||||||
|
|
||||||
assertTrue(lastEvent != null);
|
|
||||||
assertTrue(lastEvent.mSucceeded);
|
|
||||||
|
|
||||||
// Expect a callback for service lost
|
|
||||||
lastEvent = waitForCallback("onServiceLost"); // id = 6
|
|
||||||
|
|
||||||
assertTrue(lastEvent != null);
|
|
||||||
assertTrue(lastEvent.mInfo.getServiceName().equals(registeredName));
|
|
||||||
|
|
||||||
// Register service again to see if we discover it
|
|
||||||
checkForAdditionalEvents();
|
|
||||||
clearEventCache();
|
|
||||||
|
|
||||||
si = new NsdServiceInfo();
|
|
||||||
si.setServiceType(SERVICE_TYPE);
|
|
||||||
si.setServiceName(mServiceName);
|
|
||||||
si.setPort(localPort);
|
|
||||||
|
|
||||||
// Create a new registration listener and register same service again
|
|
||||||
initRegistrationListener();
|
|
||||||
|
|
||||||
mNsdManager.registerService(si, NsdManager.PROTOCOL_DNS_SD, mRegistrationListener);
|
|
||||||
|
|
||||||
lastEvent = waitForCallback("onServiceRegistered"); // id = 7
|
|
||||||
|
|
||||||
assertTrue(lastEvent != null);
|
|
||||||
assertTrue(lastEvent.mSucceeded);
|
|
||||||
|
|
||||||
registeredName = lastEvent.mInfo.getServiceName();
|
|
||||||
|
|
||||||
// Expect a record to be discovered
|
|
||||||
// Expect a service record to be discovered (and filter the ones
|
|
||||||
// that are unrelated to this test)
|
|
||||||
found = false;
|
|
||||||
for (int i = 0; i < 32; i++) {
|
|
||||||
|
|
||||||
lastEvent = waitForCallback("onServiceFound"); // id = 8
|
|
||||||
if (lastEvent == null) {
|
|
||||||
// no more onServiceFound events are being reported!
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
assertTrue(lastEvent.mSucceeded);
|
|
||||||
|
|
||||||
if (DBG) Log.d(TAG, "id = " + String.valueOf(mWaitId) + ": ServiceName = " +
|
|
||||||
lastEvent.mInfo.getServiceName());
|
|
||||||
|
|
||||||
if (lastEvent.mInfo.getServiceName().equals(registeredName)) {
|
|
||||||
// Save it, as it will get overwritten with new serviceFound events
|
|
||||||
si = lastEvent.mInfo;
|
|
||||||
found = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove this event from the event cache, so it won't be found by subsequent
|
|
||||||
// calls to waitForCallback
|
|
||||||
synchronized (mEventCache) {
|
|
||||||
mEventCache.remove(lastEvent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
assertTrue(found);
|
|
||||||
|
|
||||||
// Resolve the service
|
|
||||||
clearEventCache();
|
|
||||||
mNsdManager.resolveService(si, mResolveListener);
|
|
||||||
lastEvent = waitForCallback("onServiceResolved"); // id = 9
|
|
||||||
|
|
||||||
assertTrue(lastEvent != null);
|
|
||||||
assertTrue(lastEvent.mSucceeded);
|
|
||||||
|
|
||||||
if (DBG) Log.d(TAG, "id = " + String.valueOf(mWaitId) + ": ServiceName = " +
|
|
||||||
lastEvent.mInfo.getServiceName());
|
|
||||||
|
|
||||||
assertTrue(lastEvent.mInfo.getServiceName().equals(registeredName));
|
|
||||||
|
|
||||||
assertNotNull(mResolvedService);
|
|
||||||
|
|
||||||
// Check that we don't have any TXT records
|
|
||||||
assertEquals(0, mResolvedService.getAttributes().size());
|
|
||||||
|
|
||||||
checkForAdditionalEvents();
|
|
||||||
clearEventCache();
|
|
||||||
|
|
||||||
mNsdManager.stopServiceDiscovery(mDiscoveryListener);
|
|
||||||
lastEvent = waitForCallback("onDiscoveryStopped"); // id = 10
|
|
||||||
assertTrue(lastEvent != null);
|
|
||||||
assertTrue(lastEvent.mSucceeded);
|
|
||||||
assertTrue(checkCacheSize(1));
|
|
||||||
|
|
||||||
checkForAdditionalEvents();
|
|
||||||
clearEventCache();
|
|
||||||
|
|
||||||
mNsdManager.unregisterService(mRegistrationListener);
|
|
||||||
|
|
||||||
lastEvent = waitForCallback("onServiceUnregistered"); // id = 11
|
|
||||||
assertTrue(lastEvent != null);
|
|
||||||
assertTrue(lastEvent.mSucceeded);
|
|
||||||
assertTrue(checkCacheSize(1));
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean checkCacheSize(int size) {
|
|
||||||
synchronized (mEventCache) {
|
|
||||||
int cacheSize = mEventCache.size();
|
|
||||||
if (cacheSize != size) {
|
|
||||||
Log.d(TAG, "id = " + mWaitId + ": event cache size = " + cacheSize);
|
|
||||||
for (int i = 0; i < cacheSize; i++) {
|
|
||||||
EventData e = mEventCache.get(i);
|
|
||||||
String sname = (e.mInfo != null) ? "(" + e.mInfo.getServiceName() + ")" : "";
|
|
||||||
Log.d(TAG, "eventName is " + e.mCallbackName + sname);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return (cacheSize == size);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean checkForAdditionalEvents() {
|
|
||||||
try {
|
|
||||||
EventData e = waitForNewEvents();
|
|
||||||
if (e != null) {
|
|
||||||
String sname = (e.mInfo != null) ? "(" + e.mInfo.getServiceName() + ")" : "";
|
|
||||||
Log.d(TAG, "ignoring unexpected event " + e.mCallbackName + sname);
|
|
||||||
}
|
|
||||||
return (e == null);
|
|
||||||
}
|
|
||||||
catch (InterruptedException ex) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
325
tests/cts/net/src/android/net/cts/NsdManagerTest.kt
Normal file
325
tests/cts/net/src/android/net/cts/NsdManagerTest.kt
Normal file
@@ -0,0 +1,325 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2012 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package android.net.cts
|
||||||
|
|
||||||
|
import android.net.cts.NsdManagerTest.NsdDiscoveryRecord.DiscoveryEvent.DiscoveryStarted
|
||||||
|
import android.net.cts.NsdManagerTest.NsdDiscoveryRecord.DiscoveryEvent.DiscoveryStopped
|
||||||
|
import android.net.cts.NsdManagerTest.NsdDiscoveryRecord.DiscoveryEvent.ServiceFound
|
||||||
|
import android.net.cts.NsdManagerTest.NsdDiscoveryRecord.DiscoveryEvent.ServiceLost
|
||||||
|
import android.net.cts.NsdManagerTest.NsdDiscoveryRecord.DiscoveryEvent.StartDiscoveryFailed
|
||||||
|
import android.net.cts.NsdManagerTest.NsdDiscoveryRecord.DiscoveryEvent.StopDiscoveryFailed
|
||||||
|
import android.net.cts.NsdManagerTest.NsdRegistrationRecord.RegistrationEvent.RegistrationFailed
|
||||||
|
import android.net.cts.NsdManagerTest.NsdRegistrationRecord.RegistrationEvent.ServiceRegistered
|
||||||
|
import android.net.cts.NsdManagerTest.NsdRegistrationRecord.RegistrationEvent.ServiceUnregistered
|
||||||
|
import android.net.cts.NsdManagerTest.NsdRegistrationRecord.RegistrationEvent.UnregistrationFailed
|
||||||
|
import android.net.cts.NsdManagerTest.NsdResolveRecord.ResolveEvent.ResolveFailed
|
||||||
|
import android.net.cts.NsdManagerTest.NsdResolveRecord.ResolveEvent.ServiceResolved
|
||||||
|
import android.net.nsd.NsdManager
|
||||||
|
import android.net.nsd.NsdManager.DiscoveryListener
|
||||||
|
import android.net.nsd.NsdManager.RegistrationListener
|
||||||
|
import android.net.nsd.NsdManager.ResolveListener
|
||||||
|
import android.net.nsd.NsdServiceInfo
|
||||||
|
import android.platform.test.annotations.AppModeFull
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.test.platform.app.InstrumentationRegistry
|
||||||
|
import androidx.test.runner.AndroidJUnit4
|
||||||
|
import com.android.net.module.util.ArrayTrackRecord
|
||||||
|
import com.android.net.module.util.TrackRecord
|
||||||
|
import org.junit.Assert.assertArrayEquals
|
||||||
|
import org.junit.Assert.assertTrue
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
import java.net.ServerSocket
|
||||||
|
import java.nio.charset.StandardCharsets
|
||||||
|
import java.util.Random
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
import kotlin.test.assertFailsWith
|
||||||
|
import kotlin.test.assertNotNull
|
||||||
|
import kotlin.test.assertNull
|
||||||
|
import kotlin.test.assertTrue
|
||||||
|
import kotlin.test.fail
|
||||||
|
|
||||||
|
private const val TAG = "NsdManagerTest"
|
||||||
|
private const val SERVICE_TYPE = "_nmt._tcp"
|
||||||
|
private const val TIMEOUT_MS = 2000L
|
||||||
|
private const val DBG = false
|
||||||
|
|
||||||
|
@AppModeFull(reason = "Socket cannot bind in instant app mode")
|
||||||
|
@RunWith(AndroidJUnit4::class)
|
||||||
|
class NsdManagerTest {
|
||||||
|
private val context by lazy { InstrumentationRegistry.getInstrumentation().context }
|
||||||
|
private val nsdManager by lazy { context.getSystemService(NsdManager::class.java) }
|
||||||
|
private val serviceName = "NsdTest%04d".format(Random().nextInt(1000))
|
||||||
|
|
||||||
|
private interface NsdEvent
|
||||||
|
private open class NsdRecord<T : NsdEvent> private constructor(
|
||||||
|
private val history: ArrayTrackRecord<T>
|
||||||
|
) : TrackRecord<T> by history {
|
||||||
|
constructor() : this(ArrayTrackRecord())
|
||||||
|
|
||||||
|
val nextEvents = history.newReadHead()
|
||||||
|
|
||||||
|
inline fun <reified V : NsdEvent> expectCallbackEventually(
|
||||||
|
crossinline predicate: (V) -> Boolean = { true }
|
||||||
|
): V = nextEvents.poll(TIMEOUT_MS) { e -> e is V && predicate(e) } as V?
|
||||||
|
?: fail("Callback for ${V::class.java.simpleName} not seen after $TIMEOUT_MS ms")
|
||||||
|
|
||||||
|
inline fun <reified V : NsdEvent> expectCallback(): V {
|
||||||
|
val nextEvent = nextEvents.poll(TIMEOUT_MS)
|
||||||
|
assertNotNull(nextEvent, "No callback received after $TIMEOUT_MS ms")
|
||||||
|
assertTrue(nextEvent is V, "Expected ${V::class.java.simpleName} but got " +
|
||||||
|
nextEvent.javaClass.simpleName)
|
||||||
|
return nextEvent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class NsdRegistrationRecord : RegistrationListener,
|
||||||
|
NsdRecord<NsdRegistrationRecord.RegistrationEvent>() {
|
||||||
|
sealed class RegistrationEvent : NsdEvent {
|
||||||
|
abstract val serviceInfo: NsdServiceInfo
|
||||||
|
|
||||||
|
data class RegistrationFailed(
|
||||||
|
override val serviceInfo: NsdServiceInfo,
|
||||||
|
val errorCode: Int
|
||||||
|
) : RegistrationEvent()
|
||||||
|
|
||||||
|
data class UnregistrationFailed(
|
||||||
|
override val serviceInfo: NsdServiceInfo,
|
||||||
|
val errorCode: Int
|
||||||
|
) : RegistrationEvent()
|
||||||
|
|
||||||
|
data class ServiceRegistered(override val serviceInfo: NsdServiceInfo)
|
||||||
|
: RegistrationEvent()
|
||||||
|
data class ServiceUnregistered(override val serviceInfo: NsdServiceInfo)
|
||||||
|
: RegistrationEvent()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onRegistrationFailed(si: NsdServiceInfo, err: Int) {
|
||||||
|
add(RegistrationFailed(si, err))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onUnregistrationFailed(si: NsdServiceInfo, err: Int) {
|
||||||
|
add(UnregistrationFailed(si, err))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onServiceRegistered(si: NsdServiceInfo) {
|
||||||
|
add(ServiceRegistered(si))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onServiceUnregistered(si: NsdServiceInfo) {
|
||||||
|
add(ServiceUnregistered(si))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class NsdDiscoveryRecord : DiscoveryListener,
|
||||||
|
NsdRecord<NsdDiscoveryRecord.DiscoveryEvent>() {
|
||||||
|
sealed class DiscoveryEvent : NsdEvent {
|
||||||
|
data class StartDiscoveryFailed(val serviceType: String, val errorCode: Int)
|
||||||
|
: DiscoveryEvent()
|
||||||
|
|
||||||
|
data class StopDiscoveryFailed(val serviceType: String, val errorCode: Int)
|
||||||
|
: DiscoveryEvent()
|
||||||
|
|
||||||
|
data class DiscoveryStarted(val serviceType: String) : DiscoveryEvent()
|
||||||
|
data class DiscoveryStopped(val serviceType: String) : DiscoveryEvent()
|
||||||
|
data class ServiceFound(val serviceInfo: NsdServiceInfo) : DiscoveryEvent()
|
||||||
|
data class ServiceLost(val serviceInfo: NsdServiceInfo) : DiscoveryEvent()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onStartDiscoveryFailed(serviceType: String, err: Int) {
|
||||||
|
add(StartDiscoveryFailed(serviceType, err))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onStopDiscoveryFailed(serviceType: String, err: Int) {
|
||||||
|
add(StopDiscoveryFailed(serviceType, err))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDiscoveryStarted(serviceType: String) {
|
||||||
|
add(DiscoveryStarted(serviceType))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDiscoveryStopped(serviceType: String) {
|
||||||
|
add(DiscoveryStopped(serviceType))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onServiceFound(si: NsdServiceInfo) {
|
||||||
|
add(ServiceFound(si))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onServiceLost(si: NsdServiceInfo) {
|
||||||
|
add(ServiceLost(si))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun waitForServiceDiscovered(serviceName: String): NsdServiceInfo {
|
||||||
|
return expectCallbackEventually<ServiceFound> {
|
||||||
|
it.serviceInfo.serviceName == serviceName
|
||||||
|
}.serviceInfo
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class NsdResolveRecord : ResolveListener,
|
||||||
|
NsdRecord<NsdResolveRecord.ResolveEvent>() {
|
||||||
|
sealed class ResolveEvent : NsdEvent {
|
||||||
|
data class ResolveFailed(val serviceInfo: NsdServiceInfo, val errorCode: Int)
|
||||||
|
: ResolveEvent()
|
||||||
|
|
||||||
|
data class ServiceResolved(val serviceInfo: NsdServiceInfo) : ResolveEvent()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onResolveFailed(si: NsdServiceInfo, err: Int) {
|
||||||
|
add(ResolveFailed(si, err))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onServiceResolved(si: NsdServiceInfo) {
|
||||||
|
add(ServiceResolved(si))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testNsdManager() {
|
||||||
|
val si = NsdServiceInfo()
|
||||||
|
si.serviceType = SERVICE_TYPE
|
||||||
|
si.serviceName = serviceName
|
||||||
|
// Test binary data with various bytes
|
||||||
|
val testByteArray = byteArrayOf(-128, 127, 2, 1, 0, 1, 2)
|
||||||
|
// Test string data with 256 characters (25 blocks of 10 characters + 6)
|
||||||
|
val string256 = "1_________2_________3_________4_________5_________6_________" +
|
||||||
|
"7_________8_________9_________10________11________12________13________" +
|
||||||
|
"14________15________16________17________18________19________20________" +
|
||||||
|
"21________22________23________24________25________123456"
|
||||||
|
|
||||||
|
// Illegal attributes
|
||||||
|
listOf(
|
||||||
|
Triple(null, null, "null key"),
|
||||||
|
Triple("", null, "empty key"),
|
||||||
|
Triple(string256, null, "key with 256 characters"),
|
||||||
|
Triple("key", string256.substring(3),
|
||||||
|
"key+value combination with more than 255 characters"),
|
||||||
|
Triple("key", string256.substring(4), "key+value combination with 255 characters"),
|
||||||
|
Triple("\u0019", null, "key with invalid character"),
|
||||||
|
Triple("=", null, "key with invalid character"),
|
||||||
|
Triple("\u007f", null, "key with invalid character")
|
||||||
|
).forEach {
|
||||||
|
assertFailsWith<IllegalArgumentException>(
|
||||||
|
"Setting invalid ${it.third} unexpectedly succeeded") {
|
||||||
|
si.setAttribute(it.first, it.second)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allowed attributes
|
||||||
|
si.setAttribute("booleanAttr", null as String?)
|
||||||
|
si.setAttribute("keyValueAttr", "value")
|
||||||
|
si.setAttribute("keyEqualsAttr", "=")
|
||||||
|
si.setAttribute(" whiteSpaceKeyValueAttr ", " value ")
|
||||||
|
si.setAttribute("binaryDataAttr", testByteArray)
|
||||||
|
si.setAttribute("nullBinaryDataAttr", null as ByteArray?)
|
||||||
|
si.setAttribute("emptyBinaryDataAttr", byteArrayOf())
|
||||||
|
si.setAttribute("longkey", string256.substring(9))
|
||||||
|
val socket = ServerSocket(0)
|
||||||
|
val localPort = socket.localPort
|
||||||
|
si.port = localPort
|
||||||
|
if (DBG) Log.d(TAG, "Port = $localPort")
|
||||||
|
|
||||||
|
val registrationRecord = NsdRegistrationRecord()
|
||||||
|
val registeredInfo = registerService(registrationRecord, si)
|
||||||
|
|
||||||
|
val discoveryRecord = NsdDiscoveryRecord()
|
||||||
|
nsdManager.discoverServices(SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD, discoveryRecord)
|
||||||
|
|
||||||
|
// Expect discovery started
|
||||||
|
discoveryRecord.expectCallback<DiscoveryStarted>()
|
||||||
|
|
||||||
|
// Expect a service record to be discovered
|
||||||
|
val foundInfo = discoveryRecord.waitForServiceDiscovered(registeredInfo.serviceName)
|
||||||
|
|
||||||
|
val resolvedService = resolveService(foundInfo)
|
||||||
|
|
||||||
|
// Check Txt attributes
|
||||||
|
assertEquals(8, resolvedService.attributes.size)
|
||||||
|
assertTrue(resolvedService.attributes.containsKey("booleanAttr"))
|
||||||
|
assertNull(resolvedService.attributes["booleanAttr"])
|
||||||
|
assertEquals("value", resolvedService.attributes["keyValueAttr"].utf8ToString())
|
||||||
|
assertEquals("=", resolvedService.attributes["keyEqualsAttr"].utf8ToString())
|
||||||
|
assertEquals(" value ",
|
||||||
|
resolvedService.attributes[" whiteSpaceKeyValueAttr "].utf8ToString())
|
||||||
|
assertEquals(string256.substring(9), resolvedService.attributes["longkey"].utf8ToString())
|
||||||
|
assertArrayEquals(testByteArray, resolvedService.attributes["binaryDataAttr"])
|
||||||
|
assertTrue(resolvedService.attributes.containsKey("nullBinaryDataAttr"))
|
||||||
|
assertNull(resolvedService.attributes["nullBinaryDataAttr"])
|
||||||
|
assertTrue(resolvedService.attributes.containsKey("emptyBinaryDataAttr"))
|
||||||
|
assertNull(resolvedService.attributes["emptyBinaryDataAttr"])
|
||||||
|
assertEquals(localPort, resolvedService.port)
|
||||||
|
|
||||||
|
// Unregister the service
|
||||||
|
nsdManager.unregisterService(registrationRecord)
|
||||||
|
registrationRecord.expectCallback<ServiceUnregistered>()
|
||||||
|
|
||||||
|
// Expect a callback for service lost
|
||||||
|
discoveryRecord.expectCallbackEventually<ServiceLost> {
|
||||||
|
it.serviceInfo.serviceName == serviceName
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register service again to see if NsdManager can discover it
|
||||||
|
val si2 = NsdServiceInfo()
|
||||||
|
si2.serviceType = SERVICE_TYPE
|
||||||
|
si2.serviceName = serviceName
|
||||||
|
si2.port = localPort
|
||||||
|
val registrationRecord2 = NsdRegistrationRecord()
|
||||||
|
val registeredInfo2 = registerService(registrationRecord2, si2)
|
||||||
|
|
||||||
|
// Expect a service record to be discovered (and filter the ones
|
||||||
|
// that are unrelated to this test)
|
||||||
|
val foundInfo2 = discoveryRecord.waitForServiceDiscovered(registeredInfo2.serviceName)
|
||||||
|
|
||||||
|
// Resolve the service
|
||||||
|
val resolvedService2 = resolveService(foundInfo2)
|
||||||
|
|
||||||
|
// Check that the resolved service doesn't have any TXT records
|
||||||
|
assertEquals(0, resolvedService2.attributes.size)
|
||||||
|
|
||||||
|
nsdManager.stopServiceDiscovery(discoveryRecord)
|
||||||
|
|
||||||
|
discoveryRecord.expectCallbackEventually<DiscoveryStopped>()
|
||||||
|
|
||||||
|
nsdManager.unregisterService(registrationRecord2)
|
||||||
|
registrationRecord2.expectCallback<ServiceUnregistered>()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register a service and return its registration record.
|
||||||
|
*/
|
||||||
|
private fun registerService(record: NsdRegistrationRecord, si: NsdServiceInfo): NsdServiceInfo {
|
||||||
|
nsdManager.registerService(si, NsdManager.PROTOCOL_DNS_SD, record)
|
||||||
|
// We may not always get the name that we tried to register;
|
||||||
|
// This events tells us the name that was registered.
|
||||||
|
val cb = record.expectCallback<ServiceRegistered>()
|
||||||
|
return cb.serviceInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun resolveService(discoveredInfo: NsdServiceInfo): NsdServiceInfo {
|
||||||
|
val record = NsdResolveRecord()
|
||||||
|
nsdManager.resolveService(discoveredInfo, record)
|
||||||
|
val resolvedCb = record.expectCallback<ServiceResolved>()
|
||||||
|
assertEquals(discoveredInfo.serviceName, resolvedCb.serviceInfo.serviceName)
|
||||||
|
|
||||||
|
return resolvedCb.serviceInfo
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun ByteArray?.utf8ToString(): String {
|
||||||
|
if (this == null) return ""
|
||||||
|
return String(this, StandardCharsets.UTF_8)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user