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.assertNotEquals;
23 import static org.junit.jupiter.api.Assertions.assertThrows;
24 import static org.junit.jupiter.api.Assertions.assertTrue;
25
26 import java.io.UnsupportedEncodingException;
27 import java.nio.ByteBuffer;
28 import java.nio.charset.Charset;
29 import java.nio.charset.StandardCharsets;
30 import java.nio.charset.UnsupportedCharsetException;
31 import java.util.Arrays;
32 import java.util.concurrent.ThreadLocalRandom;
33
34 import org.apache.commons.codec.DecoderException;
35 import org.apache.commons.codec.EncoderException;
36 import org.junit.jupiter.api.Test;
37 import org.junit.jupiter.params.ParameterizedTest;
38 import org.junit.jupiter.params.provider.MethodSource;
39 import org.junit.jupiter.params.provider.ValueSource;
40
41
42
43
44 class HexTest {
45
46 private static final String BAD_ENCODING_NAME = "UNKNOWN";
47
48 private static final boolean LOG = false;
49
50
51
52
53
54
55
56
57
58
59
60 protected ByteBuffer allocate(final int capacity) {
61 return ByteBuffer.allocate(capacity);
62 }
63
64 private boolean charsetSanityCheck(final String name) {
65 final String source = "the quick brown dog jumped over the lazy fox";
66 try {
67 final byte[] bytes = source.getBytes(name);
68 final String str = new String(bytes, name);
69 final boolean equals = source.equals(str);
70 if (!equals) {
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91 log("FAILED charsetSanityCheck=Interesting Java charset oddity: Roundtrip failed for " + name);
92 }
93 return equals;
94 } catch (final UnsupportedEncodingException | UnsupportedOperationException e) {
95
96
97
98
99 if (LOG) {
100 log("FAILED charsetSanityCheck=" + name + ", e=" + e);
101 log(e);
102 }
103 return false;
104 }
105 }
106
107 private void checkDecodeHexByteBufferOddCharacters(final ByteBuffer data) {
108 assertThrows(DecoderException.class, () -> new Hex().decode(data));
109 }
110
111 private void checkDecodeHexCharArrayOddCharacters(final char[] data) {
112 assertThrows(DecoderException.class, () -> Hex.decodeHex(data));
113 }
114
115 private void checkDecodeHexCharArrayOddCharacters(final String data) {
116 assertThrows(DecoderException.class, () -> Hex.decodeHex(data));
117 }
118
119
120
121
122
123
124
125
126
127
128
129 private ByteBuffer getByteBufferUtf8(final String string) {
130 final byte[] bytes = string.getBytes(StandardCharsets.UTF_8);
131 final ByteBuffer bb = allocate(bytes.length);
132 bb.put(bytes);
133 bb.flip();
134 return bb;
135 }
136
137 private void log(final String s) {
138 if (LOG) {
139 System.out.println(s);
140 System.out.flush();
141 }
142 }
143
144 private void log(final Throwable t) {
145 if (LOG) {
146 t.printStackTrace(System.out);
147 System.out.flush();
148 }
149 }
150
151
152
153
154
155
156
157 private void testCharset(final String name, final String parent) throws UnsupportedEncodingException,
158 DecoderException {
159 if (!charsetSanityCheck(name)) {
160 return;
161 }
162 log(parent + "=" + name);
163 final Hex customCodec = new Hex(name);
164
165 final String sourceString = "Hello World";
166 final byte[] sourceBytes = sourceString.getBytes(name);
167
168
169 final byte[] actualEncodedBytes = customCodec.encode(sourceBytes);
170
171 String expectedHexString = Hex.encodeHexString(sourceBytes);
172
173 final byte[] expectedHexStringBytes = expectedHexString.getBytes(name);
174 assertArrayEquals(expectedHexStringBytes, actualEncodedBytes);
175
176 String actualStringFromBytes = new String(actualEncodedBytes, name);
177 assertEquals(expectedHexString, actualStringFromBytes, name);
178
179 final Hex utf8Codec = new Hex();
180 expectedHexString = "48656c6c6f20576f726c64";
181 final byte[] decodedUtf8Bytes = (byte[]) utf8Codec.decode(expectedHexString);
182 actualStringFromBytes = new String(decodedUtf8Bytes, utf8Codec.getCharset());
183
184 assertEquals(sourceString, actualStringFromBytes, name);
185
186 final byte[] decodedCustomBytes = customCodec.decode(actualEncodedBytes);
187 actualStringFromBytes = new String(decodedCustomBytes, name);
188 assertEquals(sourceString, actualStringFromBytes, name);
189 }
190
191 @ParameterizedTest
192 @MethodSource("org.apache.commons.codec.CharsetsTest#getAvailableCharsetNames()")
193 void testCustomCharset(final String name) throws UnsupportedEncodingException, DecoderException {
194 testCharset(name, "testCustomCharset");
195 }
196
197 @Test
198 void testCustomCharsetBadName() {
199 assertThrows(UnsupportedCharsetException.class, () -> new Hex(BAD_ENCODING_NAME));
200 }
201
202 @Test
203 void testCustomCharsetToString() {
204 assertTrue(new Hex().toString().contains(Hex.DEFAULT_CHARSET_NAME));
205 }
206
207 @Test
208 void testDecodeBadCharacterPos0() {
209 assertThrows(DecoderException.class, () -> new Hex().decode("q0"));
210 }
211
212 @Test
213 void testDecodeBadCharacterPos1() {
214 assertThrows(DecoderException.class, () -> new Hex().decode("0q"));
215 }
216
217 @Test
218 void testDecodeByteArrayEmpty() throws DecoderException {
219 assertArrayEquals(new byte[0], new Hex().decode(new byte[0]));
220 }
221
222 @Test
223 void testDecodeByteArrayObjectEmpty() throws DecoderException {
224 assertArrayEquals(new byte[0], (byte[]) new Hex().decode((Object) new byte[0]));
225 }
226
227 @Test
228 void testDecodeByteArrayOddCharacters() {
229 assertThrows(DecoderException.class, () -> new Hex().decode(new byte[] { 65 }), "odd number of characters");
230 }
231
232 @Test
233 void testDecodeByteBufferAllocatedButEmpty() throws DecoderException {
234 final ByteBuffer bb = allocate(10);
235
236 bb.flip();
237 assertArrayEquals(new byte[0], new Hex().decode(bb));
238 assertEquals(0, bb.remaining());
239 }
240
241 @Test
242 void testDecodeByteBufferEmpty() throws DecoderException {
243 assertArrayEquals(new byte[0], new Hex().decode(allocate(0)));
244 }
245
246 @Test
247 void testDecodeByteBufferObjectEmpty() throws DecoderException {
248 assertArrayEquals(new byte[0], (byte[]) new Hex().decode((Object) allocate(0)));
249 }
250
251 @Test
252 void testDecodeByteBufferOddCharacters() {
253 final ByteBuffer bb = allocate(1);
254 bb.put((byte) 65);
255 bb.flip();
256 checkDecodeHexByteBufferOddCharacters(bb);
257 }
258
259 @Test
260 void testDecodeByteBufferWithLimit() throws DecoderException {
261 final ByteBuffer bb = getByteBufferUtf8("000102030405060708090a0b0c0d0e0f");
262 final byte[] expected = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};
263
264 for (int i = 0; i < 15; i++) {
265 bb.position(i * 2);
266 bb.limit(i * 2 + 4);
267 assertEquals(new String(Arrays.copyOfRange(expected, i, i + 2)), new String(new Hex().decode(bb)));
268 assertEquals(0, bb.remaining());
269 }
270 }
271
272 @Test
273 void testDecodeByteBufferWithLimitOddCharacters() {
274 final ByteBuffer bb = allocate(10);
275 bb.put(1, (byte) 65);
276 bb.position(1);
277 bb.limit(2);
278 checkDecodeHexByteBufferOddCharacters(bb);
279 }
280
281 @Test
282 void testDecodeClassCastException() {
283 assertThrows(DecoderException.class, () -> new Hex().decode(new int[] { 65 }), "odd number of characters");
284 }
285
286 @Test
287 void testDecodeHexCharArrayEmpty() throws DecoderException {
288 assertArrayEquals(new byte[0], Hex.decodeHex(new char[0]));
289 }
290
291 @Test
292 void testDecodeHexCharArrayOddCharacters1() {
293 checkDecodeHexCharArrayOddCharacters(new char[] { 'A' });
294 }
295
296 @Test
297 void testDecodeHexCharArrayOddCharacters3() {
298 checkDecodeHexCharArrayOddCharacters(new char[] { 'A', 'B', 'C' });
299 }
300
301 @Test
302 void testDecodeHexCharArrayOddCharacters5() {
303 checkDecodeHexCharArrayOddCharacters(new char[] { 'A', 'B', 'C', 'D', 'E' });
304 }
305
306 @Test
307 void testDecodeHexCharArrayOutBufferUnderSized() {
308 final byte[] out = new byte[4];
309 assertThrows(DecoderException.class, () -> Hex.decodeHex("aabbccddeeff".toCharArray(), out, 0));
310 }
311
312 @Test
313 void testDecodeHexCharArrayOutBufferUnderSizedByOffset() {
314 final byte[] out = new byte[6];
315 assertThrows(DecoderException.class, () -> Hex.decodeHex("aabbccddeeff".toCharArray(), out, 1));
316
317 }
318
319 @Test
320 void testDecodeHexStringEmpty() throws DecoderException {
321 assertArrayEquals(new byte[0], Hex.decodeHex(""));
322 }
323
324 @Test
325 void testDecodeHexStringOddCharacters() {
326 assertThrows(DecoderException.class, () -> new Hex().decode("6"), "odd number of characters");
327
328 }
329
330 @Test
331 void testDecodeHexStringOddCharacters1() {
332 checkDecodeHexCharArrayOddCharacters("A");
333 }
334
335 @ParameterizedTest
336 @ValueSource(strings = { "\uFF14\uFF11", "\u0664\u0661", "\u096A\u0967", "\uFF21\uFF26", "\uFF41\uFF46", "4\uFF11" })
337 void testDecodeNonAsciiUnicodeDigits(final String input) {
338
339 assertThrows(DecoderException.class, () -> Hex.decodeHex(input));
340 assertThrows(DecoderException.class, () -> Hex.decodeHex(input.toCharArray()));
341 assertThrows(DecoderException.class, () -> Hex.decodeHex(input.toCharArray(), new byte[input.length() / 2], 0));
342 assertThrows(DecoderException.class, () -> new Hex().decode(input));
343 final byte[] utf8 = input.getBytes(StandardCharsets.UTF_8);
344 assertThrows(DecoderException.class, () -> new Hex().decode(utf8));
345 final ByteBuffer buffer = allocate(utf8.length);
346 buffer.put(utf8).flip();
347 assertThrows(DecoderException.class, () -> new Hex().decode(buffer));
348 }
349
350 @Test
351 void testDecodeStringEmpty() throws DecoderException {
352 assertArrayEquals(new byte[0], (byte[]) new Hex().decode(""));
353 }
354
355 @Test
356 void testEncodeByteArrayEmpty() {
357 assertArrayEquals(new byte[0], new Hex().encode(new byte[0]));
358 }
359
360 @Test
361 void testEncodeByteArrayObjectEmpty() throws EncoderException {
362 assertArrayEquals(new char[0], (char[]) new Hex().encode((Object) new byte[0]));
363 }
364
365 @Test
366 void testEncodeByteBufferAllocatedButEmpty() {
367 final ByteBuffer bb = allocate(10);
368
369 bb.flip();
370 assertArrayEquals(new byte[0], new Hex().encode(bb));
371 assertEquals(0, bb.remaining());
372 }
373
374 @Test
375 void testEncodeByteBufferEmpty() {
376 assertArrayEquals(new byte[0], new Hex().encode(allocate(0)));
377 }
378
379 @Test
380 void testEncodeByteBufferObjectEmpty() throws EncoderException {
381 assertArrayEquals(new char[0], (char[]) new Hex().encode((Object) allocate(0)));
382 }
383
384 @Test
385 void testEncodeClassCastException() {
386 assertThrows(EncoderException.class, () -> new Hex().encode(new int[] { 65 }));
387 }
388
389 @Test
390 void testEncodeDecodeHexCharArrayRandom() throws DecoderException, EncoderException {
391
392 final Hex hex = new Hex();
393 for (int i = 5; i > 0; i--) {
394 final byte[] data = new byte[ThreadLocalRandom.current().nextInt(10000) + 1];
395 ThreadLocalRandom.current().nextBytes(data);
396
397
398 final char[] encodedChars = Hex.encodeHex(data);
399 byte[] decodedBytes = Hex.decodeHex(encodedChars);
400 assertArrayEquals(data, decodedBytes);
401
402
403 final byte[] encodedStringBytes = hex.encode(data);
404 decodedBytes = hex.decode(encodedStringBytes);
405 assertArrayEquals(data, decodedBytes);
406
407
408 String dataString = new String(encodedChars);
409 char[] encodedStringChars = (char[]) hex.encode(dataString);
410 decodedBytes = (byte[]) hex.decode(encodedStringChars);
411 assertArrayEquals(StringUtils.getBytesUtf8(dataString), decodedBytes);
412
413
414 dataString = new String(encodedChars);
415 encodedStringChars = (char[]) hex.encode(dataString);
416 decodedBytes = (byte[]) hex.decode(new String(encodedStringChars));
417 assertArrayEquals(StringUtils.getBytesUtf8(dataString), decodedBytes);
418 }
419 }
420
421 @Test
422 void testEncodeDecodeHexCharArrayRandomToOutput() throws DecoderException {
423 for (int i = 5; i > 0; i--) {
424 final byte[] data = new byte[ThreadLocalRandom.current().nextInt(10000) + 1];
425 ThreadLocalRandom.current().nextBytes(data);
426
427
428 final char[] lowerEncodedChars = new char[data.length * 2];
429 Hex.encodeHex(data, 0, data.length, true, lowerEncodedChars, 0);
430 final byte[] decodedLowerCaseBytes = Hex.decodeHex(lowerEncodedChars);
431 assertArrayEquals(data, decodedLowerCaseBytes);
432
433
434 final char[] upperEncodedChars = new char[data.length * 2];
435 Hex.encodeHex(data, 0, data.length, false, upperEncodedChars, 0);
436 final byte[] decodedUpperCaseBytes = Hex.decodeHex(upperEncodedChars);
437 assertArrayEquals(data, decodedUpperCaseBytes);
438 }
439 }
440
441 @Test
442 void testEncodeHex_ByteBufferOfZeroes() {
443 final char[] c = Hex.encodeHex(allocate(36));
444 assertEquals("000000000000000000000000000000000000000000000000000000000000000000000000", new String(c));
445 }
446
447 @Test
448 void testEncodeHex_ByteBufferWithLimit() {
449 final ByteBuffer bb = allocate(16);
450 for (int i = 0; i < 16; i++) {
451 bb.put((byte) i);
452 }
453 bb.flip();
454 final String expected = "000102030405060708090a0b0c0d0e0f";
455
456 for (int i = 0; i < 15; i++) {
457 bb.position(i);
458 bb.limit(i + 2);
459 assertEquals(expected.substring(i * 2, i * 2 + 4), new String(Hex.encodeHex(bb)));
460 assertEquals(0, bb.remaining());
461 }
462 }
463
464 @Test
465 void testEncodeHexByteArrayEmpty() {
466 assertArrayEquals(new char[0], Hex.encodeHex(new byte[0]));
467 assertArrayEquals(new byte[0], new Hex().encode(new byte[0]));
468 }
469
470 @Test
471 void testEncodeHexByteArrayHelloWorldLowerCaseHex() {
472 final byte[] b = StringUtils.getBytesUtf8("Hello World");
473 final String expected = "48656c6c6f20576f726c64";
474 char[] actual;
475 actual = Hex.encodeHex(b);
476 assertEquals(expected, new String(actual));
477 actual = Hex.encodeHex(b, true);
478 assertEquals(expected, new String(actual));
479 actual = Hex.encodeHex(b, false);
480 assertNotEquals(expected, new String(actual));
481 }
482
483 @Test
484 void testEncodeHexByteArrayHelloWorldUpperCaseHex() {
485 final byte[] b = StringUtils.getBytesUtf8("Hello World");
486 final String expected = "48656C6C6F20576F726C64";
487 char[] actual;
488 actual = Hex.encodeHex(b);
489 assertNotEquals(expected, new String(actual));
490 actual = Hex.encodeHex(b, true);
491 assertNotEquals(expected, new String(actual));
492 actual = Hex.encodeHex(b, false);
493 assertEquals(expected, new String(actual));
494 }
495
496 @Test
497 void testEncodeHexByteArrayZeroes() {
498 final char[] c = Hex.encodeHex(new byte[36]);
499 assertEquals("000000000000000000000000000000000000000000000000000000000000000000000000", new String(c));
500 }
501
502 @Test
503 void testEncodeHexByteBufferEmpty() {
504 assertArrayEquals(new char[0], Hex.encodeHex(allocate(0)));
505 assertArrayEquals(new byte[0], new Hex().encode(allocate(0)));
506 }
507
508 @Test
509 void testEncodeHexByteBufferHelloWorldLowerCaseHex() {
510 final ByteBuffer b = getByteBufferUtf8("Hello World");
511 final String expected = "48656c6c6f20576f726c64";
512 char[] actual;
513
514 actual = Hex.encodeHex(b);
515 assertEquals(expected, new String(actual));
516 assertEquals(0, b.remaining());
517
518 b.flip();
519 actual = Hex.encodeHex(b, true);
520 assertEquals(expected, new String(actual));
521 assertEquals(0, b.remaining());
522
523 b.flip();
524 actual = Hex.encodeHex(b, false);
525 assertEquals(expected.toUpperCase(), new String(actual));
526 assertEquals(0, b.remaining());
527 }
528
529 @Test
530 void testEncodeHexByteBufferHelloWorldUpperCaseHex() {
531 final ByteBuffer b = getByteBufferUtf8("Hello World");
532 final String expected = "48656C6C6F20576F726C64";
533 char[] actual;
534
535 actual = Hex.encodeHex(b);
536 assertEquals(expected.toLowerCase(), new String(actual));
537 assertEquals(0, b.remaining());
538
539 b.flip();
540 actual = Hex.encodeHex(b, true);
541 assertEquals(expected.toLowerCase(), new String(actual));
542 assertEquals(0, b.remaining());
543
544 b.flip();
545 actual = Hex.encodeHex(b, false);
546 assertEquals(expected, new String(actual));
547 assertEquals(0, b.remaining());
548 }
549
550 @Test
551 void testEncodeHexByteString_ByteArrayBoolean_ToLowerCase() {
552 assertEquals("0a", Hex.encodeHexString(new byte[] { 10 }, true));
553 }
554
555 @Test
556 void testEncodeHexByteString_ByteArrayBoolean_ToUpperCase() {
557 assertEquals("0A", Hex.encodeHexString(new byte[] { 10 }, false));
558 }
559
560 @Test
561 void testEncodeHexByteString_ByteArrayOfZeroes() {
562 final String c = Hex.encodeHexString(new byte[36]);
563 assertEquals("000000000000000000000000000000000000000000000000000000000000000000000000", c);
564 }
565
566 @Test
567 void testEncodeHexByteString_ByteBufferBoolean_ToLowerCase() {
568 final ByteBuffer bb = allocate(1);
569 bb.put((byte) 10);
570 bb.flip();
571 assertEquals("0a", Hex.encodeHexString(bb, true));
572 }
573
574 @Test
575 void testEncodeHexByteString_ByteBufferBoolean_ToUpperCase() {
576 final ByteBuffer bb = allocate(1);
577 bb.put((byte) 10);
578 bb.flip();
579 assertEquals("0A", Hex.encodeHexString(bb, false));
580 }
581
582 @Test
583 void testEncodeHexByteString_ByteBufferOfZeroes() {
584 final String c = Hex.encodeHexString(allocate(36));
585 assertEquals("000000000000000000000000000000000000000000000000000000000000000000000000", c);
586 }
587
588 @Test
589 void testEncodeHexByteString_ByteBufferOfZeroesWithLimit() {
590 final ByteBuffer bb = allocate(36);
591 bb.limit(3);
592 assertEquals("000000", Hex.encodeHexString(bb));
593 assertEquals(0, bb.remaining());
594 bb.position(1);
595 bb.limit(3);
596 assertEquals("0000", Hex.encodeHexString(bb));
597 assertEquals(0, bb.remaining());
598 }
599
600 @Test
601 void testEncodeHexByteString_ByteBufferWithLimitBoolean_ToLowerCase() {
602 final ByteBuffer bb = allocate(4);
603 bb.put(1, (byte) 10);
604 bb.position(1);
605 bb.limit(2);
606 assertEquals("0a", Hex.encodeHexString(bb, true));
607 assertEquals(0, bb.remaining());
608 }
609
610 @Test
611 void testEncodeHexByteString_ByteBufferWithLimitBoolean_ToUpperCase() {
612 final ByteBuffer bb = allocate(4);
613 bb.put(1, (byte) 10);
614 bb.position(1);
615 bb.limit(2);
616 assertEquals("0A", Hex.encodeHexString(bb, false));
617 assertEquals(0, bb.remaining());
618 }
619
620 @Test
621 void testEncodeHexPartialInput() {
622 final byte[] data = "hello world".getBytes(StandardCharsets.UTF_8);
623
624 char[] hex = Hex.encodeHex(data, 0, 0, true);
625 assertArrayEquals(new char[0], hex);
626
627 hex = Hex.encodeHex(data, 0, 1, true);
628 assertArrayEquals("68".toCharArray(), hex);
629
630 hex = Hex.encodeHex(data, 0, 1, false);
631 assertArrayEquals("68".toCharArray(), hex);
632
633 hex = Hex.encodeHex(data, 2, 4, true);
634 assertArrayEquals("6c6c6f20".toCharArray(), hex);
635
636 hex = Hex.encodeHex(data, 2, 4, false);
637 assertArrayEquals("6C6C6F20".toCharArray(), hex);
638
639 hex = Hex.encodeHex(data, 10, 1, true);
640 assertArrayEquals("64".toCharArray(), hex);
641
642 hex = Hex.encodeHex(data, 10, 1, false);
643 assertArrayEquals("64".toCharArray(), hex);
644 }
645
646 @Test
647 void testEncodeHexPartialInputOverbounds() {
648 final byte[] data = "hello world".getBytes(StandardCharsets.UTF_8);
649
650 assertThrows(ArrayIndexOutOfBoundsException.class, () -> Hex.encodeHex(data, 9, 10, true));
651 }
652
653 @Test
654 void testEncodeHexPartialInputUnderbounds() {
655 final byte[] data = "hello world".getBytes(StandardCharsets.UTF_8);
656
657 assertThrows(ArrayIndexOutOfBoundsException.class, () -> Hex.encodeHex(data, -2, 10, true));
658 }
659
660
661
662
663
664 @Test
665 void testEncodeHexReadOnlyByteBuffer() {
666 final char[] chars = Hex.encodeHex(ByteBuffer.wrap(new byte[]{10}).asReadOnlyBuffer());
667 assertEquals("0a", String.valueOf(chars));
668 }
669
670 @Test
671 void testEncodeStringEmpty() throws EncoderException {
672 assertArrayEquals(new char[0], (char[]) new Hex().encode(""));
673 }
674
675 @Test
676 void testGetCharset() {
677 assertEquals(StandardCharsets.UTF_8, new Hex(StandardCharsets.UTF_8).getCharset());
678 }
679
680 @Test
681 void testGetCharsetName() {
682 assertEquals(StandardCharsets.UTF_8.name(), new Hex(StandardCharsets.UTF_8).getCharsetName());
683 }
684
685 @ParameterizedTest
686 @MethodSource("org.apache.commons.codec.CharsetsTest#getRequiredCharsets()")
687 void testRequiredCharset(final Charset charset) throws UnsupportedEncodingException, DecoderException {
688 testCharset(charset.name(), "testRequiredCharset");
689 }
690 }