1 /*
2  * Copyright (C) 2016 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.server.wifi.hotspot2;
18 
19 import android.app.AlarmManager;
20 import android.os.Handler;
21 import android.util.Log;
22 
23 import com.android.internal.annotations.VisibleForTesting;
24 import com.android.server.wifi.Clock;
25 import com.android.server.wifi.WifiInjector;
26 import com.android.server.wifi.hotspot2.anqp.Constants;
27 import com.android.wifi.flags.FeatureFlags;
28 
29 import java.io.PrintWriter;
30 import java.util.ArrayDeque;
31 import java.util.ArrayList;
32 import java.util.Arrays;
33 import java.util.HashMap;
34 import java.util.List;
35 import java.util.Map;
36 import java.util.Queue;
37 
38 /**
39  * Class for managing sending of ANQP requests.  This manager will ignore ANQP requests for a
40  * period of time (hold off time) to a specified AP if the previous request to that AP goes
41  * unanswered or failed.  The hold off time will increase exponentially until the max is reached.
42  */
43 public class ANQPRequestManager {
44     private static final int ANQP_REQUEST_ALARM_INTERVAL_MS = 2_000;
45     @VisibleForTesting
46     public static final String ANQP_REQUEST_ALARM_TAG = "anqpRequestAlarm";
47     private static final String TAG = "ANQPRequestManager";
48 
49     private final PasspointEventHandler mPasspointHandler;
50     private final AlarmManager mAlarmManager;
51     private final Clock mClock;
52     private final FeatureFlags mFeatureFlags;
53     private boolean mAnqpRequestPending;
54 
55     /**
56      * List of pending ANQP request associated with an AP (BSSID).
57      */
58     private final Map<Long, ANQPNetworkKey> mPendingQueries;
59     private final Queue<AnqpRequest> mPendingRequest = new ArrayDeque<>();
60 
61     /**
62      * List of hold off time information associated with APs specified by their BSSID.
63      * Used to determine when an ANQP request can be send to the corresponding AP after the
64      * previous request goes unanswered or failed.
65      */
66     private final Map<Long, HoldOffInfo> mHoldOffInfo;
67 
68     /**
69      * Minimum number of milliseconds to wait for before attempting ANQP queries to the same AP
70      * after previous request goes unanswered or failed.
71      */
72     @VisibleForTesting
73     public static final int BASE_HOLDOFF_TIME_MILLISECONDS = 10000;
74 
75     /**
76      * Max value for the hold off counter for unanswered/failed queries.  This limits the maximum
77      * hold off time to:
78      * BASE_HOLDOFF_TIME_MILLISECONDS * 2^MAX_HOLDOFF_COUNT
79      * which is 640 seconds.
80      */
81     @VisibleForTesting
82     public static final int MAX_HOLDOFF_COUNT = 6;
83 
84     private static final List<Constants.ANQPElementType> R1_ANQP_BASE_SET = Arrays.asList(
85             Constants.ANQPElementType.ANQPVenueName,
86             Constants.ANQPElementType.ANQPIPAddrAvailability,
87             Constants.ANQPElementType.ANQPNAIRealm,
88             Constants.ANQPElementType.ANQP3GPPNetwork,
89             Constants.ANQPElementType.ANQPDomName,
90             Constants.ANQPElementType.HSFriendlyName,
91             Constants.ANQPElementType.HSWANMetrics,
92             Constants.ANQPElementType.HSConnCapability);
93 
94     private static final List<Constants.ANQPElementType> R2_ANQP_BASE_SET = Arrays.asList(
95             Constants.ANQPElementType.HSOSUProviders);
96     private final Handler mHandler;
97 
98     /**
99      * Class to keep track of AP status for ANQP requests.
100      */
101     private class HoldOffInfo {
102         /**
103          * Current hold off count.  Will max out at {@link #MAX_HOLDOFF_COUNT}.
104          */
105         public int holdOffCount;
106         /**
107          * The time stamp in milliseconds when we're allow to send ANQP request to the
108          * corresponding AP.
109          */
110         public long holdOffExpirationTime;
111     }
112     private static class AnqpRequest {
113 
AnqpRequest(long bssid, boolean rcOIs, NetworkDetail.HSRelease hsRelease, ANQPNetworkKey anqpNetworkKey)114         AnqpRequest(long bssid, boolean rcOIs, NetworkDetail.HSRelease hsRelease,
115                 ANQPNetworkKey anqpNetworkKey) {
116             mBssid = bssid;
117             mAnqpNetworkKey = anqpNetworkKey;
118             mRcOIs = rcOIs;
119             mHsRelease = hsRelease;
120         }
121         public final long mBssid;
122         public final boolean mRcOIs;
123         public final NetworkDetail.HSRelease mHsRelease;
124         public final ANQPNetworkKey mAnqpNetworkKey;
125     }
126 
127     private final AlarmManager.OnAlarmListener mAnqpRequestListener =
128             new AlarmManager.OnAlarmListener() {
129                 public void onAlarm() {
130                     mAnqpRequestPending = false;
131                     processNextRequest();
132                 }
133             };
134 
ANQPRequestManager(PasspointEventHandler passpointEventHandler, Clock clock, WifiInjector wifiInjector, Handler handler)135     public ANQPRequestManager(PasspointEventHandler passpointEventHandler, Clock clock,
136             WifiInjector wifiInjector, Handler handler) {
137         mPasspointHandler = passpointEventHandler;
138         mClock = clock;
139         mAlarmManager = wifiInjector.getAlarmManager();
140         mFeatureFlags = wifiInjector.getDeviceConfigFacade().getFeatureFlags();
141         mHoldOffInfo = new HashMap<>();
142         mPendingQueries = new HashMap<>();
143         mHandler = handler;
144     }
145 
146     /**
147      * Request ANQP elements from the specified AP.  This will request the basic Release 1 ANQP
148      * elements {@link #R1_ANQP_BASE_SET}.  Additional elements will be requested based on the
149      * information provided in the Information Element (Roaming Consortium OI count and the
150      * supported Hotspot 2.0 release version).
151      *
152      * @param bssid The BSSID of the AP
153      * @param anqpNetworkKey The unique network key associated with this request
154      * @param rcOIs Flag indicating the inclusion of roaming consortium OIs. When set to true,
155      *              Roaming Consortium ANQP element will be requested
156      * @param hsReleaseVer Indicates Hotspot 2.0 Release version. When set to R2 or higher,
157      *              the Release 2 ANQP elements {@link #R2_ANQP_BASE_SET} will be requested
158      */
requestANQPElements(long bssid, ANQPNetworkKey anqpNetworkKey, boolean rcOIs, NetworkDetail.HSRelease hsReleaseVer)159     public void requestANQPElements(long bssid, ANQPNetworkKey anqpNetworkKey, boolean rcOIs,
160             NetworkDetail.HSRelease hsReleaseVer) {
161         // Put the new request in the queue, process it if possible(no more pending request)
162         mPendingRequest.offer(new AnqpRequest(bssid, rcOIs, hsReleaseVer, anqpNetworkKey));
163         processNextRequest();
164     }
165 
processNextRequest()166     private void processNextRequest() {
167         if (mAnqpRequestPending) {
168             return;
169         }
170         AnqpRequest request;
171         while ((request = mPendingRequest.poll()) != null) {
172             // Check if we are allow to send the request now.
173             if (!canSendRequestNow(request.mBssid)) {
174                 continue;
175             }
176             // No need to hold off future requests and set next alarm for send failures.
177             if (mPasspointHandler.requestANQP(request.mBssid, getRequestElementIDs(request.mRcOIs,
178                     request.mHsRelease))) {
179                 break;
180             }
181         }
182         if (request == null) {
183             return;
184         }
185         // Update hold off info on when we are allowed to send the next ANQP request to
186         // the given AP.
187         updateHoldOffInfo(request.mBssid);
188         mPendingQueries.put(request.mBssid, request.mAnqpNetworkKey);
189         // Schedule next request in case of time out waiting for response.
190         mAlarmManager.set(
191                 AlarmManager.ELAPSED_REALTIME,
192                 mClock.getElapsedSinceBootMillis() + ANQP_REQUEST_ALARM_INTERVAL_MS,
193                 ANQP_REQUEST_ALARM_TAG,
194                 mAnqpRequestListener,
195                 mHandler);
196         mAnqpRequestPending = true;
197     }
198 
199     /**
200      * Request Venue URL ANQP-element from the specified AP post connection.
201      *
202      * @param bssid The BSSID of the AP
203      * @param anqpNetworkKey The unique network key associated with this request
204      * @return true if a request was sent successfully
205      */
requestVenueUrlAnqpElement(long bssid, ANQPNetworkKey anqpNetworkKey)206     public boolean requestVenueUrlAnqpElement(long bssid, ANQPNetworkKey anqpNetworkKey) {
207         if (!mPasspointHandler.requestVenueUrlAnqp(bssid)) {
208             return false;
209         }
210 
211         return true;
212     }
213 
214     /**
215      * Notification of the completion of an ANQP request.
216      *
217      * @param bssid The BSSID of the AP
218      * @param success Flag indicating the result of the query
219      * @return {@link ANQPNetworkKey} associated with the completed request
220      */
onRequestCompleted(long bssid, boolean success)221     public ANQPNetworkKey onRequestCompleted(long bssid, boolean success) {
222         if (success) {
223             // Query succeeded.  No need to hold off request to the given AP.
224             mHoldOffInfo.remove(bssid);
225         }
226         // Cancel the schedule, and process next request.
227         mAlarmManager.cancel(mAnqpRequestListener);
228         mAnqpRequestPending = false;
229         processNextRequest();
230         return mPendingQueries.remove(bssid);
231     }
232 
233     /**
234      * Check if we are allowed to send ANQP request to the specified AP now.
235      *
236      * @param bssid The BSSID of an AP
237      * @return true if we are allowed to send the request now
238      */
canSendRequestNow(long bssid)239     private boolean canSendRequestNow(long bssid) {
240         long currentTime = mClock.getElapsedSinceBootMillis();
241         HoldOffInfo info = mHoldOffInfo.get(bssid);
242         if (info != null && info.holdOffExpirationTime > currentTime) {
243             Log.d(TAG, "Not allowed to send ANQP request to " + Utils.macToString(bssid)
244                     + " for another " + (info.holdOffExpirationTime - currentTime) / 1000
245                     + " seconds");
246             return false;
247         }
248 
249         return true;
250     }
251 
252     /**
253      * Update the ANQP request hold off info associated with the given AP.
254      *
255      * @param bssid The BSSID of an AP
256      */
updateHoldOffInfo(long bssid)257     private void updateHoldOffInfo(long bssid) {
258         HoldOffInfo info = mHoldOffInfo.get(bssid);
259         if (info == null) {
260             info = new HoldOffInfo();
261             mHoldOffInfo.put(bssid, info);
262         }
263         info.holdOffExpirationTime = mClock.getElapsedSinceBootMillis()
264                 + BASE_HOLDOFF_TIME_MILLISECONDS * (1 << info.holdOffCount);
265         if (info.holdOffCount < MAX_HOLDOFF_COUNT) {
266             info.holdOffCount++;
267         }
268     }
269 
270     /**
271      * Get the list of ANQP element IDs to request based on the Hotspot 2.0 release number
272      * and the ANQP OI count indicated in the Information Element.
273      *
274      * @param rcOIs Flag indicating the inclusion of roaming consortium OIs
275      * @param hsRelease Hotspot 2.0 Release version of the AP
276      * @return List of ANQP Element ID
277      */
getRequestElementIDs(boolean rcOIs, NetworkDetail.HSRelease hsRelease)278     private static List<Constants.ANQPElementType> getRequestElementIDs(boolean rcOIs,
279             NetworkDetail.HSRelease hsRelease) {
280         List<Constants.ANQPElementType> requestList = new ArrayList<>();
281         requestList.addAll(R1_ANQP_BASE_SET);
282         if (rcOIs) {
283             requestList.add(Constants.ANQPElementType.ANQPRoamingConsortium);
284         }
285 
286         if (hsRelease == NetworkDetail.HSRelease.R1) {
287             // Return R1 ANQP request list
288             return requestList;
289         }
290 
291         requestList.addAll(R2_ANQP_BASE_SET);
292 
293         // Return R2+ ANQP request list. This also includes the Unknown version, which may imply
294         // a future version.
295         return requestList;
296     }
297 
298     /**
299      * Dump the current state of ANQPRequestManager to the provided output stream.
300      *
301      * @param pw The output stream to write to
302      */
dump(PrintWriter pw)303     public void dump(PrintWriter pw) {
304         pw.println("ANQPRequestManager - Begin ---");
305         for (Map.Entry<Long, HoldOffInfo> holdOffInfo : mHoldOffInfo.entrySet()) {
306             long bssid = holdOffInfo.getKey();
307             pw.println("For BBSID: " + Utils.macToString(bssid));
308             pw.println("holdOffCount: " + holdOffInfo.getValue().holdOffCount);
309             pw.println("Not allowed to send ANQP request for another "
310                     + (holdOffInfo.getValue().holdOffExpirationTime
311                     - mClock.getElapsedSinceBootMillis()) / 1000 + " seconds");
312         }
313         pw.println("ANQPRequestManager - End ---");
314     }
315 
316     /**
317      * Clear all pending ANQP requests
318      */
clear()319     public void clear() {
320         mPendingQueries.clear();
321         mHoldOffInfo.clear();
322         mAlarmManager.cancel(mAnqpRequestListener);
323         mAnqpRequestPending = false;
324         mPendingRequest.clear();
325     }
326 }
327