1 /* 2 * Copyright (C) 2012 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.net.wifi.cts; 18 19 import android.content.Context; 20 import android.net.nsd.NsdManager; 21 import android.net.nsd.NsdServiceInfo; 22 import android.test.AndroidTestCase; 23 import android.util.Log; 24 25 import java.io.IOException; 26 import java.net.ServerSocket; 27 import java.util.Arrays; 28 import java.util.Random; 29 import java.util.List; 30 import java.util.ArrayList; 31 32 public class NsdManagerTest extends AndroidTestCase { 33 34 private static final String TAG = "NsdManagerTest"; 35 private static final String SERVICE_TYPE = "_nmt._tcp"; 36 private static final int TIMEOUT = 2000; 37 38 private static final boolean DBG = false; 39 40 NsdManager mNsdManager; 41 42 NsdManager.RegistrationListener mRegistrationListener; 43 NsdManager.DiscoveryListener mDiscoveryListener; 44 NsdManager.ResolveListener mResolveListener; 45 private NsdServiceInfo mResolvedService; 46 NsdManagerTest()47 public NsdManagerTest() { 48 initRegistrationListener(); 49 initDiscoveryListener(); 50 initResolveListener(); 51 } 52 initRegistrationListener()53 private void initRegistrationListener() { 54 mRegistrationListener = new NsdManager.RegistrationListener() { 55 @Override 56 public void onRegistrationFailed(NsdServiceInfo serviceInfo, int errorCode) { 57 setEvent("onRegistrationFailed", errorCode); 58 } 59 60 @Override 61 public void onUnregistrationFailed(NsdServiceInfo serviceInfo, int errorCode) { 62 setEvent("onUnregistrationFailed", errorCode); 63 } 64 65 @Override 66 public void onServiceRegistered(NsdServiceInfo serviceInfo) { 67 setEvent("onServiceRegistered", serviceInfo); 68 } 69 70 @Override 71 public void onServiceUnregistered(NsdServiceInfo serviceInfo) { 72 setEvent("onServiceUnregistered", serviceInfo); 73 } 74 }; 75 } 76 initDiscoveryListener()77 private void initDiscoveryListener() { 78 mDiscoveryListener = new NsdManager.DiscoveryListener() { 79 @Override 80 public void onStartDiscoveryFailed(String serviceType, int errorCode) { 81 setEvent("onStartDiscoveryFailed", errorCode); 82 } 83 84 @Override 85 public void onStopDiscoveryFailed(String serviceType, int errorCode) { 86 setEvent("onStopDiscoveryFailed", errorCode); 87 } 88 89 @Override 90 public void onDiscoveryStarted(String serviceType) { 91 NsdServiceInfo info = new NsdServiceInfo(); 92 info.setServiceType(serviceType); 93 setEvent("onDiscoveryStarted", info); 94 } 95 96 @Override 97 public void onDiscoveryStopped(String serviceType) { 98 NsdServiceInfo info = new NsdServiceInfo(); 99 info.setServiceType(serviceType); 100 setEvent("onDiscoveryStopped", info); 101 } 102 103 @Override 104 public void onServiceFound(NsdServiceInfo serviceInfo) { 105 setEvent("onServiceFound", serviceInfo); 106 } 107 108 @Override 109 public void onServiceLost(NsdServiceInfo serviceInfo) { 110 setEvent("onServiceLost", serviceInfo); 111 } 112 }; 113 } 114 initResolveListener()115 private void initResolveListener() { 116 mResolveListener = new NsdManager.ResolveListener() { 117 @Override 118 public void onResolveFailed(NsdServiceInfo serviceInfo, int errorCode) { 119 setEvent("onResolveFailed", errorCode); 120 } 121 122 @Override 123 public void onServiceResolved(NsdServiceInfo serviceInfo) { 124 mResolvedService = serviceInfo; 125 setEvent("onServiceResolved", serviceInfo); 126 } 127 }; 128 } 129 130 131 132 private final class EventData { EventData(String callbackName, NsdServiceInfo info)133 EventData(String callbackName, NsdServiceInfo info) { 134 mCallbackName = callbackName; 135 mSucceeded = true; 136 mErrorCode = 0; 137 mInfo = info; 138 } EventData(String callbackName, int errorCode)139 EventData(String callbackName, int errorCode) { 140 mCallbackName = callbackName; 141 mSucceeded = false; 142 mErrorCode = errorCode; 143 mInfo = null; 144 } 145 private final String mCallbackName; 146 private final boolean mSucceeded; 147 private final int mErrorCode; 148 private final NsdServiceInfo mInfo; 149 } 150 151 private final List<EventData> mEventCache = new ArrayList<EventData>(); 152 setEvent(String callbackName, int errorCode)153 private void setEvent(String callbackName, int errorCode) { 154 if (DBG) Log.d(TAG, callbackName + " failed with " + String.valueOf(errorCode)); 155 EventData eventData = new EventData(callbackName, errorCode); 156 synchronized (mEventCache) { 157 mEventCache.add(eventData); 158 mEventCache.notify(); 159 } 160 } 161 setEvent(String callbackName, NsdServiceInfo info)162 private void setEvent(String callbackName, NsdServiceInfo info) { 163 if (DBG) Log.d(TAG, "Received event " + callbackName + " for " + info.getServiceName()); 164 EventData eventData = new EventData(callbackName, info); 165 synchronized (mEventCache) { 166 mEventCache.add(eventData); 167 mEventCache.notify(); 168 } 169 } 170 clearEventCache()171 void clearEventCache() { 172 synchronized(mEventCache) { 173 mEventCache.clear(); 174 } 175 } 176 eventCacheSize()177 int eventCacheSize() { 178 synchronized(mEventCache) { 179 return mEventCache.size(); 180 } 181 } 182 183 private int mWaitId = 0; waitForCallback(String callbackName)184 private EventData waitForCallback(String callbackName) { 185 186 synchronized(mEventCache) { 187 188 mWaitId ++; 189 if (DBG) Log.d(TAG, "Waiting for " + callbackName + ", id=" + String.valueOf(mWaitId)); 190 191 try { 192 long startTime = android.os.SystemClock.uptimeMillis(); 193 long elapsedTime = 0; 194 int index = 0; 195 while (elapsedTime < TIMEOUT ) { 196 // first check if we've received that event 197 for (; index < mEventCache.size(); index++) { 198 EventData e = mEventCache.get(index); 199 if (e.mCallbackName.equals(callbackName)) { 200 if (DBG) Log.d(TAG, "exiting wait id=" + String.valueOf(mWaitId)); 201 return e; 202 } 203 } 204 205 // Not yet received, just wait 206 mEventCache.wait(TIMEOUT - elapsedTime); 207 elapsedTime = android.os.SystemClock.uptimeMillis() - startTime; 208 } 209 // we exited the loop because of TIMEOUT; fail the call 210 if (DBG) Log.d(TAG, "timed out waiting id=" + String.valueOf(mWaitId)); 211 return null; 212 } catch (InterruptedException e) { 213 return null; // wait timed out! 214 } 215 } 216 } 217 waitForNewEvents()218 private EventData waitForNewEvents() throws InterruptedException { 219 if (DBG) Log.d(TAG, "Waiting for a bit, id=" + String.valueOf(mWaitId)); 220 221 long startTime = android.os.SystemClock.uptimeMillis(); 222 long elapsedTime = 0; 223 synchronized (mEventCache) { 224 int index = mEventCache.size(); 225 while (elapsedTime < TIMEOUT ) { 226 // first check if we've received that event 227 for (; index < mEventCache.size(); index++) { 228 EventData e = mEventCache.get(index); 229 return e; 230 } 231 232 // Not yet received, just wait 233 mEventCache.wait(TIMEOUT - elapsedTime); 234 elapsedTime = android.os.SystemClock.uptimeMillis() - startTime; 235 } 236 } 237 238 return null; 239 } 240 241 private String mServiceName; 242 243 @Override setUp()244 public void setUp() { 245 if (DBG) Log.d(TAG, "Setup test ..."); 246 mNsdManager = (NsdManager) getContext().getSystemService(Context.NSD_SERVICE); 247 248 Random rand = new Random(); 249 mServiceName = new String("NsdTest"); 250 for (int i = 0; i < 4; i++) { 251 mServiceName = mServiceName + String.valueOf(rand.nextInt(10)); 252 } 253 } 254 255 @Override tearDown()256 public void tearDown() { 257 if (DBG) Log.d(TAG, "Tear down test ..."); 258 } 259 testNDSManager()260 public void testNDSManager() throws Exception { 261 EventData lastEvent = null; 262 263 if (DBG) Log.d(TAG, "Starting test ..."); 264 265 NsdServiceInfo si = new NsdServiceInfo(); 266 si.setServiceType(SERVICE_TYPE); 267 si.setServiceName(mServiceName); 268 269 byte testByteArray[] = new byte[] {-128, 127, 2, 1, 0, 1, 2}; 270 String String256 = "1_________2_________3_________4_________5_________6_________" + 271 "7_________8_________9_________10________11________12________13________" + 272 "14________15________16________17________18________19________20________" + 273 "21________22________23________24________25________123456"; 274 275 // Illegal attributes 276 try { 277 si.setAttribute(null, (String) null); 278 fail("Could set null key"); 279 } catch (IllegalArgumentException e) { 280 // expected 281 } 282 283 try { 284 si.setAttribute("", (String) null); 285 fail("Could set empty key"); 286 } catch (IllegalArgumentException e) { 287 // expected 288 } 289 290 try { 291 si.setAttribute(String256, (String) null); 292 fail("Could set key with 255 characters"); 293 } catch (IllegalArgumentException e) { 294 // expected 295 } 296 297 try { 298 si.setAttribute("key", String256.substring(3)); 299 fail("Could set key+value combination with more than 255 characters"); 300 } catch (IllegalArgumentException e) { 301 // expected 302 } 303 304 try { 305 si.setAttribute("key", String256.substring(4)); 306 fail("Could set key+value combination with 255 characters"); 307 } catch (IllegalArgumentException e) { 308 // expected 309 } 310 311 try { 312 si.setAttribute(new String(new byte[]{0x19}), (String) null); 313 fail("Could set key with invalid character"); 314 } catch (IllegalArgumentException e) { 315 // expected 316 } 317 318 try { 319 si.setAttribute("=", (String) null); 320 fail("Could set key with invalid character"); 321 } catch (IllegalArgumentException e) { 322 // expected 323 } 324 325 try { 326 si.setAttribute(new String(new byte[]{0x7F}), (String) null); 327 fail("Could set key with invalid character"); 328 } catch (IllegalArgumentException e) { 329 // expected 330 } 331 332 // Allowed attributes 333 si.setAttribute("booleanAttr", (String) null); 334 si.setAttribute("keyValueAttr", "value"); 335 si.setAttribute("keyEqualsAttr", "="); 336 si.setAttribute(" whiteSpaceKeyValueAttr ", " value "); 337 si.setAttribute("binaryDataAttr", testByteArray); 338 si.setAttribute("nullBinaryDataAttr", (byte[]) null); 339 si.setAttribute("emptyBinaryDataAttr", new byte[]{}); 340 si.setAttribute("longkey", String256.substring(9)); 341 342 ServerSocket socket; 343 int localPort; 344 345 try { 346 socket = new ServerSocket(0); 347 localPort = socket.getLocalPort(); 348 si.setPort(localPort); 349 } catch (IOException e) { 350 if (DBG) Log.d(TAG, "Could not open a local socket"); 351 assertTrue(false); 352 return; 353 } 354 355 if (DBG) Log.d(TAG, "Port = " + String.valueOf(localPort)); 356 357 clearEventCache(); 358 359 mNsdManager.registerService(si, NsdManager.PROTOCOL_DNS_SD, mRegistrationListener); 360 lastEvent = waitForCallback("onServiceRegistered"); // id = 1 361 assertTrue(lastEvent != null); 362 assertTrue(lastEvent.mSucceeded); 363 assertTrue(eventCacheSize() == 1); 364 365 // We may not always get the name that we tried to register; 366 // This events tells us the name that was registered. 367 String registeredName = lastEvent.mInfo.getServiceName(); 368 si.setServiceName(registeredName); 369 370 clearEventCache(); 371 372 mNsdManager.discoverServices(SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD, 373 mDiscoveryListener); 374 375 // Expect discovery started 376 lastEvent = waitForCallback("onDiscoveryStarted"); // id = 2 377 378 assertTrue(lastEvent != null); 379 assertTrue(lastEvent.mSucceeded); 380 381 // Remove this event, so accounting becomes easier later 382 synchronized (mEventCache) { 383 mEventCache.remove(lastEvent); 384 } 385 386 // Expect a service record to be discovered (and filter the ones 387 // that are unrelated to this test) 388 boolean found = false; 389 for (int i = 0; i < 32; i++) { 390 391 lastEvent = waitForCallback("onServiceFound"); // id = 3 392 if (lastEvent == null) { 393 // no more onServiceFound events are being reported! 394 break; 395 } 396 397 assertTrue(lastEvent.mSucceeded); 398 399 if (DBG) Log.d(TAG, "id = " + String.valueOf(mWaitId) + ": ServiceName = " + 400 lastEvent.mInfo.getServiceName()); 401 402 if (lastEvent.mInfo.getServiceName().equals(registeredName)) { 403 // Save it, as it will get overwritten with new serviceFound events 404 si = lastEvent.mInfo; 405 found = true; 406 } 407 408 // Remove this event from the event cache, so it won't be found by subsequent 409 // calls to waitForCallback 410 synchronized (mEventCache) { 411 mEventCache.remove(lastEvent); 412 } 413 } 414 415 assertTrue(found); 416 417 // We've removed all serviceFound events, and we've removed the discoveryStarted 418 // event as well, so now the event cache should be empty! 419 assertTrue(eventCacheSize() == 0); 420 421 // Resolve the service 422 clearEventCache(); 423 mNsdManager.resolveService(si, mResolveListener); 424 lastEvent = waitForCallback("onServiceResolved"); // id = 4 425 426 assertNotNull(mResolvedService); 427 428 // Check Txt attributes 429 assertEquals(8, mResolvedService.getAttributes().size()); 430 assertTrue(mResolvedService.getAttributes().containsKey("booleanAttr")); 431 assertNull(mResolvedService.getAttributes().get("booleanAttr")); 432 assertEquals("value", new String(mResolvedService.getAttributes().get("keyValueAttr"))); 433 assertEquals("=", new String(mResolvedService.getAttributes().get("keyEqualsAttr"))); 434 assertEquals(" value ", new String(mResolvedService.getAttributes() 435 .get(" whiteSpaceKeyValueAttr "))); 436 assertEquals(String256.substring(9), new String(mResolvedService.getAttributes() 437 .get("longkey"))); 438 assertTrue(Arrays.equals(testByteArray, 439 mResolvedService.getAttributes().get("binaryDataAttr"))); 440 assertTrue(mResolvedService.getAttributes().containsKey("nullBinaryDataAttr")); 441 assertNull(mResolvedService.getAttributes().get("nullBinaryDataAttr")); 442 assertTrue(mResolvedService.getAttributes().containsKey("emptyBinaryDataAttr")); 443 assertNull(mResolvedService.getAttributes().get("emptyBinaryDataAttr")); 444 445 assertTrue(lastEvent != null); 446 assertTrue(lastEvent.mSucceeded); 447 448 if (DBG) Log.d(TAG, "id = " + String.valueOf(mWaitId) + ": Port = " + 449 String.valueOf(lastEvent.mInfo.getPort())); 450 451 assertTrue(lastEvent.mInfo.getPort() == localPort); 452 assertTrue(eventCacheSize() == 1); 453 454 checkForAdditionalEvents(); 455 clearEventCache(); 456 457 // Unregister the service 458 mNsdManager.unregisterService(mRegistrationListener); 459 lastEvent = waitForCallback("onServiceUnregistered"); // id = 5 460 461 assertTrue(lastEvent != null); 462 assertTrue(lastEvent.mSucceeded); 463 464 // Expect a callback for service lost 465 lastEvent = waitForCallback("onServiceLost"); // id = 6 466 467 assertTrue(lastEvent != null); 468 assertTrue(lastEvent.mInfo.getServiceName().equals(registeredName)); 469 470 // Register service again to see if we discover it 471 checkForAdditionalEvents(); 472 clearEventCache(); 473 474 si = new NsdServiceInfo(); 475 si.setServiceType(SERVICE_TYPE); 476 si.setServiceName(mServiceName); 477 si.setPort(localPort); 478 479 // Create a new registration listener and register same service again 480 initRegistrationListener(); 481 482 mNsdManager.registerService(si, NsdManager.PROTOCOL_DNS_SD, mRegistrationListener); 483 484 lastEvent = waitForCallback("onServiceRegistered"); // id = 7 485 486 assertTrue(lastEvent != null); 487 assertTrue(lastEvent.mSucceeded); 488 489 registeredName = lastEvent.mInfo.getServiceName(); 490 491 // Expect a record to be discovered 492 // Expect a service record to be discovered (and filter the ones 493 // that are unrelated to this test) 494 found = false; 495 for (int i = 0; i < 32; i++) { 496 497 lastEvent = waitForCallback("onServiceFound"); // id = 8 498 if (lastEvent == null) { 499 // no more onServiceFound events are being reported! 500 break; 501 } 502 503 assertTrue(lastEvent.mSucceeded); 504 505 if (DBG) Log.d(TAG, "id = " + String.valueOf(mWaitId) + ": ServiceName = " + 506 lastEvent.mInfo.getServiceName()); 507 508 if (lastEvent.mInfo.getServiceName().equals(registeredName)) { 509 // Save it, as it will get overwritten with new serviceFound events 510 si = lastEvent.mInfo; 511 found = true; 512 } 513 514 // Remove this event from the event cache, so it won't be found by subsequent 515 // calls to waitForCallback 516 synchronized (mEventCache) { 517 mEventCache.remove(lastEvent); 518 } 519 } 520 521 assertTrue(found); 522 523 // Resolve the service 524 clearEventCache(); 525 mNsdManager.resolveService(si, mResolveListener); 526 lastEvent = waitForCallback("onServiceResolved"); // id = 9 527 528 assertTrue(lastEvent != null); 529 assertTrue(lastEvent.mSucceeded); 530 531 if (DBG) Log.d(TAG, "id = " + String.valueOf(mWaitId) + ": ServiceName = " + 532 lastEvent.mInfo.getServiceName()); 533 534 assertTrue(lastEvent.mInfo.getServiceName().equals(registeredName)); 535 536 assertNotNull(mResolvedService); 537 538 // Check that we don't have any TXT records 539 assertEquals(0, mResolvedService.getAttributes().size()); 540 541 checkForAdditionalEvents(); 542 clearEventCache(); 543 544 mNsdManager.stopServiceDiscovery(mDiscoveryListener); 545 lastEvent = waitForCallback("onDiscoveryStopped"); // id = 10 546 assertTrue(lastEvent != null); 547 assertTrue(lastEvent.mSucceeded); 548 assertTrue(checkCacheSize(1)); 549 550 checkForAdditionalEvents(); 551 clearEventCache(); 552 553 mNsdManager.unregisterService(mRegistrationListener); 554 555 lastEvent = waitForCallback("onServiceUnregistered"); // id = 11 556 assertTrue(lastEvent != null); 557 assertTrue(lastEvent.mSucceeded); 558 assertTrue(checkCacheSize(1)); 559 } 560 checkCacheSize(int size)561 boolean checkCacheSize(int size) { 562 synchronized (mEventCache) { 563 int cacheSize = mEventCache.size(); 564 if (cacheSize != size) { 565 Log.d(TAG, "id = " + mWaitId + ": event cache size = " + cacheSize); 566 for (int i = 0; i < cacheSize; i++) { 567 EventData e = mEventCache.get(i); 568 String sname = (e.mInfo != null) ? "(" + e.mInfo.getServiceName() + ")" : ""; 569 Log.d(TAG, "eventName is " + e.mCallbackName + sname); 570 } 571 } 572 return (cacheSize == size); 573 } 574 } 575 checkForAdditionalEvents()576 boolean checkForAdditionalEvents() { 577 try { 578 EventData e = waitForNewEvents(); 579 if (e != null) { 580 String sname = (e.mInfo != null) ? "(" + e.mInfo.getServiceName() + ")" : ""; 581 Log.d(TAG, "ignoring unexpected event " + e.mCallbackName + sname); 582 } 583 return (e == null); 584 } 585 catch (InterruptedException ex) { 586 return false; 587 } 588 } 589 } 590 591