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.util.Log;
20 
21 import com.android.internal.annotations.VisibleForTesting;
22 import com.android.server.wifi.Clock;
23 import com.android.server.wifi.hotspot2.anqp.Constants;
24 
25 import java.io.PrintWriter;
26 import java.util.ArrayList;
27 import java.util.Arrays;
28 import java.util.HashMap;
29 import java.util.List;
30 import java.util.Map;
31 
32 /**
33  * Class for managing sending of ANQP requests.  This manager will ignore ANQP requests for a
34  * period of time (hold off time) to a specified AP if the previous request to that AP goes
35  * unanswered or failed.  The hold off time will increase exponentially until the max is reached.
36  */
37 public class ANQPRequestManager {
38     private static final String TAG = "ANQPRequestManager";
39 
40     private final PasspointEventHandler mPasspointHandler;
41     private final Clock mClock;
42 
43     /**
44      * List of pending ANQP request associated with an AP (BSSID).
45      */
46     private final Map<Long, ANQPNetworkKey> mPendingQueries;
47 
48     /**
49      * List of hold off time information associated with APs specified by their BSSID.
50      * Used to determine when an ANQP request can be send to the corresponding AP after the
51      * previous request goes unanswered or failed.
52      */
53     private final Map<Long, HoldOffInfo> mHoldOffInfo;
54 
55     /**
56      * Minimum number of milliseconds to wait for before attempting ANQP queries to the same AP
57      * after previous request goes unanswered or failed.
58      */
59     @VisibleForTesting
60     public static final int BASE_HOLDOFF_TIME_MILLISECONDS = 10000;
61 
62     /**
63      * Max value for the hold off counter for unanswered/failed queries.  This limits the maximum
64      * hold off time to:
65      * BASE_HOLDOFF_TIME_MILLISECONDS * 2^MAX_HOLDOFF_COUNT
66      * which is 640 seconds.
67      */
68     @VisibleForTesting
69     public static final int MAX_HOLDOFF_COUNT = 6;
70 
71     private static final List<Constants.ANQPElementType> R1_ANQP_BASE_SET = Arrays.asList(
72             Constants.ANQPElementType.ANQPVenueName,
73             Constants.ANQPElementType.ANQPIPAddrAvailability,
74             Constants.ANQPElementType.ANQPNAIRealm,
75             Constants.ANQPElementType.ANQP3GPPNetwork,
76             Constants.ANQPElementType.ANQPDomName,
77             Constants.ANQPElementType.HSFriendlyName,
78             Constants.ANQPElementType.HSWANMetrics,
79             Constants.ANQPElementType.HSConnCapability);
80 
81     private static final List<Constants.ANQPElementType> R2_ANQP_BASE_SET = Arrays.asList(
82             Constants.ANQPElementType.HSOSUProviders);
83 
84     /**
85      * Class to keep track of AP status for ANQP requests.
86      */
87     private class HoldOffInfo {
88         /**
89          * Current hold off count.  Will max out at {@link #MAX_HOLDOFF_COUNT}.
90          */
91         public int holdOffCount;
92         /**
93          * The time stamp in milliseconds when we're allow to send ANQP request to the
94          * corresponding AP.
95          */
96         public long holdOffExpirationTime;
97     }
98 
ANQPRequestManager(PasspointEventHandler handler, Clock clock)99     public ANQPRequestManager(PasspointEventHandler handler, Clock clock) {
100         mPasspointHandler = handler;
101         mClock = clock;
102         mPendingQueries = new HashMap<>();
103         mHoldOffInfo = new HashMap<>();
104     }
105 
106     /**
107      * Request ANQP elements from the specified AP.  This will request the basic Release 1 ANQP
108      * elements {@link #R1_ANQP_BASE_SET}.  Additional elements will be requested based on the
109      * information provided in the Information Element (Roaming Consortium OI count and the
110      * supported Hotspot 2.0 release version).
111      *
112      * @param bssid The BSSID of the AP
113      * @param anqpNetworkKey The unique network key associated with this request
114      * @param rcOIs Flag indicating the inclusion of roaming consortium OIs. When set to true,
115      *              Roaming Consortium ANQP element will be requested
116      * @param hsReleaseVer Indicates Hotspot 2.0 Release version. When set to R2 or higher,
117      *              the Release 2 ANQP elements {@link #R2_ANQP_BASE_SET} will be requested
118      * @return true if a request was sent successfully
119      */
requestANQPElements(long bssid, ANQPNetworkKey anqpNetworkKey, boolean rcOIs, NetworkDetail.HSRelease hsReleaseVer)120     public boolean requestANQPElements(long bssid, ANQPNetworkKey anqpNetworkKey, boolean rcOIs,
121             NetworkDetail.HSRelease hsReleaseVer) {
122         // Check if we are allow to send the request now.
123         if (!canSendRequestNow(bssid)) {
124             return false;
125         }
126 
127         // No need to hold off future requests for send failures.
128         if (!mPasspointHandler.requestANQP(bssid, getRequestElementIDs(rcOIs, hsReleaseVer))) {
129             return false;
130         }
131 
132         // Update hold off info on when we are allowed to send the next ANQP request to
133         // the given AP.
134         updateHoldOffInfo(bssid);
135 
136         mPendingQueries.put(bssid, anqpNetworkKey);
137         return true;
138     }
139 
140     /**
141      * Request Venue URL ANQP-element from the specified AP post connection.
142      *
143      * @param bssid The BSSID of the AP
144      * @param anqpNetworkKey The unique network key associated with this request
145      * @return true if a request was sent successfully
146      */
requestVenueUrlAnqpElement(long bssid, ANQPNetworkKey anqpNetworkKey)147     public boolean requestVenueUrlAnqpElement(long bssid, ANQPNetworkKey anqpNetworkKey) {
148         if (!mPasspointHandler.requestVenueUrlAnqp(bssid)) {
149             return false;
150         }
151 
152         mPendingQueries.put(bssid, anqpNetworkKey);
153         return true;
154     }
155 
156     /**
157      * Notification of the completion of an ANQP request.
158      *
159      * @param bssid The BSSID of the AP
160      * @param success Flag indicating the result of the query
161      * @return {@link ANQPNetworkKey} associated with the completed request
162      */
onRequestCompleted(long bssid, boolean success)163     public ANQPNetworkKey onRequestCompleted(long bssid, boolean success) {
164         if (success) {
165             // Query succeeded.  No need to hold off request to the given AP.
166             mHoldOffInfo.remove(bssid);
167         }
168         return mPendingQueries.remove(bssid);
169     }
170 
171     /**
172      * Check if we are allowed to send ANQP request to the specified AP now.
173      *
174      * @param bssid The BSSID of an AP
175      * @return true if we are allowed to send the request now
176      */
canSendRequestNow(long bssid)177     private boolean canSendRequestNow(long bssid) {
178         long currentTime = mClock.getElapsedSinceBootMillis();
179         HoldOffInfo info = mHoldOffInfo.get(bssid);
180         if (info != null && info.holdOffExpirationTime > currentTime) {
181             Log.d(TAG, "Not allowed to send ANQP request to " + Utils.macToString(bssid)
182                     + " for another " + (info.holdOffExpirationTime - currentTime) / 1000
183                     + " seconds");
184             return false;
185         }
186 
187         return true;
188     }
189 
190     /**
191      * Update the ANQP request hold off info associated with the given AP.
192      *
193      * @param bssid The BSSID of an AP
194      */
updateHoldOffInfo(long bssid)195     private void updateHoldOffInfo(long bssid) {
196         HoldOffInfo info = mHoldOffInfo.get(bssid);
197         if (info == null) {
198             info = new HoldOffInfo();
199             mHoldOffInfo.put(bssid, info);
200         }
201         info.holdOffExpirationTime = mClock.getElapsedSinceBootMillis()
202                 + BASE_HOLDOFF_TIME_MILLISECONDS * (1 << info.holdOffCount);
203         if (info.holdOffCount < MAX_HOLDOFF_COUNT) {
204             info.holdOffCount++;
205         }
206     }
207 
208     /**
209      * Get the list of ANQP element IDs to request based on the Hotspot 2.0 release number
210      * and the ANQP OI count indicated in the Information Element.
211      *
212      * @param rcOIs Flag indicating the inclusion of roaming consortium OIs
213      * @param hsRelease Hotspot 2.0 Release version of the AP
214      * @return List of ANQP Element ID
215      */
getRequestElementIDs(boolean rcOIs, NetworkDetail.HSRelease hsRelease)216     private static List<Constants.ANQPElementType> getRequestElementIDs(boolean rcOIs,
217             NetworkDetail.HSRelease hsRelease) {
218         List<Constants.ANQPElementType> requestList = new ArrayList<>();
219         requestList.addAll(R1_ANQP_BASE_SET);
220         if (rcOIs) {
221             requestList.add(Constants.ANQPElementType.ANQPRoamingConsortium);
222         }
223 
224         if (hsRelease == NetworkDetail.HSRelease.R1) {
225             // Return R1 ANQP request list
226             return requestList;
227         }
228 
229         requestList.addAll(R2_ANQP_BASE_SET);
230 
231         // Return R2+ ANQP request list. This also includes the Unknown version, which may imply
232         // a future version.
233         return requestList;
234     }
235 
236     /**
237      * Dump the current state of ANQPRequestManager to the provided output stream.
238      *
239      * @param pw The output stream to write to
240      */
dump(PrintWriter pw)241     public void dump(PrintWriter pw) {
242         pw.println("ANQPRequestManager - Begin ---");
243         for (Map.Entry<Long, HoldOffInfo> holdOffInfo : mHoldOffInfo.entrySet()) {
244             long bssid = holdOffInfo.getKey();
245             pw.println("For BBSID: " + Utils.macToString(bssid));
246             pw.println("holdOffCount: " + holdOffInfo.getValue().holdOffCount);
247             pw.println("Not allowed to send ANQP request for another "
248                     + (holdOffInfo.getValue().holdOffExpirationTime
249                     - mClock.getElapsedSinceBootMillis()) / 1000 + " seconds");
250         }
251         pw.println("ANQPRequestManager - End ---");
252     }
253 
254     /**
255      * Clear all pending ANQP requests
256      */
clear()257     public void clear() {
258         mPendingQueries.clear();
259         mHoldOffInfo.clear();
260     }
261 }
262