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.util; 18 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.content.Context; 22 import android.content.res.Resources; 23 import android.net.wifi.ScanResult; 24 import android.net.wifi.SoftApCapability; 25 import android.net.wifi.SoftApConfiguration; 26 import android.net.wifi.SoftApConfiguration.BandType; 27 import android.net.wifi.WifiConfiguration; 28 import android.net.wifi.WifiScanner; 29 import android.util.Log; 30 import android.util.SparseArray; 31 32 import com.android.server.wifi.WifiNative; 33 import com.android.wifi.resources.R; 34 35 import java.util.ArrayList; 36 import java.util.List; 37 import java.util.Objects; 38 import java.util.Random; 39 40 /** 41 * Provide utility functions for updating soft AP related configuration. 42 */ 43 public class ApConfigUtil { 44 private static final String TAG = "ApConfigUtil"; 45 46 public static final int DEFAULT_AP_BAND = SoftApConfiguration.BAND_2GHZ; 47 public static final int DEFAULT_AP_CHANNEL = 6; 48 public static final int HIGHEST_2G_AP_CHANNEL = 14; 49 50 /* Return code for updateConfiguration. */ 51 public static final int SUCCESS = 0; 52 public static final int ERROR_NO_CHANNEL = 1; 53 public static final int ERROR_GENERIC = 2; 54 public static final int ERROR_UNSUPPORTED_CONFIGURATION = 3; 55 56 /* Random number generator used for AP channel selection. */ 57 private static final Random sRandom = new Random(); 58 59 /** 60 * Valid Global Operating classes in each wifi band 61 * Reference: Table E-4 in IEEE Std 802.11-2016. 62 */ 63 private static final SparseArray<int[]> sBandToOperatingClass = new SparseArray<>(); 64 static { sBandToOperatingClass.append(SoftApConfiguration.BAND_2GHZ, new int[]{81, 82, 83, 84})65 sBandToOperatingClass.append(SoftApConfiguration.BAND_2GHZ, new int[]{81, 82, 83, 84}); sBandToOperatingClass.append(SoftApConfiguration.BAND_5GHZ, new int[]{115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130})66 sBandToOperatingClass.append(SoftApConfiguration.BAND_5GHZ, new int[]{115, 116, 117, 118, 67 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130}); sBandToOperatingClass.append(SoftApConfiguration.BAND_6GHZ, new int[]{131, 132, 133, 134, 135, 136})68 sBandToOperatingClass.append(SoftApConfiguration.BAND_6GHZ, new int[]{131, 132, 133, 134, 69 135, 136}); 70 } 71 72 /** 73 * Helper function to get the band corresponding to the operating class. 74 * 75 * @param operatingClass Global operating class. 76 * @return band, -1 if no match. 77 * 78 */ getBandFromOperatingClass(int operatingClass)79 public static int getBandFromOperatingClass(int operatingClass) { 80 for (int i = 0; i < sBandToOperatingClass.size(); i++) { 81 int band = sBandToOperatingClass.keyAt(i); 82 int[] operatingClasses = sBandToOperatingClass.get(band); 83 84 for (int j = 0; j < operatingClasses.length; j++) { 85 if (operatingClasses[j] == operatingClass) { 86 return band; 87 } 88 } 89 } 90 return -1; 91 } 92 93 /** 94 * Convert band from SoftApConfiguration.BandType to WifiScanner.WifiBand 95 * @param band in SoftApConfiguration.BandType 96 * @return band in WifiScanner.WifiBand 97 */ apConfig2wifiScannerBand(@andType int band)98 public static @WifiScanner.WifiBand int apConfig2wifiScannerBand(@BandType int band) { 99 switch(band) { 100 case SoftApConfiguration.BAND_2GHZ: 101 return WifiScanner.WIFI_BAND_24_GHZ; 102 case SoftApConfiguration.BAND_5GHZ: 103 return WifiScanner.WIFI_BAND_5_GHZ; 104 case SoftApConfiguration.BAND_6GHZ: 105 return WifiScanner.WIFI_BAND_6_GHZ; 106 default: 107 return WifiScanner.WIFI_BAND_UNSPECIFIED; 108 } 109 } 110 111 /** 112 * Convert channel/band to frequency. 113 * Note: the utility does not perform any regulatory domain compliance. 114 * @param channel number to convert 115 * @param band of channel to convert 116 * @return center frequency in Mhz of the channel, -1 if no match 117 */ convertChannelToFrequency(int channel, @BandType int band)118 public static int convertChannelToFrequency(int channel, @BandType int band) { 119 return ScanResult.convertChannelToFrequencyMhz(channel, 120 apConfig2wifiScannerBand(band)); 121 } 122 123 /** 124 * Convert frequency to band. 125 * Note: the utility does not perform any regulatory domain compliance. 126 * @param frequency frequency to convert 127 * @return band, -1 if no match 128 */ convertFrequencyToBand(int frequency)129 public static int convertFrequencyToBand(int frequency) { 130 if (ScanResult.is24GHz(frequency)) { 131 return SoftApConfiguration.BAND_2GHZ; 132 } else if (ScanResult.is5GHz(frequency)) { 133 return SoftApConfiguration.BAND_5GHZ; 134 } else if (ScanResult.is6GHz(frequency)) { 135 return SoftApConfiguration.BAND_6GHZ; 136 } 137 138 return -1; 139 } 140 141 /** 142 * Convert band from WifiConfiguration into SoftApConfiguration 143 * 144 * @param wifiConfigBand band encoded as WifiConfiguration.AP_BAND_xxxx 145 * @return band as encoded as SoftApConfiguration.BAND_xxx 146 */ convertWifiConfigBandToSoftApConfigBand(int wifiConfigBand)147 public static int convertWifiConfigBandToSoftApConfigBand(int wifiConfigBand) { 148 switch (wifiConfigBand) { 149 case WifiConfiguration.AP_BAND_2GHZ: 150 return SoftApConfiguration.BAND_2GHZ; 151 case WifiConfiguration.AP_BAND_5GHZ: 152 return SoftApConfiguration.BAND_5GHZ; 153 case WifiConfiguration.AP_BAND_ANY: 154 return SoftApConfiguration.BAND_2GHZ | SoftApConfiguration.BAND_5GHZ; 155 default: 156 return SoftApConfiguration.BAND_2GHZ; 157 } 158 } 159 160 /** 161 * Checks if band is a valid combination of {link SoftApConfiguration#BandType} values 162 */ isBandValid(@andType int band)163 public static boolean isBandValid(@BandType int band) { 164 return ((band != 0) && ((band & ~SoftApConfiguration.BAND_ANY) == 0)); 165 } 166 167 /** 168 * Check if the band contains a certain sub-band 169 * 170 * @param band The combination of bands to validate 171 * @param testBand the test band to validate on 172 * @return true if band contains testBand, false otherwise 173 */ containsBand(@andType int band, @BandType int testBand)174 public static boolean containsBand(@BandType int band, @BandType int testBand) { 175 return ((band & testBand) != 0); 176 } 177 178 /** 179 * Checks if band contains multiple sub-bands 180 * @param band a combination of sub-bands 181 * @return true if band has multiple sub-bands, false otherwise 182 */ isMultiband(@andType int band)183 public static boolean isMultiband(@BandType int band) { 184 return ((band & (band - 1)) != 0); 185 } 186 187 /** 188 * Convert string to channel list 189 * Format of the list is a comma separated channel numbers, or range of channel numbers 190 * Example, "34-48, 149". 191 * @param channelString for a comma separated channel numbers, or range of channel numbers 192 * such as "34-48, 149" 193 * @return list of channel numbers 194 */ convertStringToChannelList(String channelString)195 public static List<Integer> convertStringToChannelList(String channelString) { 196 if (channelString == null) { 197 return null; 198 } 199 200 List<Integer> channelList = new ArrayList<Integer>(); 201 202 for (String channelRange : channelString.split(",")) { 203 try { 204 if (channelRange.contains("-")) { 205 String[] channels = channelRange.split("-"); 206 if (channels.length != 2) { 207 Log.e(TAG, "Unrecognized channel range, Length is " + channels.length); 208 continue; 209 } 210 int start = Integer.parseInt(channels[0].trim()); 211 int end = Integer.parseInt(channels[1].trim()); 212 if (start > end) { 213 Log.e(TAG, "Invalid channel range, from " + start + " to " + end); 214 continue; 215 } 216 217 for (int channel = start; channel <= end; channel++) { 218 channelList.add(channel); 219 } 220 } else { 221 channelList.add(Integer.parseInt(channelRange.trim())); 222 } 223 } catch (NumberFormatException e) { 224 // Ignore malformed string 225 Log.e(TAG, "Malformed channel value detected: " + e); 226 continue; 227 } 228 } 229 return channelList; 230 } 231 232 /** 233 * Get channel frequencies for band that are allowed by both regulatory and OEM configuration 234 * 235 * @param band to get channels for 236 * @param wifiNative reference used to get regulatory restrictionsimport java.util.Arrays; 237 * @param resources used to get OEM restrictions 238 * @return A list of frequencies that are allowed, null on error. 239 */ getAvailableChannelFreqsForBand( @andType int band, WifiNative wifiNative, Resources resources)240 public static List<Integer> getAvailableChannelFreqsForBand( 241 @BandType int band, WifiNative wifiNative, Resources resources) { 242 if (!isBandValid(band) || isMultiband(band)) { 243 return null; 244 } 245 246 List<Integer> configuredList; 247 int scannerBand; 248 switch (band) { 249 case SoftApConfiguration.BAND_2GHZ: 250 configuredList = convertStringToChannelList(resources.getString( 251 R.string.config_wifiSoftap2gChannelList)); 252 scannerBand = WifiScanner.WIFI_BAND_24_GHZ; 253 break; 254 case SoftApConfiguration.BAND_5GHZ: 255 configuredList = convertStringToChannelList(resources.getString( 256 R.string.config_wifiSoftap5gChannelList)); 257 scannerBand = WifiScanner.WIFI_BAND_5_GHZ; 258 break; 259 case SoftApConfiguration.BAND_6GHZ: 260 configuredList = convertStringToChannelList(resources.getString( 261 R.string.config_wifiSoftap6gChannelList)); 262 scannerBand = WifiScanner.WIFI_BAND_6_GHZ; 263 break; 264 default: 265 return null; 266 } 267 268 // Get the allowed list of channel frequencies in MHz 269 int[] regulatoryArray = wifiNative.getChannelsForBand(scannerBand); 270 List<Integer> regulatoryList = new ArrayList<Integer>(); 271 for (int freq : regulatoryArray) { 272 regulatoryList.add(freq); 273 } 274 275 if (configuredList == null || configuredList.isEmpty()) { 276 return regulatoryList; 277 } 278 279 List<Integer> filteredList = new ArrayList<Integer>(); 280 // Otherwise, filter the configured list 281 for (int channel : configuredList) { 282 int channelFreq = convertChannelToFrequency(channel, band); 283 284 if (regulatoryList.contains(channelFreq)) { 285 filteredList.add(channelFreq); 286 } 287 } 288 return filteredList; 289 } 290 291 /** 292 * Return a channel number for AP setup based on the frequency band. 293 * @param apBand one or combination of the values of SoftApConfiguration.BAND_*. 294 * @param wifiNative reference used to collect regulatory restrictions. 295 * @param resources the resources to use to get configured allowed channels. 296 * @return a valid channel frequency on success, -1 on failure. 297 */ chooseApChannel(int apBand, WifiNative wifiNative, Resources resources)298 public static int chooseApChannel(int apBand, WifiNative wifiNative, Resources resources) { 299 if (!isBandValid(apBand)) { 300 Log.e(TAG, "Invalid band: " + apBand); 301 return -1; 302 } 303 304 List<Integer> allowedFreqList = null; 305 306 if ((apBand & SoftApConfiguration.BAND_6GHZ) != 0) { 307 allowedFreqList = getAvailableChannelFreqsForBand(SoftApConfiguration.BAND_6GHZ, 308 wifiNative, resources); 309 if (allowedFreqList != null && allowedFreqList.size() > 0) { 310 return allowedFreqList.get(sRandom.nextInt(allowedFreqList.size())).intValue(); 311 } 312 } 313 314 if ((apBand & SoftApConfiguration.BAND_5GHZ) != 0) { 315 allowedFreqList = getAvailableChannelFreqsForBand(SoftApConfiguration.BAND_5GHZ, 316 wifiNative, resources); 317 if (allowedFreqList != null && allowedFreqList.size() > 0) { 318 return allowedFreqList.get(sRandom.nextInt(allowedFreqList.size())).intValue(); 319 } 320 } 321 322 if ((apBand & SoftApConfiguration.BAND_2GHZ) != 0) { 323 allowedFreqList = getAvailableChannelFreqsForBand(SoftApConfiguration.BAND_2GHZ, 324 wifiNative, resources); 325 if (allowedFreqList != null && allowedFreqList.size() > 0) { 326 return allowedFreqList.get(sRandom.nextInt(allowedFreqList.size())).intValue(); 327 } 328 } 329 330 // If the default AP band is allowed, just use the default channel 331 if (containsBand(apBand, DEFAULT_AP_BAND)) { 332 Log.e(TAG, "Allowed channel list not specified, selecting default channel"); 333 /* Use default channel. */ 334 return convertChannelToFrequency(DEFAULT_AP_CHANNEL, 335 DEFAULT_AP_BAND); 336 } 337 338 Log.e(TAG, "No available channels"); 339 return -1; 340 } 341 342 /** 343 * Update AP band and channel based on the provided country code and band. 344 * This will also set 345 * @param wifiNative reference to WifiNative 346 * @param resources the resources to use to get configured allowed channels. 347 * @param countryCode country code 348 * @param config configuration to update 349 * @return an integer result code 350 */ updateApChannelConfig(WifiNative wifiNative, Resources resources, String countryCode, SoftApConfiguration.Builder configBuilder, SoftApConfiguration config, boolean acsEnabled)351 public static int updateApChannelConfig(WifiNative wifiNative, 352 Resources resources, 353 String countryCode, 354 SoftApConfiguration.Builder configBuilder, 355 SoftApConfiguration config, 356 boolean acsEnabled) { 357 /* Use default band and channel for device without HAL. */ 358 if (!wifiNative.isHalStarted()) { 359 configBuilder.setChannel(DEFAULT_AP_CHANNEL, DEFAULT_AP_BAND); 360 return SUCCESS; 361 } 362 363 /* Country code is mandatory for 5GHz band. */ 364 if (config.getBand() == SoftApConfiguration.BAND_5GHZ 365 && countryCode == null) { 366 Log.e(TAG, "5GHz band is not allowed without country code"); 367 return ERROR_GENERIC; 368 } 369 370 /* Select a channel if it is not specified and ACS is not enabled */ 371 if ((config.getChannel() == 0) && !acsEnabled) { 372 int freq = chooseApChannel(config.getBand(), wifiNative, resources); 373 if (freq == -1) { 374 /* We're not able to get channel from wificond. */ 375 Log.e(TAG, "Failed to get available channel."); 376 return ERROR_NO_CHANNEL; 377 } 378 configBuilder.setChannel( 379 ScanResult.convertFrequencyMhzToChannel(freq), convertFrequencyToBand(freq)); 380 } 381 382 return SUCCESS; 383 } 384 385 /** 386 * Helper function for converting WifiConfiguration to SoftApConfiguration. 387 * 388 * Only Support None and WPA2 configuration conversion. 389 * Note that WifiConfiguration only Supports 2GHz, 5GHz, 2GHz+5GHz bands, 390 * so conversion is limited to these bands. 391 * 392 * @param wifiConfig the WifiConfiguration which need to convert. 393 * @return the SoftApConfiguration if wifiConfig is valid, null otherwise. 394 */ 395 @Nullable fromWifiConfiguration( @onNull WifiConfiguration wifiConfig)396 public static SoftApConfiguration fromWifiConfiguration( 397 @NonNull WifiConfiguration wifiConfig) { 398 SoftApConfiguration.Builder configBuilder = new SoftApConfiguration.Builder(); 399 try { 400 configBuilder.setSsid(wifiConfig.SSID); 401 if (wifiConfig.getAuthType() == WifiConfiguration.KeyMgmt.WPA2_PSK) { 402 configBuilder.setPassphrase(wifiConfig.preSharedKey, 403 SoftApConfiguration.SECURITY_TYPE_WPA2_PSK); 404 } 405 configBuilder.setHiddenSsid(wifiConfig.hiddenSSID); 406 407 int band; 408 switch (wifiConfig.apBand) { 409 case WifiConfiguration.AP_BAND_2GHZ: 410 band = SoftApConfiguration.BAND_2GHZ; 411 break; 412 case WifiConfiguration.AP_BAND_5GHZ: 413 band = SoftApConfiguration.BAND_5GHZ; 414 break; 415 default: 416 // WifiConfiguration.AP_BAND_ANY means only 2GHz and 5GHz bands 417 band = SoftApConfiguration.BAND_2GHZ | SoftApConfiguration.BAND_5GHZ; 418 break; 419 } 420 if (wifiConfig.apChannel == 0) { 421 configBuilder.setBand(band); 422 } else { 423 configBuilder.setChannel(wifiConfig.apChannel, band); 424 } 425 } catch (IllegalArgumentException iae) { 426 Log.e(TAG, "Invalid WifiConfiguration" + iae); 427 return null; 428 } catch (IllegalStateException ise) { 429 Log.e(TAG, "Invalid WifiConfiguration" + ise); 430 return null; 431 } 432 return configBuilder.build(); 433 } 434 435 /** 436 * Helper function to creating SoftApCapability instance with initial field from resource file. 437 * 438 * @param context the caller context used to get value from resource file. 439 * @return SoftApCapability which updated the feature support or not from resource. 440 */ 441 @NonNull updateCapabilityFromResource(@onNull Context context)442 public static SoftApCapability updateCapabilityFromResource(@NonNull Context context) { 443 long features = 0; 444 if (isAcsSupported(context)) { 445 Log.d(TAG, "Update Softap capability, add acs feature support"); 446 features |= SoftApCapability.SOFTAP_FEATURE_ACS_OFFLOAD; 447 } 448 449 if (isClientForceDisconnectSupported(context)) { 450 Log.d(TAG, "Update Softap capability, add client control feature support"); 451 features |= SoftApCapability.SOFTAP_FEATURE_CLIENT_FORCE_DISCONNECT; 452 } 453 454 if (isWpa3SaeSupported(context)) { 455 Log.d(TAG, "Update Softap capability, add SAE feature support"); 456 features |= SoftApCapability.SOFTAP_FEATURE_WPA3_SAE; 457 } 458 SoftApCapability capability = new SoftApCapability(features); 459 int hardwareSupportedMaxClient = context.getResources().getInteger( 460 R.integer.config_wifiHardwareSoftapMaxClientCount); 461 if (hardwareSupportedMaxClient > 0) { 462 Log.d(TAG, "Update Softap capability, max client = " + hardwareSupportedMaxClient); 463 capability.setMaxSupportedClients(hardwareSupportedMaxClient); 464 } 465 466 return capability; 467 } 468 469 /** 470 * Helper function to get hal support client force disconnect or not. 471 * 472 * @param context the caller context used to get value from resource file. 473 * @return true if supported, false otherwise. 474 */ isClientForceDisconnectSupported(@onNull Context context)475 public static boolean isClientForceDisconnectSupported(@NonNull Context context) { 476 return context.getResources().getBoolean( 477 R.bool.config_wifiSofapClientForceDisconnectSupported); 478 } 479 480 /** 481 * Helper function to get SAE support or not. 482 * 483 * @param context the caller context used to get value from resource file. 484 * @return true if supported, false otherwise. 485 */ isWpa3SaeSupported(@onNull Context context)486 public static boolean isWpa3SaeSupported(@NonNull Context context) { 487 return context.getResources().getBoolean( 488 R.bool.config_wifi_softap_sae_supported); 489 } 490 491 /** 492 * Helper function to get ACS support or not. 493 * 494 * @param context the caller context used to get value from resource file. 495 * @return true if supported, false otherwise. 496 */ isAcsSupported(@onNull Context context)497 public static boolean isAcsSupported(@NonNull Context context) { 498 return context.getResources().getBoolean( 499 R.bool.config_wifi_softap_acs_supported); 500 } 501 502 /** 503 * Helper function for comparing two SoftApConfiguration. 504 * 505 * @param currentConfig the original configuration. 506 * @param newConfig the new configuration which plan to apply. 507 * @return true if the difference between the two configurations requires a restart to apply, 508 * false otherwise. 509 */ checkConfigurationChangeNeedToRestart( SoftApConfiguration currentConfig, SoftApConfiguration newConfig)510 public static boolean checkConfigurationChangeNeedToRestart( 511 SoftApConfiguration currentConfig, SoftApConfiguration newConfig) { 512 return !Objects.equals(currentConfig.getSsid(), newConfig.getSsid()) 513 || !Objects.equals(currentConfig.getBssid(), newConfig.getBssid()) 514 || currentConfig.getSecurityType() != newConfig.getSecurityType() 515 || !Objects.equals(currentConfig.getPassphrase(), newConfig.getPassphrase()) 516 || currentConfig.isHiddenSsid() != newConfig.isHiddenSsid() 517 || currentConfig.getBand() != newConfig.getBand() 518 || currentConfig.getChannel() != newConfig.getChannel(); 519 } 520 521 522 /** 523 * Helper function for checking all of the configuration are supported or not. 524 * 525 * @param config target configuration want to check. 526 * @param capability the capability which indicate feature support or not. 527 * @return true if supported, false otherwise. 528 */ checkSupportAllConfiguration(SoftApConfiguration config, SoftApCapability capability)529 public static boolean checkSupportAllConfiguration(SoftApConfiguration config, 530 SoftApCapability capability) { 531 if (!capability.areFeaturesSupported( 532 SoftApCapability.SOFTAP_FEATURE_CLIENT_FORCE_DISCONNECT) 533 && (config.getMaxNumberOfClients() != 0 || config.isClientControlByUserEnabled() 534 || config.getBlockedClientList().size() != 0)) { 535 Log.d(TAG, "Error, Client control requires HAL support"); 536 return false; 537 } 538 539 if (!capability.areFeaturesSupported(SoftApCapability.SOFTAP_FEATURE_WPA3_SAE) 540 && (config.getSecurityType() 541 == SoftApConfiguration.SECURITY_TYPE_WPA3_SAE_TRANSITION 542 || config.getSecurityType() == SoftApConfiguration.SECURITY_TYPE_WPA3_SAE)) { 543 Log.d(TAG, "Error, SAE requires HAL support"); 544 return false; 545 } 546 return true; 547 } 548 } 549