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