This is an automated email from the ASF dual-hosted git repository. aherbert pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/commons-codec.git
commit 2c969a83454605e204e3e6977f563961f227f185 Author: Alex Herbert <aherb...@apache.org> AuthorDate: Wed Jul 15 12:52:49 2020 +0100 CODEC-291: Hex Encode/Decode with existing arrays Squashed commit of the following: commit 878d96cec17ce487775306d40171922383d1210a Author: Adam Retter <adam.ret...@googlemail.com> Date: Fri Jul 3 17:53:41 2020 +0200 Add missing tests commit c1bcf38f3cbf03979bc3de09916a53194156da8c Author: Adam Retter <adam.ret...@googlemail.com> Date: Tue Jun 30 19:08:20 2020 +0200 Make public methods commit 45533a1876f3a68f5040e6645d3db2f6c6f6e875 Author: Adam Retter <adam.ret...@googlemail.com> Date: Mon Jun 29 22:26:15 2020 +0200 Add additional encode/decode functions for writing data to existing arrays --- .../java/org/apache/commons/codec/binary/Hex.java | 81 +++++++++++++++++++-- .../org/apache/commons/codec/binary/HexTest.java | 83 +++++++++++++++++++++- 2 files changed, 156 insertions(+), 8 deletions(-) diff --git a/src/main/java/org/apache/commons/codec/binary/Hex.java b/src/main/java/org/apache/commons/codec/binary/Hex.java index 57c05bd..1cdabbc 100644 --- a/src/main/java/org/apache/commons/codec/binary/Hex.java +++ b/src/main/java/org/apache/commons/codec/binary/Hex.java @@ -73,17 +73,38 @@ public class Hex implements BinaryEncoder, BinaryDecoder { * @throws DecoderException Thrown if an odd number or illegal of characters is supplied */ public static byte[] decodeHex(final char[] data) throws DecoderException { + final byte[] out = new byte[data.length >> 1]; + decodeHex(data, out, 0); + return out; + } + /** + * Converts an array of characters representing hexadecimal values into an array of bytes of those same values. The + * returned array will be half the length of the passed array, as it takes two characters to represent any given + * byte. An exception is thrown if the passed char array has an odd number of elements. + * + * @param data An array of characters containing hexadecimal digits + * @param out A byte array to contain the binary data decoded from the supplied char array. + * @param outOffset The position within {@code out} to start writing the decoded bytes. + * @return the number of bytes written to {@code out}. + * @throws DecoderException Thrown if an odd number or illegal of characters is supplied + * + * @since 1.15 + */ + public static int decodeHex(final char[] data, final byte[] out, final int outOffset) throws DecoderException { final int len = data.length; if ((len & 0x01) != 0) { throw new DecoderException("Odd number of characters."); } - final byte[] out = new byte[len >> 1]; + final int outLen = len >> 1; + if (out.length - outOffset < outLen) { + throw new DecoderException("out is not large enough to accommodate decoded data."); + } // two characters form the hex value. - for (int i = 0, j = 0; j < len; i++) { + for (int i = outOffset, j = 0; j < len; i++) { int f = toDigit(data[j], j) << 4; j++; f = f | toDigit(data[j], j); @@ -91,7 +112,7 @@ public class Hex implements BinaryEncoder, BinaryDecoder { out[i] = (byte) (f & 0xFF); } - return out; + return outLen; } /** @@ -148,12 +169,62 @@ public class Hex implements BinaryEncoder, BinaryDecoder { protected static char[] encodeHex(final byte[] data, final char[] toDigits) { final int l = data.length; final char[] out = new char[l << 1]; + encodeHex(data, 0, data.length, toDigits, out, 0); + return out; + } + + /** + * Converts an array of bytes into an array of characters representing the hexadecimal values of each byte in order. + * + * @param data a byte[] to convert to Hex characters + * @param dataOffset the position in {@code data} to start encoding from + * @param dataLen the number of bytes from {@code dataOffset} to encode + * @param toLowerCase {@code true} converts to lowercase, {@code false} to uppercase + * @return A char[] containing the appropriate characters from the alphabet For best results, this should be either + * upper- or lower-case hex. + * @since 1.15 + */ + public static char[] encodeHex(final byte[] data, final int dataOffset, final int dataLen, + final boolean toLowerCase) { + final char[] out = new char[dataLen << 1]; + encodeHex(data, dataOffset, dataLen, toLowerCase ? DIGITS_LOWER : DIGITS_UPPER, out, 0); + return out; + } + + /** + * Converts an array of bytes into an array of characters representing the hexadecimal values of each byte in order. + * + * @param data a byte[] to convert to Hex characters + * @param dataOffset the position in {@code data} to start encoding from + * @param dataLen the number of bytes from {@code dataOffset} to encode + * @param toLowerCase {@code true} converts to lowercase, {@code false} to uppercase + * @param out a char[] which will hold the resultant appropriate characters from the alphabet. + * @param outOffset the position within {@code out} at which to start writing the encoded characters. + * @since 1.15 + */ + public static void encodeHex(final byte[] data, final int dataOffset, final int dataLen, + final boolean toLowerCase, final char[] out, final int outOffset) { + encodeHex(data, dataOffset, dataLen, toLowerCase ? DIGITS_LOWER : DIGITS_UPPER, out, outOffset); + } + + /** + * Converts an array of bytes into an array of characters representing the hexadecimal values of each byte in order. + * + * @param data a byte[] to convert to Hex characters + * @param dataOffset the position in {@code data} to start encoding from + * @param dataLen the number of bytes from {@code dataOffset} to encode + * @param toDigits the output alphabet (must contain at least 16 chars) + * @param out a char[] which will hold the resultant appropriate characters from the alphabet. + * @param outOffset the position within {@code out} at which to start writing the encoded characters. + * @since 1.15 + */ + protected static void encodeHex(final byte[] data, final int dataOffset, final int dataLen, final char[] toDigits, + final char[] out, final int outOffset) { // two characters form the hex value. - for (int i = 0, j = 0; i < l; i++) { + for (int i = dataOffset, j = outOffset; i < dataOffset + dataLen; i++) { out[j++] = toDigits[(0xF0 & data[i]) >>> 4]; out[j++] = toDigits[0x0F & data[i]]; } - return out; } /** diff --git a/src/test/java/org/apache/commons/codec/binary/HexTest.java b/src/test/java/org/apache/commons/codec/binary/HexTest.java index 4c29548..99e5013 100644 --- a/src/test/java/org/apache/commons/codec/binary/HexTest.java +++ b/src/test/java/org/apache/commons/codec/binary/HexTest.java @@ -17,6 +17,8 @@ package org.apache.commons.codec.binary; +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @@ -68,7 +70,7 @@ public class HexTest { * @return the byte buffer */ private ByteBuffer getByteBufferUtf8(final String string) { - final byte[] bytes = string.getBytes(StandardCharsets.UTF_8); + final byte[] bytes = string.getBytes(UTF_8); final ByteBuffer bb = allocate(bytes.length); bb.put(bytes); bb.flip(); @@ -340,6 +342,18 @@ public class HexTest { checkDecodeHexCharArrayOddCharacters(new char[] { 'A', 'B', 'C', 'D', 'E' }); } + @Test(expected = DecoderException.class) + public void testDecodeHexCharArrayOutBufferUnderSized() throws DecoderException { + final byte[] out = new byte[4]; + Hex.decodeHex("aabbccddeeff".toCharArray(), out, 0); + } + + @Test(expected = DecoderException.class) + public void testDecodeHexCharArrayOutBufferUnderSizedByOffset() throws DecoderException { + final byte[] out = new byte[6]; + Hex.decodeHex("aabbccddeeff".toCharArray(), out, 1); + } + @Test public void testDecodeHexStringOddCharacters() { try { @@ -441,6 +455,27 @@ public class HexTest { } @Test + public void testEncodeDecodeHexCharArrayRandomToOutput() throws DecoderException, EncoderException { + final Random random = new Random(); + for (int i = 5; i > 0; i--) { + final byte[] data = new byte[random.nextInt(10000) + 1]; + random.nextBytes(data); + + // lower-case + final char[] lowerEncodedChars = new char[data.length * 2]; + Hex.encodeHex(data, 0, data.length, true, lowerEncodedChars, 0); + final byte[] decodedLowerCaseBytes = Hex.decodeHex(lowerEncodedChars); + assertArrayEquals(data, decodedLowerCaseBytes); + + // upper-case + final char[] upperEncodedChars = new char[data.length * 2]; + Hex.encodeHex(data, 0, data.length, false, upperEncodedChars, 0); + final byte[] decodedUpperCaseBytes = Hex.decodeHex(upperEncodedChars); + assertArrayEquals(data, decodedUpperCaseBytes); + } + } + + @Test public void testEncodeHexByteArrayEmpty() { assertTrue(Arrays.equals(new char[0], Hex.encodeHex(new byte[0]))); assertTrue(Arrays.equals(new byte[0], new Hex().encode(new byte[0]))); @@ -550,6 +585,48 @@ public class HexTest { } @Test + public void testEncodeHexPartialInput() { + final byte data[] = "hello world".getBytes(UTF_8); + + char[] hex = Hex.encodeHex(data, 0, 0, true); + assertArrayEquals(new char[0], hex); + + hex = Hex.encodeHex(data, 0, 1, true); + assertArrayEquals("68".toCharArray(), hex); + + hex = Hex.encodeHex(data, 0, 1, false); + assertArrayEquals("68".toCharArray(), hex); + + hex = Hex.encodeHex(data, 2, 4, true); + assertArrayEquals("6c6c6f20".toCharArray(), hex); + + hex = Hex.encodeHex(data, 2, 4, false); + assertArrayEquals("6C6C6F20".toCharArray(), hex); + + hex = Hex.encodeHex(data, 10, 1, true); + assertArrayEquals("64".toCharArray(), hex); + + hex = Hex.encodeHex(data, 10, 1, false); + assertArrayEquals("64".toCharArray(), hex); + } + + @Test(expected=ArrayIndexOutOfBoundsException.class) + public void testEncodeHexPartialInputUnderbounds() { + final byte data[] = "hello world".getBytes(UTF_8); + + final char[] hex = Hex.encodeHex(data, -2, 10, true); + assertArrayEquals("64".toCharArray(), hex); + } + + @Test(expected=ArrayIndexOutOfBoundsException.class) + public void testEncodeHexPartialInputOverbounds() { + final byte data[] = "hello world".getBytes(UTF_8); + + final char[] hex = Hex.encodeHex(data, 9, 10, true); + assertArrayEquals("64".toCharArray(), hex); + } + + @Test public void testEncodeHexByteString_ByteBufferOfZeroes() { final String c = Hex.encodeHexString(allocate(36)); assertEquals("000000000000000000000000000000000000000000000000000000000000000000000000", c); @@ -636,12 +713,12 @@ public class HexTest { @Test public void testGetCharset() { - Assert.assertEquals(StandardCharsets.UTF_8, new Hex(StandardCharsets.UTF_8).getCharset()); + Assert.assertEquals(UTF_8, new Hex(UTF_8).getCharset()); } @Test public void testGetCharsetName() { - Assert.assertEquals(StandardCharsets.UTF_8.name(), new Hex(StandardCharsets.UTF_8).getCharsetName()); + Assert.assertEquals(UTF_8.name(), new Hex(UTF_8).getCharsetName()); } @Test