• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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