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