1 /* 2 * Copyright (C) 2017 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.Nullable; 20 import android.net.MacAddress; 21 import android.net.wifi.WifiConfiguration; 22 import android.net.wifi.util.HexEncoding; 23 import android.text.TextUtils; 24 25 import com.android.server.wifi.ByteBufferReader; 26 import com.android.server.wifi.WifiGlobals; 27 28 import java.nio.BufferUnderflowException; 29 import java.nio.ByteBuffer; 30 import java.nio.ByteOrder; 31 import java.nio.CharBuffer; 32 import java.nio.charset.CharacterCodingException; 33 import java.nio.charset.CharsetDecoder; 34 import java.nio.charset.CharsetEncoder; 35 import java.nio.charset.StandardCharsets; 36 import java.util.ArrayList; 37 import java.util.Arrays; 38 import java.util.BitSet; 39 40 /** 41 * Provide utility functions for native interfacing modules. 42 */ 43 public class NativeUtil { 44 private static final String ANY_MAC_STR = "any"; 45 public static final byte[] ANY_MAC_BYTES = {0, 0, 0, 0, 0, 0}; 46 private static final int MAC_LENGTH = 6; 47 private static final int MAC_OUI_LENGTH = 3; 48 private static final int MAC_STR_LENGTH = MAC_LENGTH * 2 + 5; 49 private static final int SSID_BYTES_MAX_LEN = 32; 50 51 /** 52 * Convert the string to byte array list. 53 * 54 * @return the UTF_8 char byte values of str, as an ArrayList. 55 * @throws IllegalArgumentException if a null or unencodable string is sent. 56 */ stringToByteArrayList(String str)57 public static ArrayList<Byte> stringToByteArrayList(String str) { 58 if (str == null) { 59 throw new IllegalArgumentException("null string"); 60 } 61 // Ensure that the provided string is UTF_8 encoded. 62 CharsetEncoder encoder = StandardCharsets.UTF_8.newEncoder(); 63 try { 64 ByteBuffer encoded = encoder.encode(CharBuffer.wrap(str)); 65 byte[] byteArray = new byte[encoded.remaining()]; 66 encoded.get(byteArray); 67 return byteArrayToArrayList(byteArray); 68 } catch (CharacterCodingException cce) { 69 throw new IllegalArgumentException("cannot be utf-8 encoded", cce); 70 } 71 } 72 73 /** 74 * Convert the byte array list to string. 75 * 76 * @return the string decoded from UTF_8 byte values in byteArrayList. 77 * @throws IllegalArgumentException if a null byte array list is sent. 78 */ stringFromByteArrayList(ArrayList<Byte> byteArrayList)79 public static String stringFromByteArrayList(ArrayList<Byte> byteArrayList) { 80 if (byteArrayList == null) { 81 throw new IllegalArgumentException("null byte array list"); 82 } 83 byte[] byteArray = new byte[byteArrayList.size()]; 84 int i = 0; 85 for (Byte b : byteArrayList) { 86 byteArray[i] = b; 87 i++; 88 } 89 return new String(byteArray, StandardCharsets.UTF_8); 90 } 91 92 /** 93 * Convert the string to byte array. 94 * 95 * @return the UTF_8 char byte values of str, as an Array. 96 * @throws IllegalArgumentException if a null string is sent. 97 */ stringToByteArray(String str)98 public static byte[] stringToByteArray(String str) { 99 if (str == null) { 100 throw new IllegalArgumentException("null string"); 101 } 102 return str.getBytes(StandardCharsets.UTF_8); 103 } 104 105 /** 106 * Convert the byte array list to string. 107 * 108 * @return the string decoded from UTF_8 byte values in byteArray. 109 * @throws IllegalArgumentException if a null byte array is sent. 110 */ stringFromByteArray(byte[] byteArray)111 public static String stringFromByteArray(byte[] byteArray) { 112 if (byteArray == null) { 113 throw new IllegalArgumentException("null byte array"); 114 } 115 return new String(byteArray); 116 } 117 118 /** 119 * Converts a mac address string to an array of Bytes. 120 * 121 * @param macStr string of format: "XX:XX:XX:XX:XX:XX" or "XXXXXXXXXXXX", where X is any 122 * hexadecimal digit. 123 * Passing null, empty string or "any" is the same as 00:00:00:00:00:00 124 * @throws IllegalArgumentException for various malformed inputs. 125 */ macAddressToByteArray(String macStr)126 public static byte[] macAddressToByteArray(String macStr) { 127 if (TextUtils.isEmpty(macStr) || ANY_MAC_STR.equals(macStr)) return ANY_MAC_BYTES; 128 String cleanMac = macStr.replace(":", ""); 129 if (cleanMac.length() != MAC_LENGTH * 2) { 130 throw new IllegalArgumentException("invalid mac string length: " + cleanMac); 131 } 132 return HexEncoding.decode(cleanMac.toCharArray(), false); 133 } 134 135 /** 136 * Converts a MAC address from the given string representation to android.net.MacAddress. A 137 * valid String representation for a MacAddress is a series of 6 values in the range [0,ff] 138 * printed in hexadecimal and joined by ':' characters. 139 * 140 * @param macAddress a String representation of a MAC address. 141 * @return the MacAddress corresponding to the given string representation or null. 142 */ getMacAddressOrNull(@ullable String macAddress)143 public static MacAddress getMacAddressOrNull(@Nullable String macAddress) { 144 if (macAddress == null) return null; 145 try { 146 return MacAddress.fromString(macAddress); 147 } catch (IllegalArgumentException e) { 148 return null; 149 } 150 } 151 152 /** 153 * Converts an array of 6 bytes to a HexEncoded String with format: "XX:XX:XX:XX:XX:XX", where X 154 * is any hexadecimal digit. 155 * 156 * @param macArray byte array of mac values, must have length 6 157 * @throws IllegalArgumentException for malformed inputs. 158 */ macAddressFromByteArray(byte[] macArray)159 public static String macAddressFromByteArray(byte[] macArray) { 160 if (macArray == null) { 161 throw new IllegalArgumentException("null mac bytes"); 162 } 163 if (macArray.length != MAC_LENGTH) { 164 throw new IllegalArgumentException("invalid macArray length: " + macArray.length); 165 } 166 StringBuilder sb = new StringBuilder(MAC_STR_LENGTH); 167 for (int i = 0; i < macArray.length; i++) { 168 if (i != 0) sb.append(":"); 169 sb.append(new String(HexEncoding.encode(macArray, i, 1))); 170 } 171 return sb.toString().toLowerCase(); 172 } 173 174 /** 175 * Converts a mac address OUI string to an array of Bytes. 176 * 177 * @param macStr string of format: "XX:XX:XX" or "XXXXXX", where X is any hexadecimal digit. 178 * @throws IllegalArgumentException for various malformed inputs. 179 */ macAddressOuiToByteArray(String macStr)180 public static byte[] macAddressOuiToByteArray(String macStr) { 181 if (macStr == null) { 182 throw new IllegalArgumentException("null mac string"); 183 } 184 String cleanMac = macStr.replace(":", ""); 185 if (cleanMac.length() != MAC_OUI_LENGTH * 2) { 186 throw new IllegalArgumentException("invalid mac oui string length: " + cleanMac); 187 } 188 return HexEncoding.decode(cleanMac.toCharArray(), false); 189 } 190 191 /** 192 * Converts an array of 6 bytes to a long representing the MAC address. 193 * 194 * @param macArray byte array of mac values, must have length 6 195 * @return Long value of the mac address. 196 * @throws IllegalArgumentException for malformed inputs. 197 */ macAddressToLong(byte[] macArray)198 public static Long macAddressToLong(byte[] macArray) { 199 if (macArray == null) { 200 throw new IllegalArgumentException("null mac bytes"); 201 } 202 if (macArray.length != MAC_LENGTH) { 203 throw new IllegalArgumentException("invalid macArray length: " + macArray.length); 204 } 205 try { 206 return ByteBufferReader.readInteger( 207 ByteBuffer.wrap(macArray), ByteOrder.BIG_ENDIAN, macArray.length); 208 } catch (BufferUnderflowException | IllegalArgumentException e) { 209 throw new IllegalArgumentException("invalid macArray"); 210 } 211 } 212 213 /** 214 * Remove enclosing quotes from the provided string. 215 * 216 * @param quotedStr String to be unquoted. 217 * @return String without the enclosing quotes. 218 */ removeEnclosingQuotes(String quotedStr)219 public static String removeEnclosingQuotes(String quotedStr) { 220 int length = quotedStr.length(); 221 if ((length >= 2) 222 && (quotedStr.charAt(0) == '"') && (quotedStr.charAt(length - 1) == '"')) { 223 return quotedStr.substring(1, length - 1); 224 } 225 return quotedStr; 226 } 227 228 /** 229 * Add enclosing quotes to the provided string. 230 * 231 * @param str String to be quoted. 232 * @return String with the enclosing quotes. 233 */ addEnclosingQuotes(String str)234 public static String addEnclosingQuotes(String str) { 235 return "\"" + str + "\""; 236 } 237 238 /** 239 * Converts an string to an arraylist of UTF_8 byte values. 240 * These forms are acceptable: 241 * a) UTF-8 String encapsulated in quotes, or 242 * b) Hex string with no delimiters. 243 * 244 * @param str String to be converted. 245 * @throws IllegalArgumentException for null string. 246 */ hexOrQuotedStringToBytes(String str)247 public static ArrayList<Byte> hexOrQuotedStringToBytes(String str) { 248 if (str == null) { 249 throw new IllegalArgumentException("null string"); 250 } 251 int length = str.length(); 252 if ((length > 1) && (str.charAt(0) == '"') && (str.charAt(length - 1) == '"')) { 253 str = str.substring(1, str.length() - 1); 254 return stringToByteArrayList(str); 255 } else { 256 return byteArrayToArrayList(hexStringToByteArray(str)); 257 } 258 } 259 260 /** 261 * Converts an ArrayList<Byte> of UTF_8 byte values to string. 262 * The string will either be: 263 * a) UTF-8 String encapsulated in quotes (if all the bytes are UTF-8 encodeable and non null), 264 * or 265 * b) Hex string with no delimiters. 266 * 267 * @param bytes List of bytes for ssid. 268 * @throws IllegalArgumentException for null bytes. 269 */ bytesToHexOrQuotedString(ArrayList<Byte> bytes)270 public static String bytesToHexOrQuotedString(ArrayList<Byte> bytes) { 271 if (bytes == null) { 272 throw new IllegalArgumentException("null ssid bytes"); 273 } 274 byte[] byteArray = byteArrayFromArrayList(bytes); 275 // Check for 0's in the byte stream in which case we cannot convert this into a string. 276 if (!bytes.contains(Byte.valueOf((byte) 0))) { 277 CharsetDecoder decoder = StandardCharsets.UTF_8.newDecoder(); 278 try { 279 CharBuffer decoded = decoder.decode(ByteBuffer.wrap(byteArray)); 280 return "\"" + decoded.toString() + "\""; 281 } catch (CharacterCodingException cce) { 282 } 283 } 284 return hexStringFromByteArray(byteArray); 285 } 286 287 /** 288 * Converts an ssid string to an arraylist of UTF_8 byte values. 289 * These forms are acceptable: 290 * a) UTF-8 String encapsulated in quotes, or 291 * b) Hex string with no delimiters. 292 * 293 * @param ssidStr String to be converted. 294 * @throws IllegalArgumentException for null string. 295 */ decodeSsid(String ssidStr)296 public static ArrayList<Byte> decodeSsid(String ssidStr) { 297 ArrayList<Byte> ssidBytes = hexOrQuotedStringToBytes(ssidStr); 298 if (ssidBytes.size() > SSID_BYTES_MAX_LEN) { 299 throw new IllegalArgumentException("ssid bytes size out of range: " + ssidBytes.size()); 300 } 301 return ssidBytes; 302 } 303 304 /** 305 * Converts an ArrayList<Byte> of UTF_8 byte values to ssid string. 306 * The string will either be: 307 * a) UTF-8 String encapsulated in quotes (if all the bytes are UTF-8 encodeable and non null), 308 * or 309 * b) Hex string with no delimiters. 310 * 311 * @param ssidBytes List of bytes for ssid. 312 * @throws IllegalArgumentException for null bytes. 313 */ encodeSsid(ArrayList<Byte> ssidBytes)314 public static String encodeSsid(ArrayList<Byte> ssidBytes) { 315 if (ssidBytes.size() > SSID_BYTES_MAX_LEN) { 316 throw new IllegalArgumentException("ssid bytes size out of range: " + ssidBytes.size()); 317 } 318 return bytesToHexOrQuotedString(ssidBytes); 319 } 320 321 /** 322 * Convert from an array of primitive bytes to an array list of Byte. 323 */ byteArrayToArrayList(byte[] bytes)324 public static ArrayList<Byte> byteArrayToArrayList(byte[] bytes) { 325 ArrayList<Byte> byteList = new ArrayList<>(); 326 for (Byte b : bytes) { 327 byteList.add(b); 328 } 329 return byteList; 330 } 331 332 /** 333 * Convert from an array list of Byte to an array of primitive bytes. 334 */ byteArrayFromArrayList(ArrayList<Byte> bytes)335 public static byte[] byteArrayFromArrayList(ArrayList<Byte> bytes) { 336 byte[] byteArray = new byte[bytes.size()]; 337 int i = 0; 338 for (Byte b : bytes) { 339 byteArray[i++] = b; 340 } 341 return byteArray; 342 } 343 344 /** 345 * Converts a hex string to byte array. 346 * 347 * @param hexStr String to be converted. 348 * @throws IllegalArgumentException for null string. 349 */ hexStringToByteArray(String hexStr)350 public static byte[] hexStringToByteArray(String hexStr) { 351 if (hexStr == null) { 352 throw new IllegalArgumentException("null hex string"); 353 } 354 return HexEncoding.decode(hexStr.toCharArray(), false); 355 } 356 357 /** 358 * Converts a byte array to hex string. 359 * 360 * @param bytes List of bytes for ssid. 361 * @throws IllegalArgumentException for null bytes. 362 */ hexStringFromByteArray(byte[] bytes)363 public static String hexStringFromByteArray(byte[] bytes) { 364 if (bytes == null) { 365 throw new IllegalArgumentException("null hex bytes"); 366 } 367 return new String(HexEncoding.encode(bytes)).toLowerCase(); 368 } 369 370 /** 371 * Converts an 8 byte array to a WPS device type string 372 * { 0, 1, 2, -1, 4, 5, 6, 7 } --> "1-02FF0405-1543"; 373 */ wpsDevTypeStringFromByteArray(byte[] devType)374 public static String wpsDevTypeStringFromByteArray(byte[] devType) { 375 byte[] a = devType; 376 int x = ((a[0] & 0xFF) << 8) | (a[1] & 0xFF); 377 String y = new String(HexEncoding.encode(Arrays.copyOfRange(devType, 2, 6))); 378 int z = ((a[6] & 0xFF) << 8) | (a[7] & 0xFF); 379 return String.format("%d-%s-%d", x, y, z); 380 } 381 382 /** 383 * Update PMF requirement if auto-upgrade offload is supported. 384 * 385 * If SAE auto-upgrade offload is supported and this config enables 386 * both PSK and SAE, do not set PMF requirement to 387 * mandatory to allow the device to roam between PSK and SAE BSSes. 388 * wpa_supplicant will set PMF requirement to optional by default. 389 */ getOptimalPmfSettingForConfig(WifiConfiguration config, boolean isPmfRequiredFromSelectedSecurityParams, WifiGlobals wifiGlobals)390 public static boolean getOptimalPmfSettingForConfig(WifiConfiguration config, 391 boolean isPmfRequiredFromSelectedSecurityParams, WifiGlobals wifiGlobals) { 392 if (isPskSaeParamsMergeable(config, wifiGlobals)) { 393 return false; 394 } 395 return isPmfRequiredFromSelectedSecurityParams; 396 } 397 398 /** 399 * Update group ciphers if auto-upgrade offload is supported. 400 * 401 * If auto-upgrade offload is supported and this config enables both PSK and 402 * SAE, merge allowed group ciphers to allow native service to roam 403 * between two types. 404 */ getOptimalGroupCiphersForConfig(WifiConfiguration config, BitSet ciphersFromSelectedParams, WifiGlobals wifiGlobals)405 public static BitSet getOptimalGroupCiphersForConfig(WifiConfiguration config, 406 BitSet ciphersFromSelectedParams, WifiGlobals wifiGlobals) { 407 BitSet ciphers = ciphersFromSelectedParams; 408 if (isPskSaeParamsMergeable(config, wifiGlobals)) { 409 ciphers = (BitSet) config.getSecurityParams(WifiConfiguration.SECURITY_TYPE_PSK) 410 .getAllowedGroupCiphers().clone(); 411 ciphers.or((BitSet) config.getSecurityParams(WifiConfiguration.SECURITY_TYPE_SAE) 412 .getAllowedGroupCiphers().clone()); 413 } 414 return ciphers; 415 } 416 417 /** 418 * Update pairwise ciphers if auto-upgrade offload is supported. 419 * 420 * If auto-upgrade offload is supported and this config enables both PSK and 421 * SAE, merge allowed pairwise ciphers to allow native service to roam 422 * between two types. 423 */ getOptimalPairwiseCiphersForConfig(WifiConfiguration config, BitSet ciphersFromSelectedParams, WifiGlobals wifiGlobal)424 public static BitSet getOptimalPairwiseCiphersForConfig(WifiConfiguration config, 425 BitSet ciphersFromSelectedParams, WifiGlobals wifiGlobal) { 426 BitSet ciphers = ciphersFromSelectedParams; 427 if (isPskSaeParamsMergeable(config, wifiGlobal)) { 428 ciphers = (BitSet) config.getSecurityParams(WifiConfiguration.SECURITY_TYPE_PSK) 429 .getAllowedPairwiseCiphers().clone(); 430 ciphers.or((BitSet) config.getSecurityParams(WifiConfiguration.SECURITY_TYPE_SAE) 431 .getAllowedPairwiseCiphers().clone()); 432 } 433 return ciphers; 434 } 435 isPskSaeParamsMergeable( WifiConfiguration config, WifiGlobals wifiGlobals)436 private static boolean isPskSaeParamsMergeable( 437 WifiConfiguration config, WifiGlobals wifiGlobals) { 438 if (config.isSecurityType(WifiConfiguration.SECURITY_TYPE_PSK) 439 && config.getSecurityParams(WifiConfiguration.SECURITY_TYPE_PSK).isEnabled() 440 && config.isSecurityType(WifiConfiguration.SECURITY_TYPE_SAE) 441 && config.getSecurityParams(WifiConfiguration.SECURITY_TYPE_SAE).isEnabled() 442 && wifiGlobals.isWpa3SaeUpgradeOffloadEnabled()) { 443 return true; 444 } 445 return false; 446 } 447 448 } 449