View Javadoc
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 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     @Override
233     void decode(final byte[] data, int offset, final int length, final Context context) {
234         if (context.eof || length < 0) {
235             context.eof = true;
236             if (context.ibitWorkArea != 0) {
237                 validateTrailingCharacter();
238             }
239             return;
240         }
241         final int dataLen = Math.min(data.length - offset, length);
242         final int availableChars = (context.ibitWorkArea != 0 ? 1 : 0) + dataLen;
243         // small optimization to short-cut the rest of this method when it is fed byte-by-byte
244         if (availableChars == 1 && availableChars == dataLen) {
245             // store 1/2 byte for next invocation of decode, we offset by +1 as empty-value is 0
246             context.ibitWorkArea = decodeOctet(data[offset]) + 1;
247             return;
248         }
249         // we must have an even number of chars to decode
250         final int charsToProcess = availableChars % BYTES_PER_ENCODED_BLOCK == 0 ? availableChars : availableChars - 1;
251         final int end = offset + dataLen;
252         final byte[] buffer = ensureBufferSize(charsToProcess / BYTES_PER_ENCODED_BLOCK, context);
253         int result;
254         if (dataLen < availableChars) {
255             // we have 1/2 byte from previous invocation to decode
256             result = context.ibitWorkArea - 1 << BITS_PER_ENCODED_BYTE;
257             result |= decodeOctet(data[offset++]);
258             buffer[context.pos++] = (byte) result;
259             // reset to empty-value for next invocation!
260             context.ibitWorkArea = 0;
261         }
262         final int loopEnd = end - 1;
263         while (offset < loopEnd) {
264             result = decodeOctet(data[offset++]) << BITS_PER_ENCODED_BYTE;
265             result |= decodeOctet(data[offset++]);
266             buffer[context.pos++] = (byte) result;
267         }
268         // we have one char of a hex-pair left over
269         if (offset < end) {
270             // store 1/2 byte for next invocation of decode, we offset by +1 as empty-value is 0
271             context.ibitWorkArea = decodeOctet(data[offset]) + 1;
272         }
273     }
274 
275     private int decodeOctet(final byte octet) {
276         int decoded = -1;
277         final int b = octet & 0xff;
278         if (b < decodeTable.length) {
279             decoded = decodeTable[b];
280         }
281         if (decoded == -1) {
282             throw new IllegalArgumentException("Invalid octet in encoded value: " + (int) octet);
283         }
284         return decoded;
285     }
286 
287     @Override
288     void encode(final byte[] data, final int offset, final int length, final Context context) {
289         if (context.eof) {
290             return;
291         }
292         if (length < 0) {
293             context.eof = true;
294             return;
295         }
296         final int size = length * BYTES_PER_ENCODED_BLOCK;
297         if (size < 0) {
298             throw new IllegalArgumentException("Input length exceeds maximum size for encoded data: " + length);
299         }
300         final byte[] buffer = ensureBufferSize(size, context);
301         final int end = offset + length;
302         for (int i = offset; i < end; i++) {
303             final int value = data[i];
304             final int high = value >> BITS_PER_ENCODED_BYTE & MASK_4_BITS;
305             final int low = value & MASK_4_BITS;
306             buffer[context.pos++] = encodeTable[high];
307             buffer[context.pos++] = encodeTable[low];
308         }
309     }
310 
311     /**
312      * Returns whether or not the {@code octet} is in the Base16 alphabet.
313      *
314      * @param octet The value to test.
315      * @return {@code true} if the value is defined in the Base16 alphabet {@code false} otherwise.
316      */
317     @Override
318     public boolean isInAlphabet(final byte octet) {
319         final int b = octet & 0xff;
320         return b < decodeTable.length && decodeTable[b] != -1;
321     }
322 
323     /**
324      * Validates whether decoding allows an entire final trailing character that cannot be used for a complete byte.
325      *
326      * @throws IllegalArgumentException if strict decoding is enabled.
327      */
328     private void validateTrailingCharacter() {
329         if (isStrictDecoding()) {
330             throw new IllegalArgumentException("Strict decoding: Last encoded character is a valid Base 16 alphabet character but not a possible encoding. " +
331                     "Decoding requires at least two characters to create one byte.");
332         }
333     }
334 }