summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--luni/src/main/java/libcore/util/HexEncoding.java99
-rw-r--r--luni/src/test/java/libcore/util/HexEncodingTest.java76
-rw-r--r--support/src/test/java/libcore/tlswire/handshake/ClientHello.java6
-rw-r--r--support/src/test/java/libcore/tlswire/handshake/HelloExtension.java2
-rw-r--r--support/src/test/java/libcore/tlswire/util/HexEncoding.java93
5 files changed, 179 insertions, 97 deletions
diff --git a/luni/src/main/java/libcore/util/HexEncoding.java b/luni/src/main/java/libcore/util/HexEncoding.java
new file mode 100644
index 0000000..f883a73
--- /dev/null
+++ b/luni/src/main/java/libcore/util/HexEncoding.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed 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 libcore.util;
+
+/**
+ * Hexadecimal encoding where each byte is represented by two hexadecimal digits.
+ */
+public class HexEncoding {
+
+ /** Hidden constructor to prevent instantiation. */
+ private HexEncoding() {}
+
+ private static final char[] HEX_DIGITS = "0123456789abcdef".toCharArray();
+
+ /**
+ * Encodes the provided data as a sequence of hexadecimal characters.
+ */
+ public static char[] encode(byte[] data) {
+ return encode(data, 0, data.length);
+ }
+
+ /**
+ * Encodes the provided data as a sequence of hexadecimal characters.
+ */
+ public static char[] encode(byte[] data, int offset, int len) {
+ char[] result = new char[len * 2];
+ for (int i = 0; i < len; i++) {
+ byte b = data[offset + i];
+ int resultIndex = 2 * i;
+ result[resultIndex] = (HEX_DIGITS[(b >>> 4) & 0x0f]);
+ result[resultIndex + 1] = (HEX_DIGITS[b & 0x0f]);
+ }
+
+ return result;
+ }
+
+ /**
+ * Decodes the provided hexadecimal string into a byte array. If {@code allowSingleChar}
+ * is {@code true} odd-length inputs are allowed and the first character is interpreted
+ * as the lower bits of the first result byte.
+ *
+ * Throws an {@code IllegalArgumentException} if the input is malformed.
+ */
+ public static byte[] decode(char[] encoded, boolean allowSingleChar) throws IllegalArgumentException {
+ int resultLengthBytes = (encoded.length + 1) / 2;
+ byte[] result = new byte[resultLengthBytes];
+
+ int resultOffset = 0;
+ int i = 0;
+ if (allowSingleChar) {
+ if ((encoded.length % 2) != 0) {
+ // Odd number of digits -- the first digit is the lower 4 bits of the first result byte.
+ result[resultOffset++] = (byte) toDigit(encoded, i);
+ i++;
+ }
+ } else {
+ if ((encoded.length % 2) != 0) {
+ throw new IllegalArgumentException("Invalid input length: " + encoded.length);
+ }
+ }
+
+ for (int len = encoded.length; i < len; i += 2) {
+ result[resultOffset++] = (byte) ((toDigit(encoded, i) << 4) | toDigit(encoded, i + 1));
+ }
+
+ return result;
+ }
+
+ private static int toDigit(char[] str, int offset) throws IllegalArgumentException {
+ // NOTE: that this isn't really a code point in the traditional sense, since we're
+ // just rejecting surrogate pairs outright.
+ int pseudoCodePoint = str[offset];
+
+ if ('0' <= pseudoCodePoint && pseudoCodePoint <= '9') {
+ return pseudoCodePoint - '0';
+ } else if ('a' <= pseudoCodePoint && pseudoCodePoint <= 'f') {
+ return 10 + (pseudoCodePoint - 'a');
+ } else if ('A' <= pseudoCodePoint && pseudoCodePoint <= 'F') {
+ return 10 + (pseudoCodePoint - 'A');
+ }
+
+ throw new IllegalArgumentException("Illegal char: " + str[offset] +
+ " at offset " + offset);
+ }
+}
diff --git a/luni/src/test/java/libcore/util/HexEncodingTest.java b/luni/src/test/java/libcore/util/HexEncodingTest.java
new file mode 100644
index 0000000..ef79f5c
--- /dev/null
+++ b/luni/src/test/java/libcore/util/HexEncodingTest.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed 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 libcore.util;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import junit.framework.TestCase;
+import static libcore.util.HexEncoding.decode;
+import static libcore.util.HexEncoding.encode;
+
+public class HexEncodingTest extends TestCase {
+ public void testEncode() {
+ final byte[] avocados = "avocados".getBytes(StandardCharsets.UTF_8);
+
+ assertArraysEqual("61766f6361646f73".toCharArray(), encode(avocados));
+ assertArraysEqual(avocados, decode(encode(avocados), false));
+ }
+
+ public void testDecode_allow4Bit() {
+ assertArraysEqual(new byte[] { 6 }, decode("6".toCharArray(), true));
+ assertArraysEqual(new byte[] { 6, 'v' }, decode("676".toCharArray(), true));
+ }
+
+ public void testDecode_disallow4Bit() {
+ try {
+ decode("676".toCharArray(), false);
+ fail();
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ public void testDecode_invalid() {
+ try {
+ decode("deadbard".toCharArray(), false);
+ fail();
+ } catch (IllegalArgumentException expected) {
+ }
+
+ // This demonstrates a difference in behaviour from apache commons : apache
+ // commons uses Character.isDigit and would successfully decode a string with
+ // arabic and devanagari characters.
+ try {
+ decode("६१٧٥٥f6361646f73".toCharArray(), false);
+ fail();
+ } catch (IllegalArgumentException expected) {
+ }
+
+ try {
+ decode("#%6361646f73".toCharArray(), false);
+ fail();
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ private static void assertArraysEqual(char[] lhs, char[] rhs) {
+ assertEquals(new String(lhs), new String(rhs));
+ }
+
+ private static void assertArraysEqual(byte[] lhs, byte[] rhs) {
+ assertEquals(Arrays.toString(lhs), Arrays.toString(rhs));
+ }
+}
diff --git a/support/src/test/java/libcore/tlswire/handshake/ClientHello.java b/support/src/test/java/libcore/tlswire/handshake/ClientHello.java
index 8d25cd5..ec88662 100644
--- a/support/src/test/java/libcore/tlswire/handshake/ClientHello.java
+++ b/support/src/test/java/libcore/tlswire/handshake/ClientHello.java
@@ -17,8 +17,8 @@
package libcore.tlswire.handshake;
import libcore.tlswire.util.TlsProtocolVersion;
-import libcore.tlswire.util.HexEncoding;
import libcore.tlswire.util.IoUtils;
+import libcore.util.HexEncoding;
import java.io.ByteArrayInputStream;
import java.io.DataInput;
import java.io.DataInputStream;
@@ -98,8 +98,8 @@ public class ClientHello extends HandshakeMessage {
@Override
public String toString() {
return "ClientHello{client version: " + clientVersion
- + ", random: " + HexEncoding.encode(random)
- + ", sessionId: " + HexEncoding.encode(sessionId)
+ + ", random: " + new String(HexEncoding.encode(random))
+ + ", sessionId: " + new String(HexEncoding.encode(sessionId))
+ ", cipher suites: " + cipherSuites
+ ", compression methods: " + compressionMethods
+ ((extensions != null) ? (", extensions: " + String.valueOf(extensions)) : "")
diff --git a/support/src/test/java/libcore/tlswire/handshake/HelloExtension.java b/support/src/test/java/libcore/tlswire/handshake/HelloExtension.java
index e3361b9..5741072 100644
--- a/support/src/test/java/libcore/tlswire/handshake/HelloExtension.java
+++ b/support/src/test/java/libcore/tlswire/handshake/HelloExtension.java
@@ -16,8 +16,8 @@
package libcore.tlswire.handshake;
-import libcore.tlswire.util.HexEncoding;
import libcore.tlswire.util.IoUtils;
+import libcore.util.HexEncoding;
import java.io.DataInput;
import java.io.IOException;
import java.util.HashMap;
diff --git a/support/src/test/java/libcore/tlswire/util/HexEncoding.java b/support/src/test/java/libcore/tlswire/util/HexEncoding.java
deleted file mode 100644
index 2061fcc..0000000
--- a/support/src/test/java/libcore/tlswire/util/HexEncoding.java
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed 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 libcore.tlswire.util;
-
-import java.nio.ByteBuffer;
-
-/**
- * Hexadecimal encoding where each byte is represented by two hexadecimal digits.
- */
-public class HexEncoding {
-
- /** Hidden constructor to prevent instantiation. */
- private HexEncoding() {}
-
- private static final char[] HEX_DIGITS = "0123456789abcdef".toCharArray();
-
- /**
- * Encodes the provided data as a hexadecimal string.
- */
- public static String encode(byte[] data) {
- return encode(data, 0, data.length);
- }
-
- /**
- * Encodes the provided data as a hexadecimal string.
- */
- public static String encode(byte[] data, int offset, int len) {
- StringBuilder result = new StringBuilder(len * 2);
- for (int i = 0; i < len; i++) {
- byte b = data[offset + i];
- result.append(HEX_DIGITS[(b >>> 4) & 0x0f]);
- result.append(HEX_DIGITS[b & 0x0f]);
- }
- return result.toString();
- }
-
- /**
- * Encodes the provided data as a hexadecimal string.
- */
- public static String encode(ByteBuffer buf) {
- return encode(buf.array(), buf.arrayOffset() + buf.position(), buf.remaining());
- }
-
- /**
- * Decodes the provided hexadecimal string into an array of bytes.
- */
- public static byte[] decode(String encoded) {
- // IMPLEMENTATION NOTE: Special care is taken to permit odd number of hexadecimal digits.
- int resultLengthBytes = (encoded.length() + 1) / 2;
- byte[] result = new byte[resultLengthBytes];
- int resultOffset = 0;
- int encodedCharOffset = 0;
- if ((encoded.length() % 2) != 0) {
- // Odd number of digits -- the first digit is the lower 4 bits of the first result byte.
- result[resultOffset++] =
- (byte) getHexadecimalDigitValue(encoded.charAt(encodedCharOffset));
- encodedCharOffset++;
- }
- for (int len = encoded.length(); encodedCharOffset < len; encodedCharOffset += 2) {
- result[resultOffset++] = (byte) (
- (getHexadecimalDigitValue(encoded.charAt(encodedCharOffset)) << 4)
- | getHexadecimalDigitValue(encoded.charAt(encodedCharOffset + 1)));
- }
- return result;
- }
-
- private static int getHexadecimalDigitValue(char c) {
- if ((c >= 'a') && (c <= 'f')) {
- return (c - 'a') + 0x0a;
- } else if ((c >= 'A') && (c <= 'F')) {
- return (c - 'A') + 0x0a;
- } else if ((c >= '0') && (c <= '9')) {
- return c - '0';
- } else {
- throw new IllegalArgumentException("Invalid hexadecimal digit at position : '" + c
- + "' (0x" + Integer.toHexString(c) + ")");
- }
- }
-}