1 package com.android.server.wifi.hotspot2; 2 3 import android.util.Log; 4 5 import com.android.server.wifi.Clock; 6 import com.android.server.wifi.anqp.ANQPElement; 7 import com.android.server.wifi.anqp.Constants; 8 9 import java.io.PrintWriter; 10 import java.util.ArrayList; 11 import java.util.HashMap; 12 import java.util.List; 13 import java.util.Map; 14 15 public class AnqpCache { 16 private static final boolean DBG = false; 17 18 private static final long CACHE_RECHECK = 60000L; 19 private static final boolean STANDARD_ESS = true; // Regular AP keying; see CacheKey below. 20 private long mLastSweep; 21 private Clock mClock; 22 23 private final HashMap<CacheKey, ANQPData> mANQPCache; 24 AnqpCache(Clock clock)25 public AnqpCache(Clock clock) { 26 mClock = clock; 27 mANQPCache = new HashMap<>(); 28 mLastSweep = mClock.currentTimeMillis(); 29 } 30 31 private static class CacheKey { 32 private final String mSSID; 33 private final long mBSSID; 34 private final long mHESSID; 35 CacheKey(String ssid, long bssid, long hessid)36 private CacheKey(String ssid, long bssid, long hessid) { 37 mSSID = ssid; 38 mBSSID = bssid; 39 mHESSID = hessid; 40 } 41 42 /** 43 * Build an ANQP cache key suitable for the granularity of the key space as follows: 44 * 45 * HESSID domainID standardESS Key content Rationale 46 * -------- ----------- --------------- ----------- -------------------- 47 * n/a zero n/a SSID/BSSID Domain ID indicates unique AP info 48 * not set set false SSID/BSSID Strict per AP keying override 49 * not set set true SSID Standard definition of an ESS 50 * set set n/a HESSID The ESS is defined by the HESSID 51 * 52 * @param network The network to build the key for. 53 * @param standardESS If this parameter is set the "standard" paradigm for an ESS is used 54 * for the cache, i.e. all APs with identical SSID is considered an ESS, 55 * otherwise caching is performed per AP. 56 * @return A CacheKey. 57 */ buildKey(NetworkDetail network, boolean standardESS)58 private static CacheKey buildKey(NetworkDetail network, boolean standardESS) { 59 String ssid; 60 long bssid; 61 long hessid; 62 if (network.getAnqpDomainID() == 0L || (network.getHESSID() == 0L && !standardESS)) { 63 ssid = network.getSSID(); 64 bssid = network.getBSSID(); 65 hessid = 0L; 66 } 67 else if (network.getHESSID() != 0L && network.getAnqpDomainID() > 0) { 68 ssid = null; 69 bssid = 0L; 70 hessid = network.getHESSID(); 71 } 72 else { 73 ssid = network.getSSID(); 74 bssid = 0L; 75 hessid = 0L; 76 } 77 78 return new CacheKey(ssid, bssid, hessid); 79 } 80 81 @Override hashCode()82 public int hashCode() { 83 if (mHESSID != 0) { 84 return (int)((mHESSID >>> 32) * 31 + mHESSID); 85 } 86 else if (mBSSID != 0) { 87 return (int)((mSSID.hashCode() * 31 + (mBSSID >>> 32)) * 31 + mBSSID); 88 } 89 else { 90 return mSSID.hashCode(); 91 } 92 } 93 94 @Override equals(Object thatObject)95 public boolean equals(Object thatObject) { 96 if (thatObject == this) { 97 return true; 98 } 99 else if (thatObject == null || thatObject.getClass() != CacheKey.class) { 100 return false; 101 } 102 CacheKey that = (CacheKey) thatObject; 103 return Utils.compare(that.mSSID, mSSID) == 0 && 104 that.mBSSID == mBSSID && 105 that.mHESSID == mHESSID; 106 } 107 108 @Override toString()109 public String toString() { 110 if (mHESSID != 0L) { 111 return "HESSID:" + NetworkDetail.toMACString(mHESSID); 112 } 113 else if (mBSSID != 0L) { 114 return NetworkDetail.toMACString(mBSSID) + 115 ":<" + Utils.toUnicodeEscapedString(mSSID) + ">"; 116 } 117 else { 118 return '<' + Utils.toUnicodeEscapedString(mSSID) + '>'; 119 } 120 } 121 } 122 initiate(NetworkDetail network, List<Constants.ANQPElementType> querySet)123 public List<Constants.ANQPElementType> initiate(NetworkDetail network, 124 List<Constants.ANQPElementType> querySet) { 125 CacheKey key = CacheKey.buildKey(network, STANDARD_ESS); 126 127 synchronized (mANQPCache) { 128 ANQPData data = mANQPCache.get(key); 129 if (data == null || data.expired()) { 130 mANQPCache.put(key, new ANQPData(mClock, network, data)); 131 return querySet; 132 } 133 else { 134 List<Constants.ANQPElementType> newList = data.disjoint(querySet); 135 Log.d(Utils.hs2LogTag(getClass()), 136 String.format("New ANQP elements for BSSID %012x: %s", 137 network.getBSSID(), newList)); 138 return newList; 139 } 140 } 141 } 142 update(NetworkDetail network, Map<Constants.ANQPElementType, ANQPElement> anqpElements)143 public void update(NetworkDetail network, 144 Map<Constants.ANQPElementType, ANQPElement> anqpElements) { 145 146 CacheKey key = CacheKey.buildKey(network, STANDARD_ESS); 147 148 // Networks with a 0 ANQP Domain ID are still cached, but with a very short expiry, just 149 // long enough to prevent excessive re-querying. 150 synchronized (mANQPCache) { 151 ANQPData data = mANQPCache.get(key); 152 if (data != null && data.hasData()) { 153 data.merge(anqpElements); 154 } 155 else { 156 data = new ANQPData(mClock, network, anqpElements); 157 mANQPCache.put(key, data); 158 } 159 } 160 } 161 getEntry(NetworkDetail network)162 public ANQPData getEntry(NetworkDetail network) { 163 ANQPData data; 164 165 CacheKey key = CacheKey.buildKey(network, STANDARD_ESS); 166 synchronized (mANQPCache) { 167 data = mANQPCache.get(key); 168 } 169 170 return data != null && data.isValid(network) ? data : null; 171 } 172 clear(boolean all, boolean debug)173 public void clear(boolean all, boolean debug) { 174 if (DBG) Log.d(Utils.hs2LogTag(getClass()), "Clearing ANQP cache: all: " + all); 175 long now = mClock.currentTimeMillis(); 176 synchronized (mANQPCache) { 177 if (all) { 178 mANQPCache.clear(); 179 mLastSweep = now; 180 } 181 else if (now > mLastSweep + CACHE_RECHECK) { 182 List<CacheKey> retirees = new ArrayList<>(); 183 for (Map.Entry<CacheKey, ANQPData> entry : mANQPCache.entrySet()) { 184 if (entry.getValue().expired(now)) { 185 retirees.add(entry.getKey()); 186 } 187 } 188 for (CacheKey key : retirees) { 189 mANQPCache.remove(key); 190 if (debug) { 191 Log.d(Utils.hs2LogTag(getClass()), "Retired " + key); 192 } 193 } 194 mLastSweep = now; 195 } 196 } 197 } 198 dump(PrintWriter out)199 public void dump(PrintWriter out) { 200 out.println("Last sweep " + Utils.toHMS(mClock.currentTimeMillis() - mLastSweep) + " ago."); 201 for (ANQPData anqpData : mANQPCache.values()) { 202 out.println(anqpData.toString(false)); 203 } 204 } 205 } 206