• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2021 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 android.net.wifi.util;
18 
19 import static android.net.wifi.ScanResult.FLAG_PASSPOINT_NETWORK;
20 
21 import android.annotation.NonNull;
22 import android.annotation.Nullable;
23 import android.net.MacAddress;
24 import android.net.wifi.ScanResult;
25 import android.net.wifi.SecurityParams;
26 import android.net.wifi.WifiConfiguration;
27 import android.util.Log;
28 
29 import com.android.internal.annotations.VisibleForTesting;
30 
31 import java.io.PrintWriter;
32 import java.util.ArrayList;
33 import java.util.List;
34 /**
35  * Scan result utility for any {@link ScanResult} related operations.
36  * Currently contains:
37  *   > Helper methods to identify the encryption of a ScanResult.
38  * @hide
39  */
40 public class ScanResultUtil {
41     private static final String TAG = "ScanResultUtil";
ScanResultUtil()42     private ScanResultUtil() { /* not constructable */ }
43 
44     /**
45      * Helper method to check if the provided |scanResult| corresponds to a PSK network or not.
46      * This checks if the provided capabilities string contains PSK encryption type or not.
47      */
isScanResultForPskNetwork(@onNull ScanResult scanResult)48     public static boolean isScanResultForPskNetwork(@NonNull ScanResult scanResult) {
49         return scanResult.capabilities.contains("PSK");
50     }
51 
52     /**
53      * Helper method to check if the provided |scanResult| corresponds to a WAPI-PSK network or not.
54      * This checks if the provided capabilities string contains PSK encryption type or not.
55      */
isScanResultForWapiPskNetwork(@onNull ScanResult scanResult)56     public static boolean isScanResultForWapiPskNetwork(@NonNull ScanResult scanResult) {
57         return scanResult.capabilities.contains("WAPI-PSK");
58     }
59 
60     /**
61      * Helper method to check if the provided |scanResult| corresponds to a WAPI-CERT
62      * network or not.
63      * This checks if the provided capabilities string contains PSK encryption type or not.
64      */
isScanResultForWapiCertNetwork(@onNull ScanResult scanResult)65     public static boolean isScanResultForWapiCertNetwork(@NonNull ScanResult scanResult) {
66         return scanResult.capabilities.contains("WAPI-CERT");
67     }
68 
69     /**
70      * Helper method to check if the provided |scanResult| corresponds to a EAP network or not.
71      * This checks these conditions:
72      * - Enable EAP/SHA1, EAP/SHA256 AKM, FT/EAP, or EAP-FILS.
73      * - Not a WPA3 Enterprise only network.
74      * - Not a WPA3 Enterprise transition network.
75      */
isScanResultForEapNetwork(@onNull ScanResult scanResult)76     public static boolean isScanResultForEapNetwork(@NonNull ScanResult scanResult) {
77         return (scanResult.capabilities.contains("EAP/SHA1")
78                         || scanResult.capabilities.contains("EAP/SHA256")
79                         || scanResult.capabilities.contains("FT/EAP")
80                         || scanResult.capabilities.contains("EAP-FILS"))
81                 && !isScanResultForWpa3EnterpriseOnlyNetwork(scanResult)
82                 && !isScanResultForWpa3EnterpriseTransitionNetwork(scanResult);
83     }
84 
isScanResultForPmfMandatoryNetwork(@onNull ScanResult scanResult)85     private static boolean isScanResultForPmfMandatoryNetwork(@NonNull ScanResult scanResult) {
86         return scanResult.capabilities.contains("[MFPR]");
87     }
88 
isScanResultForPmfCapableNetwork(@onNull ScanResult scanResult)89     private static boolean isScanResultForPmfCapableNetwork(@NonNull ScanResult scanResult) {
90         return scanResult.capabilities.contains("[MFPC]");
91     }
92 
93     /**
94      * Helper method to check if the provided |scanResult| corresponds to a Passpoint R1/R2
95      * network or not.
96      * Passpoint R1/R2 requirements:
97      * - WPA2 Enterprise network.
98      * - interworking bit is set.
99      * - HotSpot Release presents.
100      */
isScanResultForPasspointR1R2Network(@onNull ScanResult scanResult)101     public static boolean isScanResultForPasspointR1R2Network(@NonNull ScanResult scanResult) {
102         if (!isScanResultForEapNetwork(scanResult)) return false;
103 
104         return scanResult.isPasspointNetwork();
105     }
106 
107     /**
108      * Helper method to check if the provided |scanResult| corresponds to a Passpoint R3
109      * network or not.
110      * Passpoint R3 requirements:
111      * - Must be WPA2 Enterprise network, WPA3 Enterprise network,
112      *   or WPA3 Enterprise 192-bit mode network.
113      * - interworking bit is set.
114      * - HotSpot Release presents.
115      * - PMF is mandatory.
116      */
isScanResultForPasspointR3Network(@onNull ScanResult scanResult)117     public static boolean isScanResultForPasspointR3Network(@NonNull ScanResult scanResult) {
118         if (!isScanResultForEapNetwork(scanResult)
119                 && !isScanResultForWpa3EnterpriseOnlyNetwork(scanResult)
120                 && !isScanResultForEapSuiteBNetwork(scanResult)) {
121             return false;
122         }
123         if (!isScanResultForPmfMandatoryNetwork(scanResult)) return false;
124 
125         return scanResult.isPasspointNetwork();
126     }
127 
128     /**
129      * Helper method to check if the provided |scanResult| corresponds to
130      * a WPA3 Enterprise transition network or not.
131      *
132      * See Section 3.3 WPA3-Enterprise transition mode in WPA3 Specification
133      * - Enable at least EAP/SHA1 and EAP/SHA256 AKM suites.
134      * - Not enable WPA1 version 1, WEP, and TKIP.
135      * - Management Frame Protection Capable is set.
136      * - Management Frame Protection Required is not set.
137      */
isScanResultForWpa3EnterpriseTransitionNetwork( @onNull ScanResult scanResult)138     public static boolean isScanResultForWpa3EnterpriseTransitionNetwork(
139             @NonNull ScanResult scanResult) {
140         return scanResult.capabilities.contains("EAP/SHA1")
141                 && scanResult.capabilities.contains("EAP/SHA256")
142                 && scanResult.capabilities.contains("RSN")
143                 && !scanResult.capabilities.contains("WEP")
144                 && !scanResult.capabilities.contains("TKIP")
145                 && !isScanResultForPmfMandatoryNetwork(scanResult)
146                 && isScanResultForPmfCapableNetwork(scanResult);
147     }
148 
149     /**
150      * Helper method to check if the provided |scanResult| corresponds to
151      * a WPA3 Enterprise only network or not.
152      *
153      * See Section 3.2 WPA3-Enterprise only mode in WPA3 Specification
154      * - Enable at least EAP/SHA256 AKM suite.
155      * - Not enable EAP/SHA1 AKM suite.
156      * - Not enable WPA1 version 1, WEP, and TKIP.
157      * - Management Frame Protection Capable is set.
158      * - Management Frame Protection Required is set.
159      */
isScanResultForWpa3EnterpriseOnlyNetwork(@onNull ScanResult scanResult)160     public static boolean isScanResultForWpa3EnterpriseOnlyNetwork(@NonNull ScanResult scanResult) {
161         return scanResult.capabilities.contains("EAP/SHA256")
162                 && !scanResult.capabilities.contains("EAP/SHA1")
163                 && scanResult.capabilities.contains("RSN")
164                 && !scanResult.capabilities.contains("WEP")
165                 && !scanResult.capabilities.contains("TKIP")
166                 && isScanResultForPmfMandatoryNetwork(scanResult)
167                 && isScanResultForPmfCapableNetwork(scanResult);
168     }
169 
170     /**
171      * Helper method to check if the provided |scanResult| corresponds to a WPA3-Enterprise 192-bit
172      * mode network or not.
173      * This checks if the provided capabilities comply these conditions:
174      * - Enable SUITE-B-192 AKM.
175      * - Not enable EAP/SHA1 AKM suite.
176      * - Not enable WPA1 version 1, WEP, and TKIP.
177      * - Management Frame Protection Required is set.
178      */
isScanResultForEapSuiteBNetwork(@onNull ScanResult scanResult)179     public static boolean isScanResultForEapSuiteBNetwork(@NonNull ScanResult scanResult) {
180         return scanResult.capabilities.contains("SUITE_B_192")
181                 && scanResult.capabilities.contains("RSN")
182                 && !scanResult.capabilities.contains("WEP")
183                 && !scanResult.capabilities.contains("TKIP")
184                 && isScanResultForPmfMandatoryNetwork(scanResult);
185     }
186 
187     /**
188      * Helper method to check if the provided |scanResult| corresponds to a WEP network or not.
189      * This checks if the provided capabilities string contains WEP encryption type or not.
190      */
isScanResultForWepNetwork(@onNull ScanResult scanResult)191     public static boolean isScanResultForWepNetwork(@NonNull ScanResult scanResult) {
192         return scanResult.capabilities.contains("WEP");
193     }
194 
195     /**
196      * Helper method to check if the provided |scanResult| corresponds to OWE network.
197      * This checks if the provided capabilities string contains OWE or not.
198      */
isScanResultForOweNetwork(@onNull ScanResult scanResult)199     public static boolean isScanResultForOweNetwork(@NonNull ScanResult scanResult) {
200         return scanResult.capabilities.contains("OWE");
201     }
202 
203     /**
204      * Helper method to check if the provided |scanResult| corresponds to OWE transition network.
205      * This checks if the provided capabilities string contains OWE_TRANSITION or not.
206      */
isScanResultForOweTransitionNetwork(@onNull ScanResult scanResult)207     public static boolean isScanResultForOweTransitionNetwork(@NonNull ScanResult scanResult) {
208         return scanResult.capabilities.contains("OWE_TRANSITION");
209     }
210 
211     /**
212      * Helper method to check if the provided |scanResult| corresponds to SAE network.
213      * This checks if the provided capabilities string contains SAE or not.
214      */
isScanResultForSaeNetwork(@onNull ScanResult scanResult)215     public static boolean isScanResultForSaeNetwork(@NonNull ScanResult scanResult) {
216         return scanResult.capabilities.contains("SAE");
217     }
218 
219     /**
220      * Helper method to check if the provided |scanResult| corresponds to PSK-SAE transition
221      * network. This checks if the provided capabilities string contains both PSK and SAE or not.
222      */
isScanResultForPskSaeTransitionNetwork(@onNull ScanResult scanResult)223     public static boolean isScanResultForPskSaeTransitionNetwork(@NonNull ScanResult scanResult) {
224         return scanResult.capabilities.contains("PSK") && scanResult.capabilities.contains("SAE");
225     }
226 
227     /**
228      * Helper method to check if the provided |scanResult| corresponds to FILS SHA256 network.
229      * This checks if the provided capabilities string contains FILS-SHA256 or not.
230      */
isScanResultForFilsSha256Network(@onNull ScanResult scanResult)231     public static boolean isScanResultForFilsSha256Network(@NonNull ScanResult scanResult) {
232         return scanResult.capabilities.contains("FILS-SHA256");
233     }
234 
235     /**
236      * Helper method to check if the provided |scanResult| corresponds to FILS SHA384 network.
237      * This checks if the provided capabilities string contains FILS-SHA384 or not.
238      */
isScanResultForFilsSha384Network(@onNull ScanResult scanResult)239     public static boolean isScanResultForFilsSha384Network(@NonNull ScanResult scanResult) {
240         return scanResult.capabilities.contains("FILS-SHA384");
241     }
242 
243     /**
244      * Helper method to check if the provided |scanResult| corresponds to DPP network.
245      * This checks if the provided capabilities string contains DPP or not.
246      */
isScanResultForDppNetwork(@onNull ScanResult scanResult)247     public static boolean isScanResultForDppNetwork(@NonNull ScanResult scanResult) {
248         return scanResult.capabilities.contains("DPP");
249     }
250 
251     /**
252      * Helper method to check if the provided |scanResult| corresponds to only WPA-Personal network.
253      * This checks if the provided capabilities string contains WPA and not RSN.
254      */
isScanResultForWpaPersonalOnlyNetwork(@onNull ScanResult scanResult)255     public static boolean isScanResultForWpaPersonalOnlyNetwork(@NonNull ScanResult scanResult) {
256         return isScanResultForPskNetwork(scanResult) && !scanResult.capabilities.contains("RSN");
257     }
258 
259     /**
260      *  Helper method to check if the provided |scanResult| corresponds to an unknown amk network.
261      *  This checks if the provided capabilities string contains ? or not.
262      */
isScanResultForUnknownAkmNetwork(@onNull ScanResult scanResult)263     public static boolean isScanResultForUnknownAkmNetwork(@NonNull ScanResult scanResult) {
264         return scanResult.capabilities.contains("?");
265     }
266 
267     /**
268      *  Helper method to check if the provided |scanResult| corresponds to a pure PSK network.
269      */
isScanResultForPskOnlyNetwork(@onNull ScanResult r)270     public static boolean isScanResultForPskOnlyNetwork(@NonNull ScanResult r) {
271         return ScanResultUtil.isScanResultForPskNetwork(r)
272                 && !ScanResultUtil.isScanResultForSaeNetwork(r);
273     }
274 
275     /**
276      *  Helper method to check if the provided |scanResult| corresponds to a pure SAE network.
277      */
isScanResultForSaeOnlyNetwork(@onNull ScanResult r)278     public static boolean isScanResultForSaeOnlyNetwork(@NonNull ScanResult r) {
279         return !ScanResultUtil.isScanResultForPskNetwork(r)
280                 && ScanResultUtil.isScanResultForSaeNetwork(r);
281     }
282 
283     /**
284      *  Helper method to check if the provided |scanResult| corresponds to a pure OPEN network.
285      */
isScanResultForOpenOnlyNetwork(@onNull ScanResult r)286     public static boolean isScanResultForOpenOnlyNetwork(@NonNull ScanResult r) {
287         return ScanResultUtil.isScanResultForOpenNetwork(r)
288                 && !ScanResultUtil.isScanResultForOweNetwork(r);
289     }
290 
291     /**
292      *  Helper method to check if the provided |scanResult| corresponds to a pure OWE network.
293      */
isScanResultForOweOnlyNetwork(@onNull ScanResult r)294     public static boolean isScanResultForOweOnlyNetwork(@NonNull ScanResult r) {
295         return !ScanResultUtil.isScanResultForOweTransitionNetwork(r)
296                 && ScanResultUtil.isScanResultForOweNetwork(r);
297     }
298 
299     /**
300      *  Helper method to check if the provided |scanResult| corresponds to a pure
301      *  WPA2 Enterprise network.
302      */
isScanResultForWpa2EnterpriseOnlyNetwork(@onNull ScanResult r)303     public static boolean isScanResultForWpa2EnterpriseOnlyNetwork(@NonNull ScanResult r) {
304         return ScanResultUtil.isScanResultForEapNetwork(r)
305                 && !ScanResultUtil.isScanResultForWpa3EnterpriseTransitionNetwork(r)
306                 && !ScanResultUtil.isScanResultForWpa3EnterpriseOnlyNetwork(r);
307     }
308 
309     /**
310      * Helper method to check if the provided |scanResult| corresponds to an open network or not.
311      * This checks if the provided capabilities string does not contain either of WEP, PSK, SAE
312      * EAP, or unknown encryption types or not.
313      */
isScanResultForOpenNetwork(@onNull ScanResult scanResult)314     public static boolean isScanResultForOpenNetwork(@NonNull ScanResult scanResult) {
315         return (!(isScanResultForWepNetwork(scanResult) || isScanResultForPskNetwork(scanResult)
316                 || isScanResultForEapNetwork(scanResult) || isScanResultForSaeNetwork(scanResult)
317                 || isScanResultForWpa3EnterpriseTransitionNetwork(scanResult)
318                 || isScanResultForWpa3EnterpriseOnlyNetwork(scanResult)
319                 || isScanResultForWapiPskNetwork(scanResult)
320                 || isScanResultForWapiCertNetwork(scanResult)
321                 || isScanResultForEapSuiteBNetwork(scanResult)
322                 || isScanResultForDppNetwork(scanResult)
323                 || isScanResultForUnknownAkmNetwork(scanResult)));
324     }
325 
326     /**
327      * Helper method to quote the SSID in Scan result to use for comparing/filling SSID stored in
328      * WifiConfiguration object.
329      */
330     @VisibleForTesting
createQuotedSsid(@ullable String ssid)331     public static @NonNull String createQuotedSsid(@Nullable String ssid) {
332         return "\"" + ssid + "\"";
333     }
334 
335     /**
336      * Creates a network configuration object using the provided |scanResult|.
337      */
createNetworkFromScanResult( @onNull ScanResult scanResult)338     public static @Nullable WifiConfiguration createNetworkFromScanResult(
339             @NonNull ScanResult scanResult) {
340         WifiConfiguration config = new WifiConfiguration();
341         config.SSID = createQuotedSsid(scanResult.SSID);
342         List<SecurityParams> list = generateSecurityParamsListFromScanResult(scanResult);
343         if (list.isEmpty()) {
344             return null;
345         }
346         config.setSecurityParams(list);
347         return config;
348     }
349 
350     /**
351      * Generate security params from the scan result.
352      * @param scanResult the scan result to be checked.
353      * @return a list of security params. If no known security params, return an empty list.
354      */
generateSecurityParamsListFromScanResult( @onNull ScanResult scanResult)355     public static @NonNull List<SecurityParams> generateSecurityParamsListFromScanResult(
356             @NonNull ScanResult scanResult) {
357         List<SecurityParams> list = new ArrayList<>();
358 
359         // Open network & its upgradable types
360         if (ScanResultUtil.isScanResultForOweTransitionNetwork(scanResult)) {
361             list.add(SecurityParams.createSecurityParamsBySecurityType(
362                     WifiConfiguration.SECURITY_TYPE_OPEN));
363             list.add(SecurityParams.createSecurityParamsBySecurityType(
364                     WifiConfiguration.SECURITY_TYPE_OWE));
365             return list;
366         } else if (ScanResultUtil.isScanResultForOweNetwork(scanResult)) {
367             list.add(SecurityParams.createSecurityParamsBySecurityType(
368                     WifiConfiguration.SECURITY_TYPE_OWE));
369             return list;
370         } else if (ScanResultUtil.isScanResultForOpenNetwork(scanResult)) {
371             list.add(SecurityParams.createSecurityParamsBySecurityType(
372                     WifiConfiguration.SECURITY_TYPE_OPEN));
373             return list;
374         }
375 
376         // WEP network which has no upgradable type
377         if (ScanResultUtil.isScanResultForWepNetwork(scanResult)) {
378             list.add(SecurityParams.createSecurityParamsBySecurityType(
379                     WifiConfiguration.SECURITY_TYPE_WEP));
380             return list;
381         }
382 
383         // WAPI PSK network which has no upgradable type
384         if (ScanResultUtil.isScanResultForWapiPskNetwork(scanResult)) {
385             list.add(SecurityParams.createSecurityParamsBySecurityType(
386                     WifiConfiguration.SECURITY_TYPE_WAPI_PSK));
387             return list;
388         }
389 
390         // WAPI CERT network which has no upgradable type
391         if (ScanResultUtil.isScanResultForWapiCertNetwork(scanResult)) {
392             list.add(SecurityParams.createSecurityParamsBySecurityType(
393                     WifiConfiguration.SECURITY_TYPE_WAPI_CERT));
394             return list;
395         }
396 
397         // WPA2 personal network & its upgradable types
398         if (ScanResultUtil.isScanResultForPskNetwork(scanResult)
399                 && ScanResultUtil.isScanResultForSaeNetwork(scanResult)) {
400             list.add(SecurityParams.createSecurityParamsBySecurityType(
401                     WifiConfiguration.SECURITY_TYPE_PSK));
402             list.add(SecurityParams.createSecurityParamsBySecurityType(
403                     WifiConfiguration.SECURITY_TYPE_SAE));
404             return list;
405         } else if (ScanResultUtil.isScanResultForPskNetwork(scanResult)) {
406             list.add(SecurityParams.createSecurityParamsBySecurityType(
407                     WifiConfiguration.SECURITY_TYPE_PSK));
408             return list;
409         } else if (ScanResultUtil.isScanResultForSaeNetwork(scanResult)) {
410             list.add(SecurityParams.createSecurityParamsBySecurityType(
411                     WifiConfiguration.SECURITY_TYPE_SAE));
412             return list;
413         } else if (ScanResultUtil.isScanResultForDppNetwork(scanResult)) {
414             list.add(SecurityParams.createSecurityParamsBySecurityType(
415                     WifiConfiguration.SECURITY_TYPE_DPP));
416             return list;
417         }
418 
419         // WPA3 Enterprise 192-bit mode, WPA2/WPA3 enterprise network & its upgradable types
420         if (ScanResultUtil.isScanResultForEapSuiteBNetwork(scanResult)) {
421             list.add(SecurityParams.createSecurityParamsBySecurityType(
422                     WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE_192_BIT));
423         } else if (ScanResultUtil.isScanResultForWpa3EnterpriseTransitionNetwork(scanResult)) {
424             list.add(SecurityParams.createSecurityParamsBySecurityType(
425                     WifiConfiguration.SECURITY_TYPE_EAP));
426             list.add(SecurityParams.createSecurityParamsBySecurityType(
427                     WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE));
428         } else if (ScanResultUtil.isScanResultForWpa3EnterpriseOnlyNetwork(scanResult)) {
429             list.add(SecurityParams.createSecurityParamsBySecurityType(
430                     WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE));
431         } else if (ScanResultUtil.isScanResultForEapNetwork(scanResult)) {
432             list.add(SecurityParams.createSecurityParamsBySecurityType(
433                     WifiConfiguration.SECURITY_TYPE_EAP));
434         }
435         // An Enterprise network might be a Passpoint network as well.
436         // R3 network might be also a valid R1/R2 network.
437         if (isScanResultForPasspointR1R2Network(scanResult)) {
438             list.add(SecurityParams.createSecurityParamsBySecurityType(
439                     WifiConfiguration.SECURITY_TYPE_PASSPOINT_R1_R2));
440         }
441         if (isScanResultForPasspointR3Network(scanResult)) {
442             list.add(SecurityParams.createSecurityParamsBySecurityType(
443                     WifiConfiguration.SECURITY_TYPE_PASSPOINT_R3));
444         }
445         return list;
446     }
447 
448     /**
449      * Dump the provided scan results list to |pw|.
450      */
dumpScanResults(@onNull PrintWriter pw, @Nullable List<ScanResult> scanResults, long nowMs)451     public static void dumpScanResults(@NonNull PrintWriter pw,
452             @Nullable List<ScanResult> scanResults, long nowMs) {
453         if (scanResults != null && scanResults.size() != 0) {
454             pw.println("    BSSID              Frequency      RSSI           Age(sec)     SSID "
455                     + "                                Flags");
456             for (ScanResult r : scanResults) {
457                 long timeStampMs = r.timestamp / 1000;
458                 String age;
459                 if (timeStampMs <= 0) {
460                     age = "___?___";
461                 } else if (nowMs < timeStampMs) {
462                     age = "  0.000";
463                 } else if (timeStampMs < nowMs - 1000000) {
464                     age = ">1000.0";
465                 } else {
466                     age = String.format("%3.3f", (nowMs - timeStampMs) / 1000.0);
467                 }
468                 String ssid = r.SSID == null ? "" : r.SSID;
469                 String rssiInfo = "";
470                 int numRadioChainInfos = r.radioChainInfos == null ? 0 : r.radioChainInfos.length;
471                 if (numRadioChainInfos == 1) {
472                     rssiInfo = String.format("%5d(%1d:%3d)       ", r.level,
473                             r.radioChainInfos[0].id, r.radioChainInfos[0].level);
474                 } else if (numRadioChainInfos == 2) {
475                     rssiInfo = String.format("%5d(%1d:%3d/%1d:%3d)", r.level,
476                             r.radioChainInfos[0].id, r.radioChainInfos[0].level,
477                             r.radioChainInfos[1].id, r.radioChainInfos[1].level);
478                 } else {
479                     rssiInfo = String.format("%9d         ", r.level);
480                 }
481                 if ((r.flags & FLAG_PASSPOINT_NETWORK)
482                         == FLAG_PASSPOINT_NETWORK) {
483                     r.capabilities += "[PASSPOINT]";
484                 }
485                 pw.printf("  %17s  %9d  %18s   %7s    %-32s  %s\n",
486                         r.BSSID,
487                         r.frequency,
488                         rssiInfo,
489                         age,
490                         String.format("%1.32s", ssid),
491                         r.capabilities);
492             }
493         }
494     }
495 
496     /**
497      * Check if ScanResult list is valid.
498      */
validateScanResultList(@ullable List<ScanResult> scanResults)499     public static boolean validateScanResultList(@Nullable List<ScanResult> scanResults) {
500         if (scanResults == null || scanResults.isEmpty()) {
501             Log.w(TAG, "Empty or null ScanResult list");
502             return false;
503         }
504         for (ScanResult scanResult : scanResults) {
505             if (!validate(scanResult)) {
506                 Log.w(TAG, "Invalid ScanResult: " + scanResult);
507                 return false;
508             }
509         }
510         return true;
511     }
512 
validate(@ullable ScanResult scanResult)513     private static boolean validate(@Nullable ScanResult scanResult) {
514         return scanResult != null && scanResult.SSID != null
515                 && scanResult.capabilities != null && scanResult.BSSID != null;
516     }
517 
518     /**
519      * Redact bytes from a bssid.
520      */
redactBssid(MacAddress bssid, int numRedactedOctets)521     public static String redactBssid(MacAddress bssid, int numRedactedOctets) {
522         if (bssid == null) {
523             return "";
524         }
525         StringBuilder redactedBssid = new StringBuilder();
526         byte[] bssidBytes = bssid.toByteArray();
527 
528         if (numRedactedOctets < 0 || numRedactedOctets > 6) {
529             // Reset to default if passed value is invalid.
530             numRedactedOctets = 4;
531         }
532         for (int i = 0; i < 6; i++) {
533             if (i < numRedactedOctets) {
534                 redactedBssid.append("xx");
535             } else {
536                 redactedBssid.append(String.format("%02X", bssidBytes[i]));
537             }
538             if (i != 5) {
539                 redactedBssid.append(":");
540             }
541         }
542         return redactedBssid.toString();
543     }
544 }
545