package com.android.server.wifi.hotspot2; import android.util.Log; import com.android.server.wifi.Clock; import com.android.server.wifi.anqp.ANQPElement; import com.android.server.wifi.anqp.Constants; import java.io.PrintWriter; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; public class AnqpCache { private static final boolean DBG = false; 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 mANQPCache; public AnqpCache(Clock clock) { mClock = clock; mANQPCache = new HashMap<>(); mLastSweep = mClock.currentTimeMillis(); } 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) + '>'; } } } public List initiate(NetworkDetail network, List 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 newList = data.disjoint(querySet); Log.d(Utils.hs2LogTag(getClass()), String.format("New ANQP elements for BSSID %012x: %s", network.getBSSID(), newList)); return newList; } } } public void update(NetworkDetail network, Map 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); } } } public ANQPData getEntry(NetworkDetail network) { ANQPData data; CacheKey key = CacheKey.buildKey(network, STANDARD_ESS); synchronized (mANQPCache) { data = mANQPCache.get(key); } 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.currentTimeMillis(); synchronized (mANQPCache) { if (all) { mANQPCache.clear(); mLastSweep = now; } else if (now > mLastSweep + CACHE_RECHECK) { List retirees = new ArrayList<>(); for (Map.Entry 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; } } } public void dump(PrintWriter out) { out.println("Last sweep " + Utils.toHMS(mClock.currentTimeMillis() - mLastSweep) + " ago."); for (ANQPData anqpData : mANQPCache.values()) { out.println(anqpData.toString(false)); } } }