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