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