PasspointManagerTest.java revision 4f4d745ca28b915ea4a7c91ec5df3ea8a2db64ad
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.hotspot2;
18
19import static android.net.wifi.WifiManager.EXTRA_PASSPOINT_ICON_BSSID;
20import static android.net.wifi.WifiManager.EXTRA_PASSPOINT_ICON_DATA;
21import static android.net.wifi.WifiManager.EXTRA_PASSPOINT_ICON_FILE;
22import static android.net.wifi.WifiManager.PASSPOINT_ICON_RECEIVED_ACTION;
23
24import static org.junit.Assert.assertEquals;
25import static org.junit.Assert.assertFalse;
26import static org.junit.Assert.assertTrue;
27import static org.mockito.Mockito.any;
28import static org.mockito.Mockito.anyBoolean;
29import static org.mockito.Mockito.anyLong;
30import static org.mockito.Mockito.anyMap;
31import static org.mockito.Mockito.eq;
32import static org.mockito.Mockito.mock;
33import static org.mockito.Mockito.never;
34import static org.mockito.Mockito.verify;
35import static org.mockito.Mockito.when;
36import static org.mockito.MockitoAnnotations.initMocks;
37
38import android.content.Context;
39import android.content.Intent;
40import android.net.wifi.EAPConstants;
41import android.net.wifi.hotspot2.PasspointConfiguration;
42import android.net.wifi.hotspot2.pps.Credential;
43import android.net.wifi.hotspot2.pps.HomeSP;
44import android.os.UserHandle;
45import android.test.suitebuilder.annotation.SmallTest;
46import android.util.Pair;
47
48import com.android.server.wifi.Clock;
49import com.android.server.wifi.FakeKeys;
50import com.android.server.wifi.IMSIParameter;
51import com.android.server.wifi.SIMAccessor;
52import com.android.server.wifi.ScanDetail;
53import com.android.server.wifi.WifiKeyStore;
54import com.android.server.wifi.WifiNative;
55import com.android.server.wifi.hotspot2.anqp.ANQPElement;
56import com.android.server.wifi.hotspot2.anqp.Constants.ANQPElementType;
57import com.android.server.wifi.hotspot2.anqp.DomainNameElement;
58
59import org.junit.Before;
60import org.junit.Test;
61import org.mockito.ArgumentCaptor;
62import org.mockito.Mock;
63
64import java.util.ArrayList;
65import java.util.Arrays;
66import java.util.HashMap;
67import java.util.List;
68import java.util.Map;
69
70/**
71 * Unit tests for {@link com.android.server.wifi.hotspot2.PasspointManager}.
72 */
73@SmallTest
74public class PasspointManagerTest {
75    private static final long BSSID = 0x112233445566L;
76    private static final String ICON_FILENAME = "test";
77    private static final String TEST_FQDN = "test1.test.com";
78    private static final String TEST_FRIENDLY_NAME = "friendly name";
79    private static final String TEST_REALM = "realm.test.com";
80    private static final String TEST_IMSI = "1234*";
81    private static final IMSIParameter TEST_IMSI_PARAM = IMSIParameter.build(TEST_IMSI);
82
83    private static final String TEST_SSID = "TestSSID";
84    private static final long TEST_BSSID = 0x1234L;
85    private static final long TEST_HESSID = 0x5678L;
86    private static final int TEST_ANQP_DOMAIN_ID = 1;
87
88    @Mock Context mContext;
89    @Mock WifiNative mWifiNative;
90    @Mock WifiKeyStore mWifiKeyStore;
91    @Mock Clock mClock;
92    @Mock SIMAccessor mSimAccessor;
93    @Mock PasspointObjectFactory mObjectFactory;
94    @Mock PasspointEventHandler.Callbacks mCallbacks;
95    @Mock AnqpCache mAnqpCache;
96    @Mock ANQPRequestManager mAnqpRequestManager;
97    PasspointManager mManager;
98
99    /** Sets up test. */
100    @Before
101    public void setUp() throws Exception {
102        initMocks(this);
103        when(mObjectFactory.makeAnqpCache(mClock)).thenReturn(mAnqpCache);
104        when(mObjectFactory.makeANQPRequestManager(any(PasspointEventHandler.class), eq(mClock)))
105                .thenReturn(mAnqpRequestManager);
106        mManager = new PasspointManager(mContext, mWifiNative, mWifiKeyStore, mClock,
107                mSimAccessor, mObjectFactory);
108        ArgumentCaptor<PasspointEventHandler.Callbacks> callbacks =
109                ArgumentCaptor.forClass(PasspointEventHandler.Callbacks.class);
110        verify(mObjectFactory).makePasspointEventHandler(any(WifiNative.class),
111                                                         callbacks.capture());
112        mCallbacks = callbacks.getValue();
113    }
114
115    /**
116     * Verify PASSPOINT_ICON_RECEIVED_ACTION broadcast intent.
117     * @param bssid BSSID of the AP
118     * @param fileName Name of the icon file
119     * @param data icon data byte array
120     */
121    private void verifyIconIntent(long bssid, String fileName, byte[] data) {
122        ArgumentCaptor<Intent> intent = ArgumentCaptor.forClass(Intent.class);
123        verify(mContext).sendBroadcastAsUser(intent.capture(), eq(UserHandle.ALL));
124        assertEquals(PASSPOINT_ICON_RECEIVED_ACTION, intent.getValue().getAction());
125        assertTrue(intent.getValue().getExtras().containsKey(EXTRA_PASSPOINT_ICON_BSSID));
126        assertEquals(bssid, intent.getValue().getExtras().getLong(EXTRA_PASSPOINT_ICON_BSSID));
127        assertTrue(intent.getValue().getExtras().containsKey(EXTRA_PASSPOINT_ICON_FILE));
128        assertEquals(fileName, intent.getValue().getExtras().getString(EXTRA_PASSPOINT_ICON_FILE));
129        if (data != null) {
130            assertTrue(intent.getValue().getExtras().containsKey(EXTRA_PASSPOINT_ICON_DATA));
131            assertEquals(data,
132                         intent.getValue().getExtras().getByteArray(EXTRA_PASSPOINT_ICON_DATA));
133        }
134    }
135
136    /**
137     * Verify that the given Passpoint configuration matches the one that's added to
138     * the PasspointManager.
139     *
140     * @param expectedConfig The expected installed Passpoint configuration
141     */
142    private void verifyInstalledConfig(PasspointConfiguration expectedConfig) {
143        List<PasspointConfiguration> installedConfigs = mManager.getProviderConfigs();
144        assertEquals(1, installedConfigs.size());
145        assertEquals(expectedConfig, installedConfigs.get(0));
146    }
147
148    /**
149     * Create a mock PasspointProvider with default expectations.
150     *
151     * @param config The configuration associated with the provider
152     * @return {@link com.android.server.wifi.hotspot2.PasspointProvider}
153     */
154    private PasspointProvider createMockProvider(PasspointConfiguration config) {
155        PasspointProvider provider = mock(PasspointProvider.class);
156        when(provider.installCertsAndKeys()).thenReturn(true);
157        when(provider.getConfig()).thenReturn(config);
158        return provider;
159    }
160
161    /**
162     * Helper function for creating a test configuration with user credential.
163     *
164     * @return {@link PasspointConfiguration}
165     */
166    private PasspointConfiguration createTestConfigWithUserCredential() {
167        PasspointConfiguration config = new PasspointConfiguration();
168        HomeSP homeSp = new HomeSP();
169        homeSp.setFqdn(TEST_FQDN);
170        homeSp.setFriendlyName(TEST_FRIENDLY_NAME);
171        config.setHomeSp(homeSp);
172        Credential credential = new Credential();
173        credential.setRealm(TEST_REALM);
174        credential.setCaCertificate(FakeKeys.CA_CERT0);
175        Credential.UserCredential userCredential = new Credential.UserCredential();
176        userCredential.setUsername("username");
177        userCredential.setPassword("password");
178        userCredential.setEapType(EAPConstants.EAP_TTLS);
179        userCredential.setNonEapInnerMethod("MS-CHAP");
180        credential.setUserCredential(userCredential);
181        config.setCredential(credential);
182        return config;
183    }
184
185    /**
186     * Helper function for creating a test configuration with SIM credential.
187     *
188     * @return {@link PasspointConfiguration}
189     */
190    private PasspointConfiguration createTestConfigWithSimCredential() {
191        PasspointConfiguration config = new PasspointConfiguration();
192        HomeSP homeSp = new HomeSP();
193        homeSp.setFqdn(TEST_FQDN);
194        homeSp.setFriendlyName(TEST_FRIENDLY_NAME);
195        config.setHomeSp(homeSp);
196        Credential credential = new Credential();
197        credential.setRealm(TEST_REALM);
198        Credential.SimCredential simCredential = new Credential.SimCredential();
199        simCredential.setImsi(TEST_IMSI);
200        simCredential.setEapType(EAPConstants.EAP_SIM);
201        credential.setSimCredential(simCredential);
202        config.setCredential(credential);
203        return config;
204    }
205
206    /**
207     * Helper function for adding a test provider to the manager.  Return the mock
208     * provider that's added to the manager.
209     *
210     * @return {@link PasspointProvider}
211     */
212    private PasspointProvider addTestProvider() {
213        PasspointConfiguration config = createTestConfigWithUserCredential();
214        PasspointProvider provider = createMockProvider(config);
215        when(mObjectFactory.makePasspointProvider(eq(config), eq(mWifiKeyStore),
216                eq(mSimAccessor), anyLong())).thenReturn(provider);
217        assertTrue(mManager.addOrUpdateProvider(config));
218
219        return provider;
220    }
221
222    /**
223     * Helper function for creating a mock ScanDetail.
224     *
225     * @return {@link ScanDetail}
226     */
227    private ScanDetail createMockScanDetail() {
228        NetworkDetail networkDetail = mock(NetworkDetail.class);
229        when(networkDetail.getSSID()).thenReturn(TEST_SSID);
230        when(networkDetail.getBSSID()).thenReturn(TEST_BSSID);
231        when(networkDetail.getHESSID()).thenReturn(TEST_HESSID);
232        when(networkDetail.getAnqpDomainID()).thenReturn(TEST_ANQP_DOMAIN_ID);
233
234        ScanDetail scanDetail = mock(ScanDetail.class);
235        when(scanDetail.getNetworkDetail()).thenReturn(networkDetail);
236        return scanDetail;
237    }
238
239    /**
240     * Verify that the ANQP elements will be added to the ANQP cache on receiving a successful
241     * response.
242     *
243     * @throws Exception
244     */
245    @Test
246    public void anqpResponseSuccess() throws Exception {
247        Map<ANQPElementType, ANQPElement> anqpElementMap = new HashMap<>();
248        anqpElementMap.put(ANQPElementType.ANQPDomName,
249                new DomainNameElement(Arrays.asList(new String[] {"test.com"})));
250
251        ScanDetail scanDetail = createMockScanDetail();
252        ANQPNetworkKey anqpKey = ANQPNetworkKey.buildKey(TEST_SSID, TEST_BSSID, TEST_HESSID,
253                TEST_ANQP_DOMAIN_ID);
254        when(mAnqpRequestManager.onRequestCompleted(TEST_BSSID, true)).thenReturn(scanDetail);
255        mCallbacks.onANQPResponse(TEST_BSSID, anqpElementMap);
256        verify(mAnqpCache).addEntry(anqpKey, anqpElementMap);
257        verify(scanDetail).propagateANQPInfo(anqpElementMap);
258    }
259
260    /**
261     * Verify that no ANQP elements will be added to the ANQP cache on receiving a successful
262     * response for a request that's not sent by us.
263     *
264     * @throws Exception
265     */
266    @Test
267    public void anqpResponseSuccessWithUnknownRequest() throws Exception {
268        Map<ANQPElementType, ANQPElement> anqpElementMap = new HashMap<>();
269        anqpElementMap.put(ANQPElementType.ANQPDomName,
270                new DomainNameElement(Arrays.asList(new String[] {"test.com"})));
271
272        when(mAnqpRequestManager.onRequestCompleted(TEST_BSSID, true)).thenReturn(null);
273        mCallbacks.onANQPResponse(TEST_BSSID, anqpElementMap);
274        verify(mAnqpCache, never()).addEntry(any(ANQPNetworkKey.class), anyMap());
275    }
276
277    /**
278     * Verify that no ANQP elements will be added to the ANQP cache on receiving a failure response.
279     *
280     * @throws Exception
281     */
282    @Test
283    public void anqpResponseFailure() throws Exception {
284        ANQPNetworkKey anqpKey = ANQPNetworkKey.buildKey(TEST_SSID, TEST_BSSID, TEST_HESSID,
285                TEST_ANQP_DOMAIN_ID);
286
287        ScanDetail scanDetail = createMockScanDetail();
288        when(mAnqpRequestManager.onRequestCompleted(TEST_BSSID, false)).thenReturn(scanDetail);
289        mCallbacks.onANQPResponse(TEST_BSSID, null);
290        verify(mAnqpCache, never()).addEntry(any(ANQPNetworkKey.class), anyMap());
291
292    }
293
294    /**
295     * Validate the broadcast intent when icon file retrieval succeeded.
296     *
297     * @throws Exception
298     */
299    @Test
300    public void iconResponseSuccess() throws Exception {
301        byte[] iconData = new byte[] {0x00, 0x11};
302        mCallbacks.onIconResponse(BSSID, ICON_FILENAME, iconData);
303        verifyIconIntent(BSSID, ICON_FILENAME, iconData);
304    }
305
306    /**
307     * Validate the broadcast intent when icon file retrieval failed.
308     *
309     * @throws Exception
310     */
311    @Test
312    public void iconResponseFailure() throws Exception {
313        mCallbacks.onIconResponse(BSSID, ICON_FILENAME, null);
314        verifyIconIntent(BSSID, ICON_FILENAME, null);
315    }
316
317    /**
318     * Verify that adding a provider with a null configuration will fail.
319     *
320     * @throws Exception
321     */
322    @Test
323    public void addProviderWithNullConfig() throws Exception {
324        assertFalse(mManager.addOrUpdateProvider(null));
325    }
326
327    /**
328     * Verify that adding a provider with a empty configuration will fail.
329     *
330     * @throws Exception
331     */
332    @Test
333    public void addProviderWithEmptyConfig() throws Exception {
334        assertFalse(mManager.addOrUpdateProvider(new PasspointConfiguration()));
335    }
336
337    /**
338     * Verify taht adding a provider with an invalid credential will fail (using EAP-TLS
339     * for user credential).
340     *
341     * @throws Exception
342     */
343    @Test
344    public void addProviderWithInvalidCredential() throws Exception {
345        PasspointConfiguration config = createTestConfigWithUserCredential();
346        // EAP-TLS not allowed for user credential.
347        config.getCredential().getUserCredential().setEapType(EAPConstants.EAP_TLS);
348        assertFalse(mManager.addOrUpdateProvider(config));
349    }
350
351    /**
352     * Verify that adding a provider with a valid configuration and user credential will succeed.
353     *
354     * @throws Exception
355     */
356    @Test
357    public void addRemoveProviderWithValidUserCredential() throws Exception {
358        PasspointConfiguration config = createTestConfigWithUserCredential();
359        PasspointProvider provider = createMockProvider(config);
360        when(mObjectFactory.makePasspointProvider(eq(config), eq(mWifiKeyStore),
361                eq(mSimAccessor), anyLong())).thenReturn(provider);
362        assertTrue(mManager.addOrUpdateProvider(config));
363        verifyInstalledConfig(config);
364
365        // Remove the provider.
366        assertTrue(mManager.removeProvider(TEST_FQDN));
367        verify(provider).uninstallCertsAndKeys();
368        assertTrue(mManager.getProviderConfigs().isEmpty());
369    }
370
371    /**
372     * Verify that adding a provider with a valid configuration and SIM credential will succeed.
373     *
374     * @throws Exception
375     */
376    @Test
377    public void addRemoveProviderWithValidSimCredential() throws Exception {
378        PasspointConfiguration config = createTestConfigWithSimCredential();
379        when(mSimAccessor.getMatchingImsis(TEST_IMSI_PARAM)).thenReturn(new ArrayList<String>());
380        PasspointProvider provider = createMockProvider(config);
381        when(mObjectFactory.makePasspointProvider(eq(config), eq(mWifiKeyStore),
382                eq(mSimAccessor), anyLong())).thenReturn(provider);
383        assertTrue(mManager.addOrUpdateProvider(config));
384        verifyInstalledConfig(config);
385
386        // Remove the provider.
387        assertTrue(mManager.removeProvider(TEST_FQDN));
388        verify(provider).uninstallCertsAndKeys();
389        assertTrue(mManager.getProviderConfigs().isEmpty());
390    }
391
392    /**
393     * Verify that adding a provider with an invalid SIM credential (configured IMSI doesn't
394     * match the IMSI of the installed SIM cards) will fail.
395     *
396     * @throws Exception
397     */
398    @Test
399    public void addProviderWithValidSimCredentialWithInvalidIMSI() throws Exception {
400        PasspointConfiguration config = createTestConfigWithSimCredential();
401        when(mSimAccessor.getMatchingImsis(TEST_IMSI_PARAM)).thenReturn(null);
402        assertFalse(mManager.addOrUpdateProvider(config));
403    }
404
405    /**
406     * Verify that adding a provider with the same base domain as the existing provider will
407     * succeed, and verify that the existing provider is replaced by the new provider with
408     * the new configuration.
409     *
410     * @throws Exception
411     */
412    @Test
413    public void addProviderWithExistingConfig() throws Exception {
414        // Add a provider with the original configuration.
415        PasspointConfiguration origConfig = createTestConfigWithSimCredential();
416        when(mSimAccessor.getMatchingImsis(TEST_IMSI_PARAM)).thenReturn(new ArrayList<String>());
417        PasspointProvider origProvider = createMockProvider(origConfig);
418        when(mObjectFactory.makePasspointProvider(eq(origConfig), eq(mWifiKeyStore),
419                eq(mSimAccessor), anyLong())).thenReturn(origProvider);
420        assertTrue(mManager.addOrUpdateProvider(origConfig));
421        verifyInstalledConfig(origConfig);
422
423        // Add another provider with the same base domain as the existing provider.
424        // This should replace the existing provider with the new configuration.
425        PasspointConfiguration newConfig = createTestConfigWithUserCredential();
426        PasspointProvider newProvider = createMockProvider(newConfig);
427        when(mObjectFactory.makePasspointProvider(eq(newConfig), eq(mWifiKeyStore),
428                eq(mSimAccessor), anyLong())).thenReturn(newProvider);
429        assertTrue(mManager.addOrUpdateProvider(newConfig));
430        verifyInstalledConfig(newConfig);
431    }
432
433    /**
434     * Verify that adding a provider will fail when failing to install certificates and
435     * key to the keystore.
436     *
437     * @throws Exception
438     */
439    @Test
440    public void addProviderOnKeyInstallationFailiure() throws Exception {
441        PasspointConfiguration config = createTestConfigWithUserCredential();
442        PasspointProvider provider = mock(PasspointProvider.class);
443        when(provider.installCertsAndKeys()).thenReturn(false);
444        when(mObjectFactory.makePasspointProvider(eq(config), eq(mWifiKeyStore),
445                eq(mSimAccessor), anyLong())).thenReturn(provider);
446        assertFalse(mManager.addOrUpdateProvider(config));
447    }
448
449    /**
450     * Verify that removing a non-existing provider will fail.
451     *
452     * @throws Exception
453     */
454    @Test
455    public void removeNonExistingProvider() throws Exception {
456        assertFalse(mManager.removeProvider(TEST_FQDN));
457    }
458
459    /**
460     * Verify that an empty list will be returned when no providers are installed.
461     *
462     * @throws Exception
463     */
464    @Test
465    public void matchProviderWithNoProvidersInstalled() throws Exception {
466        List<Pair<PasspointProvider, PasspointMatch>> result =
467                mManager.matchProvider(createMockScanDetail());
468        assertTrue(result.isEmpty());
469    }
470
471    /**
472     * Verify that an empty list will be returned when ANQP entry doesn't exist in the cache.
473     *
474     * @throws Exception
475     */
476    @Test
477    public void matchProviderWithAnqpCacheMissed() throws Exception {
478        addTestProvider();
479
480        ANQPNetworkKey anqpKey = ANQPNetworkKey.buildKey(TEST_SSID, TEST_BSSID, TEST_HESSID,
481                TEST_ANQP_DOMAIN_ID);
482        when(mAnqpCache.getEntry(anqpKey)).thenReturn(null);
483        List<Pair<PasspointProvider, PasspointMatch>> result =
484                mManager.matchProvider(createMockScanDetail());
485        // Verify that a request for ANQP elements is initiated.
486        verify(mAnqpRequestManager).requestANQPElements(eq(TEST_BSSID), any(ScanDetail.class),
487                anyBoolean(), anyBoolean());
488        assertTrue(result.isEmpty());
489    }
490
491    /**
492     * Verify that the returned list will contained an expected provider when a HomeProvider
493     * is matched.
494     *
495     * @throws Exception
496     */
497    @Test
498    public void matchProviderAsHomeProvider() throws Exception {
499        PasspointProvider provider = addTestProvider();
500        ANQPData entry = new ANQPData(mClock, null);
501        ANQPNetworkKey anqpKey = ANQPNetworkKey.buildKey(TEST_SSID, TEST_BSSID, TEST_HESSID,
502                TEST_ANQP_DOMAIN_ID);
503
504        when(mAnqpCache.getEntry(anqpKey)).thenReturn(entry);
505        when(provider.match(anyMap())).thenReturn(PasspointMatch.HomeProvider);
506        List<Pair<PasspointProvider, PasspointMatch>> result =
507                mManager.matchProvider(createMockScanDetail());
508        assertEquals(1, result.size());
509        assertEquals(PasspointMatch.HomeProvider, result.get(0).second);
510    }
511
512    /**
513     * Verify that the returned list will contained an expected provider when a RoamingProvider
514     * is matched.
515     *
516     * @throws Exception
517     */
518    @Test
519    public void matchProviderAsRoamingProvider() throws Exception {
520        PasspointProvider provider = addTestProvider();
521        ANQPData entry = new ANQPData(mClock, null);
522        ANQPNetworkKey anqpKey = ANQPNetworkKey.buildKey(TEST_SSID, TEST_BSSID, TEST_HESSID,
523                TEST_ANQP_DOMAIN_ID);
524
525        when(mAnqpCache.getEntry(anqpKey)).thenReturn(entry);
526        when(provider.match(anyMap())).thenReturn(PasspointMatch.RoamingProvider);
527        List<Pair<PasspointProvider, PasspointMatch>> result =
528                mManager.matchProvider(createMockScanDetail());
529        assertEquals(1, result.size());
530        assertEquals(PasspointMatch.RoamingProvider, result.get(0).second);
531        assertEquals(TEST_FQDN, provider.getConfig().getHomeSp().getFqdn());
532    }
533
534    /**
535     * Verify that an empty list will be returned when there is no matching provider.
536     *
537     * @throws Exception
538     */
539    @Test
540    public void matchProviderWithNoMatch() throws Exception {
541        PasspointProvider provider = addTestProvider();
542        ANQPData entry = new ANQPData(mClock, null);
543        ANQPNetworkKey anqpKey = ANQPNetworkKey.buildKey(TEST_SSID, TEST_BSSID, TEST_HESSID,
544                TEST_ANQP_DOMAIN_ID);
545
546        when(mAnqpCache.getEntry(anqpKey)).thenReturn(entry);
547        when(provider.match(anyMap())).thenReturn(PasspointMatch.None);
548        List<Pair<PasspointProvider, PasspointMatch>> result =
549                mManager.matchProvider(createMockScanDetail());
550        assertEquals(0, result.size());
551    }
552
553    /**
554     * Verify the expectations for sweepCache.
555     *
556     * @throws Exception
557     */
558    @Test
559    public void sweepCache() throws Exception {
560        mManager.sweepCache();
561        verify(mAnqpCache).sweep();
562    }
563}
564