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.text.TextUtils;
20 
21 import com.android.server.wifi.ByteBufferReader;
22 
23 import libcore.util.HexEncoding;
24 
25 import java.nio.BufferUnderflowException;
26 import java.nio.ByteBuffer;
27 import java.nio.ByteOrder;
28 import java.nio.CharBuffer;
29 import java.nio.charset.CharacterCodingException;
30 import java.nio.charset.CharsetDecoder;
31 import java.nio.charset.CharsetEncoder;
32 import java.nio.charset.StandardCharsets;
33 import java.util.ArrayList;
34 import java.util.Arrays;
35 
36 /**
37  * Provide utility functions for native interfacing modules.
38  */
39 public class NativeUtil {
40     private static final String ANY_MAC_STR = "any";
41     public static final byte[] ANY_MAC_BYTES = {0, 0, 0, 0, 0, 0};
42     private static final int MAC_LENGTH = 6;
43     private static final int MAC_OUI_LENGTH = 3;
44     private static final int MAC_STR_LENGTH = MAC_LENGTH * 2 + 5;
45     private static final int SSID_BYTES_MAX_LEN = 32;
46 
47     /**
48      * Convert the string to byte array list.
49      *
50      * @return the UTF_8 char byte values of str, as an ArrayList.
51      * @throws IllegalArgumentException if a null or unencodable string is sent.
52      */
stringToByteArrayList(String str)53     public static ArrayList<Byte> stringToByteArrayList(String str) {
54         if (str == null) {
55             throw new IllegalArgumentException("null string");
56         }
57         // Ensure that the provided string is UTF_8 encoded.
58         CharsetEncoder encoder = StandardCharsets.UTF_8.newEncoder();
59         try {
60             ByteBuffer encoded = encoder.encode(CharBuffer.wrap(str));
61             byte[] byteArray = new byte[encoded.remaining()];
62             encoded.get(byteArray);
63             return byteArrayToArrayList(byteArray);
64         } catch (CharacterCodingException cce) {
65             throw new IllegalArgumentException("cannot be utf-8 encoded", cce);
66         }
67     }
68 
69     /**
70      * Convert the byte array list to string.
71      *
72      * @return the string decoded from UTF_8 byte values in byteArrayList.
73      * @throws IllegalArgumentException if a null byte array list is sent.
74      */
stringFromByteArrayList(ArrayList<Byte> byteArrayList)75     public static String stringFromByteArrayList(ArrayList<Byte> byteArrayList) {
76         if (byteArrayList == null) {
77             throw new IllegalArgumentException("null byte array list");
78         }
79         byte[] byteArray = new byte[byteArrayList.size()];
80         int i = 0;
81         for (Byte b : byteArrayList) {
82             byteArray[i] = b;
83             i++;
84         }
85         return new String(byteArray, StandardCharsets.UTF_8);
86     }
87 
88     /**
89      * Convert the string to byte array.
90      *
91      * @return the UTF_8 char byte values of str, as an Array.
92      * @throws IllegalArgumentException if a null string is sent.
93      */
stringToByteArray(String str)94     public static byte[] stringToByteArray(String str) {
95         if (str == null) {
96             throw new IllegalArgumentException("null string");
97         }
98         return str.getBytes(StandardCharsets.UTF_8);
99     }
100 
101     /**
102      * Convert the byte array list to string.
103      *
104      * @return the string decoded from UTF_8 byte values in byteArray.
105      * @throws IllegalArgumentException if a null byte array is sent.
106      */
stringFromByteArray(byte[] byteArray)107     public static String stringFromByteArray(byte[] byteArray) {
108         if (byteArray == null) {
109             throw new IllegalArgumentException("null byte array");
110         }
111         return new String(byteArray);
112     }
113 
114     /**
115      * Converts a mac address string to an array of Bytes.
116      *
117      * @param macStr string of format: "XX:XX:XX:XX:XX:XX" or "XXXXXXXXXXXX", where X is any
118      *        hexadecimal digit.
119      *        Passing null, empty string or "any" is the same as 00:00:00:00:00:00
120      * @throws IllegalArgumentException for various malformed inputs.
121      */
macAddressToByteArray(String macStr)122     public static byte[] macAddressToByteArray(String macStr) {
123         if (TextUtils.isEmpty(macStr) || ANY_MAC_STR.equals(macStr)) return ANY_MAC_BYTES;
124         String cleanMac = macStr.replace(":", "");
125         if (cleanMac.length() != MAC_LENGTH * 2) {
126             throw new IllegalArgumentException("invalid mac string length: " + cleanMac);
127         }
128         return HexEncoding.decode(cleanMac.toCharArray(), false);
129     }
130 
131     /**
132      * Converts an array of 6 bytes to a HexEncoded String with format: "XX:XX:XX:XX:XX:XX", where X
133      * is any hexadecimal digit.
134      *
135      * @param macArray byte array of mac values, must have length 6
136      * @throws IllegalArgumentException for malformed inputs.
137      */
macAddressFromByteArray(byte[] macArray)138     public static String macAddressFromByteArray(byte[] macArray) {
139         if (macArray == null) {
140             throw new IllegalArgumentException("null mac bytes");
141         }
142         if (macArray.length != MAC_LENGTH) {
143             throw new IllegalArgumentException("invalid macArray length: " + macArray.length);
144         }
145         StringBuilder sb = new StringBuilder(MAC_STR_LENGTH);
146         for (int i = 0; i < macArray.length; i++) {
147             if (i != 0) sb.append(":");
148             sb.append(new String(HexEncoding.encode(macArray, i, 1)));
149         }
150         return sb.toString().toLowerCase();
151     }
152 
153     /**
154      * Converts a mac address OUI string to an array of Bytes.
155      *
156      * @param macStr string of format: "XX:XX:XX" or "XXXXXX", where X is any hexadecimal digit.
157      * @throws IllegalArgumentException for various malformed inputs.
158      */
macAddressOuiToByteArray(String macStr)159     public static byte[] macAddressOuiToByteArray(String macStr) {
160         if (macStr == null) {
161             throw new IllegalArgumentException("null mac string");
162         }
163         String cleanMac = macStr.replace(":", "");
164         if (cleanMac.length() != MAC_OUI_LENGTH * 2) {
165             throw new IllegalArgumentException("invalid mac oui string length: " + cleanMac);
166         }
167         return HexEncoding.decode(cleanMac.toCharArray(), false);
168     }
169 
170     /**
171      * Converts an array of 6 bytes to a long representing the MAC address.
172      *
173      * @param macArray byte array of mac values, must have length 6
174      * @return Long value of the mac address.
175      * @throws IllegalArgumentException for malformed inputs.
176      */
macAddressToLong(byte[] macArray)177     public static Long macAddressToLong(byte[] macArray) {
178         if (macArray == null) {
179             throw new IllegalArgumentException("null mac bytes");
180         }
181         if (macArray.length != MAC_LENGTH) {
182             throw new IllegalArgumentException("invalid macArray length: " + macArray.length);
183         }
184         try {
185             return ByteBufferReader.readInteger(
186                     ByteBuffer.wrap(macArray), ByteOrder.BIG_ENDIAN, macArray.length);
187         } catch (BufferUnderflowException | IllegalArgumentException e) {
188             throw new IllegalArgumentException("invalid macArray");
189         }
190     }
191 
192     /**
193      * Remove enclosing quotes from the provided string.
194      *
195      * @param quotedStr String to be unquoted.
196      * @return String without the enclosing quotes.
197      */
removeEnclosingQuotes(String quotedStr)198     public static String removeEnclosingQuotes(String quotedStr) {
199         int length = quotedStr.length();
200         if ((length >= 2)
201                 && (quotedStr.charAt(0) == '"') && (quotedStr.charAt(length - 1) == '"')) {
202             return quotedStr.substring(1, length - 1);
203         }
204         return quotedStr;
205     }
206 
207     /**
208      * Add enclosing quotes to the provided string.
209      *
210      * @param str String to be quoted.
211      * @return String with the enclosing quotes.
212      */
addEnclosingQuotes(String str)213     public static String addEnclosingQuotes(String str) {
214         return "\"" + str + "\"";
215     }
216 
217     /**
218      * Converts an string to an arraylist of UTF_8 byte values.
219      * These forms are acceptable:
220      * a) UTF-8 String encapsulated in quotes, or
221      * b) Hex string with no delimiters.
222      *
223      * @param str String to be converted.
224      * @throws IllegalArgumentException for null string.
225      */
hexOrQuotedStringToBytes(String str)226     public static ArrayList<Byte> hexOrQuotedStringToBytes(String str) {
227         if (str == null) {
228             throw new IllegalArgumentException("null string");
229         }
230         int length = str.length();
231         if ((length > 1) && (str.charAt(0) == '"') && (str.charAt(length - 1) == '"')) {
232             str = str.substring(1, str.length() - 1);
233             return stringToByteArrayList(str);
234         } else {
235             return byteArrayToArrayList(hexStringToByteArray(str));
236         }
237     }
238 
239     /**
240      * Converts an ArrayList<Byte> of UTF_8 byte values to string.
241      * The string will either be:
242      * a) UTF-8 String encapsulated in quotes (if all the bytes are UTF-8 encodeable and non null),
243      * or
244      * b) Hex string with no delimiters.
245      *
246      * @param bytes List of bytes for ssid.
247      * @throws IllegalArgumentException for null bytes.
248      */
bytesToHexOrQuotedString(ArrayList<Byte> bytes)249     public static String bytesToHexOrQuotedString(ArrayList<Byte> bytes) {
250         if (bytes == null) {
251             throw new IllegalArgumentException("null ssid bytes");
252         }
253         byte[] byteArray = byteArrayFromArrayList(bytes);
254         // Check for 0's in the byte stream in which case we cannot convert this into a string.
255         if (!bytes.contains(Byte.valueOf((byte) 0))) {
256             CharsetDecoder decoder = StandardCharsets.UTF_8.newDecoder();
257             try {
258                 CharBuffer decoded = decoder.decode(ByteBuffer.wrap(byteArray));
259                 return "\"" + decoded.toString() + "\"";
260             } catch (CharacterCodingException cce) {
261             }
262         }
263         return hexStringFromByteArray(byteArray);
264     }
265 
266     /**
267      * Converts an ssid string to an arraylist of UTF_8 byte values.
268      * These forms are acceptable:
269      * a) UTF-8 String encapsulated in quotes, or
270      * b) Hex string with no delimiters.
271      *
272      * @param ssidStr String to be converted.
273      * @throws IllegalArgumentException for null string.
274      */
decodeSsid(String ssidStr)275     public static ArrayList<Byte> decodeSsid(String ssidStr) {
276         ArrayList<Byte> ssidBytes = hexOrQuotedStringToBytes(ssidStr);
277         if (ssidBytes.size() > SSID_BYTES_MAX_LEN) {
278             throw new IllegalArgumentException("ssid bytes size out of range: " + ssidBytes.size());
279         }
280         return ssidBytes;
281     }
282 
283     /**
284      * Converts an ArrayList<Byte> of UTF_8 byte values to ssid string.
285      * The string will either be:
286      * a) UTF-8 String encapsulated in quotes (if all the bytes are UTF-8 encodeable and non null),
287      * or
288      * b) Hex string with no delimiters.
289      *
290      * @param ssidBytes List of bytes for ssid.
291      * @throws IllegalArgumentException for null bytes.
292      */
encodeSsid(ArrayList<Byte> ssidBytes)293     public static String encodeSsid(ArrayList<Byte> ssidBytes) {
294         if (ssidBytes.size() > SSID_BYTES_MAX_LEN) {
295             throw new IllegalArgumentException("ssid bytes size out of range: " + ssidBytes.size());
296         }
297         return bytesToHexOrQuotedString(ssidBytes);
298     }
299 
300     /**
301      * Convert from an array of primitive bytes to an array list of Byte.
302      */
byteArrayToArrayList(byte[] bytes)303     public static ArrayList<Byte> byteArrayToArrayList(byte[] bytes) {
304         ArrayList<Byte> byteList = new ArrayList<>();
305         for (Byte b : bytes) {
306             byteList.add(b);
307         }
308         return byteList;
309     }
310 
311     /**
312      * Convert from an array list of Byte to an array of primitive bytes.
313      */
byteArrayFromArrayList(ArrayList<Byte> bytes)314     public static byte[] byteArrayFromArrayList(ArrayList<Byte> bytes) {
315         byte[] byteArray = new byte[bytes.size()];
316         int i = 0;
317         for (Byte b : bytes) {
318             byteArray[i++] = b;
319         }
320         return byteArray;
321     }
322 
323     /**
324      * Converts a hex string to byte array.
325      *
326      * @param hexStr String to be converted.
327      * @throws IllegalArgumentException for null string.
328      */
hexStringToByteArray(String hexStr)329     public static byte[] hexStringToByteArray(String hexStr) {
330         if (hexStr == null) {
331             throw new IllegalArgumentException("null hex string");
332         }
333         return HexEncoding.decode(hexStr.toCharArray(), false);
334     }
335 
336     /**
337      * Converts a byte array to hex string.
338      *
339      * @param bytes List of bytes for ssid.
340      * @throws IllegalArgumentException for null bytes.
341      */
hexStringFromByteArray(byte[] bytes)342     public static String hexStringFromByteArray(byte[] bytes) {
343         if (bytes == null) {
344             throw new IllegalArgumentException("null hex bytes");
345         }
346         return new String(HexEncoding.encode(bytes)).toLowerCase();
347     }
348 
349     /**
350      * Converts an 8 byte array to a WPS device type string
351      * { 0, 1, 2, -1, 4, 5, 6, 7 } --> "1-02FF0405-1543";
352      */
wpsDevTypeStringFromByteArray(byte[] devType)353     public static String wpsDevTypeStringFromByteArray(byte[] devType) {
354         byte[] a = devType;
355         int x = ((a[0] & 0xFF) << 8) | (a[1] & 0xFF);
356         String y = new String(HexEncoding.encode(Arrays.copyOfRange(devType, 2, 6)));
357         int z = ((a[6] & 0xFF) << 8) | (a[7] & 0xFF);
358         return String.format("%d-%s-%d", x, y, z);
359     }
360 }
361