1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.apache.commons.codec.binary;
19
20 import java.util.Objects;
21
22 import org.apache.commons.codec.CodecPolicy;
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43 public class Base16 extends BaseNCodec {
44
45
46
47
48 private static final int BITS_PER_ENCODED_BYTE = 4;
49 private static final int BYTES_PER_ENCODED_BLOCK = 2;
50 private static final int BYTES_PER_UNENCODED_BLOCK = 1;
51
52
53
54
55
56
57 private static final byte[] UPPER_CASE_DECODE_TABLE = {
58
59 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
60 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
61 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
62 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1,
63 -1, 10, 11, 12, 13, 14, 15
64 };
65
66
67
68
69
70
71 private static final byte[] UPPER_CASE_ENCODE_TABLE = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
72
73
74
75
76
77
78 private static final byte[] LOWER_CASE_DECODE_TABLE = {
79
80 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
81 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
82 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
83 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1,
84 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
85 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
86 -1, 10, 11, 12, 13, 14, 15
87 };
88
89
90
91
92
93 private static final byte[] LOWER_CASE_ENCODE_TABLE = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
94
95
96 private static final int MASK_4BITS = 0x0f;
97
98
99
100
101 private final byte[] decodeTable;
102
103
104
105
106 private final byte[] encodeTable;
107
108
109
110
111 public Base16() {
112 this(false);
113 }
114
115
116
117
118
119
120 public Base16(final boolean lowerCase) {
121 this(lowerCase, DECODING_POLICY_DEFAULT);
122 }
123
124
125
126
127
128
129
130
131 private Base16(final boolean lowerCase, final byte[] encodeTable, final CodecPolicy decodingPolicy) {
132 super(BYTES_PER_UNENCODED_BLOCK, BYTES_PER_ENCODED_BLOCK, 0, 0, PAD_DEFAULT, decodingPolicy);
133 Objects.requireNonNull(encodeTable, "encodeTable");
134 this.encodeTable = encodeTable;
135 this.decodeTable = encodeTable == LOWER_CASE_ENCODE_TABLE ? LOWER_CASE_DECODE_TABLE : UPPER_CASE_DECODE_TABLE;
136 }
137
138
139
140
141
142
143
144 public Base16(final boolean lowerCase, final CodecPolicy decodingPolicy) {
145 this(lowerCase, lowerCase ? LOWER_CASE_ENCODE_TABLE : UPPER_CASE_ENCODE_TABLE, decodingPolicy);
146 }
147
148 @Override
149 void decode(final byte[] data, int offset, final int length, final Context context) {
150 if (context.eof || length < 0) {
151 context.eof = true;
152 if (context.ibitWorkArea != 0) {
153 validateTrailingCharacter();
154 }
155 return;
156 }
157 final int dataLen = Math.min(data.length - offset, length);
158 final int availableChars = (context.ibitWorkArea != 0 ? 1 : 0) + dataLen;
159
160 if (availableChars == 1 && availableChars == dataLen) {
161
162 context.ibitWorkArea = decodeOctet(data[offset]) + 1;
163 return;
164 }
165
166 final int charsToProcess = availableChars % BYTES_PER_ENCODED_BLOCK == 0 ? availableChars : availableChars - 1;
167 final int end = offset + dataLen;
168 final byte[] buffer = ensureBufferSize(charsToProcess / BYTES_PER_ENCODED_BLOCK, context);
169 int result;
170 if (dataLen < availableChars) {
171
172 result = context.ibitWorkArea - 1 << BITS_PER_ENCODED_BYTE;
173 result |= decodeOctet(data[offset++]);
174 buffer[context.pos++] = (byte) result;
175
176 context.ibitWorkArea = 0;
177 }
178 final int loopEnd = end - 1;
179 while (offset < loopEnd) {
180 result = decodeOctet(data[offset++]) << BITS_PER_ENCODED_BYTE;
181 result |= decodeOctet(data[offset++]);
182 buffer[context.pos++] = (byte) result;
183 }
184
185 if (offset < end) {
186
187 context.ibitWorkArea = decodeOctet(data[offset]) + 1;
188 }
189 }
190
191 private int decodeOctet(final byte octet) {
192 int decoded = -1;
193 if ((octet & 0xff) < decodeTable.length) {
194 decoded = decodeTable[octet];
195 }
196 if (decoded == -1) {
197 throw new IllegalArgumentException("Invalid octet in encoded value: " + (int) octet);
198 }
199 return decoded;
200 }
201
202 @Override
203 void encode(final byte[] data, final int offset, final int length, final Context context) {
204 if (context.eof) {
205 return;
206 }
207 if (length < 0) {
208 context.eof = true;
209 return;
210 }
211 final int size = length * BYTES_PER_ENCODED_BLOCK;
212 if (size < 0) {
213 throw new IllegalArgumentException("Input length exceeds maximum size for encoded data: " + length);
214 }
215 final byte[] buffer = ensureBufferSize(size, context);
216 final int end = offset + length;
217 for (int i = offset; i < end; i++) {
218 final int value = data[i];
219 final int high = value >> BITS_PER_ENCODED_BYTE & MASK_4BITS;
220 final int low = value & MASK_4BITS;
221 buffer[context.pos++] = encodeTable[high];
222 buffer[context.pos++] = encodeTable[low];
223 }
224 }
225
226
227
228
229
230
231
232
233 @Override
234 public boolean isInAlphabet(final byte octet) {
235 return (octet & 0xff) < decodeTable.length && decodeTable[octet] != -1;
236 }
237
238
239
240
241
242
243 private void validateTrailingCharacter() {
244 if (isStrictDecoding()) {
245 throw new IllegalArgumentException("Strict decoding: Last encoded character is a valid base 16 alphabet character but not a possible encoding. " +
246 "Decoding requires at least two characters to create one byte.");
247 }
248 }
249 }