1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * https://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18 package org.apache.commons.codec.binary;
19
20 import java.util.Arrays;
21
22 import org.apache.commons.codec.CodecPolicy;
23
24 /**
25 * Provides Base16 encoding and decoding as defined by <a href="https://tools.ietf.org/html/rfc4648#section-8">RFC 4648 - 8. Base 16 Encoding</a>.
26 *
27 * <p>
28 * This class is thread-safe.
29 * </p>
30 * <p>
31 * This implementation strictly follows RFC 4648, and as such unlike the {@link Base32} and {@link Base64} implementations, it does not ignore invalid alphabet
32 * characters or whitespace, neither does it offer chunking or padding characters.
33 * </p>
34 * <p>
35 * The only additional feature above those specified in RFC 4648 is support for working with a lower-case alphabet in addition to the default upper-case
36 * alphabet, and configuring a custom 16-byte alphabet with {@link Builder#setEncodeTable(byte...)}.
37 * </p>
38 *
39 * @see Base16InputStream
40 * @see Base16OutputStream
41 * @see <a href="https://tools.ietf.org/html/rfc4648#section-8">RFC 4648 - 8. Base 16 Encoding</a>
42 * @since 1.15
43 */
44 public class Base16 extends BaseNCodec {
45
46 /**
47 * Builds {@link Base16} instances.
48 *
49 * <p>
50 * To configure a new instance, use a {@link Builder}. For example:
51 * </p>
52 *
53 * <pre>
54 * Base16 Base16 = Base16.builder()
55 * .setDecodingPolicy(DecodingPolicy.LENIENT) // default is lenient
56 * .get()
57 * </pre>
58 *
59 * @since 1.20.0
60 */
61 public static class Builder extends AbstractBuilder<Base16, Builder> {
62
63 /**
64 * Constructs a new instance.
65 */
66 public Builder() {
67 super(null);
68 setDecodeTable(UPPER_CASE_DECODE_TABLE);
69 setEncodeTable(UPPER_CASE_ENCODE_TABLE);
70 setEncodedBlockSize(BYTES_PER_ENCODED_BLOCK);
71 setUnencodedBlockSize(BYTES_PER_UNENCODED_BLOCK);
72 setLineLength(0);
73 setLineSeparator(EMPTY_BYTE_ARRAY);
74 }
75
76 @Override
77 public Base16 get() {
78 return new Base16(this);
79 }
80
81 /**
82 * Sets the Base16 encode table and derives the matching decode table.
83 *
84 * @param encodeTable 16 unique bytes, null resets to the default upper-case table.
85 * @return {@code this} instance.
86 * @throws IllegalArgumentException Thrown if {@code encodeTable} does not contain 16 unique bytes.
87 */
88 @Override
89 public Builder setEncodeTable(final byte... encodeTable) {
90 final byte[] table = encodeTable != null ? encodeTable : UPPER_CASE_ENCODE_TABLE;
91 super.setDecodeTableRaw(toDecodeTable(table));
92 return super.setEncodeTable(table);
93 }
94
95 /**
96 * Sets whether to use the lower-case Base16 alphabet.
97 *
98 * @param lowerCase {@code true} to use the lower-case Base16 alphabet.
99 * @return {@code this} instance.
100 */
101 public Builder setLowerCase(final boolean lowerCase) {
102 return setEncodeTable(lowerCase ? LOWER_CASE_ENCODE_TABLE : UPPER_CASE_ENCODE_TABLE);
103 }
104
105 }
106
107 /**
108 * BASE16 characters are 4 bits in length. They are formed by taking an 8-bit group, which is converted into two BASE16 characters.
109 */
110 private static final int BITS_PER_ENCODED_BYTE = 4;
111
112 private static final int BYTES_PER_ENCODED_BLOCK = 2;
113
114 private static final int BYTES_PER_UNENCODED_BLOCK = 1;
115
116 /**
117 * This array is a lookup table that translates Unicode characters drawn from the "Base16 Alphabet" (as specified in Table 5 of RFC 4648) into their 4-bit
118 * positive integer equivalents. Characters that are not in the Base16 alphabet but fall within the bounds of the array are translated to -1.
119 */
120 // @formatter:off
121 private static final byte[] UPPER_CASE_DECODE_TABLE = {
122 // 0 1 2 3 4 5 6 7 8 9 A B C D E F
123 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 00-0f
124 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 10-1f
125 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 20-2f
126 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, // 30-3f 0-9
127 -1, 10, 11, 12, 13, 14, 15 // 40-46 A-F
128 };
129 // @formatter:on
130
131 /**
132 * This array is a lookup table that translates 4-bit positive integer index values into their "Base16 Alphabet" equivalents as specified in Table 5 of RFC
133 * 4648.
134 */
135 private static final byte[] UPPER_CASE_ENCODE_TABLE = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
136
137 /**
138 * This array is a lookup table that translates Unicode characters drawn from the a lower-case "Base16 Alphabet" into their 4-bit positive integer
139 * equivalents. Characters that are not in the Base16 alphabet but fall within the bounds of the array are translated to -1.
140 */
141 // @formatter:off
142 private static final byte[] LOWER_CASE_DECODE_TABLE = {
143 // 0 1 2 3 4 5 6 7 8 9 A B C D E F
144 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 00-0f
145 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 10-1f
146 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 20-2f
147 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, // 30-3f 0-9
148 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 40-4f
149 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 50-5f
150 -1, 10, 11, 12, 13, 14, 15 // 60-66 a-f
151 };
152 // @formatter:on
153
154 /**
155 * This array is a lookup table that translates 4-bit positive integer index values into their "Base16 Alphabet" lower-case equivalents.
156 */
157 private static final byte[] LOWER_CASE_ENCODE_TABLE = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
158
159 /** Mask used to extract 4 bits, used when decoding character. */
160 private static final int MASK_4_BITS = 0x0f;
161
162 /**
163 * Constructs a new builder.
164 *
165 * @return A new builder.
166 * @since 1.20.0
167 */
168 public static Builder builder() {
169 return new Builder();
170 }
171
172 private static byte[] toDecodeTable(final byte[] encodeTable) {
173 if (Arrays.equals(encodeTable, UPPER_CASE_ENCODE_TABLE)) {
174 return UPPER_CASE_DECODE_TABLE;
175 }
176 if (Arrays.equals(encodeTable, LOWER_CASE_ENCODE_TABLE)) {
177 return LOWER_CASE_DECODE_TABLE;
178 }
179 if (encodeTable.length != 1 << BITS_PER_ENCODED_BYTE) {
180 throw new IllegalArgumentException("Base16 encode table must contain 16 entries.");
181 }
182 int max = -1;
183 for (final byte b : encodeTable) {
184 max = Math.max(max, b & 0xff);
185 }
186 final byte[] decodeTable = new byte[max + 1];
187 Arrays.fill(decodeTable, (byte) -1);
188 for (int i = 0; i < encodeTable.length; i++) {
189 final int b = encodeTable[i] & 0xff;
190 if (decodeTable[b] != -1) {
191 throw new IllegalArgumentException("Duplicate value in Base16 encode table: " + b);
192 }
193 decodeTable[b] = (byte) i;
194 }
195 return decodeTable;
196 }
197
198 /**
199 * Constructs a Base16 codec used for decoding and encoding.
200 */
201 public Base16() {
202 this(false);
203 }
204
205 /**
206 * Constructs a Base16 codec used for decoding and encoding.
207 *
208 * @param lowerCase {@code true} to use the lower-case Base16 alphabet.
209 * @deprecated Use {@link #builder()} and {@link Builder}.
210 */
211 @Deprecated
212 public Base16(final boolean lowerCase) {
213 this(lowerCase, DECODING_POLICY_DEFAULT);
214 }
215
216 /**
217 * Constructs a Base16 codec used for decoding and encoding.
218 *
219 * @param lowerCase {@code true} to use the lower-case Base16 alphabet.
220 * @param decodingPolicy Decoding policy.
221 * @deprecated Use {@link #builder()} and {@link Builder}.
222 */
223 @Deprecated
224 public Base16(final boolean lowerCase, final CodecPolicy decodingPolicy) {
225 this(builder().setEncodeTable(lowerCase ? LOWER_CASE_ENCODE_TABLE : UPPER_CASE_ENCODE_TABLE).setDecodingPolicy(decodingPolicy));
226 }
227
228 private Base16(final Builder builder) {
229 super(builder);
230 }
231
232 /**
233 * {@inheritDoc}
234 *
235 * @throws IllegalArgumentException Thrown when a problem is detected processing data.
236 */
237 @Override
238 void decode(final byte[] data, int offset, final int length, final Context context) {
239 if (context.eof || length < 0) {
240 context.eof = true;
241 if (context.ibitWorkArea != 0) {
242 validateTrailingCharacter();
243 }
244 return;
245 }
246 final int dataLen = Math.min(data.length - offset, length);
247 final int availableChars = (context.ibitWorkArea != 0 ? 1 : 0) + dataLen;
248 // small optimization to short-cut the rest of this method when it is fed byte-by-byte
249 if (availableChars == 1 && availableChars == dataLen) {
250 // store 1/2 byte for next invocation of decode, we offset by +1 as empty-value is 0
251 context.ibitWorkArea = decodeOctet(data[offset]) + 1;
252 return;
253 }
254 // we must have an even number of chars to decode
255 final int charsToProcess = availableChars % BYTES_PER_ENCODED_BLOCK == 0 ? availableChars : availableChars - 1;
256 final int end = offset + dataLen;
257 final byte[] buffer = ensureBufferSize(charsToProcess / BYTES_PER_ENCODED_BLOCK, context);
258 int result;
259 if (dataLen < availableChars) {
260 // we have 1/2 byte from previous invocation to decode
261 result = context.ibitWorkArea - 1 << BITS_PER_ENCODED_BYTE;
262 result |= decodeOctet(data[offset++]);
263 buffer[context.pos++] = (byte) result;
264 // reset to empty-value for next invocation!
265 context.ibitWorkArea = 0;
266 }
267 final int loopEnd = end - 1;
268 while (offset < loopEnd) {
269 result = decodeOctet(data[offset++]) << BITS_PER_ENCODED_BYTE;
270 result |= decodeOctet(data[offset++]);
271 buffer[context.pos++] = (byte) result;
272 }
273 // we have one char of a hex-pair left over
274 if (offset < end) {
275 // store 1/2 byte for next invocation of decode, we offset by +1 as empty-value is 0
276 context.ibitWorkArea = decodeOctet(data[offset]) + 1;
277 }
278 }
279
280 private int decodeOctet(final byte octet) {
281 int decoded = -1;
282 final int b = octet & 0xff;
283 if (b < decodeTable.length) {
284 decoded = decodeTable[b];
285 }
286 if (decoded == -1) {
287 throw new IllegalArgumentException("Invalid octet in encoded value: " + (int) octet);
288 }
289 return decoded;
290 }
291
292 @Override
293 void encode(final byte[] data, final int offset, final int length, final Context context) {
294 if (context.eof) {
295 return;
296 }
297 if (length < 0) {
298 context.eof = true;
299 return;
300 }
301 final int size = length * BYTES_PER_ENCODED_BLOCK;
302 if (size < 0) {
303 throw new IllegalArgumentException("Input length exceeds maximum size for encoded data: " + length);
304 }
305 final byte[] buffer = ensureBufferSize(size, context);
306 final int end = offset + length;
307 for (int i = offset; i < end; i++) {
308 final int value = data[i];
309 final int high = value >> BITS_PER_ENCODED_BYTE & MASK_4_BITS;
310 final int low = value & MASK_4_BITS;
311 buffer[context.pos++] = encodeTable[high];
312 buffer[context.pos++] = encodeTable[low];
313 }
314 }
315
316 /**
317 * Tests whether the {@code octet} is in the Base16 alphabet.
318 *
319 * @param octet The value to test.
320 * @return {@code true} if the value is defined in the Base16 alphabet {@code false} otherwise.
321 */
322 @Override
323 public boolean isInAlphabet(final byte octet) {
324 final int b = octet & 0xff;
325 return b < decodeTable.length && decodeTable[b] != -1;
326 }
327
328 /**
329 * Validates whether decoding allows an entire final trailing character that cannot be used for a complete byte.
330 *
331 * @throws IllegalArgumentException Thrown if strict decoding is enabled.
332 */
333 private void validateTrailingCharacter() {
334 if (isStrictDecoding()) {
335 throw new IllegalArgumentException("Strict decoding: Last encoded character is a valid Base 16 alphabet character but not a possible encoding. " +
336 "Decoding requires at least two characters to create one byte.");
337 }
338 }
339 }