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