001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * https://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 018package org.apache.commons.codec.binary; 019 020import java.util.Arrays; 021 022import org.apache.commons.codec.CodecPolicy; 023 024/** 025 * 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>. 026 * 027 * <p> 028 * This class is thread-safe. 029 * </p> 030 * <p> 031 * This implementation strictly follows RFC 4648, and as such unlike the {@link Base32} and {@link Base64} implementations, it does not ignore invalid alphabet 032 * characters or whitespace, neither does it offer chunking or padding characters. 033 * </p> 034 * <p> 035 * 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 036 * alphabet, and configuring a custom 16-byte alphabet with {@link Builder#setEncodeTable(byte...)}. 037 * </p> 038 * 039 * @see Base16InputStream 040 * @see Base16OutputStream 041 * @see <a href="https://tools.ietf.org/html/rfc4648#section-8">RFC 4648 - 8. Base 16 Encoding</a> 042 * @since 1.15 043 */ 044public class Base16 extends BaseNCodec { 045 046 /** 047 * Builds {@link Base16} instances. 048 * 049 * <p> 050 * To configure a new instance, use a {@link Builder}. For example: 051 * </p> 052 * 053 * <pre> 054 * Base16 Base16 = Base16.builder() 055 * .setDecodingPolicy(DecodingPolicy.LENIENT) // default is lenient 056 * .get() 057 * </pre> 058 * 059 * @since 1.20.0 060 */ 061 public static class Builder extends AbstractBuilder<Base16, Builder> { 062 063 /** 064 * Constructs a new instance. 065 */ 066 public Builder() { 067 super(null); 068 setDecodeTable(UPPER_CASE_DECODE_TABLE); 069 setEncodeTable(UPPER_CASE_ENCODE_TABLE); 070 setEncodedBlockSize(BYTES_PER_ENCODED_BLOCK); 071 setUnencodedBlockSize(BYTES_PER_UNENCODED_BLOCK); 072 setLineLength(0); 073 setLineSeparator(EMPTY_BYTE_ARRAY); 074 } 075 076 @Override 077 public Base16 get() { 078 return new Base16(this); 079 } 080 081 /** 082 * Sets the Base16 encode table and derives the matching decode table. 083 * 084 * @param encodeTable 16 unique bytes, null resets to the default upper-case table. 085 * @return {@code this} instance. 086 * @throws IllegalArgumentException Thrown if {@code encodeTable} does not contain 16 unique bytes. 087 */ 088 @Override 089 public Builder setEncodeTable(final byte... encodeTable) { 090 final byte[] table = encodeTable != null ? encodeTable : UPPER_CASE_ENCODE_TABLE; 091 super.setDecodeTableRaw(toDecodeTable(table)); 092 return super.setEncodeTable(table); 093 } 094 095 /** 096 * Sets whether to use the lower-case Base16 alphabet. 097 * 098 * @param lowerCase {@code true} to use the lower-case Base16 alphabet. 099 * @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}