diff options
author | Treehugger Robot <treehugger-gerrit@google.com> | 2016-12-19 05:35:26 +0000 |
---|---|---|
committer | Gerrit Code Review <noreply-gerritcodereview@google.com> | 2016-12-19 05:35:27 +0000 |
commit | 3114533107f4fb87b4f1f9cca61829d37d2c2d03 (patch) | |
tree | 1cb9dd49ba78347e51ae47c13e1b4f47bfb6d0ef | |
parent | c8d379103f5162a2d50127df651a77d66ffa8fb4 (diff) | |
parent | 3d42402e0d282dc75f9c65f29d0f9e0eea753100 (diff) |
Merge "passpoint: initial support for matching passpoint providers"
4 files changed, 253 insertions, 2 deletions
diff --git a/service/java/com/android/server/wifi/hotspot2/PasspointManager.java b/service/java/com/android/server/wifi/hotspot2/PasspointManager.java index e67d10cbd..d0ced7b00 100644 --- a/service/java/com/android/server/wifi/hotspot2/PasspointManager.java +++ b/service/java/com/android/server/wifi/hotspot2/PasspointManager.java @@ -33,10 +33,12 @@ import android.net.wifi.hotspot2.PasspointConfiguration; import android.os.UserHandle; import android.text.TextUtils; import android.util.Log; +import android.util.Pair; import com.android.server.wifi.Clock; import com.android.server.wifi.IMSIParameter; import com.android.server.wifi.SIMAccessor; +import com.android.server.wifi.ScanDetail; import com.android.server.wifi.WifiKeyStore; import com.android.server.wifi.WifiNative; import com.android.server.wifi.hotspot2.anqp.ANQPElement; @@ -61,6 +63,8 @@ public class PasspointManager { private final Clock mClock; private final PasspointObjectFactory mObjectFactory; private final Map<String, PasspointProvider> mProviders; + private final AnqpCache mAnqpCache; + private final Map<Long, ANQPNetworkKey> mPendingAnqpQueries; private class CallbackHandler implements PasspointEventHandler.Callbacks { private final Context mContext; @@ -71,7 +75,22 @@ public class PasspointManager { @Override public void onANQPResponse(long bssid, Map<Constants.ANQPElementType, ANQPElement> anqpElements) { - // TO BE IMPLEMENTED. + // Remove the entry from pending list. + ANQPNetworkKey anqpKey = mPendingAnqpQueries.remove(bssid); + + if (anqpElements == null) { + // Query failed. + // TODO(b/33246489): keep track of failed ANQP queries for backing off + // future queries. + return; + } + + if (anqpKey == null) { + return; + } + + // Add new entry to the cache. + mAnqpCache.addEntry(anqpKey, anqpElements); } @Override @@ -118,6 +137,8 @@ public class PasspointManager { mSimAccessor = simAccessor; mObjectFactory = objectFactory; mProviders = new HashMap<>(); + mAnqpCache = objectFactory.makeAnqpCache(clock); + mPendingAnqpQueries = new HashMap<>(); // TODO(zqiu): load providers from the persistent storage. } @@ -214,6 +235,55 @@ public class PasspointManager { } /** + * Find the providers that can provide service through the given AP, which means the + * providers contained credential to authenticate with the given AP. + * + * An empty list will returned in the case when no match is found. + * + * @param scanDetail The detail information of the AP + * @return List of {@link PasspointProvider} + */ + public List<Pair<PasspointProvider, PasspointMatch>> matchProvider(ScanDetail scanDetail) { + // Nothing to be done if no Passpoint provider is installed. + if (mProviders.isEmpty()) { + return new ArrayList<Pair<PasspointProvider, PasspointMatch>>(); + } + + // Lookup ANQP data in the cache. + NetworkDetail networkDetail = scanDetail.getNetworkDetail(); + ANQPNetworkKey anqpKey = ANQPNetworkKey.buildKey(networkDetail.getSSID(), + networkDetail.getBSSID(), networkDetail.getHESSID(), + networkDetail.getAnqpDomainID()); + ANQPData anqpEntry = mAnqpCache.getEntry(anqpKey); + + if (anqpEntry == null) { + if (!mPendingAnqpQueries.containsValue(anqpKey)) { + // TODO(b/33246489): Request ANQP data. + mPendingAnqpQueries.put(networkDetail.getBSSID(), anqpKey); + } + return new ArrayList<Pair<PasspointProvider, PasspointMatch>>(); + } + + List<Pair<PasspointProvider, PasspointMatch>> results = new ArrayList<>(); + for (Map.Entry<String, PasspointProvider> entry : mProviders.entrySet()) { + PasspointProvider provider = entry.getValue(); + PasspointMatch matchStatus = provider.match(anqpEntry.getElements()); + if (matchStatus == PasspointMatch.HomeProvider + || matchStatus == PasspointMatch.RoamingProvider) { + results.add(new Pair<PasspointProvider, PasspointMatch>(provider, matchStatus)); + } + } + return results; + } + + /** + * Sweep the ANQP cache to remove expired entries. + */ + public void sweepCache() { + mAnqpCache.sweep(); + } + + /** * Notify the completion of an ANQP request. * TODO(zqiu): currently the notification is done through WifiMonitor, * will no longer be the case once we switch over to use wificond. diff --git a/service/java/com/android/server/wifi/hotspot2/PasspointObjectFactory.java b/service/java/com/android/server/wifi/hotspot2/PasspointObjectFactory.java index 41ec9fa73..b5793e7e0 100644 --- a/service/java/com/android/server/wifi/hotspot2/PasspointObjectFactory.java +++ b/service/java/com/android/server/wifi/hotspot2/PasspointObjectFactory.java @@ -18,6 +18,7 @@ package com.android.server.wifi.hotspot2; import android.net.wifi.hotspot2.PasspointConfiguration; +import com.android.server.wifi.Clock; import com.android.server.wifi.WifiKeyStore; import com.android.server.wifi.WifiNative; @@ -50,4 +51,14 @@ public class PasspointObjectFactory{ WifiKeyStore keyStore, long providerId) { return new PasspointProvider(config, keyStore, providerId); } + + /** + * Create a AnqpCache instance. + * + * @param clock Instance of {@link Clock} + * @return {@link AnqpCache} + */ + public AnqpCache makeAnqpCache(Clock clock) { + return new AnqpCache(clock); + } } diff --git a/service/java/com/android/server/wifi/hotspot2/PasspointProvider.java b/service/java/com/android/server/wifi/hotspot2/PasspointProvider.java index 6d120902a..a0a52f6ea 100644 --- a/service/java/com/android/server/wifi/hotspot2/PasspointProvider.java +++ b/service/java/com/android/server/wifi/hotspot2/PasspointProvider.java @@ -21,12 +21,15 @@ import android.security.Credentials; import android.util.Log; import com.android.server.wifi.WifiKeyStore; +import com.android.server.wifi.hotspot2.anqp.ANQPElement; +import com.android.server.wifi.hotspot2.anqp.Constants.ANQPElementType; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.cert.CertificateEncodingException; import java.security.cert.X509Certificate; import java.util.Arrays; +import java.util.Map; /** * Abstraction for Passpoint service provider. This class contains the both static @@ -156,6 +159,16 @@ public class PasspointProvider { } /** + * Return the matching status with the given AP, based on the ANQP elements from the AP. + * @param anqpElements ANQP elements from the AP + * @return {@link com.android.server.wifi.hotspot2.PasspointMatch} + */ + public PasspointMatch match(Map<ANQPElementType, ANQPElement> anqpElements) { + // TODO(b/33246489): To be implemented. + return PasspointMatch.None; + } + + /** * Create and return a certificate or key alias name based on the given prefix and uid. * * @param type The key or certificate type string diff --git a/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointManagerTest.java b/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointManagerTest.java index 69d7b8bc3..d776b3195 100644 --- a/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointManagerTest.java +++ b/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointManagerTest.java @@ -25,6 +25,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.any; +import static org.mockito.Mockito.anyMap; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; @@ -39,12 +40,13 @@ import android.net.wifi.hotspot2.pps.Credential; import android.net.wifi.hotspot2.pps.HomeSP; import android.os.UserHandle; import android.test.suitebuilder.annotation.SmallTest; +import android.util.Pair; import com.android.server.wifi.Clock; import com.android.server.wifi.FakeKeys; import com.android.server.wifi.IMSIParameter; import com.android.server.wifi.SIMAccessor; -import com.android.server.wifi.WifiInjector; +import com.android.server.wifi.ScanDetail; import com.android.server.wifi.WifiKeyStore; import com.android.server.wifi.WifiNative; @@ -70,6 +72,11 @@ public class PasspointManagerTest { private static final String TEST_IMSI = "1234*"; private static final long PROVIDER_ID = 1L; + private static final String TEST_SSID = "TestSSID"; + private static final long TEST_BSSID = 0x1234L; + private static final long TEST_HESSID = 0x5678L; + private static final int TEST_ANQP_DOMAIN_ID = 1; + @Mock Context mContext; @Mock WifiNative mWifiNative; @Mock WifiKeyStore mWifiKeyStore; @@ -77,12 +84,14 @@ public class PasspointManagerTest { @Mock SIMAccessor mSimAccessor; @Mock PasspointObjectFactory mObjectFactory; @Mock PasspointEventHandler.Callbacks mCallbacks; + @Mock AnqpCache mAnqpCache; PasspointManager mManager; /** Sets up test. */ @Before public void setUp() throws Exception { initMocks(this); + when(mObjectFactory.makeAnqpCache(mClock)).thenReturn(mAnqpCache); mManager = new PasspointManager(mContext, mWifiNative, mWifiKeyStore, mClock, mSimAccessor, mObjectFactory); ArgumentCaptor<PasspointEventHandler.Callbacks> callbacks = @@ -139,6 +148,51 @@ public class PasspointManagerTest { } /** + * Helper function for adding a test provider to the manager. Return the mock + * provider that's added to the manager. + * + * @return {@link PasspointProvider} + */ + private PasspointProvider addTestProvider() { + PasspointConfiguration config = new PasspointConfiguration(); + config.homeSp = new HomeSP(); + config.homeSp.fqdn = TEST_FQDN; + config.homeSp.friendlyName = TEST_FRIENDLY_NAME; + config.credential = new Credential(); + config.credential.realm = TEST_REALM; + config.credential.caCertificate = FakeKeys.CA_CERT0; + config.credential.userCredential = new Credential.UserCredential(); + config.credential.userCredential.username = "username"; + config.credential.userCredential.password = "password"; + config.credential.userCredential.eapType = EAPConstants.EAP_TTLS; + config.credential.userCredential.nonEapInnerMethod = "MS-CHAP"; + PasspointProvider provider = createMockProvider(config); + when(mClock.getWallClockMillis()).thenReturn(PROVIDER_ID); + when(mObjectFactory.makePasspointProvider(config, mWifiKeyStore, PROVIDER_ID)) + .thenReturn(provider); + assertTrue(mManager.addProvider(config)); + + return provider; + } + + /** + * Helper function for creating a mock ScanDetail. + * + * @return {@link ScanDetail} + */ + private ScanDetail createMockScanDetail() { + NetworkDetail networkDetail = mock(NetworkDetail.class); + when(networkDetail.getSSID()).thenReturn(TEST_SSID); + when(networkDetail.getBSSID()).thenReturn(TEST_BSSID); + when(networkDetail.getHESSID()).thenReturn(TEST_HESSID); + when(networkDetail.getAnqpDomainID()).thenReturn(TEST_ANQP_DOMAIN_ID); + + ScanDetail scanDetail = mock(ScanDetail.class); + when(scanDetail.getNetworkDetail()).thenReturn(networkDetail); + return scanDetail; + } + + /** * Validate the broadcast intent when icon file retrieval succeeded. * * @throws Exception @@ -376,4 +430,107 @@ public class PasspointManagerTest { public void removeNonExistingProvider() throws Exception { assertFalse(mManager.removeProvider(TEST_FQDN)); } + + /** + * Verify that an empty list will be returned when no providers are installed. + * + * @throws Exception + */ + @Test + public void matchProviderWithNoProvidersInstalled() throws Exception { + List<Pair<PasspointProvider, PasspointMatch>> result = + mManager.matchProvider(createMockScanDetail()); + assertTrue(result.isEmpty()); + } + + /** + * Verify that an empty list will be returned when ANQP entry doesn't exist in the cache. + * + * @throws Exception + */ + @Test + public void matchProviderWithAnqpCacheMissed() throws Exception { + addTestProvider(); + + ANQPNetworkKey anqpKey = ANQPNetworkKey.buildKey(TEST_SSID, TEST_BSSID, TEST_HESSID, + TEST_ANQP_DOMAIN_ID); + when(mAnqpCache.getEntry(anqpKey)).thenReturn(null); + List<Pair<PasspointProvider, PasspointMatch>> result = + mManager.matchProvider(createMockScanDetail()); + assertTrue(result.isEmpty()); + } + + /** + * Verify that the returned list will contained an expected provider when a HomeProvider + * is matched. + * + * @throws Exception + */ + @Test + public void matchProviderAsHomeProvider() throws Exception { + PasspointProvider provider = addTestProvider(); + ANQPData entry = new ANQPData(mClock, null); + ANQPNetworkKey anqpKey = ANQPNetworkKey.buildKey(TEST_SSID, TEST_BSSID, TEST_HESSID, + TEST_ANQP_DOMAIN_ID); + + when(mAnqpCache.getEntry(anqpKey)).thenReturn(entry); + when(provider.match(anyMap())).thenReturn(PasspointMatch.HomeProvider); + List<Pair<PasspointProvider, PasspointMatch>> result = + mManager.matchProvider(createMockScanDetail()); + assertEquals(1, result.size()); + assertEquals(PasspointMatch.HomeProvider, result.get(0).second); + assertEquals(TEST_FQDN, provider.getConfig().homeSp.fqdn); + } + + /** + * Verify that the returned list will contained an expected provider when a RoamingProvider + * is matched. + * + * @throws Exception + */ + @Test + public void matchProviderAsRoamingProvider() throws Exception { + PasspointProvider provider = addTestProvider(); + ANQPData entry = new ANQPData(mClock, null); + ANQPNetworkKey anqpKey = ANQPNetworkKey.buildKey(TEST_SSID, TEST_BSSID, TEST_HESSID, + TEST_ANQP_DOMAIN_ID); + + when(mAnqpCache.getEntry(anqpKey)).thenReturn(entry); + when(provider.match(anyMap())).thenReturn(PasspointMatch.RoamingProvider); + List<Pair<PasspointProvider, PasspointMatch>> result = + mManager.matchProvider(createMockScanDetail()); + assertEquals(1, result.size()); + assertEquals(PasspointMatch.RoamingProvider, result.get(0).second); + assertEquals(TEST_FQDN, provider.getConfig().homeSp.fqdn); + } + + /** + * Verify that an empty list will be returned when there is no matching provider. + * + * @throws Exception + */ + @Test + public void matchProviderWithNoMatch() throws Exception { + PasspointProvider provider = addTestProvider(); + ANQPData entry = new ANQPData(mClock, null); + ANQPNetworkKey anqpKey = ANQPNetworkKey.buildKey(TEST_SSID, TEST_BSSID, TEST_HESSID, + TEST_ANQP_DOMAIN_ID); + + when(mAnqpCache.getEntry(anqpKey)).thenReturn(entry); + when(provider.match(anyMap())).thenReturn(PasspointMatch.None); + List<Pair<PasspointProvider, PasspointMatch>> result = + mManager.matchProvider(createMockScanDetail()); + assertEquals(0, result.size()); + } + + /** + * Verify the expectations for sweepCache. + * + * @throws Exception + */ + @Test + public void sweepCache() throws Exception { + mManager.sweepCache(); + verify(mAnqpCache).sweep(); + } } |