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