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 import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;
20 
21 import android.annotation.SystemApi;
22 
23 /**
24  * Hexadecimal encoding where each byte is represented by two hexadecimal digits.
25  * @hide
26  */
27 @SystemApi(client = MODULE_LIBRARIES)
28 public class HexEncoding {
29 
30     private static final char[] LOWER_CASE_DIGITS = {
31             '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
32     };
33 
34     private static final char[] UPPER_CASE_DIGITS = {
35             '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
36     };
37 
38     /** Hidden constructor to prevent instantiation. */
HexEncoding()39     private HexEncoding() {}
40 
41     /**
42      * Encodes the provided byte as a two-digit hexadecimal String value.
43      *
44      * @param  b byte to encode
45      * @param  upperCase {@code true} to use uppercase letters, {@code false}
46      *         for lowercase
47      * @return the encoded string
48      *
49      * @hide
50      */
51     @SystemApi(client = MODULE_LIBRARIES)
encodeToString(byte b, boolean upperCase)52     public static String encodeToString(byte b, boolean upperCase) {
53         char[] digits = upperCase ? UPPER_CASE_DIGITS : LOWER_CASE_DIGITS;
54         char[] buf = new char[2]; // We always want two digits.
55         buf[0] = digits[(b >> 4) & 0xf];
56         buf[1] = digits[b & 0xf];
57         return new String(buf, 0, 2);
58     }
59 
60     /**
61      * Encodes the provided data as a sequence of hexadecimal characters.
62      *
63      * @param  data byte array to encode
64      * @return the encoded data, using uppercase letters
65      *
66      * @hide
67      */
68     @SystemApi(client = MODULE_LIBRARIES)
encode(byte[] data)69     public static char[] encode(byte[] data) {
70         return encode(data, 0, data.length, true /* upperCase */);
71     }
72 
73     /**
74      * Encodes the provided data as a sequence of hexadecimal characters.
75      *
76      * @param  data byte array to encode
77      * @param  upperCase {@code true} to use uppercase letters, {@code false}
78      *         for lowercase
79      * @return the encoded data
80      *
81      * @hide
82      */
83     @SystemApi(client = MODULE_LIBRARIES)
encode(byte[] data, boolean upperCase)84     public static char[] encode(byte[] data, boolean upperCase) {
85         return encode(data, 0, data.length, upperCase);
86     }
87 
88     /**
89      * Encodes the provided data as a sequence of hexadecimal characters.
90      *
91      * @param  data byte array containing the data to encode
92      * @param  offset offset of the data to encode in the {@code data} array
93      * @param  len length of the data to encode in the {@code data} array
94      * @return the encoded data, using uppercase letters
95      *
96      * @hide
97      */
98     @SystemApi(client = MODULE_LIBRARIES)
encode(byte[] data, int offset, int len)99     public static char[] encode(byte[] data, int offset, int len) {
100         return encode(data, offset, len, true /* upperCase */);
101     }
102 
103     /**
104      * Encodes the provided data as a sequence of hexadecimal characters.
105      */
encode(byte[] data, int offset, int len, boolean upperCase)106     private static char[] encode(byte[] data, int offset, int len, boolean upperCase) {
107         char[] digits = upperCase ? UPPER_CASE_DIGITS : LOWER_CASE_DIGITS;
108         char[] result = new char[len * 2];
109         for (int i = 0; i < len; i++) {
110             byte b = data[offset + i];
111             int resultIndex = 2 * i;
112             result[resultIndex] = (digits[(b >> 4) & 0x0f]);
113             result[resultIndex + 1] = (digits[b & 0x0f]);
114         }
115 
116         return result;
117     }
118 
119     /**
120      * Encodes the provided data as a sequence of hexadecimal characters.
121      *
122      * @param  data byte array to encode
123      * @return the encoded data, using uppercase letters
124      *
125      * @hide
126      */
127     @SystemApi(client = MODULE_LIBRARIES)
encodeToString(byte[] data)128     public static String encodeToString(byte[] data) {
129         return encodeToString(data, true /* upperCase */);
130     }
131 
132     /**
133      * Encodes the provided data as a sequence of hexadecimal characters.
134      *
135      * @param  data byte array to encode.
136      * @param  upperCase {@code true} to use uppercase letters, {@code false}
137      *         for lowercase
138      * @return the encoded data
139      *
140      * @hide
141      */
142     @SystemApi(client = MODULE_LIBRARIES)
encodeToString(byte[] data, boolean upperCase)143     public static String encodeToString(byte[] data, boolean upperCase) {
144         return new String(encode(data, upperCase));
145     }
146 
147     /**
148      * Decodes the provided hexadecimal sequence. Odd-length inputs are not
149      * allowed.
150      *
151      * @param  encoded string of hexadecimal characters to decode. Letters
152      *         can be either uppercase or lowercase.
153      * @return the decoded data
154      * @throws IllegalArgumentException if the input is malformed
155      *
156      * @hide
157      */
158     @SystemApi(client = MODULE_LIBRARIES)
decode(String encoded)159     public static byte[] decode(String encoded) throws IllegalArgumentException {
160         return decode(encoded.toCharArray());
161     }
162 
163     /**
164      * Decodes the provided hexadecimal sequence.
165      *
166      * @param  encoded string of hexadecimal characters to decode. Letters
167      *         can be either uppercase or lowercase.
168      * @param  allowSingleChar If {@code true} odd-length inputs are allowed and
169      *         the first character is interpreted as the lower bits of the first
170      *         result byte. If {@code false} odd-length inputs are not allowed.
171      * @return the decoded data
172      * @throws IllegalArgumentException if the input is malformed
173      *
174      * @hide
175      */
176     @SystemApi(client = MODULE_LIBRARIES)
decode(String encoded, boolean allowSingleChar)177     public static byte[] decode(String encoded, boolean allowSingleChar)
178             throws IllegalArgumentException {
179         return decode(encoded.toCharArray(), allowSingleChar);
180     }
181 
182     /**
183      * Decodes the provided hexadecimal sequence. Odd-length inputs are not
184      * allowed.
185      *
186      * @param  encoded char array of hexadecimal characters to decode. Letters
187      *         can be either uppercase or lowercase.
188      * @return the decoded data
189      * @throws IllegalArgumentException if the input is malformed
190      *
191      * @hide
192      */
193     @SystemApi(client = MODULE_LIBRARIES)
decode(char[] encoded)194     public static byte[] decode(char[] encoded) throws IllegalArgumentException {
195         return decode(encoded, false);
196     }
197 
198     /**
199      * Decodes the provided hexadecimal sequence.
200      *
201      * @param  encoded char array of hexadecimal characters to decode. Letters
202      *         can be either uppercase or lowercase.
203      * @param  allowSingleChar If {@code true} odd-length inputs are allowed and
204      *         the first character is interpreted as the lower bits of the first
205      *         result byte. If {@code false} odd-length inputs are not allowed.
206      * @return the decoded data
207      * @throws IllegalArgumentException if the input is malformed
208      *
209      * @hide
210      */
211     @SystemApi(client = MODULE_LIBRARIES)
decode(char[] encoded, boolean allowSingleChar)212     public static byte[] decode(char[] encoded, boolean allowSingleChar)
213             throws IllegalArgumentException {
214         int encodedLength = encoded.length;
215         int resultLengthBytes = (encodedLength + 1) / 2;
216         byte[] result = new byte[resultLengthBytes];
217 
218         int resultOffset = 0;
219         int i = 0;
220         if (allowSingleChar) {
221             if ((encodedLength % 2) != 0) {
222                 // Odd number of digits -- the first digit is the lower 4 bits of the first result
223                 // byte.
224                 result[resultOffset++] = (byte) toDigit(encoded, i);
225                 i++;
226             }
227         } else {
228             if ((encodedLength % 2) != 0) {
229                 throw new IllegalArgumentException("Invalid input length: " + encodedLength);
230             }
231         }
232 
233         for (; i < encodedLength; i += 2) {
234             result[resultOffset++] = (byte) ((toDigit(encoded, i) << 4) | toDigit(encoded, i + 1));
235         }
236 
237         return result;
238     }
239 
toDigit(char[] str, int offset)240     private static int toDigit(char[] str, int offset) throws IllegalArgumentException {
241         // NOTE: that this isn't really a code point in the traditional sense, since we're
242         // just rejecting surrogate pairs outright.
243         int pseudoCodePoint = str[offset];
244 
245         if ('0' <= pseudoCodePoint && pseudoCodePoint <= '9') {
246             return pseudoCodePoint - '0';
247         } else if ('a' <= pseudoCodePoint && pseudoCodePoint <= 'f') {
248             return 10 + (pseudoCodePoint - 'a');
249         } else if ('A' <= pseudoCodePoint && pseudoCodePoint <= 'F') {
250             return 10 + (pseudoCodePoint - 'A');
251         }
252 
253         throw new IllegalArgumentException("Illegal char: " + str[offset] + " at offset " + offset);
254     }
255 }
256