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 * http://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 com.jsql.util.bruter;
19
20 import org.apache.commons.codec.binary.Base32;
21
22 import java.util.Base64;
23
24 /**
25 * Provides Base16 encoding and decoding.
26 *
27 * <p>
28 * This class is thread-safe.
29 * </p>
30 * <p>
31 * This implementation strictly follows RFC 4648, and as such unlike
32 * the {@link Base32} and {@link Base64} implementations,
33 * it does not ignore invalid alphabet characters or whitespace,
34 * neither does it offer chunking or padding characters.
35 * </p>
36 * <p>
37 * The only additional feature above those specified in RFC 4648
38 * is support for working with a lower-case alphabet in addition
39 * to the default upper-case alphabet.
40 * </p>
41 *
42 * @see <a href="https://tools.ietf.org/html/rfc4648#section-8">RFC 4648 - 8. Base 16 Encoding</a>
43 *
44 * @since 1.15
45 */
46 public class Base16 extends BaseNCodec {
47
48 /**
49 * BASE16 characters are 4 bits in length.
50 * They are formed by taking an 8-bit group,
51 * which is converted into two BASE16 characters.
52 */
53 private static final int BITS_PER_ENCODED_BYTE = 4;
54 private static final int BYTES_PER_ENCODED_BLOCK = 2;
55 private static final int BYTES_PER_UNENCODED_BLOCK = 1;
56
57 /**
58 * This array is a lookup table that translates Unicode characters drawn from the "Base16 Alphabet" (as specified
59 * in Table 5 of RFC 4648) into their 4-bit positive integer equivalents. Characters that are not in the Base16
60 * alphabet but fall within the bounds of the array are translated to -1.
61 */
62 private static final byte[] UPPER_CASE_DECODE_TABLE = {
63 // 0 1 2 3 4 5 6 7 8 9 A B C D E F
64 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 00-0f
65 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 10-1f
66 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 20-2f
67 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, // 30-3f 0-9
68 -1, 10, 11, 12, 13, 14, 15 // 40-46 A-F
69 };
70
71 /**
72 * This array is a lookup table that translates 4-bit positive integer index values into their "Base16 Alphabet"
73 * equivalents as specified in Table 5 of RFC 4648.
74 */
75 private static final byte[] UPPER_CASE_ENCODE_TABLE = {
76 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
77 'A', 'B', 'C', 'D', 'E', 'F'
78 };
79
80 /**
81 * This array is a lookup table that translates Unicode characters drawn from a lower-case "Base16 Alphabet"
82 * into their 4-bit positive integer equivalents. Characters that are not in the Base16
83 * alphabet but fall within the bounds of the array are translated to -1.
84 */
85 private static final byte[] LOWER_CASE_DECODE_TABLE = {
86 // 0 1 2 3 4 5 6 7 8 9 A B C D E F
87 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 00-0f
88 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 10-1f
89 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 20-2f
90 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, // 30-3f 0-9
91 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 40-4f
92 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 50-5f
93 -1, 10, 11, 12, 13, 14, 15 // 60-66 a-f
94 };
95
96 /**
97 * This array is a lookup table that translates 4-bit positive integer index values into their "Base16 Alphabet"
98 * lower-case equivalents.
99 */
100 private static final byte[] LOWER_CASE_ENCODE_TABLE = {
101 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
102 'a', 'b', 'c', 'd', 'e', 'f'
103 };
104
105 /** Mask used to extract 4 bits, used when decoding character. */
106 private static final int MASK_4BITS = 0x0f;
107
108 /**
109 * Decode table to use.
110 */
111 private final byte[] decodeTable;
112
113 /**
114 * Encode table to use.
115 */
116 private final byte[] encodeTable;
117
118 /**
119 * Creates a Base16 codec used for decoding and encoding.
120 */
121 public Base16() {
122 this(false);
123 }
124
125 /**
126 * Creates a Base16 codec used for decoding and encoding.
127 *
128 * @param lowerCase if {@code true} then use a lower-case Base16 alphabet.
129 */
130 public Base16(final boolean lowerCase) {
131 this(lowerCase, BaseNCodec.DECODING_POLICY_DEFAULT);
132 }
133
134 /**
135 * Creates a Base16 codec used for decoding and encoding.
136 *
137 * @param lowerCase if {@code true} then use a lower-case Base16 alphabet.
138 * @param decodingPolicy Decoding policy.
139 */
140 public Base16(final boolean lowerCase, final CodecPolicy decodingPolicy) {
141 super(Base16.BYTES_PER_UNENCODED_BLOCK, Base16.BYTES_PER_ENCODED_BLOCK, 0, 0, BaseNCodec.PAD_DEFAULT, decodingPolicy);
142 if (lowerCase) {
143 this.encodeTable = Base16.LOWER_CASE_ENCODE_TABLE;
144 this.decodeTable = Base16.LOWER_CASE_DECODE_TABLE;
145 } else {
146 this.encodeTable = Base16.UPPER_CASE_ENCODE_TABLE;
147 this.decodeTable = Base16.UPPER_CASE_DECODE_TABLE;
148 }
149 }
150
151 @Override
152 public void decode(final byte[] data, int offsetInput, final int length, final Context context) {
153 int offset = offsetInput;
154
155 if (context.eof || length < 0) {
156 context.eof = true;
157 if (context.ibitWorkArea != 0) {
158 this.validateTrailingCharacter();
159 }
160 return;
161 }
162
163 final int dataLen = Math.min(data.length - offset, length);
164 final int availableChars = (context.ibitWorkArea != 0 ? 1 : 0) + dataLen;
165
166 // small optimisation to short-cut the rest of this method when it is fed byte-by-byte
167 if (availableChars == 1 && availableChars == dataLen) {
168 context.ibitWorkArea = this.decodeOctet(data[offset]) + 1; // store 1/2 byte for next invocation of decode, we offset by +1 as empty-value is 0
169 return;
170 }
171
172 // we must have an even number of chars to decode
173 final int charsToProcess = availableChars % Base16.BYTES_PER_ENCODED_BLOCK == 0 ? availableChars : availableChars - 1;
174 final byte[] buffer = this.ensureBufferSize(charsToProcess / Base16.BYTES_PER_ENCODED_BLOCK, context);
175
176 int result;
177 var i = 0;
178 if (dataLen < availableChars) {
179 // we have 1/2 byte from previous invocation to decode
180 result = (context.ibitWorkArea - 1) << Base16.BITS_PER_ENCODED_BYTE;
181 result |= this.decodeOctet(data[offset++]);
182 i = 2;
183 buffer[context.pos++] = (byte) result;
184 // reset to empty-value for next invocation!
185 context.ibitWorkArea = 0;
186 }
187
188 while (i < charsToProcess) {
189 result = this.decodeOctet(data[offset++]) << Base16.BITS_PER_ENCODED_BYTE;
190 result |= this.decodeOctet(data[offset++]);
191 i += 2;
192 buffer[context.pos++] = (byte) result;
193 }
194
195 // we have one char of a hex-pair left over
196 if (i < dataLen) {
197 context.ibitWorkArea = this.decodeOctet(data[i]) + 1; // store 1/2 byte for next invocation of decode, we offset by +1 as empty-value is 0
198 }
199 }
200
201 private int decodeOctet(final byte octet) {
202 int decoded = -1;
203 if ((octet & 0xff) < this.decodeTable.length) {
204 decoded = this.decodeTable[octet];
205 }
206 if (decoded == -1) {
207 throw new IllegalArgumentException("Invalid octet in encoded value: " + (int) octet);
208 }
209 return decoded;
210 }
211
212 @Override
213 public void encode(final byte[] data, final int offset, final int length, final Context context) {
214 if (context.eof) {
215 return;
216 }
217 if (length < 0) {
218 context.eof = true;
219 return;
220 }
221 final int size = length * Base16.BYTES_PER_ENCODED_BLOCK;
222 if (size < 0) {
223 throw new IllegalArgumentException("Input length exceeds maximum size for encoded data: " + length);
224 }
225 final byte[] buffer = this.ensureBufferSize(size, context);
226 final int end = offset + length;
227 for (int i = offset; i < end; i++) {
228 final int value = data[i];
229 final int high = (value >> Base16.BITS_PER_ENCODED_BYTE) & Base16.MASK_4BITS;
230 final int low = value & Base16.MASK_4BITS;
231 buffer[context.pos++] = this.encodeTable[high];
232 buffer[context.pos++] = this.encodeTable[low];
233 }
234 }
235
236 /**
237 * Returns whether the {@code octet} is in the Base16 alphabet.
238 *
239 * @param octet The value to test.
240 *
241 * @return {@code true} if the value is defined in the Base16 alphabet {@code false} otherwise.
242 */
243 @Override
244 public boolean isInAlphabet(final byte octet) {
245 return (octet & 0xff) < this.decodeTable.length && this.decodeTable[octet] != -1;
246 }
247
248 /**
249 * Validates whether decoding allows an entire final trailing character that cannot be
250 * used for a complete byte.
251 *
252 * @throws IllegalArgumentException if strict decoding is enabled
253 */
254 private void validateTrailingCharacter() {
255 if (this.isStrictDecoding()) {
256 throw new IllegalArgumentException(
257 "Strict decoding: Last encoded character is a valid base 16 alphabet" +
258 "character but not a possible encoding. " +
259 "Decoding requires at least two characters to create one byte."
260 );
261 }
262 }
263 }
264