summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTreehugger Robot <treehugger-gerrit@google.com>2016-12-19 05:35:26 +0000
committerGerrit Code Review <noreply-gerritcodereview@google.com>2016-12-19 05:35:27 +0000
commit3114533107f4fb87b4f1f9cca61829d37d2c2d03 (patch)
tree1cb9dd49ba78347e51ae47c13e1b4f47bfb6d0ef
parentc8d379103f5162a2d50127df651a77d66ffa8fb4 (diff)
parent3d42402e0d282dc75f9c65f29d0f9e0eea753100 (diff)
Merge "passpoint: initial support for matching passpoint providers"
-rw-r--r--service/java/com/android/server/wifi/hotspot2/PasspointManager.java72
-rw-r--r--service/java/com/android/server/wifi/hotspot2/PasspointObjectFactory.java11
-rw-r--r--service/java/com/android/server/wifi/hotspot2/PasspointProvider.java13
-rw-r--r--tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointManagerTest.java159
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();
+ }
}