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