Move methods for decode hex dump into StringUtil

Motivation:

PR #6811 introduced a public utility methods to decode hex dump and its parts, but they are not visible from netty-common.

Modifications:

1. Move the `decodeHexByte`, `decodeHexDump` and `decodeHexNibble` methods into `StringUtils`.
2. Apply these methods where applicable.
3. Remove similar methods from other locations (e.g. `HpackHex` test class).

Result:

Less code duplication.
This commit is contained in:
Nikolay Fedorovskikh 2017-06-23 02:44:09 +05:00 committed by Norman Maurer
parent 1767814a46
commit 01eb428b39
11 changed files with 92 additions and 272 deletions

View File

@ -21,7 +21,6 @@ import io.netty.util.CharsetUtil;
import io.netty.util.Recycler;
import io.netty.util.Recycler.Handle;
import io.netty.util.concurrent.FastThreadLocal;
import io.netty.util.internal.EmptyArrays;
import io.netty.util.internal.PlatformDependent;
import io.netty.util.internal.StringUtil;
import io.netty.util.internal.SystemPropertyUtil;
@ -129,21 +128,21 @@ public final class ByteBufUtil {
* Decode a 2-digit hex byte from within a string.
*/
public static byte decodeHexByte(CharSequence s, int pos) {
return HexUtil.decodeHexByte(s, pos);
return StringUtil.decodeHexByte(s, pos);
}
/**
* Decodes a string generated by {@link #hexDump(byte[])}
*/
public static byte[] decodeHexDump(CharSequence hexDump) {
return HexUtil.decodeHexDump(hexDump, 0, hexDump.length());
return StringUtil.decodeHexDump(hexDump, 0, hexDump.length());
}
/**
* Decodes part of a string generated by {@link #hexDump(byte[])}
*/
public static byte[] decodeHexDump(CharSequence hexDump, int fromIndex, int length) {
return HexUtil.decodeHexDump(hexDump, fromIndex, length);
return StringUtil.decodeHexDump(hexDump, fromIndex, length);
}
/**
@ -891,55 +890,6 @@ public final class ByteBufUtil {
return new String(buf);
}
/**
* Helper to decode half of a hexadecimal number from a string.
* @param c The ASCII character of the hexadecimal number to decode.
* Must be in the range {@code [0-9a-fA-F]}.
* @return The hexadecimal value represented in the ASCII character
* given, or {@code -1} if the character is invalid.
*/
private static int decodeHexNibble(final char c) {
// Character.digit() is not used here, as it addresses a larger
// set of characters (both ASCII and full-width latin letters).
if ('0' <= c && c <= '9') {
return c - '0';
}
if ('a' <= c && c <= 'f') {
return c - 'a' + 0xA;
}
if ('A' <= c && c <= 'F') {
return c - 'A' + 0xA;
}
return -1;
}
private static byte decodeHexByte(CharSequence s, int pos) {
int hi = decodeHexNibble(s.charAt(pos));
int lo = decodeHexNibble(s.charAt(pos + 1));
if (hi == -1 || lo == -1) {
throw new IllegalArgumentException(String.format(
"invalid hex byte '%s' at index %d of '%s'", s.subSequence(pos, pos + 2), pos, s));
}
return (byte) ((hi << 4) + lo);
}
private static byte[] decodeHexDump(CharSequence hexDump, int fromIndex, int length) {
if (length < 0 || (length & 1) != 0) {
throw new IllegalArgumentException("length: " + length);
}
if (length == 0) {
return EmptyArrays.EMPTY_BYTES;
}
byte[] bytes = new byte[length >>> 1];
for (int i = 0; i < length; i += 2) {
bytes[i >>> 1] = decodeHexByte(hexDump, fromIndex + i);
}
return bytes;
}
private static String hexDump(byte[] array, int fromIndex, int length) {
if (length < 0) {
throw new IllegalArgumentException("length: " + length);

View File

@ -33,7 +33,6 @@ import java.util.Map;
import static io.netty.util.internal.ObjectUtil.*;
import static io.netty.util.internal.StringUtil.*;
import static io.netty.buffer.ByteBufUtil.decodeHexByte;
/**
* Splits an HTTP query string into a path string and key-value parameter pairs.

View File

@ -33,6 +33,7 @@ package io.netty.handler.codec.http2;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.util.internal.StringUtil;
import org.junit.Before;
import org.junit.Test;
@ -55,11 +56,11 @@ public class HpackDecoderTest {
private Http2Headers mockHeaders;
private static String hex(String s) {
return HpackHex.encodeHexString(s.getBytes());
return StringUtil.toHexString(s.getBytes());
}
private void decode(String encoded) throws Http2Exception {
byte[] b = HpackHex.decodeHex(encoded.toCharArray());
byte[] b = StringUtil.decodeHexDump(encoded);
ByteBuf in = Unpooled.wrappedBuffer(b);
try {
hpackDecoder.decode(0, in, mockHeaders);
@ -79,7 +80,7 @@ public class HpackDecoderTest {
byte[] input = {(byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0x07};
ByteBuf in = Unpooled.wrappedBuffer(input);
try {
assertEquals(Integer.MAX_VALUE, decodeULE128(in, 0));
assertEquals(MAX_VALUE, decodeULE128(in, 0));
} finally {
in.release();
}
@ -224,7 +225,7 @@ public class HpackDecoderTest {
@Test(expected = Http2Exception.class)
public void testIncompleteIndex() throws Http2Exception {
byte[] compressed = HpackHex.decodeHex("FFF0".toCharArray());
byte[] compressed = StringUtil.decodeHexDump("FFF0");
ByteBuf in = Unpooled.wrappedBuffer(compressed);
try {
hpackDecoder.decode(0, in, mockHeaders);

View File

@ -1,162 +0,0 @@
/*
* Copyright 2015 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.netty.handler.codec.http2;
/**
* Extracted from org/apache/commons/codec/binary/Hex.java Copyright Apache Software Foundation
*/
final class HpackHex {
private HpackHex() {
}
/**
* Used to build output as HpackHex
*/
private static final char[] DIGITS_LOWER =
{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
/**
* Used to build output as HpackHex
*/
private static final char[] DIGITS_UPPER =
{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
/**
* 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
* @return A byte array containing binary data decoded from the supplied char array.
* @throws IllegalArgumentException Thrown if an odd number or illegal of characters is supplied
*/
public static byte[] decodeHex(char[] data) {
int len = data.length;
if ((len & 0x01) != 0) {
throw new IllegalArgumentException("Odd number of characters.");
}
byte[] out = new byte[len >> 1];
// two characters form the hex value.
for (int i = 0, j = 0; j < len; i++) {
int f = toDigit(data[j], j) << 4;
j++;
f = f | toDigit(data[j], j);
j++;
out[i] = (byte) (f & 0xFF);
}
return out;
}
/**
* Converts an array of bytes into an array of characters representing the hexadecimal values of
* each byte in order. The returned array will be double the length of the passed array, as it
* takes two characters to represent any given byte.
*
* @param data a byte[] to convert to HpackHex characters
* @return A char[] containing hexadecimal characters
*/
public static char[] encodeHex(byte[] data) {
return encodeHex(data, true);
}
/**
* Converts an array of bytes into an array of characters representing the hexadecimal values of
* each byte in order. The returned array will be double the length of the passed array, as it
* takes two characters to represent any given byte.
*
* @param data a byte[] to convert to HpackHex characters
* @param toLowerCase <code>true</code> converts to lowercase, <code>false</code> to uppercase
* @return A char[] containing hexadecimal characters
* @since 1.4
*/
public static char[] encodeHex(byte[] data, boolean toLowerCase) {
return encodeHex(data, toLowerCase ? DIGITS_LOWER : DIGITS_UPPER);
}
/**
* Converts an array of bytes into an array of characters representing the hexadecimal values of
* each byte in order. The returned array will be double the length of the passed array, as it
* takes two characters to represent any given byte.
*
* @param data a byte[] to convert to HpackHex characters
* @param toDigits the output alphabet
* @return A char[] containing hexadecimal characters
* @since 1.4
*/
protected static char[] encodeHex(byte[] data, char[] toDigits) {
int l = data.length;
char[] out = new char[l << 1];
// two characters form the hex value.
for (int i = 0, j = 0; i < l; i++) {
out[j++] = toDigits[(0xF0 & data[i]) >>> 4];
out[j++] = toDigits[0x0F & data[i]];
}
return out;
}
/**
* Converts an array of bytes into a String representing the hexadecimal values of each byte in
* order. The returned String will be double the length of the passed array, as it takes two
* characters to represent any given byte.
*
* @param data a byte[] to convert to HpackHex characters
* @return A String containing hexadecimal characters
* @since 1.4
*/
public static String encodeHexString(byte[] data) {
return new String(encodeHex(data));
}
/**
* Converts a hexadecimal character to an integer.
*
* @param ch A character to convert to an integer digit
* @param index The index of the character in the source
* @return An integer
* @throws IllegalArgumentException Thrown if ch is an illegal hex character
*/
protected static int toDigit(char ch, int index) throws IllegalArgumentException {
int digit = Character.digit(ch, 16);
if (digit == -1) {
throw new IllegalArgumentException("Illegal hexadecimal character " + ch + " at index " + index);
}
return digit;
}
}

View File

@ -41,8 +41,8 @@ import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.util.internal.StringUtil;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Type;
@ -71,11 +71,11 @@ final class HpackTestCase {
private HpackTestCase() {
}
static HpackTestCase load(InputStream is) throws IOException {
static HpackTestCase load(InputStream is) {
InputStreamReader r = new InputStreamReader(is);
HpackTestCase hpackTestCase = GSON.fromJson(r, HpackTestCase.class);
for (HeaderBlock headerBlock : hpackTestCase.headerBlocks) {
headerBlock.encodedBytes = HpackHex.decodeHex(headerBlock.getEncodedStr().toCharArray());
headerBlock.encodedBytes = StringUtil.decodeHexDump(headerBlock.getEncodedStr());
}
return hpackTestCase;
}
@ -92,7 +92,7 @@ final class HpackTestCase {
if (!Arrays.equals(actual, headerBlock.encodedBytes)) {
throw new AssertionError(
"\nEXPECTED:\n" + headerBlock.getEncodedStr() +
"\nACTUAL:\n" + HpackHex.encodeHexString(actual));
"\nACTUAL:\n" + StringUtil.toHexString(actual));
}
List<HpackHeaderField> actualDynamicTable = new ArrayList<HpackHeaderField>();

View File

@ -17,6 +17,7 @@ package io.netty.util;
import io.netty.util.internal.PlatformDependent;
import io.netty.util.internal.SocketUtils;
import io.netty.util.internal.StringUtil;
import io.netty.util.internal.SystemPropertyUtil;
import io.netty.util.internal.logging.InternalLogger;
import io.netty.util.internal.logging.InternalLoggerFactory;
@ -381,25 +382,6 @@ public final class NetUtil {
return null;
}
/**
* Convert ASCII hexadecimal character to the {@code int} value.
* Unlike {@link Character#digit(char, int)}, returns {@code 0} if character is not a HEX-represented.
*/
private static int getIntValue(char c) {
if (c >= '0' && c <= '9') {
return c - '0';
}
if (c >= 'A' && c <= 'F') {
// 0xA - a start value in sequence 'A'..'F'
return c - 'A' + 0xA;
}
if (c >= 'a' && c <= 'f') {
// 0xA - a start value in sequence 'a'..'f'
return c - 'a' + 0xA;
}
return 0;
}
private static int decimalDigit(String str, int pos) {
return str.charAt(pos) - '0';
}
@ -808,7 +790,7 @@ public final class NetUtil {
// at most 4 consecutive bytes we can use bit shifting to accomplish this.
// The most significant byte will be encountered first, and reside in the right most
// position of the following integer
value += getIntValue(c) << ((i - begin) << 2);
value += StringUtil.decodeHexNibble(c) << ((i - begin) << 2);
break;
}
}

View File

@ -135,13 +135,13 @@ public final class MacAddressUtil {
* address.
*/
public static byte[] defaultMachineId() {
byte[] bestMacAddr = MacAddressUtil.bestAvailableMac();
byte[] bestMacAddr = bestAvailableMac();
if (bestMacAddr == null) {
bestMacAddr = new byte[EUI64_MAC_ADDRESS_LENGTH];
PlatformDependent.threadLocalRandom().nextBytes(bestMacAddr);
logger.warn(
"Failed to find a usable hardware address from the network interfaces; using random bytes: {}",
MacAddressUtil.formatAddress(bestMacAddr));
formatAddress(bestMacAddr));
}
return bestMacAddr;
}
@ -173,14 +173,14 @@ public final class MacAddressUtil {
int j = 0;
for (int i = 0; i < end; ++i, j += 3) {
final int sIndex = j + 2;
machineId[i] = (byte) Integer.parseInt(value.substring(j, sIndex), 16);
machineId[i] = StringUtil.decodeHexByte(value, j);
if (value.charAt(sIndex) != separator) {
throw new IllegalArgumentException("expected separator '" + separator + " but got '" +
value.charAt(sIndex) + "' at index: " + sIndex);
}
}
machineId[end] = (byte) Integer.parseInt(value.substring(j, value.length()), 16);
machineId[end] = StringUtil.decodeHexByte(value, j);
return machineId;
}

View File

@ -212,6 +212,69 @@ public final class StringUtil {
return dst;
}
/**
* Helper to decode half of a hexadecimal number from a string.
* @param c The ASCII character of the hexadecimal number to decode.
* Must be in the range {@code [0-9a-fA-F]}.
* @return The hexadecimal value represented in the ASCII character
* given, or {@code -1} if the character is invalid.
*/
public static int decodeHexNibble(final char c) {
// Character.digit() is not used here, as it addresses a larger
// set of characters (both ASCII and full-width latin letters).
if (c >= '0' && c <= '9') {
return c - '0';
}
if (c >= 'A' && c <= 'F') {
return c - 'A' + 0xA;
}
if (c >= 'a' && c <= 'f') {
return c - 'a' + 0xA;
}
return -1;
}
/**
* Decode a 2-digit hex byte from within a string.
*/
public static byte decodeHexByte(CharSequence s, int pos) {
int hi = decodeHexNibble(s.charAt(pos));
int lo = decodeHexNibble(s.charAt(pos + 1));
if (hi == -1 || lo == -1) {
throw new IllegalArgumentException(String.format(
"invalid hex byte '%s' at index %d of '%s'", s.subSequence(pos, pos + 2), pos, s));
}
return (byte) ((hi << 4) + lo);
}
/**
* Decodes part of a string with <a href="http://en.wikipedia.org/wiki/Hex_dump">hex dump</a>
*
* @param hexDump a {@link CharSequence} which contains the hex dump
* @param fromIndex start of hex dump in {@code hexDump}
* @param length hex string length
*/
public static byte[] decodeHexDump(CharSequence hexDump, int fromIndex, int length) {
if (length < 0 || (length & 1) != 0) {
throw new IllegalArgumentException("length: " + length);
}
if (length == 0) {
return EmptyArrays.EMPTY_BYTES;
}
byte[] bytes = new byte[length >>> 1];
for (int i = 0; i < length; i += 2) {
bytes[i >>> 1] = decodeHexByte(hexDump, fromIndex + i);
}
return bytes;
}
/**
* Decodes a <a href="http://en.wikipedia.org/wiki/Hex_dump">hex dump</a>
*/
public static byte[] decodeHexDump(CharSequence hexDump) {
return decodeHexDump(hexDump, 0, hexDump.length());
}
/**
* The shortcut to {@link #simpleClassName(Class) simpleClassName(o.getClass())}.
*/

View File

@ -15,6 +15,7 @@
*/
package io.netty.util;
import io.netty.util.internal.StringUtil;
import org.junit.Test;
import java.net.Inet6Address;
@ -772,7 +773,7 @@ public class NetUtilTest {
StringBuilder buf = new StringBuilder(value.length << 1);
for (byte b: value) {
String hex = Integer.toHexString(b & 0xFF);
String hex = StringUtil.byteToHexString(b);
if (hex.length() == 1) {
buf.append('0');
}
@ -782,15 +783,6 @@ public class NetUtilTest {
}
private static byte[] unhex(String value) {
if (value == null) {
return null;
}
byte[] buf = new byte[value.length() >>> 1];
for (int i = 0; i < buf.length; i ++) {
buf[i] = (byte) Integer.parseInt(value.substring(i << 1, i + 1 << 1), 16);
}
return buf;
return value != null ? StringUtil.decodeHexDump(value) : null;
}
}

View File

@ -20,6 +20,7 @@ import io.netty.buffer.ByteBufUtil;
import io.netty.buffer.Unpooled;
import io.netty.util.internal.EmptyArrays;
import io.netty.util.concurrent.FastThreadLocal;
import io.netty.util.internal.StringUtil;
import javax.net.ssl.ManagerFactoryParameters;
import javax.net.ssl.TrustManager;
@ -193,12 +194,7 @@ public final class FingerprintTrustManagerFactory extends SimpleTrustManagerFact
throw new IllegalArgumentException("malformed fingerprint: " + f + " (expected: SHA1)");
}
byte[] farr = new byte[SHA1_BYTE_LEN];
for (int i = 0; i < farr.length; i ++) {
int strIdx = i << 1;
farr[i] = (byte) Integer.parseInt(f.substring(strIdx, strIdx + 2), 16);
}
list.add(farr);
list.add(StringUtil.decodeHexDump(f));
}
return list.toArray(new byte[list.size()][]);

View File

@ -21,7 +21,6 @@ import static org.hamcrest.CoreMatchers.nullValue;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.junit.Assume.assumeTrue;
import java.io.File;
@ -32,7 +31,6 @@ import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import javax.net.ssl.SSLEngine;
import javax.xml.bind.DatatypeConverter;
import org.junit.Test;
@ -64,6 +62,7 @@ import io.netty.util.ReferenceCountUtil;
import io.netty.util.ReferenceCounted;
import io.netty.util.concurrent.Promise;
import io.netty.util.internal.ObjectUtil;
import io.netty.util.internal.StringUtil;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@ -125,7 +124,7 @@ public class SniHandlerTest {
}
@Parameterized.Parameters(name = "{index}: sslProvider={0}")
public static Iterable<? extends Object> data() {
public static Iterable<?> data() {
List<SslProvider> params = new ArrayList<SslProvider>(3);
if (OpenSsl.isAvailable()) {
params.add(SslProvider.OPENSSL);
@ -172,8 +171,8 @@ public class SniHandlerTest {
"170019001800230000000d0020001e060106020603050105020503040104" +
"0204030301030203030201020202030016000000170000";
ch.writeInbound(Unpooled.wrappedBuffer(DatatypeConverter.parseHexBinary(tlsHandshakeMessageHex1)));
ch.writeInbound(Unpooled.wrappedBuffer(DatatypeConverter.parseHexBinary(tlsHandshakeMessageHex)));
ch.writeInbound(Unpooled.wrappedBuffer(StringUtil.decodeHexDump(tlsHandshakeMessageHex1)));
ch.writeInbound(Unpooled.wrappedBuffer(StringUtil.decodeHexDump(tlsHandshakeMessageHex)));
// This should produce an alert
assertTrue(ch.finish());
@ -221,8 +220,8 @@ public class SniHandlerTest {
// Push the handshake message.
// Decode should fail because of the badly encoded "HostName" string in the SNI extension
// that isn't ASCII as per RFC 6066 - https://tools.ietf.org/html/rfc6066#page-6
ch.writeInbound(Unpooled.wrappedBuffer(DatatypeConverter.parseHexBinary(tlsHandshakeMessageHex1)));
ch.writeInbound(Unpooled.wrappedBuffer(DatatypeConverter.parseHexBinary(tlsHandshakeMessageHex)));
ch.writeInbound(Unpooled.wrappedBuffer(StringUtil.decodeHexDump(tlsHandshakeMessageHex1)));
ch.writeInbound(Unpooled.wrappedBuffer(StringUtil.decodeHexDump(tlsHandshakeMessageHex)));
} finally {
ch.finishAndReleaseAll();
}