1 /*
2  * Copyright (C) 2014 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 libcore.util;
18 
19 /**
20  * Hexadecimal encoding where each byte is represented by two hexadecimal digits.
21  */
22 public class HexEncoding {
23 
24     /** Hidden constructor to prevent instantiation. */
HexEncoding()25     private HexEncoding() {}
26 
27     private static final char[] HEX_DIGITS = "0123456789ABCDEF".toCharArray();
28 
29     /**
30      * Encodes the provided data as a sequence of hexadecimal characters.
31      */
encode(byte[] data)32     public static char[] encode(byte[] data) {
33         return encode(data, 0, data.length);
34     }
35 
36     /**
37      * Encodes the provided data as a sequence of hexadecimal characters.
38      */
encode(byte[] data, int offset, int len)39     public static char[] encode(byte[] data, int offset, int len) {
40         char[] result = new char[len * 2];
41         for (int i = 0; i < len; i++) {
42             byte b = data[offset + i];
43             int resultIndex = 2 * i;
44             result[resultIndex] = (HEX_DIGITS[(b >>> 4) & 0x0f]);
45             result[resultIndex + 1] = (HEX_DIGITS[b & 0x0f]);
46         }
47 
48         return result;
49     }
50 
51     /**
52      * Decodes the provided hexadecimal string into a byte array. If {@code allowSingleChar}
53      * is {@code true} odd-length inputs are allowed and the first character is interpreted
54      * as the lower bits of the first result byte.
55      *
56      * Throws an {@code IllegalArgumentException} if the input is malformed.
57      */
decode(char[] encoded, boolean allowSingleChar)58     public static byte[] decode(char[] encoded, boolean allowSingleChar) throws IllegalArgumentException {
59         int resultLengthBytes = (encoded.length + 1) / 2;
60         byte[] result = new byte[resultLengthBytes];
61 
62         int resultOffset = 0;
63         int i = 0;
64         if (allowSingleChar) {
65             if ((encoded.length % 2) != 0) {
66                 // Odd number of digits -- the first digit is the lower 4 bits of the first result byte.
67                 result[resultOffset++] = (byte) toDigit(encoded, i);
68                 i++;
69             }
70         } else {
71             if ((encoded.length % 2) != 0) {
72                 throw new IllegalArgumentException("Invalid input length: " + encoded.length);
73             }
74         }
75 
76         for (int len = encoded.length; i < len; i += 2) {
77             result[resultOffset++] = (byte) ((toDigit(encoded, i) << 4) | toDigit(encoded, i + 1));
78         }
79 
80         return result;
81     }
82 
toDigit(char[] str, int offset)83     private static int toDigit(char[] str, int offset) throws IllegalArgumentException {
84         // NOTE: that this isn't really a code point in the traditional sense, since we're
85         // just rejecting surrogate pairs outright.
86         int pseudoCodePoint = str[offset];
87 
88         if ('0' <= pseudoCodePoint && pseudoCodePoint <= '9') {
89             return pseudoCodePoint - '0';
90         } else if ('a' <= pseudoCodePoint && pseudoCodePoint <= 'f') {
91             return 10 + (pseudoCodePoint - 'a');
92         } else if ('A' <= pseudoCodePoint && pseudoCodePoint <= 'F') {
93             return 10 + (pseudoCodePoint - 'A');
94         }
95 
96         throw new IllegalArgumentException("Illegal char: " + str[offset] +
97                 " at offset " + offset);
98     }
99 }
100