View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      https://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
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   * Tests {@link Hex}.
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       * Allocate a ByteBuffer.
52       * <p>
53       * The default implementation uses {@link ByteBuffer#allocate(int)}. The method is overridden in AllocateDirectHexTest to use
54       * {@link ByteBuffer#allocateDirect(int)}
55       * </p>
56       *
57       * @param capacity The capacity
58       * @return The byte buffer
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                  // Here with:
72                  //
73                  // Java Sun 1.4.2_19 x86 32-bits on Windows XP
74                  // JIS_X0212-1990
75                  // x-JIS0208
76                  //
77                  // Java Sun 1.5.0_17 x86 32-bits on Windows XP
78                  // JIS_X0212-1990
79                  // x-IBM834
80                  // x-JIS0208
81                  // x-MacDingbat
82                  // x-MacSymbol
83                  //
84                  // Java Sun 1.6.0_14 x86 32-bits
85                  // JIS_X0212-1990
86                  // x-IBM834
87                  // x-JIS0208
88                  // x-MacDingbat
89                  // x-MacSymbol
90                  //
91                  log("FAILED charsetSanityCheck=Interesting Java charset oddity: Roundtrip failed for " + name);
92              }
93              return equals;
94          } catch (final UnsupportedEncodingException | UnsupportedOperationException e) {
95              // Caught here with:
96              // x-JISAutoDetect on Windows XP and Java Sun 1.4.2_19 x86 32-bits
97              // x-JISAutoDetect on Windows XP and Java Sun 1.5.0_17 x86 32-bits
98              // x-JISAutoDetect on Windows XP and Java Sun 1.6.0_14 x86 32-bits
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      * Gets the encoding of the given string as a byte buffer using the UTF-8 charset.
121      *
122      * <p>
123      * The buffer is allocated using {@link #allocate(int)}.
124      * </p>
125      *
126      * @param string The String to encode
127      * @return The byte buffer
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      * @param name
153      * @param parent
154      * @throws UnsupportedEncodingException Thrown if the named charset is unavailable.
155      * @throws DecoderException Thrown if decoding fails.
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         // source data
165         final String sourceString = "Hello World";
166         final byte[] sourceBytes = sourceString.getBytes(name);
167         // test 1
168         // encode source to hex string to bytes with charset
169         final byte[] actualEncodedBytes = customCodec.encode(sourceBytes);
170         // encode source to hex string...
171         String expectedHexString = Hex.encodeHexString(sourceBytes);
172         // ... and get the bytes in the expected charset
173         final byte[] expectedHexStringBytes = expectedHexString.getBytes(name);
174         assertArrayEquals(expectedHexStringBytes, actualEncodedBytes);
175         // test 2
176         String actualStringFromBytes = new String(actualEncodedBytes, name);
177         assertEquals(expectedHexString, actualStringFromBytes, name);
178         // second test:
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         // sanity check:
184         assertEquals(sourceString, actualStringFromBytes, name);
185         // actual check:
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         // Effectively set remaining == 0 => empty
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         // Test pairs of bytes
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         // Alternate spellings must not decode to the same bytes as ASCII hexadecimal strings.
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         // Effectively set remaining == 0 => empty
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             // static API
398             final char[] encodedChars = Hex.encodeHex(data);
399             byte[] decodedBytes = Hex.decodeHex(encodedChars);
400             assertArrayEquals(data, decodedBytes);
401 
402             // instance API with array parameter
403             final byte[] encodedStringBytes = hex.encode(data);
404             decodedBytes = hex.decode(encodedStringBytes);
405             assertArrayEquals(data, decodedBytes);
406 
407             // instance API with char[] (Object) parameter
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             // instance API with String (Object) parameter
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             // lower-case
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             // upper-case
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         // Test pairs of bytes
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         // Default lower-case
514         actual = Hex.encodeHex(b);
515         assertEquals(expected, new String(actual));
516         assertEquals(0, b.remaining());
517         // lower-case
518         b.flip();
519         actual = Hex.encodeHex(b, true);
520         assertEquals(expected, new String(actual));
521         assertEquals(0, b.remaining());
522         // upper-case
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         // Default lower-case
535         actual = Hex.encodeHex(b);
536         assertEquals(expected.toLowerCase(), new String(actual));
537         assertEquals(0, b.remaining());
538         // lower-case
539         b.flip();
540         actual = Hex.encodeHex(b, true);
541         assertEquals(expected.toLowerCase(), new String(actual));
542         assertEquals(0, b.remaining());
543         // upper-case
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      * Test encoding of a read only byte buffer.
662      * See CODEC-261.
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 }