1/*
2 * Copyright (C) 2017 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
17package android.net.nsd;
18
19import static org.junit.Assert.assertEquals;
20import static org.junit.Assert.assertNotNull;
21import static org.junit.Assert.fail;
22import static org.mockito.Mockito.any;
23import static org.mockito.Mockito.mock;
24import static org.mockito.Mockito.never;
25import static org.mockito.Mockito.reset;
26import static org.mockito.Mockito.spy;
27import static org.mockito.Mockito.timeout;
28import static org.mockito.Mockito.times;
29import static org.mockito.Mockito.verify;
30import static org.mockito.Mockito.when;
31import static com.android.internal.util.TestUtils.waitForIdleHandler;
32
33import android.os.HandlerThread;
34import android.os.Handler;
35import android.os.Looper;
36import android.content.Context;
37import android.support.test.filters.SmallTest;
38import android.support.test.runner.AndroidJUnit4;
39import android.os.Message;
40import android.os.Messenger;
41import com.android.internal.util.AsyncChannel;
42import org.junit.After;
43import org.junit.Before;
44import org.junit.Test;
45import org.junit.runner.RunWith;
46import org.mockito.Mock;
47import org.mockito.MockitoAnnotations;
48
49import java.util.function.Consumer;
50
51@RunWith(AndroidJUnit4.class)
52@SmallTest
53public class NsdManagerTest {
54
55    static final int PROTOCOL = NsdManager.PROTOCOL_DNS_SD;
56
57    @Mock Context mContext;
58    @Mock INsdManager mService;
59    MockServiceHandler mServiceHandler;
60
61    NsdManager mManager;
62
63    long mTimeoutMs = 200; // non-final so that tests can adjust the value.
64
65    @Before
66    public void setUp() throws Exception {
67        MockitoAnnotations.initMocks(this);
68
69        mServiceHandler = spy(MockServiceHandler.create(mContext));
70        when(mService.getMessenger()).thenReturn(new Messenger(mServiceHandler));
71
72        mManager = makeManager();
73    }
74
75    @After
76    public void tearDown() throws Exception {
77        mServiceHandler.waitForIdle(mTimeoutMs);
78        mServiceHandler.chan.disconnect();
79        mServiceHandler.stop();
80        if (mManager != null) {
81            mManager.disconnect();
82        }
83    }
84
85    @Test
86    public void testResolveService() {
87        NsdManager manager = mManager;
88
89        NsdServiceInfo request = new NsdServiceInfo("a_name", "a_type");
90        NsdServiceInfo reply = new NsdServiceInfo("resolved_name", "resolved_type");
91        NsdManager.ResolveListener listener = mock(NsdManager.ResolveListener.class);
92
93        manager.resolveService(request, listener);
94        int key1 = verifyRequest(NsdManager.RESOLVE_SERVICE);
95        int err = 33;
96        sendResponse(NsdManager.RESOLVE_SERVICE_FAILED, err, key1, null);
97        verify(listener, timeout(mTimeoutMs).times(1)).onResolveFailed(request, err);
98
99        manager.resolveService(request, listener);
100        int key2 = verifyRequest(NsdManager.RESOLVE_SERVICE);
101        sendResponse(NsdManager.RESOLVE_SERVICE_SUCCEEDED, 0, key2, reply);
102        verify(listener, timeout(mTimeoutMs).times(1)).onServiceResolved(reply);
103    }
104
105    @Test
106    public void testParallelResolveService() {
107        NsdManager manager = mManager;
108
109        NsdServiceInfo request = new NsdServiceInfo("a_name", "a_type");
110        NsdServiceInfo reply = new NsdServiceInfo("resolved_name", "resolved_type");
111
112        NsdManager.ResolveListener listener1 = mock(NsdManager.ResolveListener.class);
113        NsdManager.ResolveListener listener2 = mock(NsdManager.ResolveListener.class);
114
115        manager.resolveService(request, listener1);
116        int key1 = verifyRequest(NsdManager.RESOLVE_SERVICE);
117
118        manager.resolveService(request, listener2);
119        int key2 = verifyRequest(NsdManager.RESOLVE_SERVICE);
120
121        sendResponse(NsdManager.RESOLVE_SERVICE_SUCCEEDED, 0, key2, reply);
122        sendResponse(NsdManager.RESOLVE_SERVICE_SUCCEEDED, 0, key1, reply);
123
124        verify(listener1, timeout(mTimeoutMs).times(1)).onServiceResolved(reply);
125        verify(listener2, timeout(mTimeoutMs).times(1)).onServiceResolved(reply);
126    }
127
128    @Test
129    public void testRegisterService() {
130        NsdManager manager = mManager;
131
132        NsdServiceInfo request1 = new NsdServiceInfo("a_name", "a_type");
133        NsdServiceInfo request2 = new NsdServiceInfo("another_name", "another_type");
134        request1.setPort(2201);
135        request2.setPort(2202);
136        NsdManager.RegistrationListener listener1 = mock(NsdManager.RegistrationListener.class);
137        NsdManager.RegistrationListener listener2 = mock(NsdManager.RegistrationListener.class);
138
139        // Register two services
140        manager.registerService(request1, PROTOCOL, listener1);
141        int key1 = verifyRequest(NsdManager.REGISTER_SERVICE);
142
143        manager.registerService(request2, PROTOCOL, listener2);
144        int key2 = verifyRequest(NsdManager.REGISTER_SERVICE);
145
146        // First reques fails, second request succeeds
147        sendResponse(NsdManager.REGISTER_SERVICE_SUCCEEDED, 0, key2, request2);
148        verify(listener2, timeout(mTimeoutMs).times(1)).onServiceRegistered(request2);
149
150        int err = 1;
151        sendResponse(NsdManager.REGISTER_SERVICE_FAILED, err, key1, request1);
152        verify(listener1, timeout(mTimeoutMs).times(1)).onRegistrationFailed(request1, err);
153
154        // Client retries first request, it succeeds
155        manager.registerService(request1, PROTOCOL, listener1);
156        int key3 = verifyRequest(NsdManager.REGISTER_SERVICE);
157
158        sendResponse(NsdManager.REGISTER_SERVICE_SUCCEEDED, 0, key3, request1);
159        verify(listener1, timeout(mTimeoutMs).times(1)).onServiceRegistered(request1);
160
161        // First request is unregistered, it succeeds
162        manager.unregisterService(listener1);
163        int key3again = verifyRequest(NsdManager.UNREGISTER_SERVICE);
164        assertEquals(key3, key3again);
165
166        sendResponse(NsdManager.UNREGISTER_SERVICE_SUCCEEDED, 0, key3again, null);
167        verify(listener1, timeout(mTimeoutMs).times(1)).onServiceUnregistered(request1);
168
169        // Second request is unregistered, it fails
170        manager.unregisterService(listener2);
171        int key2again = verifyRequest(NsdManager.UNREGISTER_SERVICE);
172        assertEquals(key2, key2again);
173
174        sendResponse(NsdManager.UNREGISTER_SERVICE_FAILED, err, key2again, null);
175        verify(listener2, timeout(mTimeoutMs).times(1)).onUnregistrationFailed(request2, err);
176
177        // TODO: do not unregister listener until service is unregistered
178        // Client retries unregistration of second request, it succeeds
179        //manager.unregisterService(listener2);
180        //int key2yetAgain = verifyRequest(NsdManager.UNREGISTER_SERVICE);
181        //assertEquals(key2, key2yetAgain);
182
183        //sendResponse(NsdManager.UNREGISTER_SERVICE_SUCCEEDED, 0, key2yetAgain, null);
184        //verify(listener2, timeout(mTimeoutMs).times(1)).onServiceUnregistered(request2);
185    }
186
187    @Test
188    public void testDiscoverService() {
189        NsdManager manager = mManager;
190
191        NsdServiceInfo reply1 = new NsdServiceInfo("a_name", "a_type");
192        NsdServiceInfo reply2 = new NsdServiceInfo("another_name", "a_type");
193        NsdServiceInfo reply3 = new NsdServiceInfo("a_third_name", "a_type");
194
195        NsdManager.DiscoveryListener listener = mock(NsdManager.DiscoveryListener.class);
196
197        // Client registers for discovery, request fails
198        manager.discoverServices("a_type", PROTOCOL, listener);
199        int key1 = verifyRequest(NsdManager.DISCOVER_SERVICES);
200
201        int err = 1;
202        sendResponse(NsdManager.DISCOVER_SERVICES_FAILED, err, key1, null);
203        verify(listener, timeout(mTimeoutMs).times(1)).onStartDiscoveryFailed("a_type", err);
204
205        // Client retries, request succeeds
206        manager.discoverServices("a_type", PROTOCOL, listener);
207        int key2 = verifyRequest(NsdManager.DISCOVER_SERVICES);
208
209        sendResponse(NsdManager.DISCOVER_SERVICES_STARTED, 0, key2, reply1);
210        verify(listener, timeout(mTimeoutMs).times(1)).onDiscoveryStarted("a_type");
211
212
213        // mdns notifies about services
214        sendResponse(NsdManager.SERVICE_FOUND, 0, key2, reply1);
215        verify(listener, timeout(mTimeoutMs).times(1)).onServiceFound(reply1);
216
217        sendResponse(NsdManager.SERVICE_FOUND, 0, key2, reply2);
218        verify(listener, timeout(mTimeoutMs).times(1)).onServiceFound(reply2);
219
220        sendResponse(NsdManager.SERVICE_LOST, 0, key2, reply2);
221        verify(listener, timeout(mTimeoutMs).times(1)).onServiceLost(reply2);
222
223
224        // Client unregisters its listener
225        manager.stopServiceDiscovery(listener);
226        int key2again = verifyRequest(NsdManager.STOP_DISCOVERY);
227        assertEquals(key2, key2again);
228
229        // TODO: unregister listener immediately and stop notifying it about services
230        // Notifications are still passed to the client's listener
231        sendResponse(NsdManager.SERVICE_LOST, 0, key2, reply1);
232        verify(listener, timeout(mTimeoutMs).times(1)).onServiceLost(reply1);
233
234        // Client is notified of complete unregistration
235        sendResponse(NsdManager.STOP_DISCOVERY_SUCCEEDED, 0, key2again, "a_type");
236        verify(listener, timeout(mTimeoutMs).times(1)).onDiscoveryStopped("a_type");
237
238        // Notifications are not passed to the client anymore
239        sendResponse(NsdManager.SERVICE_FOUND, 0, key2, reply3);
240        verify(listener, timeout(mTimeoutMs).times(0)).onServiceLost(reply3);
241
242
243        // Client registers for service discovery
244        reset(listener);
245        manager.discoverServices("a_type", PROTOCOL, listener);
246        int key3 = verifyRequest(NsdManager.DISCOVER_SERVICES);
247
248        sendResponse(NsdManager.DISCOVER_SERVICES_STARTED, 0, key3, reply1);
249        verify(listener, timeout(mTimeoutMs).times(1)).onDiscoveryStarted("a_type");
250
251        // Client unregisters immediately, it fails
252        manager.stopServiceDiscovery(listener);
253        int key3again = verifyRequest(NsdManager.STOP_DISCOVERY);
254        assertEquals(key3, key3again);
255
256        err = 2;
257        sendResponse(NsdManager.STOP_DISCOVERY_FAILED, err, key3again, "a_type");
258        verify(listener, timeout(mTimeoutMs).times(1)).onStopDiscoveryFailed("a_type", err);
259
260        // New notifications are not passed to the client anymore
261        sendResponse(NsdManager.SERVICE_FOUND, 0, key3, reply1);
262        verify(listener, timeout(mTimeoutMs).times(0)).onServiceFound(reply1);
263    }
264
265    @Test
266    public void testInvalidCalls() {
267        NsdManager manager = mManager;
268
269        NsdManager.RegistrationListener listener1 = mock(NsdManager.RegistrationListener.class);
270        NsdManager.DiscoveryListener listener2 = mock(NsdManager.DiscoveryListener.class);
271        NsdManager.ResolveListener listener3 = mock(NsdManager.ResolveListener.class);
272
273        NsdServiceInfo invalidService = new NsdServiceInfo(null, null);
274        NsdServiceInfo validService = new NsdServiceInfo("a_name", "a_type");
275        validService.setPort(2222);
276
277        // Service registration
278        //  - invalid arguments
279        mustFail(() -> { manager.unregisterService(null); });
280        mustFail(() -> { manager.registerService(null, -1, null); });
281        mustFail(() -> { manager.registerService(null, PROTOCOL, listener1); });
282        mustFail(() -> { manager.registerService(invalidService, PROTOCOL, listener1); });
283        mustFail(() -> { manager.registerService(validService, -1, listener1); });
284        mustFail(() -> { manager.registerService(validService, PROTOCOL, null); });
285        manager.registerService(validService, PROTOCOL, listener1);
286        //  - listener already registered
287        mustFail(() -> { manager.registerService(validService, PROTOCOL, listener1); });
288        manager.unregisterService(listener1);
289        // TODO: make listener immediately reusable
290        //mustFail(() -> { manager.unregisterService(listener1); });
291        //manager.registerService(validService, PROTOCOL, listener1);
292
293        // Discover service
294        //  - invalid arguments
295        mustFail(() -> { manager.stopServiceDiscovery(null); });
296        mustFail(() -> { manager.discoverServices(null, -1, null); });
297        mustFail(() -> { manager.discoverServices(null, PROTOCOL, listener2); });
298        mustFail(() -> { manager.discoverServices("a_service", -1, listener2); });
299        mustFail(() -> { manager.discoverServices("a_service", PROTOCOL, null); });
300        manager.discoverServices("a_service", PROTOCOL, listener2);
301        //  - listener already registered
302        mustFail(() -> { manager.discoverServices("another_service", PROTOCOL, listener2); });
303        manager.stopServiceDiscovery(listener2);
304        // TODO: make listener immediately reusable
305        //mustFail(() -> { manager.stopServiceDiscovery(listener2); });
306        //manager.discoverServices("another_service", PROTOCOL, listener2);
307
308        // Resolver service
309        //  - invalid arguments
310        mustFail(() -> { manager.resolveService(null, null); });
311        mustFail(() -> { manager.resolveService(null, listener3); });
312        mustFail(() -> { manager.resolveService(invalidService, listener3); });
313        mustFail(() -> { manager.resolveService(validService, null); });
314        manager.resolveService(validService, listener3);
315        //  - listener already registered:w
316        mustFail(() -> { manager.resolveService(validService, listener3); });
317    }
318
319    public void mustFail(Runnable fn) {
320        try {
321            fn.run();
322            fail();
323        } catch (Exception expected) {
324        }
325    }
326
327    NsdManager makeManager() {
328        NsdManager manager = new NsdManager(mContext, mService);
329        // Acknowledge first two messages connecting the AsyncChannel.
330        verify(mServiceHandler, timeout(mTimeoutMs).times(2)).handleMessage(any());
331        reset(mServiceHandler);
332        assertNotNull(mServiceHandler.chan);
333        return manager;
334    }
335
336    int verifyRequest(int expectedMessageType) {
337        mServiceHandler.waitForIdle(mTimeoutMs);
338        verify(mServiceHandler, timeout(mTimeoutMs)).handleMessage(any());
339        reset(mServiceHandler);
340        Message received = mServiceHandler.getLastMessage();
341        assertEquals(NsdManager.nameOf(expectedMessageType), NsdManager.nameOf(received.what));
342        return received.arg2;
343    }
344
345    void sendResponse(int replyType, int arg, int key, Object obj) {
346        mServiceHandler.chan.sendMessage(replyType, arg, key, obj);
347    }
348
349    // Implements the server side of AsyncChannel connection protocol
350    public static class MockServiceHandler extends Handler {
351        public final Context context;
352        public AsyncChannel chan;
353        public Message lastMessage;
354
355        MockServiceHandler(Looper l, Context c) {
356            super(l);
357            context = c;
358        }
359
360        synchronized Message getLastMessage() {
361            return lastMessage;
362        }
363
364        synchronized void setLastMessage(Message msg) {
365            lastMessage = obtainMessage();
366            lastMessage.copyFrom(msg);
367        }
368
369        void waitForIdle(long timeoutMs) {
370            waitForIdleHandler(this, timeoutMs);
371        }
372
373        @Override
374        public void handleMessage(Message msg) {
375            setLastMessage(msg);
376            if (msg.what == AsyncChannel.CMD_CHANNEL_FULL_CONNECTION) {
377                chan = new AsyncChannel();
378                chan.connect(context, this, msg.replyTo);
379                chan.sendMessage(AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED);
380            }
381        }
382
383        void stop() {
384            getLooper().quitSafely();
385        }
386
387        static MockServiceHandler create(Context context) {
388            HandlerThread t = new HandlerThread("mock-service-handler");
389            t.start();
390            return new MockServiceHandler(t.getLooper(), context);
391        }
392    }
393}
394