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