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