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