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