blob: a6cd42ee1a6d6f169c408656f5a4104fa9f58772 [file] [log] [blame]
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<CacheKey, ANQPData> 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<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;
}
}
}
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);
}
}
}
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<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;
}
}
}
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));
}
}
}