Home | History | Annotate | Download | only in hotspot2
      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 
     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 
     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          */
     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
     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
     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
    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 
    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 
    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 
    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 
    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 
    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