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