1 /*
2  * Copyright (C) 2021 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 package com.android.server.uwb.util;
17 
18 import androidx.annotation.VisibleForTesting;
19 
20 import com.google.common.base.Preconditions;
21 
22 /**
23  * Utility class for converting hex strings to and from byte arrays.
24  *
25  * <p>We can't use org.apache.commons.codec.binary.Hex because Android already hides it as part of
26  * /system/frameworks/ext.jar
27  *
28  * <p>Unlike standard org.apache.commons.codec.binary.Hex we allow strings with odd length to be
29  * decoded, in order to accommodate unusual partner decisions.
30  */
31 public class Hex {
32     /** Base-16 encoding/decoding. */
33     @VisibleForTesting static final int RADIX = 16;
34 
35     /** Upper-case encoding. */
36     @VisibleForTesting
37     static final char[] UPPER = {
38             '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F',
39     };
40 
41     /** Lower-case encoding. */
42     @VisibleForTesting
43     static final char[] LOWER = {
44             '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f',
45     };
46 
47     /** No instances. */
Hex()48     private Hex() {}
49 
50     /** Returns a lower-case hex string encoding of the given byte array. */
encode(byte[] bytes)51     public static String encode(byte[] bytes) {
52         return doEncode(bytes, LOWER);
53     }
54 
55     /** Returns an upper-case hex string encoding of the given byte array. */
encodeUpper(byte[] bytes)56     public static String encodeUpper(byte[] bytes) {
57         return doEncode(bytes, UPPER);
58     }
59 
60     /**
61      * Decode the hex string to byte array.
62      */
decode(String s)63     public static byte[] decode(String s) throws IllegalArgumentException {
64         if (s.length() % 2 != 0) {
65             s = "0" + s;
66         }
67         return decodeEven(s);
68     }
69 
70     /**
71      * Returns the byte array represented by the given string. Can handle both upper- and lower-case
72      * ASCII characters.
73      *
74      * @throws IllegalArgumentException if the string is not of even length, or if it contains a
75      *     non-hexadecimal character.
76      */
decodeEven(String s)77     private static byte[] decodeEven(String s) throws IllegalArgumentException {
78         int length = s.length();
79         Preconditions.checkArgument(length % 2 == 0, "String not of even length: %s", s);
80         byte[] result = new byte[length / 2];
81         int resultPos = 0;
82         for (int pos = 0; pos < length; pos += 2) {
83             char c0 = s.charAt(pos);
84             char c1 = s.charAt(pos + 1);
85             int n0 = Character.digit(c0, RADIX);
86             int n1 = Character.digit(c1, RADIX);
87             Preconditions.checkArgument(n0 != -1, "Invalid character: '%s'", String.valueOf(c0));
88             Preconditions.checkArgument(n1 != -1, "Invalid character: '%s'", String.valueOf(c1));
89             result[resultPos++] = (byte) (n0 << 4 | n1);
90         }
91         return result;
92     }
93 
94     /** Returns a hex string encoding of the given byte array using the given alphabet. */
doEncode(byte[] bytes, char[] alphabet)95     private static String doEncode(byte[] bytes, char[] alphabet) {
96         StringBuilder sb = new StringBuilder(bytes.length * 2);
97         for (byte b : bytes) {
98             sb.append(alphabet[(b & 0xF0) >> 4]).append(alphabet[b & 0x0F]);
99         }
100         return sb.toString();
101     }
102 }
103