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