WifiAwareServiceImplTest.java revision c29acea6ceda3aa4ee537c05ce7d05dac2655cf9
1/*
2 * Copyright (C) 2016 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 com.android.server.wifi.aware;
18
19import static org.junit.Assert.assertArrayEquals;
20import static org.junit.Assert.assertEquals;
21import static org.junit.Assert.assertTrue;
22import static org.mockito.Matchers.any;
23import static org.mockito.Matchers.anyInt;
24import static org.mockito.Matchers.anyString;
25import static org.mockito.Matchers.eq;
26import static org.mockito.Mockito.inOrder;
27import static org.mockito.Mockito.mock;
28import static org.mockito.Mockito.verify;
29import static org.mockito.Mockito.when;
30
31import android.content.Context;
32import android.content.pm.PackageManager;
33import android.net.wifi.RttManager;
34import android.net.wifi.aware.ConfigRequest;
35import android.net.wifi.aware.IWifiAwareDiscoverySessionCallback;
36import android.net.wifi.aware.IWifiAwareEventCallback;
37import android.net.wifi.aware.PublishConfig;
38import android.net.wifi.aware.SubscribeConfig;
39import android.net.wifi.aware.WifiAwareCharacteristics;
40import android.os.IBinder;
41import android.os.Looper;
42import android.test.suitebuilder.annotation.SmallTest;
43import android.util.SparseArray;
44import android.util.SparseIntArray;
45
46import org.junit.Before;
47import org.junit.Test;
48import org.mockito.ArgumentCaptor;
49import org.mockito.InOrder;
50import org.mockito.Mock;
51import org.mockito.MockitoAnnotations;
52
53import java.lang.reflect.Field;
54
55/**
56 * Unit test harness for WifiAwareStateManager.
57 */
58@SmallTest
59public class WifiAwareServiceImplTest {
60    private static final int MAX_LENGTH = 255;
61
62    private WifiAwareServiceImplSpy mDut;
63    private int mDefaultUid = 1500;
64
65    @Mock
66    private Context mContextMock;
67    @Mock
68    private PackageManager mPackageManagerMock;
69    @Mock
70    private WifiAwareStateManager mAwareStateManagerMock;
71    @Mock
72    private IBinder mBinderMock;
73    @Mock
74    private IWifiAwareEventCallback mCallbackMock;
75    @Mock
76    private IWifiAwareDiscoverySessionCallback mSessionCallbackMock;
77
78    /**
79     * Using instead of spy to avoid native crash failures - possibly due to
80     * spy's copying of state.
81     */
82    private class WifiAwareServiceImplSpy extends WifiAwareServiceImpl {
83        public int fakeUid;
84
85        WifiAwareServiceImplSpy(Context context) {
86            super(context);
87        }
88
89        /**
90         * Return the fake UID instead of the real one: pseudo-spy
91         * implementation.
92         */
93        @Override
94        public int getMockableCallingUid() {
95            return fakeUid;
96        }
97    }
98
99    /**
100     * Initializes mocks.
101     */
102    @Before
103    public void setup() throws Exception {
104        MockitoAnnotations.initMocks(this);
105
106        when(mContextMock.getApplicationContext()).thenReturn(mContextMock);
107        when(mContextMock.getPackageManager()).thenReturn(mPackageManagerMock);
108        when(mPackageManagerMock.hasSystemFeature(PackageManager.FEATURE_WIFI_AWARE))
109                .thenReturn(true);
110        when(mAwareStateManagerMock.getCharacteristics()).thenReturn(getCharacteristics());
111
112        installMockAwareStateManager();
113
114        mDut = new WifiAwareServiceImplSpy(mContextMock);
115        mDut.fakeUid = mDefaultUid;
116    }
117
118    /**
119     * Validate start() function: passes a valid looper.
120     */
121    @Test
122    public void testStart() {
123        mDut.start();
124
125        verify(mAwareStateManagerMock).start(eq(mContextMock), any(Looper.class));
126    }
127
128    /**
129     * Validate enableUsage() function
130     */
131    @Test
132    public void testEnableUsage() {
133        mDut.enableUsage();
134
135        verify(mAwareStateManagerMock).enableUsage();
136    }
137
138    /**
139     * Validate disableUsage() function
140     */
141    @Test
142    public void testDisableUsage() throws Exception {
143        mDut.enableUsage();
144        doConnect();
145        mDut.disableUsage();
146
147        verify(mAwareStateManagerMock).disableUsage();
148    }
149
150    /**
151     * Validate isUsageEnabled() function
152     */
153    @Test
154    public void testIsUsageEnabled() {
155        mDut.isUsageEnabled();
156
157        verify(mAwareStateManagerMock).isUsageEnabled();
158    }
159
160
161    /**
162     * Validate connect() - returns and uses a client ID.
163     */
164    @Test
165    public void testConnect() {
166        doConnect();
167    }
168
169    /**
170     * Validate connect() when a non-null config is passed.
171     */
172    @Test
173    public void testConnectWithConfig() {
174        ConfigRequest configRequest = new ConfigRequest.Builder().setMasterPreference(55).build();
175        String callingPackage = "com.google.somePackage";
176
177        mDut.connect(mBinderMock, callingPackage, mCallbackMock,
178                configRequest, false);
179
180        verify(mAwareStateManagerMock).connect(anyInt(), anyInt(), anyInt(),
181                eq(callingPackage), eq(mCallbackMock), eq(configRequest), eq(false));
182    }
183
184    /**
185     * Validate disconnect() - correct pass-through args.
186     *
187     * @throws Exception
188     */
189    @Test
190    public void testDisconnect() throws Exception {
191        int clientId = doConnect();
192
193        mDut.disconnect(clientId, mBinderMock);
194
195        verify(mAwareStateManagerMock).disconnect(clientId);
196        validateInternalStateCleanedUp(clientId);
197    }
198
199    /**
200     * Validate that security exception thrown when attempting operation using
201     * an invalid client ID.
202     */
203    @Test(expected = SecurityException.class)
204    public void testFailOnInvalidClientId() {
205        mDut.disconnect(-1, mBinderMock);
206    }
207
208    /**
209     * Validate that security exception thrown when attempting operation using a
210     * client ID which was already cleared-up.
211     */
212    @Test(expected = SecurityException.class)
213    public void testFailOnClearedUpClientId() throws Exception {
214        int clientId = doConnect();
215
216        mDut.disconnect(clientId, mBinderMock);
217
218        verify(mAwareStateManagerMock).disconnect(clientId);
219        validateInternalStateCleanedUp(clientId);
220
221        mDut.disconnect(clientId, mBinderMock);
222    }
223
224    /**
225     * Validate that trying to use a client ID from a UID which is different
226     * from the one that created it fails - and that the internal state is not
227     * modified so that a valid call (from the correct UID) will subsequently
228     * succeed.
229     */
230    @Test
231    public void testFailOnAccessClientIdFromWrongUid() throws Exception {
232        int clientId = doConnect();
233
234        mDut.fakeUid = mDefaultUid + 1;
235
236        /*
237         * Not using thrown.expect(...) since want to test that subsequent
238         * access works.
239         */
240        boolean failsAsExpected = false;
241        try {
242            mDut.disconnect(clientId, mBinderMock);
243        } catch (SecurityException e) {
244            failsAsExpected = true;
245        }
246
247        mDut.fakeUid = mDefaultUid;
248
249        PublishConfig publishConfig = new PublishConfig.Builder().setServiceName("valid.value")
250                .build();
251        mDut.publish(clientId, publishConfig, mSessionCallbackMock);
252
253        verify(mAwareStateManagerMock).publish(clientId, publishConfig, mSessionCallbackMock);
254        assertTrue("SecurityException for invalid access from wrong UID thrown", failsAsExpected);
255    }
256
257    /**
258     * Validates that on binder death we get a disconnect().
259     */
260    @Test
261    public void testBinderDeath() throws Exception {
262        ArgumentCaptor<IBinder.DeathRecipient> deathRecipient = ArgumentCaptor
263                .forClass(IBinder.DeathRecipient.class);
264
265        int clientId = doConnect();
266
267        verify(mBinderMock).linkToDeath(deathRecipient.capture(), eq(0));
268        deathRecipient.getValue().binderDied();
269        verify(mAwareStateManagerMock).disconnect(clientId);
270        validateInternalStateCleanedUp(clientId);
271    }
272
273    /**
274     * Validates that sequential connect() calls return increasing client IDs.
275     */
276    @Test
277    public void testClientIdIncrementing() {
278        int loopCount = 100;
279
280        InOrder inOrder = inOrder(mAwareStateManagerMock);
281        ArgumentCaptor<Integer> clientIdCaptor = ArgumentCaptor.forClass(Integer.class);
282
283        int prevId = 0;
284        for (int i = 0; i < loopCount; ++i) {
285            mDut.connect(mBinderMock, "", mCallbackMock, null, false);
286            inOrder.verify(mAwareStateManagerMock).connect(clientIdCaptor.capture(), anyInt(),
287                    anyInt(), anyString(), eq(mCallbackMock), any(ConfigRequest.class), eq(false));
288            int id = clientIdCaptor.getValue();
289            if (i != 0) {
290                assertTrue("Client ID incrementing", id > prevId);
291            }
292            prevId = id;
293        }
294    }
295
296    /**
297     * Validate terminateSession() - correct pass-through args.
298     */
299    @Test
300    public void testTerminateSession() {
301        int sessionId = 1024;
302        int clientId = doConnect();
303
304        mDut.terminateSession(clientId, sessionId);
305
306        verify(mAwareStateManagerMock).terminateSession(clientId, sessionId);
307    }
308
309    /**
310     * Validate publish() - correct pass-through args.
311     */
312    @Test
313    public void testPublish() {
314        PublishConfig publishConfig = new PublishConfig.Builder().setServiceName("something.valid")
315                .build();
316        int clientId = doConnect();
317        IWifiAwareDiscoverySessionCallback mockCallback = mock(
318                IWifiAwareDiscoverySessionCallback.class);
319
320        mDut.publish(clientId, publishConfig, mockCallback);
321
322        verify(mAwareStateManagerMock).publish(clientId, publishConfig, mockCallback);
323    }
324
325    /**
326     * Validate that publish() verifies the input PublishConfig and fails on an invalid service
327     * name.
328     */
329    @Test(expected = IllegalArgumentException.class)
330    public void testPublishInvalidServiceName() {
331        doBadPublishConfiguration("Including invalid characters - spaces", null, null);
332    }
333
334    /**
335     * Validate that publish() verifies the input PublishConfig and fails on a "very long"
336     * service name.
337     */
338    @Test(expected = IllegalArgumentException.class)
339    public void testPublishServiceNameTooLong() {
340        byte[] byteArray = new byte[MAX_LENGTH + 1];
341        for (int i = 0; i < MAX_LENGTH + 1; ++i) {
342            byteArray[i] = 'a';
343        }
344        doBadPublishConfiguration(new String(byteArray), null, null);
345    }
346
347    /**
348     * Validate that publish() verifies the input PublishConfig and fails on a "very long" ssi.
349     */
350    @Test(expected = IllegalArgumentException.class)
351    public void testPublishSsiTooLong() {
352        doBadPublishConfiguration("correctservicename", new byte[MAX_LENGTH + 1], null);
353    }
354
355    /**
356     * Validate that publish() verifies the input PublishConfig and fails on a "very long" match
357     * filter.
358     */
359    @Test(expected = IllegalArgumentException.class)
360    public void testPublishMatchFilterTooLong() {
361        doBadPublishConfiguration("correctservicename", null, new byte[MAX_LENGTH + 1]);
362    }
363
364    /**
365     * Validate updatePublish() - correct pass-through args.
366     */
367    @Test
368    public void testUpdatePublish() {
369        int sessionId = 1232;
370        PublishConfig publishConfig = new PublishConfig.Builder().setServiceName("something.valid")
371                .build();
372        int clientId = doConnect();
373
374        mDut.updatePublish(clientId, sessionId, publishConfig);
375
376        verify(mAwareStateManagerMock).updatePublish(clientId, sessionId, publishConfig);
377    }
378
379    /**
380     * Validate updatePublish() error checking.
381     */
382    @Test(expected = IllegalArgumentException.class)
383    public void testUpdatePublishInvalid() {
384        int sessionId = 1232;
385        PublishConfig publishConfig = new PublishConfig.Builder()
386                .setServiceName("something with spaces").build();
387        int clientId = doConnect();
388
389        mDut.updatePublish(clientId, sessionId, publishConfig);
390
391        verify(mAwareStateManagerMock).updatePublish(clientId, sessionId, publishConfig);
392    }
393
394    /**
395     * Validate subscribe() - correct pass-through args.
396     */
397    @Test
398    public void testSubscribe() {
399        SubscribeConfig subscribeConfig = new SubscribeConfig.Builder()
400                .setServiceName("something.valid").build();
401        int clientId = doConnect();
402        IWifiAwareDiscoverySessionCallback mockCallback = mock(
403                IWifiAwareDiscoverySessionCallback.class);
404
405        mDut.subscribe(clientId, subscribeConfig, mockCallback);
406
407        verify(mAwareStateManagerMock).subscribe(clientId, subscribeConfig, mockCallback);
408    }
409
410    /**
411     * Validate that subscribe() verifies the input SubscribeConfig and fails on an invalid service
412     * name.
413     */
414    @Test(expected = IllegalArgumentException.class)
415    public void testSubscribeInvalidServiceName() {
416        doBadSubscribeConfiguration("Including invalid characters - spaces", null, null);
417    }
418
419    /**
420     * Validate that subscribe() verifies the input SubscribeConfig and fails on a "very long"
421     * service name.
422     */
423    @Test(expected = IllegalArgumentException.class)
424    public void testSubscribeServiceNameTooLong() {
425        byte[] byteArray = new byte[MAX_LENGTH + 1];
426        for (int i = 0; i < MAX_LENGTH + 1; ++i) {
427            byteArray[i] = 'a';
428        }
429        doBadSubscribeConfiguration(new String(byteArray), null, null);
430    }
431
432    /**
433     * Validate that subscribe() verifies the input SubscribeConfig and fails on a "very long" ssi.
434     */
435    @Test(expected = IllegalArgumentException.class)
436    public void testSubscribeSsiTooLong() {
437        doBadSubscribeConfiguration("correctservicename", new byte[MAX_LENGTH + 1], null);
438    }
439
440    /**
441     * Validate that subscribe() verifies the input SubscribeConfig and fails on a "very long" match
442     * filter.
443     */
444    @Test(expected = IllegalArgumentException.class)
445    public void testSubscribeMatchFilterTooLong() {
446        doBadSubscribeConfiguration("correctservicename", null, new byte[MAX_LENGTH + 1]);
447    }
448
449    /**
450     * Validate updateSubscribe() error checking.
451     */
452    @Test(expected = IllegalArgumentException.class)
453    public void testUpdateSubscribeInvalid() {
454        int sessionId = 1232;
455        SubscribeConfig subscribeConfig = new SubscribeConfig.Builder()
456                .setServiceName("something.valid")
457                .setServiceSpecificInfo(new byte[MAX_LENGTH + 1]).build();
458        int clientId = doConnect();
459
460        mDut.updateSubscribe(clientId, sessionId, subscribeConfig);
461
462        verify(mAwareStateManagerMock).updateSubscribe(clientId, sessionId, subscribeConfig);
463    }
464
465    /**
466     * Validate updateSubscribe() validates configuration.
467     */
468    @Test
469    public void testUpdateSubscribe() {
470        int sessionId = 1232;
471        SubscribeConfig subscribeConfig = new SubscribeConfig.Builder()
472                .setServiceName("something.valid").build();
473        int clientId = doConnect();
474
475        mDut.updateSubscribe(clientId, sessionId, subscribeConfig);
476
477        verify(mAwareStateManagerMock).updateSubscribe(clientId, sessionId, subscribeConfig);
478    }
479
480    /**
481     * Validate sendMessage() - correct pass-through args.
482     */
483    @Test
484    public void testSendMessage() {
485        int sessionId = 2394;
486        int peerId = 2032;
487        byte[] message = new byte[MAX_LENGTH];
488        int messageId = 2043;
489        int clientId = doConnect();
490
491        mDut.sendMessage(clientId, sessionId, peerId, message, messageId, 0);
492
493        verify(mAwareStateManagerMock).sendMessage(clientId, sessionId, peerId, message, messageId,
494                0);
495    }
496
497    /**
498     * Validate sendMessage() validates that message length is correct.
499     */
500    @Test(expected = IllegalArgumentException.class)
501    public void testSendMessageTooLong() {
502        int sessionId = 2394;
503        int peerId = 2032;
504        byte[] message = new byte[MAX_LENGTH + 1];
505        int messageId = 2043;
506        int clientId = doConnect();
507
508        mDut.sendMessage(clientId, sessionId, peerId, message, messageId, 0);
509
510        verify(mAwareStateManagerMock).sendMessage(clientId, sessionId, peerId, message, messageId,
511                0);
512    }
513
514    /**
515     * Validate startRanging() - correct pass-through args
516     */
517    @Test
518    public void testStartRanging() {
519        int clientId = doConnect();
520        int sessionId = 65345;
521        RttManager.ParcelableRttParams params =
522                new RttManager.ParcelableRttParams(new RttManager.RttParams[1]);
523
524        ArgumentCaptor<RttManager.RttParams[]> paramsCaptor =
525                ArgumentCaptor.forClass(RttManager.RttParams[].class);
526
527        int rangingId = mDut.startRanging(clientId, sessionId, params);
528
529        verify(mAwareStateManagerMock).startRanging(eq(clientId), eq(sessionId),
530                paramsCaptor.capture(), eq(rangingId));
531
532        assertArrayEquals(paramsCaptor.getValue(), params.mParams);
533    }
534
535    /**
536     * Validates that sequential startRanging() calls return increasing ranging IDs.
537     */
538    @Test
539    public void testRangingIdIncrementing() {
540        int loopCount = 100;
541        int clientId = doConnect();
542        int sessionId = 65345;
543        RttManager.ParcelableRttParams params =
544                new RttManager.ParcelableRttParams(new RttManager.RttParams[1]);
545
546        int prevRangingId = 0;
547        for (int i = 0; i < loopCount; ++i) {
548            int rangingId = mDut.startRanging(clientId, sessionId, params);
549            if (i != 0) {
550                assertTrue("Client ID incrementing", rangingId > prevRangingId);
551            }
552            prevRangingId = rangingId;
553        }
554    }
555
556    /**
557     * Validates that startRanging() requires a non-empty list
558     */
559    @Test(expected = IllegalArgumentException.class)
560    public void testStartRangingZeroArgs() {
561        int clientId = doConnect();
562        int sessionId = 65345;
563        RttManager.ParcelableRttParams params =
564                new RttManager.ParcelableRttParams(new RttManager.RttParams[0]);
565
566        ArgumentCaptor<RttManager.RttParams[]> paramsCaptor =
567                ArgumentCaptor.forClass(RttManager.RttParams[].class);
568
569        int rangingId = mDut.startRanging(clientId, sessionId, params);
570    }
571
572    /*
573     * Tests of internal state of WifiAwareServiceImpl: very limited (not usually
574     * a good idea). However, these test that the internal state is cleaned-up
575     * appropriately. Alternatively would cause issues with memory leaks or
576     * information leak between sessions.
577     */
578
579    private void validateInternalStateCleanedUp(int clientId) throws Exception {
580        int uidEntry = getInternalStateUid(clientId);
581        assertEquals(-1, uidEntry);
582
583        IBinder.DeathRecipient dr = getInternalStateDeathRecipient(clientId);
584        assertEquals(null, dr);
585    }
586
587    /*
588     * Utilities
589     */
590
591    private void doBadPublishConfiguration(String serviceName, byte[] ssi, byte[] matchFilter)
592            throws IllegalArgumentException {
593        PublishConfig publishConfig = new PublishConfig.Builder().setServiceName(serviceName)
594                .setServiceSpecificInfo(ssi).setMatchFilter(matchFilter).build();
595        int clientId = doConnect();
596        IWifiAwareDiscoverySessionCallback mockCallback = mock(
597                IWifiAwareDiscoverySessionCallback.class);
598
599        mDut.publish(clientId, publishConfig, mockCallback);
600
601        verify(mAwareStateManagerMock).publish(clientId, publishConfig, mockCallback);
602    }
603
604    private void doBadSubscribeConfiguration(String serviceName, byte[] ssi, byte[] matchFilter)
605            throws IllegalArgumentException {
606        SubscribeConfig subscribeConfig = new SubscribeConfig.Builder().setServiceName(serviceName)
607                .setServiceSpecificInfo(ssi).setMatchFilter(matchFilter).build();
608        int clientId = doConnect();
609        IWifiAwareDiscoverySessionCallback mockCallback = mock(
610                IWifiAwareDiscoverySessionCallback.class);
611
612        mDut.subscribe(clientId, subscribeConfig, mockCallback);
613
614        verify(mAwareStateManagerMock).subscribe(clientId, subscribeConfig, mockCallback);
615    }
616
617    private int doConnect() {
618        String callingPackage = "com.google.somePackage";
619
620        mDut.connect(mBinderMock, callingPackage, mCallbackMock, null, false);
621
622        ArgumentCaptor<Integer> clientId = ArgumentCaptor.forClass(Integer.class);
623        verify(mAwareStateManagerMock).connect(clientId.capture(), anyInt(), anyInt(),
624                eq(callingPackage), eq(mCallbackMock), eq(new ConfigRequest.Builder().build()),
625                eq(false));
626
627        return clientId.getValue();
628    }
629
630    private void installMockAwareStateManager()
631            throws Exception {
632        Field field = WifiAwareStateManager.class.getDeclaredField("sAwareStateManagerSingleton");
633        field.setAccessible(true);
634        field.set(null, mAwareStateManagerMock);
635    }
636
637    private static WifiAwareCharacteristics getCharacteristics() {
638        WifiAwareNative.Capabilities cap = new WifiAwareNative.Capabilities();
639        cap.maxConcurrentAwareClusters = 1;
640        cap.maxPublishes = 2;
641        cap.maxSubscribes = 2;
642        cap.maxServiceNameLen = MAX_LENGTH;
643        cap.maxMatchFilterLen = MAX_LENGTH;
644        cap.maxTotalMatchFilterLen = 255;
645        cap.maxServiceSpecificInfoLen = MAX_LENGTH;
646        cap.maxVsaDataLen = 255;
647        cap.maxMeshDataLen = 255;
648        cap.maxNdiInterfaces = 1;
649        cap.maxNdpSessions = 1;
650        cap.maxAppInfoLen = 255;
651        cap.maxQueuedTransmitMessages = 6;
652        return cap.toPublicCharacteristics();
653    }
654
655    private int getInternalStateUid(int clientId) throws Exception {
656        Field field = WifiAwareServiceImpl.class.getDeclaredField("mUidByClientId");
657        field.setAccessible(true);
658        @SuppressWarnings("unchecked")
659        SparseIntArray uidByClientId = (SparseIntArray) field.get(mDut);
660
661        return uidByClientId.get(clientId, -1);
662    }
663
664    private IBinder.DeathRecipient getInternalStateDeathRecipient(int clientId) throws Exception {
665        Field field = WifiAwareServiceImpl.class.getDeclaredField("mDeathRecipientsByClientId");
666        field.setAccessible(true);
667        @SuppressWarnings("unchecked")
668        SparseArray<IBinder.DeathRecipient> deathRecipientsByClientId =
669                            (SparseArray<IBinder.DeathRecipient>) field.get(mDut);
670
671        return deathRecipientsByClientId.get(clientId);
672    }
673}
674