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