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.util.ArrayList;
26 import java.util.Arrays;
27 import java.util.HashMap;
28 import java.util.List;
29 import java.util.Map;
30 
31 /**
32  * Class for managing sending of ANQP requests.  This manager will ignore ANQP requests for a
33  * period of time (hold off time) to a specified AP if the previous request to that AP goes
34  * unanswered or failed.  The hold off time will increase exponentially until the max is reached.
35  */
36 public class ANQPRequestManager {
37     private static final String TAG = "ANQPRequestManager";
38 
39     private final PasspointEventHandler mPasspointHandler;
40     private final Clock mClock;
41 
42     /**
43      * List of pending ANQP request associated with an AP (BSSID).
44      */
45     private final Map<Long, ANQPNetworkKey> mPendingQueries;
46 
47     /**
48      * List of hold off time information associated with APs specified by their BSSID.
49      * Used to determine when an ANQP request can be send to the corresponding AP after the
50      * previous request goes unanswered or failed.
51      */
52     private final Map<Long, HoldOffInfo> mHoldOffInfo;
53 
54     /**
55      * Minimum number of milliseconds to wait for before attempting ANQP queries to the same AP
56      * after previous request goes unanswered or failed.
57      */
58     @VisibleForTesting
59     public static final int BASE_HOLDOFF_TIME_MILLISECONDS = 10000;
60 
61     /**
62      * Max value for the hold off counter for unanswered/failed queries.  This limits the maximum
63      * hold off time to:
64      * BASE_HOLDOFF_TIME_MILLISECONDS * 2^MAX_HOLDOFF_COUNT
65      * which is 640 seconds.
66      */
67     @VisibleForTesting
68     public static final int MAX_HOLDOFF_COUNT = 6;
69 
70     private static final List<Constants.ANQPElementType> R1_ANQP_BASE_SET = Arrays.asList(
71             Constants.ANQPElementType.ANQPVenueName,
72             Constants.ANQPElementType.ANQPIPAddrAvailability,
73             Constants.ANQPElementType.ANQPNAIRealm,
74             Constants.ANQPElementType.ANQP3GPPNetwork,
75             Constants.ANQPElementType.ANQPDomName);
76 
77     private static final List<Constants.ANQPElementType> R2_ANQP_BASE_SET = Arrays.asList(
78             Constants.ANQPElementType.HSFriendlyName,
79             Constants.ANQPElementType.HSWANMetrics,
80             Constants.ANQPElementType.HSConnCapability,
81             Constants.ANQPElementType.HSOSUProviders);
82 
83     /**
84      * Class to keep track of AP status for ANQP requests.
85      */
86     private class HoldOffInfo {
87         /**
88          * Current hold off count.  Will max out at {@link #MAX_HOLDOFF_COUNT}.
89          */
90         public int holdOffCount;
91         /**
92          * The time stamp in milliseconds when we're allow to send ANQP request to the
93          * corresponding AP.
94          */
95         public long holdOffExpirationTime;
96     }
97 
ANQPRequestManager(PasspointEventHandler handler, Clock clock)98     public ANQPRequestManager(PasspointEventHandler handler, Clock clock) {
99         mPasspointHandler = handler;
100         mClock = clock;
101         mPendingQueries = new HashMap<>();
102         mHoldOffInfo = new HashMap<>();
103     }
104 
105     /**
106      * Request ANQP elements from the specified AP.  This will request the basic Release 1 ANQP
107      * elements {@link #R1_ANQP_BASE_SET}.  Additional elements will be requested based on the
108      * information provided in the Information Element (Roaming Consortium OI count and the
109      * supported Hotspot 2.0 release version).
110      *
111      * @param bssid The BSSID of the AP
112      * @param anqpNetworkKey The unique network key associated with this request
113      * @param rcOIs Flag indicating the inclusion of roaming consortium OIs. When set to true,
114      *              Roaming Consortium ANQP element will be requested
115      * @param hsReleaseR2 Flag indicating the support of Hotspot 2.0 Release 2. When set to true,
116      *              the Release 2 ANQP elements {@link #R2_ANQP_BASE_SET} will be requested
117      * @return true if a request was sent successfully
118      */
requestANQPElements(long bssid, ANQPNetworkKey anqpNetworkKey, boolean rcOIs, boolean hsReleaseR2)119     public boolean requestANQPElements(long bssid, ANQPNetworkKey anqpNetworkKey, boolean rcOIs,
120             boolean hsReleaseR2) {
121         // Check if we are allow to send the request now.
122         if (!canSendRequestNow(bssid)) {
123             return false;
124         }
125 
126         // No need to hold off future requests for send failures.
127         if (!mPasspointHandler.requestANQP(bssid, getRequestElementIDs(rcOIs, hsReleaseR2))) {
128             return false;
129         }
130 
131         // Update hold off info on when we are allowed to send the next ANQP request to
132         // the given AP.
133         updateHoldOffInfo(bssid);
134 
135         mPendingQueries.put(bssid, anqpNetworkKey);
136         return true;
137     }
138 
139     /**
140      * Notification of the completion of an ANQP request.
141      *
142      * @param bssid The BSSID of the AP
143      * @param success Flag indicating the result of the query
144      * @return {@link ANQPNetworkKey} associated with the completed request
145      */
onRequestCompleted(long bssid, boolean success)146     public ANQPNetworkKey onRequestCompleted(long bssid, boolean success) {
147         if (success) {
148             // Query succeeded.  No need to hold off request to the given AP.
149             mHoldOffInfo.remove(bssid);
150         }
151         return mPendingQueries.remove(bssid);
152     }
153 
154     /**
155      * Check if we are allowed to send ANQP request to the specified AP now.
156      *
157      * @param bssid The BSSID of an AP
158      * @return true if we are allowed to send the request now
159      */
canSendRequestNow(long bssid)160     private boolean canSendRequestNow(long bssid) {
161         long currentTime = mClock.getElapsedSinceBootMillis();
162         HoldOffInfo info = mHoldOffInfo.get(bssid);
163         if (info != null && info.holdOffExpirationTime > currentTime) {
164             Log.d(TAG, "Not allowed to send ANQP request to " + bssid + " for another "
165                     + (info.holdOffExpirationTime - currentTime) / 1000 + " seconds");
166             return false;
167         }
168 
169         return true;
170     }
171 
172     /**
173      * Update the ANQP request hold off info associated with the given AP.
174      *
175      * @param bssid The BSSID of an AP
176      */
updateHoldOffInfo(long bssid)177     private void updateHoldOffInfo(long bssid) {
178         HoldOffInfo info = mHoldOffInfo.get(bssid);
179         if (info == null) {
180             info = new HoldOffInfo();
181             mHoldOffInfo.put(bssid, info);
182         }
183         info.holdOffExpirationTime = mClock.getElapsedSinceBootMillis()
184                 + BASE_HOLDOFF_TIME_MILLISECONDS * (1 << info.holdOffCount);
185         if (info.holdOffCount < MAX_HOLDOFF_COUNT) {
186             info.holdOffCount++;
187         }
188     }
189 
190     /**
191      * Get the list of ANQP element IDs to request based on the Hotspot 2.0 release number
192      * and the ANQP OI count indicated in the Information Element.
193      *
194      * @param rcOIs Flag indicating the inclusion of roaming consortium OIs
195      * @param hsReleaseR2 Flag indicating support of Hotspot 2.0 Release 2
196      * @return List of ANQP Element ID
197      */
getRequestElementIDs(boolean rcOIs, boolean hsReleaseR2)198     private static List<Constants.ANQPElementType> getRequestElementIDs(boolean rcOIs,
199             boolean hsReleaseR2) {
200         List<Constants.ANQPElementType> requestList = new ArrayList<>();
201         requestList.addAll(R1_ANQP_BASE_SET);
202         if (rcOIs) {
203             requestList.add(Constants.ANQPElementType.ANQPRoamingConsortium);
204         }
205 
206         if (hsReleaseR2) {
207             requestList.addAll(R2_ANQP_BASE_SET);
208         }
209         return requestList;
210     }
211 }
212