/* * Copyright (C) 2021 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.server.uwb.util; import androidx.annotation.VisibleForTesting; import com.google.common.base.Preconditions; /** * Utility class for converting hex strings to and from byte arrays. * * <p>We can't use org.apache.commons.codec.binary.Hex because Android already hides it as part of * /system/frameworks/ext.jar * * <p>Unlike standard org.apache.commons.codec.binary.Hex we allow strings with odd length to be * decoded, in order to accommodate unusual partner decisions. */ public class Hex { /** Base-16 encoding/decoding. */ @VisibleForTesting static final int RADIX = 16; /** Upper-case encoding. */ @VisibleForTesting static final char[] UPPER = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', }; /** Lower-case encoding. */ @VisibleForTesting static final char[] LOWER = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', }; /** No instances. */ private Hex() {} /** Returns a lower-case hex string encoding of the given byte array. */ public static String encode(byte[] bytes) { return doEncode(bytes, LOWER); } /** Returns an upper-case hex string encoding of the given byte array. */ public static String encodeUpper(byte[] bytes) { return doEncode(bytes, UPPER); } /** * Decode the hex string to byte array. */ public static byte[] decode(String s) throws IllegalArgumentException { if (s.length() % 2 != 0) { s = "0" + s; } return decodeEven(s); } /** * Returns the byte array represented by the given string. Can handle both upper- and lower-case * ASCII characters. * * @throws IllegalArgumentException if the string is not of even length, or if it contains a * non-hexadecimal character. */ private static byte[] decodeEven(String s) throws IllegalArgumentException { int length = s.length(); Preconditions.checkArgument(length % 2 == 0, "String not of even length: %s", s); byte[] result = new byte[length / 2]; int resultPos = 0; for (int pos = 0; pos < length; pos += 2) { char c0 = s.charAt(pos); char c1 = s.charAt(pos + 1); int n0 = Character.digit(c0, RADIX); int n1 = Character.digit(c1, RADIX); Preconditions.checkArgument(n0 != -1, "Invalid character: '%s'", String.valueOf(c0)); Preconditions.checkArgument(n1 != -1, "Invalid character: '%s'", String.valueOf(c1)); result[resultPos++] = (byte) (n0 << 4 | n1); } return result; } /** Returns a hex string encoding of the given byte array using the given alphabet. */ private static String doEncode(byte[] bytes, char[] alphabet) { StringBuilder sb = new StringBuilder(bytes.length * 2); for (byte b : bytes) { sb.append(alphabet[(b & 0xF0) >> 4]).append(alphabet[b & 0x0F]); } return sb.toString(); } }