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