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