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