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  
40  /**
41   * Tests {@link org.apache.commons.codec.binary.Hex}.
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       * Allocate a ByteBuffer.
51       *
52       * <p>The default implementation uses {@link ByteBuffer#allocate(int)}.
53       * The method is overridden in AllocateDirectHexTest to use
54       * {@link ByteBuffer#allocateDirect(int)}
55       *
56       * @param capacity the capacity
57       * @return the byte buffer
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                  // Here with:
71                  //
72                  // Java Sun 1.4.2_19 x86 32-bits on Windows XP
73                  // JIS_X0212-1990
74                  // x-JIS0208
75                  //
76                  // Java Sun 1.5.0_17 x86 32-bits on Windows XP
77                  // JIS_X0212-1990
78                  // x-IBM834
79                  // x-JIS0208
80                  // x-MacDingbat
81                  // x-MacSymbol
82                  //
83                  // Java Sun 1.6.0_14 x86 32-bits
84                  // JIS_X0212-1990
85                  // x-IBM834
86                  // x-JIS0208
87                  // x-MacDingbat
88                  // x-MacSymbol
89                  //
90                  log("FAILED charsetSanityCheck=Interesting Java charset oddity: Roundtrip failed for " + name);
91              }
92              return equals;
93          } catch (final UnsupportedEncodingException | UnsupportedOperationException e) {
94              // Caught here with:
95              // x-JISAutoDetect on Windows XP and Java Sun 1.4.2_19 x86 32-bits
96              // x-JISAutoDetect on Windows XP and Java Sun 1.5.0_17 x86 32-bits
97              // x-JISAutoDetect on Windows XP and Java Sun 1.6.0_14 x86 32-bits
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      * Encodes the given string into a byte buffer using the UTF-8 charset.
120      *
121      * <p>The buffer is allocated using {@link #allocate(int)}.
122      *
123      * @param string the String to encode
124      * @return the byte buffer
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      * @param name
150      * @param parent
151      * @throws UnsupportedEncodingException
152      * @throws DecoderException
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         // source data
162         final String sourceString = "Hello World";
163         final byte[] sourceBytes = sourceString.getBytes(name);
164         // test 1
165         // encode source to hex string to bytes with charset
166         final byte[] actualEncodedBytes = customCodec.encode(sourceBytes);
167         // encode source to hex string...
168         String expectedHexString = Hex.encodeHexString(sourceBytes);
169         // ... and get the bytes in the expected charset
170         final byte[] expectedHexStringBytes = expectedHexString.getBytes(name);
171         assertArrayEquals(expectedHexStringBytes, actualEncodedBytes);
172         // test 2
173         String actualStringFromBytes = new String(actualEncodedBytes, name);
174         assertEquals(expectedHexString, actualStringFromBytes, name);
175         // second test:
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         // sanity check:
181         assertEquals(sourceString, actualStringFromBytes, name);
182         // actual check:
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         // Effectively set remaining == 0 => empty
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         // Test pairs of bytes
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         // Effectively set remaining == 0 => empty
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             // static API
380             final char[] encodedChars = Hex.encodeHex(data);
381             byte[] decodedBytes = Hex.decodeHex(encodedChars);
382             assertArrayEquals(data, decodedBytes);
383 
384             // instance API with array parameter
385             final byte[] encodedStringBytes = hex.encode(data);
386             decodedBytes = hex.decode(encodedStringBytes);
387             assertArrayEquals(data, decodedBytes);
388 
389             // instance API with char[] (Object) parameter
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             // instance API with String (Object) parameter
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             // lower-case
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             // upper-case
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         // Test pairs of bytes
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         // Default lower-case
496         actual = Hex.encodeHex(b);
497         assertEquals(expected, new String(actual));
498         assertEquals(0, b.remaining());
499         // lower-case
500         b.flip();
501         actual = Hex.encodeHex(b, true);
502         assertEquals(expected, new String(actual));
503         assertEquals(0, b.remaining());
504         // upper-case
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         // Default lower-case
517         actual = Hex.encodeHex(b);
518         assertEquals(expected.toLowerCase(), new String(actual));
519         assertEquals(0, b.remaining());
520         // lower-case
521         b.flip();
522         actual = Hex.encodeHex(b, true);
523         assertEquals(expected.toLowerCase(), new String(actual));
524         assertEquals(0, b.remaining());
525         // upper-case
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      * Test encoding of a read only byte buffer.
644      * See CODEC-261.
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 }