Skip to content

Commit 2c969a8

Browse files
committed
CODEC-291: Hex Encode/Decode with existing arrays
Squashed commit of the following: commit 878d96c Author: Adam Retter <adam.retter@googlemail.com> Date: Fri Jul 3 17:53:41 2020 +0200 Add missing tests commit c1bcf38 Author: Adam Retter <adam.retter@googlemail.com> Date: Tue Jun 30 19:08:20 2020 +0200 Make public methods commit 45533a1 Author: Adam Retter <adam.retter@googlemail.com> Date: Mon Jun 29 22:26:15 2020 +0200 Add additional encode/decode functions for writing data to existing arrays
1 parent e80304c commit 2c969a8

2 files changed

Lines changed: 156 additions & 8 deletions

File tree

  • src
    • main/java/org/apache/commons/codec/binary
    • test/java/org/apache/commons/codec/binary

src/main/java/org/apache/commons/codec/binary/Hex.java

Lines changed: 76 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -73,25 +73,46 @@ public class Hex implements BinaryEncoder, BinaryDecoder {
7373
* @throws DecoderException Thrown if an odd number or illegal of characters is supplied
7474
*/
7575
public static byte[] decodeHex(final char[] data) throws DecoderException {
76+
final byte[] out = new byte[data.length >> 1];
77+
decodeHex(data, out, 0);
78+
return out;
79+
}
7680

81+
/**
82+
* Converts an array of characters representing hexadecimal values into an array of bytes of those same values. The
83+
* returned array will be half the length of the passed array, as it takes two characters to represent any given
84+
* byte. An exception is thrown if the passed char array has an odd number of elements.
85+
*
86+
* @param data An array of characters containing hexadecimal digits
87+
* @param out A byte array to contain the binary data decoded from the supplied char array.
88+
* @param outOffset The position within {@code out} to start writing the decoded bytes.
89+
* @return the number of bytes written to {@code out}.
90+
* @throws DecoderException Thrown if an odd number or illegal of characters is supplied
91+
*
92+
* @since 1.15
93+
*/
94+
public static int decodeHex(final char[] data, final byte[] out, final int outOffset) throws DecoderException {
7795
final int len = data.length;
7896

7997
if ((len & 0x01) != 0) {
8098
throw new DecoderException("Odd number of characters.");
8199
}
82100

83-
final byte[] out = new byte[len >> 1];
101+
final int outLen = len >> 1;
102+
if (out.length - outOffset < outLen) {
103+
throw new DecoderException("out is not large enough to accommodate decoded data.");
104+
}
84105

85106
// two characters form the hex value.
86-
for (int i = 0, j = 0; j < len; i++) {
107+
for (int i = outOffset, j = 0; j < len; i++) {
87108
int f = toDigit(data[j], j) << 4;
88109
j++;
89110
f = f | toDigit(data[j], j);
90111
j++;
91112
out[i] = (byte) (f & 0xFF);
92113
}
93114

94-
return out;
115+
return outLen;
95116
}
96117

97118
/**
@@ -148,12 +169,62 @@ public static char[] encodeHex(final byte[] data, final boolean toLowerCase) {
148169
protected static char[] encodeHex(final byte[] data, final char[] toDigits) {
149170
final int l = data.length;
150171
final char[] out = new char[l << 1];
172+
encodeHex(data, 0, data.length, toDigits, out, 0);
173+
return out;
174+
}
175+
176+
/**
177+
* Converts an array of bytes into an array of characters representing the hexadecimal values of each byte in order.
178+
*
179+
* @param data a byte[] to convert to Hex characters
180+
* @param dataOffset the position in {@code data} to start encoding from
181+
* @param dataLen the number of bytes from {@code dataOffset} to encode
182+
* @param toLowerCase {@code true} converts to lowercase, {@code false} to uppercase
183+
* @return A char[] containing the appropriate characters from the alphabet For best results, this should be either
184+
* upper- or lower-case hex.
185+
* @since 1.15
186+
*/
187+
public static char[] encodeHex(final byte[] data, final int dataOffset, final int dataLen,
188+
final boolean toLowerCase) {
189+
final char[] out = new char[dataLen << 1];
190+
encodeHex(data, dataOffset, dataLen, toLowerCase ? DIGITS_LOWER : DIGITS_UPPER, out, 0);
191+
return out;
192+
}
193+
194+
/**
195+
* Converts an array of bytes into an array of characters representing the hexadecimal values of each byte in order.
196+
*
197+
* @param data a byte[] to convert to Hex characters
198+
* @param dataOffset the position in {@code data} to start encoding from
199+
* @param dataLen the number of bytes from {@code dataOffset} to encode
200+
* @param toLowerCase {@code true} converts to lowercase, {@code false} to uppercase
201+
* @param out a char[] which will hold the resultant appropriate characters from the alphabet.
202+
* @param outOffset the position within {@code out} at which to start writing the encoded characters.
203+
* @since 1.15
204+
*/
205+
public static void encodeHex(final byte[] data, final int dataOffset, final int dataLen,
206+
final boolean toLowerCase, final char[] out, final int outOffset) {
207+
encodeHex(data, dataOffset, dataLen, toLowerCase ? DIGITS_LOWER : DIGITS_UPPER, out, outOffset);
208+
}
209+
210+
/**
211+
* Converts an array of bytes into an array of characters representing the hexadecimal values of each byte in order.
212+
*
213+
* @param data a byte[] to convert to Hex characters
214+
* @param dataOffset the position in {@code data} to start encoding from
215+
* @param dataLen the number of bytes from {@code dataOffset} to encode
216+
* @param toDigits the output alphabet (must contain at least 16 chars)
217+
* @param out a char[] which will hold the resultant appropriate characters from the alphabet.
218+
* @param outOffset the position within {@code out} at which to start writing the encoded characters.
219+
* @since 1.15
220+
*/
221+
protected static void encodeHex(final byte[] data, final int dataOffset, final int dataLen, final char[] toDigits,
222+
final char[] out, final int outOffset) {
151223
// two characters form the hex value.
152-
for (int i = 0, j = 0; i < l; i++) {
224+
for (int i = dataOffset, j = outOffset; i < dataOffset + dataLen; i++) {
153225
out[j++] = toDigits[(0xF0 & data[i]) >>> 4];
154226
out[j++] = toDigits[0x0F & data[i]];
155227
}
156-
return out;
157228
}
158229

159230
/**

src/test/java/org/apache/commons/codec/binary/HexTest.java

Lines changed: 80 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717

1818
package org.apache.commons.codec.binary;
1919

20+
import static java.nio.charset.StandardCharsets.UTF_8;
21+
import static org.junit.Assert.assertArrayEquals;
2022
import static org.junit.Assert.assertEquals;
2123
import static org.junit.Assert.assertFalse;
2224
import static org.junit.Assert.assertTrue;
@@ -68,7 +70,7 @@ protected ByteBuffer allocate(final int capacity) {
6870
* @return the byte buffer
6971
*/
7072
private ByteBuffer getByteBufferUtf8(final String string) {
71-
final byte[] bytes = string.getBytes(StandardCharsets.UTF_8);
73+
final byte[] bytes = string.getBytes(UTF_8);
7274
final ByteBuffer bb = allocate(bytes.length);
7375
bb.put(bytes);
7476
bb.flip();
@@ -340,6 +342,18 @@ public void testDecodeHexCharArrayOddCharacters5() {
340342
checkDecodeHexCharArrayOddCharacters(new char[] { 'A', 'B', 'C', 'D', 'E' });
341343
}
342344

345+
@Test(expected = DecoderException.class)
346+
public void testDecodeHexCharArrayOutBufferUnderSized() throws DecoderException {
347+
final byte[] out = new byte[4];
348+
Hex.decodeHex("aabbccddeeff".toCharArray(), out, 0);
349+
}
350+
351+
@Test(expected = DecoderException.class)
352+
public void testDecodeHexCharArrayOutBufferUnderSizedByOffset() throws DecoderException {
353+
final byte[] out = new byte[6];
354+
Hex.decodeHex("aabbccddeeff".toCharArray(), out, 1);
355+
}
356+
343357
@Test
344358
public void testDecodeHexStringOddCharacters() {
345359
try {
@@ -440,6 +454,27 @@ public void testEncodeDecodeHexCharArrayRandom() throws DecoderException, Encode
440454
}
441455
}
442456

457+
@Test
458+
public void testEncodeDecodeHexCharArrayRandomToOutput() throws DecoderException, EncoderException {
459+
final Random random = new Random();
460+
for (int i = 5; i > 0; i--) {
461+
final byte[] data = new byte[random.nextInt(10000) + 1];
462+
random.nextBytes(data);
463+
464+
// lower-case
465+
final char[] lowerEncodedChars = new char[data.length * 2];
466+
Hex.encodeHex(data, 0, data.length, true, lowerEncodedChars, 0);
467+
final byte[] decodedLowerCaseBytes = Hex.decodeHex(lowerEncodedChars);
468+
assertArrayEquals(data, decodedLowerCaseBytes);
469+
470+
// upper-case
471+
final char[] upperEncodedChars = new char[data.length * 2];
472+
Hex.encodeHex(data, 0, data.length, false, upperEncodedChars, 0);
473+
final byte[] decodedUpperCaseBytes = Hex.decodeHex(upperEncodedChars);
474+
assertArrayEquals(data, decodedUpperCaseBytes);
475+
}
476+
}
477+
443478
@Test
444479
public void testEncodeHexByteArrayEmpty() {
445480
assertTrue(Arrays.equals(new char[0], Hex.encodeHex(new byte[0])));
@@ -549,6 +584,48 @@ public void testEncodeHex_ByteBufferWithLimit() {
549584
}
550585
}
551586

587+
@Test
588+
public void testEncodeHexPartialInput() {
589+
final byte data[] = "hello world".getBytes(UTF_8);
590+
591+
char[] hex = Hex.encodeHex(data, 0, 0, true);
592+
assertArrayEquals(new char[0], hex);
593+
594+
hex = Hex.encodeHex(data, 0, 1, true);
595+
assertArrayEquals("68".toCharArray(), hex);
596+
597+
hex = Hex.encodeHex(data, 0, 1, false);
598+
assertArrayEquals("68".toCharArray(), hex);
599+
600+
hex = Hex.encodeHex(data, 2, 4, true);
601+
assertArrayEquals("6c6c6f20".toCharArray(), hex);
602+
603+
hex = Hex.encodeHex(data, 2, 4, false);
604+
assertArrayEquals("6C6C6F20".toCharArray(), hex);
605+
606+
hex = Hex.encodeHex(data, 10, 1, true);
607+
assertArrayEquals("64".toCharArray(), hex);
608+
609+
hex = Hex.encodeHex(data, 10, 1, false);
610+
assertArrayEquals("64".toCharArray(), hex);
611+
}
612+
613+
@Test(expected=ArrayIndexOutOfBoundsException.class)
614+
public void testEncodeHexPartialInputUnderbounds() {
615+
final byte data[] = "hello world".getBytes(UTF_8);
616+
617+
final char[] hex = Hex.encodeHex(data, -2, 10, true);
618+
assertArrayEquals("64".toCharArray(), hex);
619+
}
620+
621+
@Test(expected=ArrayIndexOutOfBoundsException.class)
622+
public void testEncodeHexPartialInputOverbounds() {
623+
final byte data[] = "hello world".getBytes(UTF_8);
624+
625+
final char[] hex = Hex.encodeHex(data, 9, 10, true);
626+
assertArrayEquals("64".toCharArray(), hex);
627+
}
628+
552629
@Test
553630
public void testEncodeHexByteString_ByteBufferOfZeroes() {
554631
final String c = Hex.encodeHexString(allocate(36));
@@ -636,12 +713,12 @@ public void testEncodeStringEmpty() throws EncoderException {
636713

637714
@Test
638715
public void testGetCharset() {
639-
Assert.assertEquals(StandardCharsets.UTF_8, new Hex(StandardCharsets.UTF_8).getCharset());
716+
Assert.assertEquals(UTF_8, new Hex(UTF_8).getCharset());
640717
}
641718

642719
@Test
643720
public void testGetCharsetName() {
644-
Assert.assertEquals(StandardCharsets.UTF_8.name(), new Hex(StandardCharsets.UTF_8).getCharsetName());
721+
Assert.assertEquals(UTF_8.name(), new Hex(UTF_8).getCharsetName());
645722
}
646723

647724
@Test

0 commit comments

Comments
 (0)