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 static org.junit.jupiter.api.Assertions.assertArrayEquals;
21 import static org.junit.jupiter.api.Assertions.assertEquals;
22 import static org.junit.jupiter.api.Assertions.assertFalse;
23 import static org.junit.jupiter.api.Assertions.assertNotNull;
24 import static org.junit.jupiter.api.Assertions.assertNull;
25 import static org.junit.jupiter.api.Assertions.assertThrows;
26 import static org.junit.jupiter.api.Assertions.assertTrue;
27
28 import java.nio.charset.Charset;
29 import java.nio.charset.StandardCharsets;
30 import java.util.Arrays;
31 import java.util.stream.IntStream;
32
33 import org.apache.commons.codec.CodecPolicy;
34 import org.apache.commons.codec.DecoderException;
35 import org.apache.commons.lang3.ArrayUtils;
36 import org.junit.jupiter.api.Test;
37 import org.junit.jupiter.params.ParameterizedTest;
38 import org.junit.jupiter.params.provider.MethodSource;
39
40
41
42
43 class Base32Test {
44
45 private static final Charset CHARSET_UTF8 = StandardCharsets.UTF_8;
46
47
48
49 private static final String [][] BASE32_TEST_CASES = {
50 { "" , "" },
51 { "f" , "MY======" },
52 { "fo" , "MZXQ====" },
53 { "foo" , "MZXW6===" },
54 { "foob" , "MZXW6YQ=" },
55 { "fooba" , "MZXW6YTB" },
56 { "foobar" , "MZXW6YTBOI======" }
57 };
58
59
60
61
62
63
64
65 static final String[] BASE32_IMPOSSIBLE_CASES = {
66 "MC======",
67 "MZXE====",
68 "MZXWB===",
69 "MZXW6YB=",
70 "MZXW6YTBOC======",
71 "AB======"
72 };
73
74
75
76 private static final String[] BASE32_IMPOSSIBLE_CASES_CHUNKED = {
77 "M2======\r\n",
78 "MZX0====\r\n",
79 "MZXW0===\r\n",
80 "MZXW6Y2=\r\n",
81 "MZXW6YTBO2======\r\n"
82 };
83
84
85
86 private static final String[] BASE32HEX_IMPOSSIBLE_CASES = {
87 "C2======",
88 "CPN4====",
89 "CPNM1===",
90 "CPNMUO1=",
91 "CPNMUOJ1E2======"
92 };
93
94
95
96
97
98
99
100 private static final byte[] ENCODE_TABLE = {
101 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
102 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
103 '2', '3', '4', '5', '6', '7'
104 };
105
106
107 private static final Object[][] BASE32_BINARY_TEST_CASES;
108
109
110
111
112 static {
113 final Hex hex = new Hex();
114 try {
115 BASE32_BINARY_TEST_CASES = new Object[][] {
116 new Object[] { hex.decode("623a01735836e9a126e12fbf95e013ee6892997c"),
117 "MI5AC42YG3U2CJXBF67ZLYAT5ZUJFGL4" },
118 new Object[] { hex.decode("623a01735836e9a126e12fbf95e013ee6892997c"),
119 "mi5ac42yg3u2cjxbf67zlyat5zujfgl4" },
120 new Object[] { hex.decode("739ce42108"),
121 "OOOOIIII" }
122 };
123 } catch (final DecoderException de) {
124 throw new AssertionError(":(", de);
125 }
126 }
127
128
129 private static final String [][] BASE32HEX_TEST_CASES = {
130 { "" , "" },
131 { "f" , "CO======" },
132 { "fo" , "CPNG====" },
133 { "foo" , "CPNMU===" },
134 { "foob" , "CPNMUOG=" },
135 { "fooba" , "CPNMUOJ1" },
136 { "foobar" , "CPNMUOJ1E8======" }
137 };
138
139
140
141 private static final String [][] BASE32_TEST_CASES_CHUNKED = {
142 { "" , "" },
143 { "f" , "MY======\r\n" },
144 { "fo" , "MZXQ====\r\n" },
145 { "foo" , "MZXW6===\r\n" },
146 { "foob" , "MZXW6YQ=\r\n" },
147 { "fooba" , "MZXW6YTB\r\n" },
148 { "foobar" , "MZXW6YTBOI======\r\n" }
149 };
150
151
152
153 private static final String [][] BASE32_PAD_TEST_CASES = {
154 { "" , "" },
155 { "f" , "MY%%%%%%" },
156 { "fo" , "MZXQ%%%%" },
157 { "foo" , "MZXW6%%%" },
158 { "foob" , "MZXW6YQ%" },
159 { "fooba" , "MZXW6YTB" },
160 { "foobar" , "MZXW6YTBOI%%%%%%" }
161 };
162
163
164
165
166
167
168
169
170
171
172 private static void assertBase32DecodingOfTrailingBits(final int nbits) {
173
174 final Base32 codec = new Base32(0, null, false, BaseNCodec.PAD_DEFAULT, CodecPolicy.STRICT);
175 assertTrue(codec.isStrictDecoding());
176 assertEquals(CodecPolicy.STRICT, codec.getCodecPolicy());
177
178 final Base32 defaultCodec = new Base32();
179 assertFalse(defaultCodec.isStrictDecoding());
180 assertEquals(CodecPolicy.LENIENT, defaultCodec.getCodecPolicy());
181
182
183
184 final int length = nbits / 5;
185 final byte[] encoded = new byte[8];
186 Arrays.fill(encoded, 0, length, ENCODE_TABLE[0]);
187 Arrays.fill(encoded, length, encoded.length, (byte) '=');
188
189 final int discard = nbits % 8;
190 final int emptyBitsMask = (1 << discard) - 1;
191
192 final boolean invalid = length == 1 || length == 3 || length == 6;
193
194 final int last = length - 1;
195 for (int i = 0; i < 32; i++) {
196 encoded[last] = ENCODE_TABLE[i];
197
198
199 if (invalid || (i & emptyBitsMask) != 0) {
200 assertThrows(IllegalArgumentException.class, () -> codec.decode(encoded), "Final base-32 digit should not be allowed");
201
202 final byte[] decoded = defaultCodec.decode(encoded);
203
204 assertFalse(Arrays.equals(encoded, defaultCodec.encode(decoded)));
205 } else {
206
207 final byte[] decoded = codec.decode(encoded);
208
209 final int bitsEncoded = i >> discard;
210 assertEquals(bitsEncoded, decoded[decoded.length - 1], "Invalid decoding of last character");
211
212 assertArrayEquals(encoded, codec.encode(decoded));
213 }
214 }
215 }
216
217 static IntStream rangeProvider() {
218 return IntStream.range(0, 21);
219 }
220
221 @Test
222 void testBase32AtBufferEnd() {
223 testBase32InBuffer(100, 0);
224 }
225
226 @Test
227 void testBase32AtBufferMiddle() {
228 testBase32InBuffer(100, 100);
229 }
230
231 @Test
232 void testBase32AtBufferStart() {
233 testBase32InBuffer(0, 100);
234 }
235
236 @Test
237 void testBase32BinarySamples() throws Exception {
238 final Base32 codec = new Base32();
239 for (final Object[] element : BASE32_BINARY_TEST_CASES) {
240 final String expected;
241 if (element.length > 2) {
242 expected = (String) element[2];
243 } else {
244 expected = (String) element[1];
245 }
246 assertEquals(expected.toUpperCase(), codec.encodeAsString((byte[]) element[0]));
247 }
248 }
249
250 @Test
251 void testBase32BinarySamplesReverse() throws Exception {
252 final Base32 codec = new Base32();
253 for (final Object[] element : BASE32_BINARY_TEST_CASES) {
254 assertArrayEquals((byte[]) element[0], codec.decode((String) element[1]));
255 }
256 }
257
258 @Test
259 void testBase32Chunked() throws Exception {
260 final Base32 codec = new Base32(20);
261 for (final String[] element : BASE32_TEST_CASES_CHUNKED) {
262 assertEquals(element[1], codec.encodeAsString(element[0].getBytes(CHARSET_UTF8)));
263 }
264 }
265
266 @Test
267 void testBase32DecodingOfTrailing10Bits() {
268 assertBase32DecodingOfTrailingBits(10);
269 }
270
271 @Test
272 void testBase32DecodingOfTrailing15Bits() {
273 assertBase32DecodingOfTrailingBits(15);
274 }
275
276 @Test
277 void testBase32DecodingOfTrailing20Bits() {
278 assertBase32DecodingOfTrailingBits(20);
279 }
280
281 @Test
282 void testBase32DecodingOfTrailing25Bits() {
283 assertBase32DecodingOfTrailingBits(25);
284 }
285
286 @Test
287 void testBase32DecodingOfTrailing30Bits() {
288 assertBase32DecodingOfTrailingBits(30);
289 }
290
291 @Test
292 void testBase32DecodingOfTrailing35Bits() {
293 assertBase32DecodingOfTrailingBits(35);
294 }
295
296 @Test
297 void testBase32DecodingOfTrailing5Bits() {
298 assertBase32DecodingOfTrailingBits(5);
299 }
300
301 @Test
302 void testBase32HexImpossibleSamples() {
303 testImpossibleCases(new Base32(0, null, true, BaseNCodec.PAD_DEFAULT, CodecPolicy.STRICT), BASE32HEX_IMPOSSIBLE_CASES);
304
305 testImpossibleCases(Base32.builder()
306 .setHexEncodeTable(true)
307 .setDecodingPolicy(CodecPolicy.STRICT)
308 .get(), BASE32HEX_IMPOSSIBLE_CASES);
309
310
311
312 testImpossibleCases(Base32.builder()
313 .setHexDecodeTable(false)
314 .setHexDecodeTable(true)
315 .setHexEncodeTable(false)
316 .setHexEncodeTable(true)
317 .setDecodingPolicy(CodecPolicy.STRICT)
318 .get(), BASE32HEX_IMPOSSIBLE_CASES);
319
320 }
321
322 @Test
323 void testBase32HexSamples() throws Exception {
324 final Base32 codec = new Base32(true);
325 for (final String[] element : BASE32HEX_TEST_CASES) {
326 assertEquals(element[1], codec.encodeAsString(element[0].getBytes(CHARSET_UTF8)));
327 }
328 }
329
330 @Test
331 void testBase32HexSamplesReverse() throws Exception {
332 final Base32 codec = new Base32(true);
333 for (final String[] element : BASE32HEX_TEST_CASES) {
334 assertEquals(element[0], new String(codec.decode(element[1]), CHARSET_UTF8));
335 }
336 }
337
338 @Test
339 void testBase32HexSamplesReverseLowercase() throws Exception {
340 final Base32 codec = new Base32(true);
341 for (final String[] element : BASE32HEX_TEST_CASES) {
342 assertEquals(element[0], new String(codec.decode(element[1].toLowerCase()), CHARSET_UTF8));
343 }
344 }
345
346 @Test
347 void testBase32ImpossibleChunked() {
348 testImpossibleCases(new Base32(20, BaseNCodec.CHUNK_SEPARATOR, false, BaseNCodec.PAD_DEFAULT, CodecPolicy.STRICT), BASE32_IMPOSSIBLE_CASES_CHUNKED);
349 }
350
351 @Test
352 void testBase32ImpossibleSamples() {
353 testImpossibleCases(new Base32(0, null, false, BaseNCodec.PAD_DEFAULT, CodecPolicy.STRICT), BASE32_IMPOSSIBLE_CASES);
354 }
355
356 private void testBase32InBuffer(final int startPasSize, final int endPadSize) {
357 final Base32 codec = new Base32();
358 for (final String[] element : BASE32_TEST_CASES) {
359 final byte[] bytes = element[0].getBytes(CHARSET_UTF8);
360 byte[] buffer = ArrayUtils.addAll(bytes, new byte[endPadSize]);
361 buffer = ArrayUtils.addAll(new byte[startPasSize], buffer);
362 assertEquals(element[1], StringUtils.newStringUtf8(codec.encode(buffer, startPasSize, bytes.length)));
363 }
364 }
365
366 @Test
367 void testBase32Samples() throws Exception {
368 final Base32 codec = new Base32();
369 for (final String[] element : BASE32_TEST_CASES) {
370 assertEquals(element[1], codec.encodeAsString(element[0].getBytes(CHARSET_UTF8)));
371 }
372 }
373
374 @Test
375 void testBase32SamplesNonDefaultPadding() throws Exception {
376 final Base32 codec = new Base32((byte) 0x25);
377
378 for (final String[] element : BASE32_PAD_TEST_CASES) {
379 assertEquals(element[1], codec.encodeAsString(element[0].getBytes(CHARSET_UTF8)));
380 }
381 }
382
383 @Test
384 void testBuilderCodecPolicy() {
385 assertEquals(CodecPolicy.LENIENT, Base32.builder().get().getCodecPolicy());
386 assertEquals(CodecPolicy.LENIENT, Base32.builder().setDecodingPolicy(CodecPolicy.LENIENT).get().getCodecPolicy());
387 assertEquals(CodecPolicy.STRICT, Base32.builder().setDecodingPolicy(CodecPolicy.STRICT).get().getCodecPolicy());
388 assertEquals(CodecPolicy.LENIENT, Base32.builder().setDecodingPolicy(CodecPolicy.STRICT).setDecodingPolicy(null).get().getCodecPolicy());
389 assertEquals(CodecPolicy.LENIENT, Base32.builder().setDecodingPolicy(null).get().getCodecPolicy());
390 }
391
392 @Test
393 void testBuilderLineAttributes() {
394 assertNull(Base32.builder().get().getLineSeparator());
395 assertNull(Base32.builder().setLineSeparator(BaseNCodec.CHUNK_SEPARATOR).get().getLineSeparator());
396 assertArrayEquals(BaseNCodec.CHUNK_SEPARATOR, Base32.builder().setLineLength(4).setLineSeparator(BaseNCodec.CHUNK_SEPARATOR).get().getLineSeparator());
397 assertArrayEquals(BaseNCodec.CHUNK_SEPARATOR, Base32.builder().setLineLength(4).setLineSeparator(null).get().getLineSeparator());
398 assertArrayEquals(BaseNCodec.CHUNK_SEPARATOR, Base32.builder().setLineLength(10).setLineSeparator(null).get().getLineSeparator());
399 assertNull(Base32.builder().setLineLength(-1).setLineSeparator(null).get().getLineSeparator());
400 assertNull(Base32.builder().setLineLength(0).setLineSeparator(null).get().getLineSeparator());
401 assertArrayEquals(new byte[] { 1 }, Base32.builder().setLineLength(4).setLineSeparator((byte) 1).get().getLineSeparator());
402 assertEquals("MZXXQ===", Base32.builder().setLineLength(4).get().encodeToString("fox".getBytes(CHARSET_UTF8)));
403 }
404
405 @Test
406 void testBuilderPadingByte() {
407 assertNull(Base32.builder().get().getLineSeparator());
408 assertNull(Base32.builder().setLineSeparator(BaseNCodec.CHUNK_SEPARATOR).get().getLineSeparator());
409 assertArrayEquals(BaseNCodec.CHUNK_SEPARATOR, Base32.builder().setLineLength(4).setLineSeparator(BaseNCodec.CHUNK_SEPARATOR).get().getLineSeparator());
410 assertArrayEquals(BaseNCodec.CHUNK_SEPARATOR, Base32.builder().setLineLength(4).setLineSeparator(null).get().getLineSeparator());
411 assertArrayEquals(BaseNCodec.CHUNK_SEPARATOR, Base32.builder().setLineLength(10).setLineSeparator(null).get().getLineSeparator());
412 assertNull(Base32.builder().setLineLength(-1).setLineSeparator(null).get().getLineSeparator());
413 assertNull(Base32.builder().setLineLength(0).setLineSeparator(null).get().getLineSeparator());
414 assertArrayEquals(new byte[] { 1 }, Base32.builder().setLineLength(4).setLineSeparator((byte) 1).get().getLineSeparator());
415 assertEquals("MZXXQ___", Base32.builder().setLineLength(4).setPadding((byte) '_').get().encodeToString("fox".getBytes(CHARSET_UTF8)));
416 }
417
418 @Test
419 void testCodec200() {
420 final Base32 codec = new Base32(true, (byte) 'W');
421 assertNotNull(codec);
422 }
423
424 @Test
425 void testConstructors() {
426 Base32 base32;
427 base32 = new Base32();
428 base32 = new Base32(-1);
429 base32 = new Base32(-1, new byte[] {});
430 base32 = new Base32(32, new byte[] {});
431 base32 = new Base32(32, new byte[] {}, false);
432
433
434 base32 = new Base32(-1, new byte[] { 'A' });
435 base32 = new Base32(32, new byte[] { '$' });
436 assertArrayEquals(new byte[0], new Base32(32, null).getLineSeparator(), "null line separator use an empty array");
437 assertThrows(IllegalArgumentException.class, () -> new Base32(32, new byte[] { 'A' }), "'A' as a line separator");
438 assertThrows(IllegalArgumentException.class, () -> new Base32(32, new byte[] { '=' }), "'=' as a line separator");
439 assertThrows(IllegalArgumentException.class, () -> new Base32(32, new byte[] { 'A', '$' }), "'A$' as a line separator");
440 assertThrows(IllegalArgumentException.class, () -> new Base32(32, new byte[] { '\n' }, false, (byte) 'A'), "'A' as padding");
441 assertThrows(IllegalArgumentException.class, () -> new Base32(32, new byte[] { '\n' }, false, (byte) ' '), "' ' as padding");
442 base32 = new Base32(32, new byte[] { ' ', '$', '\n', '\r', '\t' });
443 assertNotNull(base32);
444 }
445
446
447
448
449 @Test
450 void testEmptyBase32() {
451 byte[] empty = {};
452 byte[] result = new Base32().encode(empty);
453 assertEquals(0, result.length, "empty Base32 encode");
454 assertNull(new Base32().encode(null), "empty Base32 encode");
455 result = new Base32().encode(empty, 0, 1);
456 assertEquals(0, result.length, "empty Base32 encode with offset");
457 assertNull(new Base32().encode(null), "empty Base32 encode with offset");
458
459 empty = new byte[0];
460 result = new Base32().decode(empty);
461 assertEquals(0, result.length, "empty Base32 decode");
462 assertNull(new Base32().decode((byte[]) null), "empty Base32 encode");
463 }
464
465 private void testImpossibleCases(final Base32 codec, final String[] impossible_cases) {
466 for (final String impossible : impossible_cases) {
467 assertThrows(IllegalArgumentException.class, () -> codec.decode(impossible));
468 }
469 }
470
471 @Test
472 void testIsInAlphabet() {
473
474 Base32 b32 = new Base32(true);
475 assertFalse(b32.isInAlphabet((byte) 0));
476 assertFalse(b32.isInAlphabet((byte) 1));
477 assertFalse(b32.isInAlphabet((byte) -1));
478 assertFalse(b32.isInAlphabet((byte) -15));
479 assertFalse(b32.isInAlphabet((byte) -32));
480 assertFalse(b32.isInAlphabet((byte) 127));
481 assertFalse(b32.isInAlphabet((byte) 128));
482 assertFalse(b32.isInAlphabet((byte) 255));
483
484
485 b32 = new Base32(false);
486 for (char c = '2'; c <= '7'; c++) {
487 assertTrue(b32.isInAlphabet((byte) c));
488 }
489 for (char c = 'A'; c <= 'Z'; c++) {
490 assertTrue(b32.isInAlphabet((byte) c));
491 }
492 for (char c = 'a'; c <= 'z'; c++) {
493 assertTrue(b32.isInAlphabet((byte) c));
494 }
495 assertFalse(b32.isInAlphabet((byte) '1'));
496 assertFalse(b32.isInAlphabet((byte) '8'));
497 assertFalse(b32.isInAlphabet((byte) ('A' - 1)));
498 assertFalse(b32.isInAlphabet((byte) ('Z' + 1)));
499
500
501 b32 = new Base32(true);
502 for (char c = '0'; c <= '9'; c++) {
503 assertTrue(b32.isInAlphabet((byte) c), String.valueOf(c));
504 }
505 for (char c = 'A'; c <= 'V'; c++) {
506 assertTrue(b32.isInAlphabet((byte) c));
507 }
508 for (char c = 'a'; c <= 'v'; c++) {
509 assertTrue(b32.isInAlphabet((byte) c));
510 }
511 assertFalse(b32.isInAlphabet((byte) ('0' - 1)));
512 assertFalse(b32.isInAlphabet((byte) ('9' + 1)));
513 assertFalse(b32.isInAlphabet((byte) ('A' - 1)));
514 assertFalse(b32.isInAlphabet((byte) ('V' + 1)));
515 assertFalse(b32.isInAlphabet((byte) ('a' - 1)));
516 assertFalse(b32.isInAlphabet((byte) ('v' + 1)));
517 }
518
519 @ParameterizedTest
520 @MethodSource("rangeProvider")
521 void testRandomBytes(final int i) {
522 final Base32 codec = new Base32();
523 final byte[][] b = BaseNTestData.randomData(codec, i);
524 assertEquals(b[1].length, codec.getEncodedLength(b[0]), i + " " + codec.lineLength);
525
526 }
527
528 @ParameterizedTest
529 @MethodSource("rangeProvider")
530 void testRandomBytesChunked(final int i) {
531 final Base32 codec = new Base32(10);
532 final byte[][] b = BaseNTestData.randomData(codec, i);
533 assertEquals(b[1].length, codec.getEncodedLength(b[0]), i + " " + codec.lineLength);
534
535 }
536
537 @ParameterizedTest
538 @MethodSource("rangeProvider")
539 void testRandomBytesHex(final int i) {
540 final Base32 codec = new Base32(true);
541 final byte[][] b = BaseNTestData.randomData(codec, i);
542 assertEquals(b[1].length, codec.getEncodedLength(b[0]), i + " " + codec.lineLength);
543
544 }
545
546 @ParameterizedTest
547 @MethodSource("rangeProvider")
548 void testSingleCharEncoding(final int i) {
549 Base32 codec = new Base32();
550 final BaseNCodec.Context context = new BaseNCodec.Context();
551 final byte[] unencoded = new byte[i];
552 final byte[] allInOne = codec.encode(unencoded);
553 codec = new Base32();
554 for (int j = 0; j < unencoded.length; j++) {
555 codec.encode(unencoded, j, 1, context);
556 }
557 codec.encode(unencoded, 0, -1, context);
558 final byte[] singly = new byte[allInOne.length];
559 codec.readResults(singly, 0, 100, context);
560 assertArrayEquals(allInOne, singly);
561 }
562 }