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