1 package com.android.hotspot2.osu;
2 
3 import android.net.wifi.AnqpInformationElement;
4 import android.net.wifi.ScanResult;
5 import android.util.Log;
6 
7 import com.android.anqp.Constants;
8 import com.android.anqp.HSOsuProvidersElement;
9 import com.android.anqp.OSUProvider;
10 
11 import java.net.ProtocolException;
12 import java.nio.ByteBuffer;
13 import java.nio.ByteOrder;
14 import java.util.Collection;
15 import java.util.HashMap;
16 import java.util.Map;
17 import java.util.Set;
18 
19 /**
20  * This class holds a stable set of OSU information as well as scan results based on a trail of
21  * scan results.
22  * The purpose of this class is to provide a stable set of information over a a limited span of
23  * time (SCAN_BATCH_HISTORY_SIZE scan batches) so that OSU entries in the selection list does not
24  * come and go with temporarily lost scan results.
25  * The stable set of scan results are used by the remediation flow to retrieve ANQP information
26  * for the current network to determine whether the currently associated network is a roaming
27  * network for the Home SP whose timer has currently fired.
28  */
29 public class OSUCache {
30     private static final int SCAN_BATCH_HISTORY_SIZE = 8;
31 
32     private int mInstant;
33     private final Map<OSUProvider, ScanResult> mBatchedOSUs = new HashMap<>();
34     private final Map<OSUProvider, ScanInstance> mCache = new HashMap<>();
35 
36     private static class ScanInstance {
37         private final ScanResult mScanResult;
38         private int mInstant;
39 
ScanInstance(ScanResult scanResult, int instant)40         private ScanInstance(ScanResult scanResult, int instant) {
41             mScanResult = scanResult;
42             mInstant = instant;
43         }
44 
getScanResult()45         public ScanResult getScanResult() {
46             return mScanResult;
47         }
48 
getInstant()49         public int getInstant() {
50             return mInstant;
51         }
52 
bssidEqual(ScanResult scanResult)53         private boolean bssidEqual(ScanResult scanResult) {
54             return mScanResult.BSSID.equals(scanResult.BSSID);
55         }
56 
updateInstant(int newInstant)57         private void updateInstant(int newInstant) {
58             mInstant = newInstant;
59         }
60 
61         @Override
toString()62         public String toString() {
63             return mScanResult.SSID + " @ " + mInstant;
64         }
65     }
66 
OSUCache()67     public OSUCache() {
68         mInstant = 0;
69     }
70 
clear()71     private void clear() {
72         mBatchedOSUs.clear();
73     }
74 
clearAll()75     public void clearAll() {
76         clear();
77         mCache.clear();
78     }
79 
pushScanResults(Collection<ScanResult> scanResults)80     public Map<OSUProvider, ScanResult> pushScanResults(Collection<ScanResult> scanResults) {
81         for (ScanResult scanResult : scanResults) {
82             AnqpInformationElement[] osuInfo = scanResult.anqpElements;
83             if (osuInfo != null && osuInfo.length > 0) {
84                 putResult(scanResult, osuInfo);
85             }
86         }
87         return scanEnd();
88     }
89 
putResult(ScanResult scanResult, AnqpInformationElement[] elements)90     private void putResult(ScanResult scanResult, AnqpInformationElement[] elements) {
91         for (AnqpInformationElement ie : elements) {
92             if (ie.getElementId() == AnqpInformationElement.HS_OSU_PROVIDERS
93                     && ie.getVendorId() == AnqpInformationElement.HOTSPOT20_VENDOR_ID) {
94                 try {
95                     HSOsuProvidersElement providers = new HSOsuProvidersElement(
96                             Constants.ANQPElementType.HSOSUProviders,
97                             ByteBuffer.wrap(ie.getPayload()).order(ByteOrder.LITTLE_ENDIAN));
98 
99                     putProviders(scanResult, providers);
100                 } catch (ProtocolException pe) {
101                     Log.w(OSUManager.TAG,
102                             "Failed to parse OSU element: " + pe);
103                 }
104             }
105         }
106     }
107 
putProviders(ScanResult scanResult, HSOsuProvidersElement osuProviders)108     private void putProviders(ScanResult scanResult, HSOsuProvidersElement osuProviders) {
109         for (OSUProvider provider : osuProviders.getProviders()) {
110             // Make a predictive put
111             ScanResult existing = mBatchedOSUs.put(provider, scanResult);
112             if (existing != null && existing.level > scanResult.level) {
113                 // But undo it if the entry already held a better RSSI
114                 mBatchedOSUs.put(provider, existing);
115             }
116         }
117     }
118 
scanEnd()119     private Map<OSUProvider, ScanResult> scanEnd() {
120         // Update the trail of OSU Providers:
121         int changes = 0;
122         Map<OSUProvider, ScanInstance> aged = new HashMap<>(mCache);
123         for (Map.Entry<OSUProvider, ScanResult> entry : mBatchedOSUs.entrySet()) {
124             ScanInstance current = aged.remove(entry.getKey());
125             if (current == null || !current.bssidEqual(entry.getValue())) {
126                 mCache.put(entry.getKey(), new ScanInstance(entry.getValue(), mInstant));
127                 changes++;
128                 if (current == null) {
129                     Log.d("ZXZ", "Add OSU " + entry.getKey() + " from " + entry.getValue().SSID);
130                 } else {
131                     Log.d("ZXZ", "Update OSU " + entry.getKey() + " with " +
132                             entry.getValue().SSID + " to " + current);
133                 }
134             } else {
135                 Log.d("ZXZ", "Existing OSU " + entry.getKey() + ", "
136                         + current.getInstant() + " -> " + mInstant);
137                 current.updateInstant(mInstant);
138             }
139         }
140 
141         for (Map.Entry<OSUProvider, ScanInstance> entry : aged.entrySet()) {
142             if (mInstant - entry.getValue().getInstant() > SCAN_BATCH_HISTORY_SIZE) {
143                 Log.d("ZXZ", "Remove OSU " + entry.getKey() + ", "
144                         + entry.getValue().getInstant() + " @ " + mInstant);
145                 mCache.remove(entry.getKey());
146                 changes++;
147             }
148         }
149 
150         mInstant++;
151         clear();
152 
153         // Return the latest results if there were any changes from last batch
154         if (changes > 0) {
155             Map<OSUProvider, ScanResult> results = new HashMap<>(mCache.size());
156             for (Map.Entry<OSUProvider, ScanInstance> entry : mCache.entrySet()) {
157                 results.put(entry.getKey(), entry.getValue().getScanResult());
158             }
159             return results;
160         } else {
161             return null;
162         }
163     }
164 
toBSSIDStrings(Set<Long> bssids)165     private static String toBSSIDStrings(Set<Long> bssids) {
166         StringBuilder sb = new StringBuilder();
167         for (Long bssid : bssids) {
168             sb.append(String.format(" %012x", bssid));
169         }
170         return sb.toString();
171     }
172 }
173