1 /*
2  * Copyright (C) 2023 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.tradefed.device;
18 
19 import com.android.tradefed.log.LogUtil.CLog;
20 
21 import java.util.LinkedHashMap;
22 import java.util.LinkedList;
23 import java.util.List;
24 import java.util.Map;
25 import java.util.regex.Matcher;
26 import java.util.regex.Pattern;
27 
28 /** A utility class that can parse wifi command outputs. */
29 public class WifiCommandUtil {
30 
31     public static final Pattern SSID_PATTERN =
32             Pattern.compile(".*WifiInfo:.*SSID:\\s*\"([^,]*)\".*");
33     public static final Pattern BSSID_PATTERN = Pattern.compile(".*WifiInfo:.*BSSID:\\s*([^,]*).*");
34     public static final Pattern LINK_SPEED_PATTERN =
35             Pattern.compile(
36                     ".*WifiInfo:.*(?<!\\bTx\\s\\b|\\bRx\\s\\b)Link speed:\\s*([^,]*)Mbps.*");
37     public static final Pattern RSSI_PATTERN = Pattern.compile(".*WifiInfo:.*RSSI:\\s*([^,]*).*");
38     public static final Pattern MAC_ADDRESS_PATTERN =
39             Pattern.compile(".*WifiInfo:.*MAC:\\s*([^,]*).*");
40 
41     /** Represents a wifi network containing its related info. */
42     public static class ScanResult {
43         private Map<String, String> scanInfo = new LinkedHashMap<>();
44         /** Adds an info related to the current wifi network. */
addInfo(String key, String value)45         private void addInfo(String key, String value) {
46             scanInfo.put(key, value);
47         }
48         /** Returns the wifi network information related to the key */
getInfo(String infoKey)49         public String getInfo(String infoKey) {
50             return scanInfo.get(infoKey);
51         }
52 
53         @Override
toString()54         public String toString() {
55             return scanInfo.toString();
56         }
57     }
58 
59     /**
60      * Parse the `wifi list-scan-results` command output and returns a list of {@link ScanResult}s.
61      *
62      * @param input Output of the list-scan-results command to parse.
63      * @return List of {@link ScanResult}s.
64      */
parseScanResults(String input)65     public static List<ScanResult> parseScanResults(String input) {
66         // EXAMPLE INPUT:
67 
68         // BSSID             Frequency   RSSI           Age(sec)   SSID         Flags
69         // 20:9c:b4:16:09:f2   5580    -70(0:-70/1:-79)  4.731   GoogleGuest-2  [ESS]
70         // 20:9c:b4:16:09:f0   5580    -69(0:-70/1:-78)  4.732   Google-A       [WPA2-PSK-CCMP][ESS]
71         // 20:9c:b4:16:09:f1   5580    -69(0:-69/1:-78)  4.731   GoogleGuest    [ESS]
72 
73         if (input == null || input.isEmpty()) {
74             return new LinkedList<>();
75         }
76         List<ScanResult> results = new LinkedList<>();
77 
78         // Figure out the column names from the first line of the output
79         String[] scanResultLines = input.split("\n");
80         String[] columnNames = scanResultLines[0].split("\\s+");
81 
82         // All lines after that should be related to wifi networks
83         for (int i = 1; i < scanResultLines.length; i++) {
84             if (scanResultLines[i].trim().isEmpty()) {
85                 continue;
86             }
87             String[] columnValues = scanResultLines[i].split("\\s+");
88             if (columnValues.length != columnNames.length) {
89                 CLog.d(
90                         "Skipping scan result since one or more of its value is undetermined:\n%s",
91                         scanResultLines[i]);
92             } else {
93                 ScanResult scanResult = new ScanResult();
94                 for (int j = 0; j < columnNames.length; j++) {
95                     scanResult.addInfo(columnNames[j], columnValues[j]);
96                 }
97                 results.add(scanResult);
98             }
99         }
100         return results;
101     }
102 
103     /** Resolves the network type given the flags returned from list-scan-result cmd. */
resolveNetworkType(String flags)104     public static String resolveNetworkType(String flags) {
105         if (flags.contains("WEP")) {
106             return "wep";
107         } else if (flags.contains("OWE")) {
108             return "owe";
109         } else if (flags.contains("WPA2")) {
110             return "wpa2";
111         } else if (flags.contains("SAE")) {
112             return "wpa3";
113         } else {
114             return "open";
115         }
116     }
117 
118     /**
119      * Parse the 'wifi status' output and returns a map of info about connected wifi network.
120      *
121      * @param input Output of the 'wifi status' command to parse.
122      * @return a map of info about the connected network.
123      */
parseWifiInfo(String input)124     public static Map<String, String> parseWifiInfo(String input) {
125         Map<String, String> wifiInfo = new LinkedHashMap<>();
126 
127         Matcher ssidMatcher = SSID_PATTERN.matcher(input);
128         if (ssidMatcher.find()) {
129             wifiInfo.put("ssid", ssidMatcher.group(1));
130         }
131 
132         Matcher bssidMatcher = BSSID_PATTERN.matcher(input);
133         if (bssidMatcher.find()) {
134             wifiInfo.put("bssid", bssidMatcher.group(1));
135         }
136 
137         // TODO: also gather ip address, which is not availabled in the 'wifi status" output
138 
139         Matcher linkSpeedMatcher = LINK_SPEED_PATTERN.matcher(input);
140         if (linkSpeedMatcher.find()) {
141             wifiInfo.put("linkSpeed", linkSpeedMatcher.group(1));
142         }
143 
144         Matcher rssiMatcher = RSSI_PATTERN.matcher(input);
145         if (rssiMatcher.find()) {
146             wifiInfo.put("rssi", rssiMatcher.group(1));
147         }
148 
149         Matcher macAddressMatcher = MAC_ADDRESS_PATTERN.matcher(input);
150         if (macAddressMatcher.find()) {
151             wifiInfo.put("macAddress", macAddressMatcher.group(1));
152         }
153 
154         return wifiInfo;
155     }
156 }
157