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