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