diff options
author | Treehugger Robot <treehugger-gerrit@google.com> | 2016-12-14 03:29:12 +0000 |
---|---|---|
committer | Gerrit Code Review <noreply-gerritcodereview@google.com> | 2016-12-14 03:29:14 +0000 |
commit | 79c9eb4cfa9b7a45fc4f26ec2ed304f5b76fb9ff (patch) | |
tree | e9e1a2ab965c9c4403de4ac0aeef7fcc96d92131 /service | |
parent | 227865949b847b2598867947b96e63ee6a04860c (diff) | |
parent | f1b7517b04fedc6fd81f34a8cb84ce583b8ea63e (diff) |
Merge "hotspot2: simplify ANQP cache management"
Diffstat (limited to 'service')
3 files changed, 218 insertions, 286 deletions
diff --git a/service/java/com/android/server/wifi/hotspot2/ANQPData.java b/service/java/com/android/server/wifi/hotspot2/ANQPData.java index 9f04c872c..7a708c5dd 100644 --- a/service/java/com/android/server/wifi/hotspot2/ANQPData.java +++ b/service/java/com/android/server/wifi/hotspot2/ANQPData.java @@ -1,156 +1,85 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package com.android.server.wifi.hotspot2; +import com.android.internal.annotations.VisibleForTesting; import com.android.server.wifi.Clock; import com.android.server.wifi.hotspot2.anqp.ANQPElement; import com.android.server.wifi.hotspot2.anqp.Constants; -import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; -import java.util.List; import java.util.Map; +/** + * Class for maintaining ANQP elements and managing the lifetime of the elements. + */ public class ANQPData { /** - * The regular cache time for entries with a non-zero domain id. - */ - private static final long ANQP_QUALIFIED_CACHE_TIMEOUT = 3600000L; - /** - * The cache time for entries with a zero domain id. The zero domain id indicates that ANQP - * data from the AP may change at any time, thus a relatively short cache time is given to - * such data, but still long enough to avoid excessive querying. + * Entry lifetime. */ - private static final long ANQP_UNQUALIFIED_CACHE_TIMEOUT = 300000L; - /** - * This is the hold off time for pending queries, i.e. the time during which subsequent queries - * are squelched. - */ - private static final long ANQP_HOLDOFF_TIME = 10000L; + @VisibleForTesting + public static final long DATA_LIFETIME_MILLISECONDS = 3600000L; - /** - * Max value for the retry counter for unanswered queries. This limits the maximum time-out to - * ANQP_HOLDOFF_TIME * 2^MAX_RETRY. With current values this results in 640s. - */ - private static final int MAX_RETRY = 6; - - private final NetworkDetail mNetwork; - private final Map<Constants.ANQPElementType, ANQPElement> mANQPElements; - private final long mCtime; - private final long mExpiry; - private final int mRetry; private final Clock mClock; + private final Map<Constants.ANQPElementType, ANQPElement> mANQPElements; + private final long mExpiryTime; - public ANQPData(Clock clock, NetworkDetail network, - Map<Constants.ANQPElementType, ANQPElement> anqpElements) { - - mClock = clock; - mNetwork = network; - mANQPElements = anqpElements != null ? new HashMap<>(anqpElements) : null; - mCtime = mClock.getWallClockMillis(); - mRetry = 0; - if (anqpElements == null) { - mExpiry = mCtime + ANQP_HOLDOFF_TIME; - } - else if (network.getAnqpDomainID() == 0) { - mExpiry = mCtime + ANQP_UNQUALIFIED_CACHE_TIMEOUT; - } - else { - mExpiry = mCtime + ANQP_QUALIFIED_CACHE_TIMEOUT; - } - } - - public ANQPData(Clock clock, NetworkDetail network, ANQPData existing) { + public ANQPData(Clock clock, Map<Constants.ANQPElementType, ANQPElement> anqpElements) { mClock = clock; - mNetwork = network; - mANQPElements = null; - mCtime = mClock.getWallClockMillis(); - if (existing == null) { - mRetry = 0; - mExpiry = mCtime + ANQP_HOLDOFF_TIME; - } - else { - mRetry = Math.max(existing.getRetry() + 1, MAX_RETRY); - mExpiry = ANQP_HOLDOFF_TIME * (1<<mRetry); - } - } - - public List<Constants.ANQPElementType> disjoint(List<Constants.ANQPElementType> querySet) { - if (mANQPElements == null) { - // Ignore the query set for pending responses, it has minimal probability to happen - // and a new query will be reissued on the next round anyway. - return null; - } - else { - List<Constants.ANQPElementType> additions = new ArrayList<>(); - for (Constants.ANQPElementType element : querySet) { - if (!mANQPElements.containsKey(element)) { - additions.add(element); - } - } - return additions.isEmpty() ? null : additions; + mANQPElements = new HashMap<>(); + if (anqpElements != null) { + mANQPElements.putAll(anqpElements); } + mExpiryTime = mClock.getElapsedSinceBootMillis() + DATA_LIFETIME_MILLISECONDS; } - public Map<Constants.ANQPElementType, ANQPElement> getANQPElements() { + /** + * Return the ANQP elements. + * + * @return Map of ANQP elements + */ + public Map<Constants.ANQPElementType, ANQPElement> getElements() { return Collections.unmodifiableMap(mANQPElements); } - public NetworkDetail getNetwork() { - return mNetwork; - } - - public boolean expired() { - return expired(mClock.getWallClockMillis()); - } - + /** + * Check if this entry is expired at the specified time. + * + * @param at The time to check for + * @return true if it is expired at the given time + */ public boolean expired(long at) { - return mExpiry <= at; - } - - protected boolean hasData() { - return mANQPElements != null; - } - - protected void merge(Map<Constants.ANQPElementType, ANQPElement> data) { - if (data != null) { - mANQPElements.putAll(data); - } + return mExpiryTime <= at; } - protected boolean isValid(NetworkDetail nwk) { - return mANQPElements != null && - nwk.getAnqpDomainID() == mNetwork.getAnqpDomainID() && - mExpiry > mClock.getWallClockMillis(); - } - - private int getRetry() { - return mRetry; - } - - public String toString(boolean brief) { + @Override + public String toString() { StringBuilder sb = new StringBuilder(); - sb.append(mNetwork.toKeyString()).append(", domid ").append(mNetwork.getAnqpDomainID()); if (mANQPElements == null) { sb.append(", unresolved, "); } else { sb.append(", ").append(mANQPElements.size()).append(" elements, "); } - long now = mClock.getWallClockMillis(); - sb.append(Utils.toHMS(now-mCtime)).append(" old, expires in "). - append(Utils.toHMS(mExpiry-now)).append(' '); - if (brief) { - sb.append(expired(now) ? 'x' : '-'); - sb.append(mANQPElements == null ? 'u' : '-'); - } - else if (mANQPElements != null) { - sb.append(" data=").append(mANQPElements); - } + long now = mClock.getElapsedSinceBootMillis(); + sb.append(" expires in ").append(Utils.toHMS(mExpiryTime - now)).append(' '); + sb.append(expired(now) ? 'x' : '-'); + sb.append(mANQPElements == null ? 'u' : '-'); return sb.toString(); } - - @Override - public String toString() { - return toString(true); - } } diff --git a/service/java/com/android/server/wifi/hotspot2/ANQPNetworkKey.java b/service/java/com/android/server/wifi/hotspot2/ANQPNetworkKey.java new file mode 100644 index 000000000..aaaedb37d --- /dev/null +++ b/service/java/com/android/server/wifi/hotspot2/ANQPNetworkKey.java @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wifi.hotspot2; + +import android.text.TextUtils; + +/** + * Unique key for identifying APs that will contain the same ANQP information. + * + * APs in the same ESS (SSID or HESSID) with the same ANQP domain ID will have the same ANQP + * information. Thus, those APs will be keyed by the ESS identifier (SSID or HESSID) and the + * ANQP domain ID. + * + * APs without ANQP domain ID set will assumed to have unique ANQP information. Thus, those + * APs will be keyed by SSID and BSSID. + */ +public class ANQPNetworkKey { + private final String mSSID; + private final long mBSSID; + private final long mHESSID; + private final int mAnqpDomainID; + + public ANQPNetworkKey(String ssid, long bssid, long hessid, int anqpDomainID) { + mSSID = ssid; + mBSSID = bssid; + mHESSID = hessid; + mAnqpDomainID = anqpDomainID; + } + + /** + * Build an ANQP network key suitable for the granularity of the key space as follows: + * + * HESSID domainID Key content Rationale + * -------- ----------- ----------- -------------------- + * n/a zero SSID/BSSID Domain ID indicates unique AP info + * not set set SSID/domainID Standard definition of an ESS + * set set HESSID/domainID The ESS is defined by the HESSID + * + * @param ssid The SSID of the AP + * @param bssid The BSSID of the AP + * @param hessid The HESSID of the AP + * @param anqpDomainId The ANQP Domain ID of the AP + * @return {@link ANQPNetworkKey} + */ + public static ANQPNetworkKey buildKey(String ssid, long bssid, long hessid, int anqpDomainId) { + if (anqpDomainId == 0) { + return new ANQPNetworkKey(ssid, bssid, 0, 0); + } else if (hessid != 0L) { + return new ANQPNetworkKey(null, 0, hessid, anqpDomainId); + } + return new ANQPNetworkKey(ssid, 0, 0, anqpDomainId); + } + + @Override + public int hashCode() { + if (mHESSID != 0) { + return (int) (((mHESSID >>> 32) * 31 + mHESSID) * 31 + mAnqpDomainID); + } else if (mBSSID != 0) { + return (int) ((mSSID.hashCode() * 31 + (mBSSID >>> 32)) * 31 + mBSSID); + } else { + return mSSID.hashCode() * 31 + mAnqpDomainID; + } + } + + @Override + public boolean equals(Object thatObject) { + if (thatObject == this) { + return true; + } + if (!(thatObject instanceof ANQPNetworkKey)) { + return false; + } + ANQPNetworkKey that = (ANQPNetworkKey) thatObject; + return TextUtils.equals(that.mSSID, mSSID) + && that.mBSSID == mBSSID + && that.mHESSID == mHESSID + && that.mAnqpDomainID == mAnqpDomainID; + } + + @Override + public String toString() { + if (mHESSID != 0L) { + return Utils.macToString(mHESSID) + ":" + mAnqpDomainID; + } else if (mBSSID != 0L) { + return Utils.macToString(mBSSID) + + ":<" + Utils.toUnicodeEscapedString(mSSID) + ">"; + } else { + return "<" + Utils.toUnicodeEscapedString(mSSID) + ">:" + mAnqpDomainID; + } + } +} diff --git a/service/java/com/android/server/wifi/hotspot2/AnqpCache.java b/service/java/com/android/server/wifi/hotspot2/AnqpCache.java index 06e504ed4..ba96cff39 100644 --- a/service/java/com/android/server/wifi/hotspot2/AnqpCache.java +++ b/service/java/com/android/server/wifi/hotspot2/AnqpCache.java @@ -1,7 +1,22 @@ -package com.android.server.wifi.hotspot2; +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ -import android.util.Log; +package com.android.server.wifi.hotspot2; +import com.android.internal.annotations.VisibleForTesting; import com.android.server.wifi.Clock; import com.android.server.wifi.hotspot2.anqp.ANQPElement; import com.android.server.wifi.hotspot2.anqp.Constants; @@ -12,194 +27,77 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +/** + * Cache for storing ANQP data. This is simply a data cache, all the logic related to + * ANQP data query will be handled elsewhere (e.g. the consumer of the cache). + */ public class AnqpCache { - private static final boolean DBG = false; + @VisibleForTesting + public static final long CACHE_SWEEP_INTERVAL_MILLISECONDS = 60000L; - private static final long CACHE_RECHECK = 60000L; - private static final boolean STANDARD_ESS = true; // Regular AP keying; see CacheKey below. private long mLastSweep; private Clock mClock; - private final HashMap<CacheKey, ANQPData> mANQPCache; + private final Map<ANQPNetworkKey, ANQPData> mANQPCache; public AnqpCache(Clock clock) { mClock = clock; mANQPCache = new HashMap<>(); - mLastSweep = mClock.getWallClockMillis(); + mLastSweep = mClock.getElapsedSinceBootMillis(); } - private static class CacheKey { - private final String mSSID; - private final long mBSSID; - private final long mHESSID; - - private CacheKey(String ssid, long bssid, long hessid) { - mSSID = ssid; - mBSSID = bssid; - mHESSID = hessid; - } - - /** - * Build an ANQP cache key suitable for the granularity of the key space as follows: - * - * HESSID domainID standardESS Key content Rationale - * -------- ----------- --------------- ----------- -------------------- - * n/a zero n/a SSID/BSSID Domain ID indicates unique AP info - * not set set false SSID/BSSID Strict per AP keying override - * not set set true SSID Standard definition of an ESS - * set set n/a HESSID The ESS is defined by the HESSID - * - * @param network The network to build the key for. - * @param standardESS If this parameter is set the "standard" paradigm for an ESS is used - * for the cache, i.e. all APs with identical SSID is considered an ESS, - * otherwise caching is performed per AP. - * @return A CacheKey. - */ - private static CacheKey buildKey(NetworkDetail network, boolean standardESS) { - String ssid; - long bssid; - long hessid; - if (network.getAnqpDomainID() == 0L || (network.getHESSID() == 0L && !standardESS)) { - ssid = network.getSSID(); - bssid = network.getBSSID(); - hessid = 0L; - } - else if (network.getHESSID() != 0L && network.getAnqpDomainID() > 0) { - ssid = null; - bssid = 0L; - hessid = network.getHESSID(); - } - else { - ssid = network.getSSID(); - bssid = 0L; - hessid = 0L; - } - - return new CacheKey(ssid, bssid, hessid); - } - - @Override - public int hashCode() { - if (mHESSID != 0) { - return (int)((mHESSID >>> 32) * 31 + mHESSID); - } - else if (mBSSID != 0) { - return (int)((mSSID.hashCode() * 31 + (mBSSID >>> 32)) * 31 + mBSSID); - } - else { - return mSSID.hashCode(); - } - } - - @Override - public boolean equals(Object thatObject) { - if (thatObject == this) { - return true; - } - else if (thatObject == null || thatObject.getClass() != CacheKey.class) { - return false; - } - CacheKey that = (CacheKey) thatObject; - return Utils.compare(that.mSSID, mSSID) == 0 && - that.mBSSID == mBSSID && - that.mHESSID == mHESSID; - } - - @Override - public String toString() { - if (mHESSID != 0L) { - return "HESSID:" + NetworkDetail.toMACString(mHESSID); - } - else if (mBSSID != 0L) { - return NetworkDetail.toMACString(mBSSID) + - ":<" + Utils.toUnicodeEscapedString(mSSID) + ">"; - } - else { - return '<' + Utils.toUnicodeEscapedString(mSSID) + '>'; - } - } + /** + * Add an ANQP entry associated with the given key. + * + * @param key The key that's associated with the entry + * @param anqpElements The ANQP elements from the AP + */ + public void addEntry(ANQPNetworkKey key, + Map<Constants.ANQPElementType, ANQPElement> anqpElements) { + ANQPData data = new ANQPData(mClock, anqpElements); + mANQPCache.put(key, data); } - public List<Constants.ANQPElementType> initiate(NetworkDetail network, - List<Constants.ANQPElementType> querySet) { - CacheKey key = CacheKey.buildKey(network, STANDARD_ESS); - - synchronized (mANQPCache) { - ANQPData data = mANQPCache.get(key); - if (data == null || data.expired()) { - mANQPCache.put(key, new ANQPData(mClock, network, data)); - return querySet; - } - else { - List<Constants.ANQPElementType> newList = data.disjoint(querySet); - Log.d(Utils.hs2LogTag(getClass()), - String.format("New ANQP elements for BSSID %012x: %s", - network.getBSSID(), newList)); - return newList; - } - } + /** + * Get the ANQP data associated with the given AP. + * + * @param key The key that's associated with the entry + * @return {@link ANQPData} + */ + public ANQPData getEntry(ANQPNetworkKey key) { + return mANQPCache.get(key); } - public void update(NetworkDetail network, - Map<Constants.ANQPElementType, ANQPElement> anqpElements) { - - CacheKey key = CacheKey.buildKey(network, STANDARD_ESS); - - // Networks with a 0 ANQP Domain ID are still cached, but with a very short expiry, just - // long enough to prevent excessive re-querying. - synchronized (mANQPCache) { - ANQPData data = mANQPCache.get(key); - if (data != null && data.hasData()) { - data.merge(anqpElements); - } - else { - data = new ANQPData(mClock, network, anqpElements); - mANQPCache.put(key, data); - } + /** + * Go through the cache to remove any expired entries. + */ + public void sweep() { + long now = mClock.getElapsedSinceBootMillis(); + // Check if it is time to perform the sweep. + if (now < mLastSweep + CACHE_SWEEP_INTERVAL_MILLISECONDS) { + return; } - } - public ANQPData getEntry(NetworkDetail network) { - ANQPData data; - - CacheKey key = CacheKey.buildKey(network, STANDARD_ESS); - synchronized (mANQPCache) { - data = mANQPCache.get(key); + // Get all expired keys. + List<ANQPNetworkKey> expiredKeys = new ArrayList<>(); + for (Map.Entry<ANQPNetworkKey, ANQPData> entry : mANQPCache.entrySet()) { + if (entry.getValue().expired(now)) { + expiredKeys.add(entry.getKey()); + } } - return data != null && data.isValid(network) ? data : null; - } - - public void clear(boolean all, boolean debug) { - if (DBG) Log.d(Utils.hs2LogTag(getClass()), "Clearing ANQP cache: all: " + all); - long now = mClock.getWallClockMillis(); - synchronized (mANQPCache) { - if (all) { - mANQPCache.clear(); - mLastSweep = now; - } - else if (now > mLastSweep + CACHE_RECHECK) { - List<CacheKey> retirees = new ArrayList<>(); - for (Map.Entry<CacheKey, ANQPData> entry : mANQPCache.entrySet()) { - if (entry.getValue().expired(now)) { - retirees.add(entry.getKey()); - } - } - for (CacheKey key : retirees) { - mANQPCache.remove(key); - if (debug) { - Log.d(Utils.hs2LogTag(getClass()), "Retired " + key); - } - } - mLastSweep = now; - } + // Remove all expired entries. + for (ANQPNetworkKey key : expiredKeys) { + mANQPCache.remove(key); } + mLastSweep = now; } public void dump(PrintWriter out) { - out.println("Last sweep " + Utils.toHMS(mClock.getWallClockMillis() - mLastSweep) + " ago."); + out.println("Last sweep " + Utils.toHMS(mClock.getElapsedSinceBootMillis() - mLastSweep) + + " ago."); for (ANQPData anqpData : mANQPCache.values()) { - out.println(anqpData.toString(false)); + out.println(anqpData); } } } |