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.Random;
28 import java.util.List;
29 import java.util.ArrayList;
30 
31 public class NsdManagerTest extends AndroidTestCase {
32 
33     private static final String TAG = "NsdManagerTest";
34     private static final String SERVICE_TYPE = "_nmt._tcp";
35     private static final int TIMEOUT = 2000;
36 
37     private static final boolean DBG = false;
38 
39     NsdManager mNsdManager;
40 
41     NsdManager.RegistrationListener mRegistrationListener;
42     NsdManager.DiscoveryListener mDiscoveryListener;
43     NsdManager.ResolveListener mResolveListener;
44 
NsdManagerTest()45     public NsdManagerTest() {
46         initRegistrationListener();
47         initDiscoveryListener();
48         initResolveListener();
49     }
50 
initRegistrationListener()51     private void initRegistrationListener() {
52         mRegistrationListener = new NsdManager.RegistrationListener() {
53             @Override
54             public void onRegistrationFailed(NsdServiceInfo serviceInfo, int errorCode) {
55                 setEvent("onRegistrationFailed", errorCode);
56             }
57 
58             @Override
59             public void onUnregistrationFailed(NsdServiceInfo serviceInfo, int errorCode) {
60                 setEvent("onUnregistrationFailed", errorCode);
61             }
62 
63             @Override
64             public void onServiceRegistered(NsdServiceInfo serviceInfo) {
65                 setEvent("onServiceRegistered", serviceInfo);
66             }
67 
68             @Override
69             public void onServiceUnregistered(NsdServiceInfo serviceInfo) {
70                 setEvent("onServiceUnregistered", serviceInfo);
71             }
72         };
73     }
74 
initDiscoveryListener()75     private void initDiscoveryListener() {
76         mDiscoveryListener = new NsdManager.DiscoveryListener() {
77             @Override
78             public void onStartDiscoveryFailed(String serviceType, int errorCode) {
79                 setEvent("onStartDiscoveryFailed", errorCode);
80             }
81 
82             @Override
83             public void onStopDiscoveryFailed(String serviceType, int errorCode) {
84                 setEvent("onStopDiscoveryFailed", errorCode);
85             }
86 
87             @Override
88             public void onDiscoveryStarted(String serviceType) {
89                 NsdServiceInfo info = new NsdServiceInfo();
90                 info.setServiceType(serviceType);
91                 setEvent("onDiscoveryStarted", info);
92             }
93 
94             @Override
95             public void onDiscoveryStopped(String serviceType) {
96                 NsdServiceInfo info = new NsdServiceInfo();
97                 info.setServiceType(serviceType);
98                 setEvent("onDiscoveryStopped", info);
99             }
100 
101             @Override
102             public void onServiceFound(NsdServiceInfo serviceInfo) {
103                 setEvent("onServiceFound", serviceInfo);
104             }
105 
106             @Override
107             public void onServiceLost(NsdServiceInfo serviceInfo) {
108                 setEvent("onServiceLost", serviceInfo);
109             }
110         };
111     }
112 
initResolveListener()113     private void initResolveListener() {
114         mResolveListener = new NsdManager.ResolveListener() {
115             @Override
116             public void onResolveFailed(NsdServiceInfo serviceInfo, int errorCode) {
117                 setEvent("onResolveFailed", errorCode);
118             }
119 
120             @Override
121             public void onServiceResolved(NsdServiceInfo serviceInfo) {
122                 setEvent("onServiceResolved", serviceInfo);
123             }
124         };
125     }
126 
127 
128 
129     private final class EventData {
EventData(String callbackName, NsdServiceInfo info)130         EventData(String callbackName, NsdServiceInfo info) {
131             mCallbackName = callbackName;
132             mSucceeded = true;
133             mErrorCode = 0;
134             mInfo = info;
135         }
EventData(String callbackName, int errorCode)136         EventData(String callbackName, int errorCode) {
137             mCallbackName = callbackName;
138             mSucceeded = false;
139             mErrorCode = errorCode;
140             mInfo = null;
141         }
142         private final String mCallbackName;
143         private final boolean mSucceeded;
144         private final int mErrorCode;
145         private final NsdServiceInfo mInfo;
146     }
147 
148     private final List<EventData> mEventCache = new ArrayList<EventData>();
149 
setEvent(String callbackName, int errorCode)150     private void setEvent(String callbackName, int errorCode) {
151         if (DBG) Log.d(TAG, callbackName + " failed with " + String.valueOf(errorCode));
152         EventData eventData = new EventData(callbackName, errorCode);
153         synchronized (mEventCache) {
154             mEventCache.add(eventData);
155             mEventCache.notify();
156         }
157     }
158 
setEvent(String callbackName, NsdServiceInfo info)159     private void setEvent(String callbackName, NsdServiceInfo info) {
160         if (DBG) Log.d(TAG, "Received event " + callbackName + " for " + info.getServiceName());
161         EventData eventData = new EventData(callbackName, info);
162         synchronized (mEventCache) {
163             mEventCache.add(eventData);
164             mEventCache.notify();
165         }
166     }
167 
clearEventCache()168     void clearEventCache() {
169         synchronized(mEventCache) {
170             mEventCache.clear();
171         }
172     }
173 
eventCacheSize()174     int eventCacheSize() {
175         synchronized(mEventCache) {
176             return mEventCache.size();
177         }
178     }
179 
180     private int mWaitId = 0;
waitForCallback(String callbackName)181     private EventData waitForCallback(String callbackName) {
182 
183         synchronized(mEventCache) {
184 
185             mWaitId ++;
186             if (DBG) Log.d(TAG, "Waiting for " + callbackName + ", id=" + String.valueOf(mWaitId));
187 
188             try {
189                 long startTime = android.os.SystemClock.uptimeMillis();
190                 long elapsedTime = 0;
191                 int index = 0;
192                 while (elapsedTime < TIMEOUT ) {
193                     // first check if we've received that event
194                     for (; index < mEventCache.size(); index++) {
195                         EventData e = mEventCache.get(index);
196                         if (e.mCallbackName.equals(callbackName)) {
197                             if (DBG) Log.d(TAG, "exiting wait id=" + String.valueOf(mWaitId));
198                             return e;
199                         }
200                     }
201 
202                     // Not yet received, just wait
203                     mEventCache.wait(TIMEOUT - elapsedTime);
204                     elapsedTime = android.os.SystemClock.uptimeMillis() - startTime;
205                 }
206                 // we exited the loop because of TIMEOUT; fail the call
207                 if (DBG) Log.d(TAG, "timed out waiting id=" + String.valueOf(mWaitId));
208                 return null;
209             } catch (InterruptedException e) {
210                 return null;                       // wait timed out!
211             }
212         }
213     }
214 
waitForNewEvents()215     private EventData waitForNewEvents() throws InterruptedException {
216         if (DBG) Log.d(TAG, "Waiting for a bit, id=" + String.valueOf(mWaitId));
217 
218         long startTime = android.os.SystemClock.uptimeMillis();
219         long elapsedTime = 0;
220         synchronized (mEventCache) {
221             int index = mEventCache.size();
222             while (elapsedTime < TIMEOUT ) {
223                 // first check if we've received that event
224                 for (; index < mEventCache.size(); index++) {
225                     EventData e = mEventCache.get(index);
226                     return e;
227                 }
228 
229                 // Not yet received, just wait
230                 mEventCache.wait(TIMEOUT - elapsedTime);
231                 elapsedTime = android.os.SystemClock.uptimeMillis() - startTime;
232             }
233         }
234 
235         return null;
236     }
237 
238     private String mServiceName;
239 
240     @Override
setUp()241     public void setUp() {
242         if (DBG) Log.d(TAG, "Setup test ...");
243         mNsdManager = (NsdManager) getContext().getSystemService(Context.NSD_SERVICE);
244 
245         Random rand = new Random();
246         mServiceName = new String("NsdTest");
247         for (int i = 0; i < 4; i++) {
248             mServiceName = mServiceName + String.valueOf(rand.nextInt(10));
249         }
250     }
251 
252     @Override
tearDown()253     public void tearDown() {
254         if (DBG) Log.d(TAG, "Tear down test ...");
255     }
256 
runTest()257     public void runTest() throws Exception {
258         NsdServiceInfo si = new NsdServiceInfo();
259         si.setServiceType(SERVICE_TYPE);
260         si.setServiceName(mServiceName);
261 
262         EventData lastEvent = null;
263 
264         if (DBG) Log.d(TAG, "Starting test ...");
265 
266         ServerSocket socket;
267         int localPort;
268 
269         try {
270             socket = new ServerSocket(0);
271             localPort = socket.getLocalPort();
272             si.setPort(localPort);
273         } catch (IOException e) {
274             if (DBG) Log.d(TAG, "Could not open a local socket");
275             assertTrue(false);
276             return;
277         }
278 
279         if (DBG) Log.d(TAG, "Port = " + String.valueOf(localPort));
280 
281         clearEventCache();
282 
283         mNsdManager.registerService(si, NsdManager.PROTOCOL_DNS_SD, mRegistrationListener);
284         lastEvent = waitForCallback("onServiceRegistered");                 // id = 1
285         assertTrue(lastEvent != null);
286         assertTrue(lastEvent.mSucceeded);
287         assertTrue(eventCacheSize() == 1);
288 
289         // We may not always get the name that we tried to register;
290         // This events tells us the name that was registered.
291         String registeredName = lastEvent.mInfo.getServiceName();
292         si.setServiceName(registeredName);
293 
294         clearEventCache();
295 
296         mNsdManager.discoverServices(SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD,
297                 mDiscoveryListener);
298 
299         // Expect discovery started
300         lastEvent = waitForCallback("onDiscoveryStarted");                  // id = 2
301 
302         assertTrue(lastEvent != null);
303         assertTrue(lastEvent.mSucceeded);
304 
305         // Remove this event, so accounting becomes easier later
306         synchronized (mEventCache) {
307             mEventCache.remove(lastEvent);
308         }
309 
310         // Expect a service record to be discovered (and filter the ones
311         // that are unrelated to this test)
312         boolean found = false;
313         for (int i = 0; i < 32; i++) {
314 
315             lastEvent = waitForCallback("onServiceFound");                  // id = 3
316             if (lastEvent == null) {
317                 // no more onServiceFound events are being reported!
318                 break;
319             }
320 
321             assertTrue(lastEvent.mSucceeded);
322 
323             if (DBG) Log.d(TAG, "id = " + String.valueOf(mWaitId) + ": ServiceName = " +
324                     lastEvent.mInfo.getServiceName());
325 
326             if (lastEvent.mInfo.getServiceName().equals(registeredName)) {
327                 // Save it, as it will get overwritten with new serviceFound events
328                 si = lastEvent.mInfo;
329                 found = true;
330             }
331 
332             // Remove this event from the event cache, so it won't be found by subsequent
333             // calls to waitForCallback
334             synchronized (mEventCache) {
335                 mEventCache.remove(lastEvent);
336             }
337         }
338 
339         assertTrue(found);
340 
341         // We've removed all serviceFound events, and we've removed the discoveryStarted
342         // event as well, so now the event cache should be empty!
343         assertTrue(eventCacheSize() == 0);
344 
345         // Resolve the service
346         clearEventCache();
347         mNsdManager.resolveService(si, mResolveListener);
348         lastEvent = waitForCallback("onServiceResolved");                   // id = 4
349 
350         assertTrue(lastEvent != null);
351         assertTrue(lastEvent.mSucceeded);
352 
353         if (DBG) Log.d(TAG, "id = " + String.valueOf(mWaitId) + ": Port = " +
354                 String.valueOf(lastEvent.mInfo.getPort()));
355 
356         assertTrue(lastEvent.mInfo.getPort() == localPort);
357         assertTrue(eventCacheSize() == 1);
358 
359         checkForAdditionalEvents();
360         clearEventCache();
361 
362         // Unregister the service
363         mNsdManager.unregisterService(mRegistrationListener);
364         lastEvent = waitForCallback("onServiceUnregistered");               // id = 5
365 
366         assertTrue(lastEvent != null);
367         assertTrue(lastEvent.mSucceeded);
368 
369         // Expect a callback for service lost
370         lastEvent = waitForCallback("onServiceLost");                       // id = 6
371 
372         assertTrue(lastEvent != null);
373         assertTrue(lastEvent.mInfo.getServiceName().equals(registeredName));
374 
375         // Register service again to see if we discover it
376         checkForAdditionalEvents();
377         clearEventCache();
378 
379         si = new NsdServiceInfo();
380         si.setServiceType(SERVICE_TYPE);
381         si.setServiceName(mServiceName);
382         si.setPort(localPort);
383 
384         // Create a new registration listener and register same service again
385         initRegistrationListener();
386 
387         mNsdManager.registerService(si, NsdManager.PROTOCOL_DNS_SD, mRegistrationListener);
388 
389         lastEvent = waitForCallback("onServiceRegistered");                 // id = 7
390 
391         assertTrue(lastEvent != null);
392         assertTrue(lastEvent.mSucceeded);
393 
394         registeredName = lastEvent.mInfo.getServiceName();
395 
396         // Expect a record to be discovered
397         lastEvent = waitForCallback("onServiceFound");                      // id = 8
398 
399         assertTrue(lastEvent != null);
400         assertTrue(lastEvent.mSucceeded);
401 
402         if (DBG) Log.d(TAG, "id = " + String.valueOf(mWaitId) + ": ServiceName = " +
403                 lastEvent.mInfo.getServiceName());
404 
405         assertTrue(lastEvent.mInfo.getServiceName().equals(registeredName));
406 
407         checkForAdditionalEvents();
408         clearEventCache();
409 
410         mNsdManager.stopServiceDiscovery(mDiscoveryListener);
411         lastEvent = waitForCallback("onDiscoveryStopped");                  // id = 9
412         assertTrue(lastEvent != null);
413         assertTrue(lastEvent.mSucceeded);
414         assertTrue(checkCacheSize(1));
415 
416         checkForAdditionalEvents();
417         clearEventCache();
418 
419         mNsdManager.unregisterService(mRegistrationListener);
420 
421         lastEvent =  waitForCallback("onServiceUnregistered");              // id = 10
422         assertTrue(lastEvent != null);
423         assertTrue(lastEvent.mSucceeded);
424         assertTrue(checkCacheSize(1));
425     }
426 
checkCacheSize(int size)427     boolean checkCacheSize(int size) {
428         synchronized (mEventCache) {
429             int cacheSize = mEventCache.size();
430             if (cacheSize != size) {
431                 Log.d(TAG, "id = " + mWaitId + ": event cache size = " + cacheSize);
432                 for (int i = 0; i < cacheSize; i++) {
433                     EventData e = mEventCache.get(i);
434                     String sname = (e.mInfo != null) ? "(" + e.mInfo.getServiceName() + ")" : "";
435                     Log.d(TAG, "eventName is " + e.mCallbackName + sname);
436                 }
437             }
438             return (cacheSize == size);
439         }
440     }
441 
checkForAdditionalEvents()442     boolean checkForAdditionalEvents() {
443         try {
444             EventData e = waitForNewEvents();
445             if (e != null) {
446                 String sname = (e.mInfo != null) ? "(" + e.mInfo.getServiceName() + ")" : "";
447                 Log.d(TAG, "ignoring unexpected event " + e.mCallbackName + sname);
448             }
449             return (e == null);
450         }
451         catch (InterruptedException ex) {
452             return false;
453         }
454     }
455 }
456 
457