diff options
| author | 2019-12-06 12:02:51 -0800 | |
|---|---|---|
| committer | 2019-12-07 01:10:03 +0000 | |
| commit | 5b62f3d496ebdb91ef60f01c1276b66cd8364e1a (patch) | |
| tree | abf69ceccf3bc0de46a1a6145fefc07939fc2c1a | |
| parent | 69015a21119f7604ddf41636ecfd927b4b639ac7 (diff) | |
Copy libcore.utils.HexEncoding into Wifi module
@CorePlatformApi's cannot be depended on by Wifi
Mainline module. Thus create a copy and migrate
usages over.
Bug: 145409537
Test: atest FrameworksWifiApiTests
Change-Id: If4133bdd9ac9ca005538a03592462d2e1365c0d8
7 files changed, 318 insertions, 10 deletions
diff --git a/wifi/java/android/net/wifi/aware/PublishConfig.java b/wifi/java/android/net/wifi/aware/PublishConfig.java index 1886b7ef4c8d..a8844c1d3812 100644 --- a/wifi/java/android/net/wifi/aware/PublishConfig.java +++ b/wifi/java/android/net/wifi/aware/PublishConfig.java @@ -19,11 +19,10 @@ package android.net.wifi.aware; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.net.wifi.util.HexEncoding; import android.os.Parcel; import android.os.Parcelable; -import libcore.util.HexEncoding; - import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.nio.charset.StandardCharsets; diff --git a/wifi/java/android/net/wifi/aware/SubscribeConfig.java b/wifi/java/android/net/wifi/aware/SubscribeConfig.java index f0f758170bf2..76780f421af2 100644 --- a/wifi/java/android/net/wifi/aware/SubscribeConfig.java +++ b/wifi/java/android/net/wifi/aware/SubscribeConfig.java @@ -19,11 +19,10 @@ package android.net.wifi.aware; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.net.wifi.util.HexEncoding; import android.os.Parcel; import android.os.Parcelable; -import libcore.util.HexEncoding; - import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.nio.charset.StandardCharsets; diff --git a/wifi/java/android/net/wifi/aware/WifiAwareAgentNetworkSpecifier.java b/wifi/java/android/net/wifi/aware/WifiAwareAgentNetworkSpecifier.java index 5ec4c8b13ee8..c66733472d0e 100644 --- a/wifi/java/android/net/wifi/aware/WifiAwareAgentNetworkSpecifier.java +++ b/wifi/java/android/net/wifi/aware/WifiAwareAgentNetworkSpecifier.java @@ -17,12 +17,11 @@ package android.net.wifi.aware; import android.net.NetworkSpecifier; +import android.net.wifi.util.HexEncoding; import android.os.Parcel; import android.os.Parcelable; import android.util.Log; -import libcore.util.HexEncoding; - import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Arrays; diff --git a/wifi/java/android/net/wifi/aware/WifiAwareManager.java b/wifi/java/android/net/wifi/aware/WifiAwareManager.java index 7b37d652426d..65a6c4d051cd 100644 --- a/wifi/java/android/net/wifi/aware/WifiAwareManager.java +++ b/wifi/java/android/net/wifi/aware/WifiAwareManager.java @@ -26,6 +26,7 @@ import android.content.Context; import android.net.ConnectivityManager; import android.net.NetworkRequest; import android.net.NetworkSpecifier; +import android.net.wifi.util.HexEncoding; import android.os.Binder; import android.os.Build; import android.os.Bundle; @@ -36,8 +37,6 @@ import android.os.Process; import android.os.RemoteException; import android.util.Log; -import libcore.util.HexEncoding; - import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.ref.WeakReference; diff --git a/wifi/java/android/net/wifi/util/HexEncoding.java b/wifi/java/android/net/wifi/util/HexEncoding.java new file mode 100644 index 000000000000..9ebf947e2dc3 --- /dev/null +++ b/wifi/java/android/net/wifi/util/HexEncoding.java @@ -0,0 +1,183 @@ +/* + * 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 android.net.wifi.util; + +/** + * Hexadecimal encoding where each byte is represented by two hexadecimal digits. + * + * Note: this is copied from {@link libcore.util.HexEncoding}. + * + * @hide + */ +public class HexEncoding { + + private static final char[] LOWER_CASE_DIGITS = { + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' + }; + + private static final char[] UPPER_CASE_DIGITS = { + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' + }; + + /** Hidden constructor to prevent instantiation. */ + private HexEncoding() {} + + /** + * Encodes the provided byte as a two-digit hexadecimal String value. + */ + public static String encodeToString(byte b, boolean upperCase) { + char[] digits = upperCase ? UPPER_CASE_DIGITS : LOWER_CASE_DIGITS; + char[] buf = new char[2]; // We always want two digits. + buf[0] = digits[(b >> 4) & 0xf]; + buf[1] = digits[b & 0xf]; + return new String(buf, 0, 2); + } + + /** + * Encodes the provided data as a sequence of hexadecimal characters. + */ + public static char[] encode(byte[] data) { + return encode(data, 0, data.length, true /* upperCase */); + } + + /** + * Encodes the provided data as a sequence of hexadecimal characters. + */ + public static char[] encode(byte[] data, boolean upperCase) { + return encode(data, 0, data.length, upperCase); + } + + /** + * Encodes the provided data as a sequence of hexadecimal characters. + */ + public static char[] encode(byte[] data, int offset, int len) { + return encode(data, offset, len, true /* upperCase */); + } + + /** + * Encodes the provided data as a sequence of hexadecimal characters. + */ + private static char[] encode(byte[] data, int offset, int len, boolean upperCase) { + char[] digits = upperCase ? UPPER_CASE_DIGITS : LOWER_CASE_DIGITS; + char[] result = new char[len * 2]; + for (int i = 0; i < len; i++) { + byte b = data[offset + i]; + int resultIndex = 2 * i; + result[resultIndex] = (digits[(b >> 4) & 0x0f]); + result[resultIndex + 1] = (digits[b & 0x0f]); + } + + return result; + } + + /** + * Encodes the provided data as a sequence of hexadecimal characters. + */ + public static String encodeToString(byte[] data) { + return encodeToString(data, true /* upperCase */); + } + + /** + * Encodes the provided data as a sequence of hexadecimal characters. + */ + public static String encodeToString(byte[] data, boolean upperCase) { + return new String(encode(data, upperCase)); + } + + /** + * Decodes the provided hexadecimal string into a byte array. Odd-length inputs + * are not allowed. + * + * Throws an {@code IllegalArgumentException} if the input is malformed. + */ + public static byte[] decode(String encoded) throws IllegalArgumentException { + return decode(encoded.toCharArray()); + } + + /** + * 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(String encoded, boolean allowSingleChar) + throws IllegalArgumentException { + return decode(encoded.toCharArray(), allowSingleChar); + } + + /** + * Decodes the provided hexadecimal string into a byte array. Odd-length inputs + * are not allowed. + * + * Throws an {@code IllegalArgumentException} if the input is malformed. + */ + public static byte[] decode(char[] encoded) throws IllegalArgumentException { + return decode(encoded, false); + } + + /** + * 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 encodedLength = encoded.length; + int resultLengthBytes = (encodedLength + 1) / 2; + byte[] result = new byte[resultLengthBytes]; + + int resultOffset = 0; + int i = 0; + if (allowSingleChar) { + if ((encodedLength % 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 ((encodedLength % 2) != 0) { + throw new IllegalArgumentException("Invalid input length: " + encodedLength); + } + } + + for (; i < encodedLength; 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/wifi/tests/src/android/net/wifi/aware/WifiAwareManagerTest.java b/wifi/tests/src/android/net/wifi/aware/WifiAwareManagerTest.java index 3483ff8a4664..200c0e31cb22 100644 --- a/wifi/tests/src/android/net/wifi/aware/WifiAwareManagerTest.java +++ b/wifi/tests/src/android/net/wifi/aware/WifiAwareManagerTest.java @@ -36,6 +36,7 @@ import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.net.MacAddress; import android.net.wifi.RttManager; +import android.net.wifi.util.HexEncoding; import android.os.Build; import android.os.Handler; import android.os.IBinder; @@ -44,8 +45,6 @@ import android.os.test.TestLooper; import androidx.test.filters.SmallTest; -import libcore.util.HexEncoding; - import org.junit.Before; import org.junit.Rule; import org.junit.Test; diff --git a/wifi/tests/src/android/net/wifi/util/HexEncodingTest.java b/wifi/tests/src/android/net/wifi/util/HexEncodingTest.java new file mode 100644 index 000000000000..0d751389e244 --- /dev/null +++ b/wifi/tests/src/android/net/wifi/util/HexEncodingTest.java @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2019 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 android.net.wifi.util; + +import static android.net.wifi.util.HexEncoding.decode; +import static android.net.wifi.util.HexEncoding.encode; +import static android.net.wifi.util.HexEncoding.encodeToString; + +import junit.framework.TestCase; + +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.Locale; + +/** Copied from {@link libcore.libcore.util.HexEncodingTest}. */ +public class HexEncodingTest extends TestCase { + + public void testEncodeByte() { + Object[][] testCases = new Object[][]{ + {0x01, "01"}, + {0x09, "09"}, + {0x0A, "0A"}, + {0x0F, "0F"}, + {0x10, "10"}, + {0x1F, "1F"}, + {0x20, "20"}, + {0x7F, "7F"}, + {0x80, "80"}, + {0xFF, "FF"}, + }; + for (Object[] testCase : testCases) { + Number toEncode = (Number) testCase[0]; + String expected = (String) testCase[1]; + + String actualUpper = encodeToString(toEncode.byteValue(), true /* upperCase */); + assertEquals(upper(expected), actualUpper); + + String actualLower = encodeToString(toEncode.byteValue(), false /* upperCase */); + assertEquals(lower(expected), actualLower); + } + } + + public void testEncodeBytes() { + Object[][] testCases = new Object[][]{ + {"avocados".getBytes(StandardCharsets.UTF_8), "61766F6361646F73"}, + }; + + for (Object[] testCase : testCases) { + byte[] bytes = (byte[]) testCase[0]; + String encodedLower = lower((String) testCase[1]); + String encodedUpper = upper((String) testCase[1]); + + assertArraysEqual(encodedUpper.toCharArray(), encode(bytes)); + assertArraysEqual(encodedUpper.toCharArray(), encode(bytes, true /* upperCase */)); + assertArraysEqual(encodedLower.toCharArray(), encode(bytes, false /* upperCase */)); + + assertArraysEqual(bytes, decode(encode(bytes), false /* allowSingleChar */)); + + // Make sure we can handle lower case hex encodings as well. + assertArraysEqual(bytes, + decode(encodedLower.toCharArray(), false /* allowSingleChar */)); + } + } + + public void testDecode_allow4Bit() { + assertArraysEqual(new byte[]{6}, decode("6".toCharArray(), true)); + assertArraysEqual(new byte[]{6, 0x76}, decode("676".toCharArray(), true)); + } + + public void testDecode_disallow4Bit() { + try { + decode("676".toCharArray(), false /* allowSingleChar */); + fail(); + } catch (IllegalArgumentException expected) { + } + } + + public void testDecode_invalid() { + try { + decode("DEADBARD".toCharArray(), false /* allowSingleChar */); + 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 /* allowSingleChar */); + fail(); + } catch (IllegalArgumentException expected) { + } + + try { + decode("#%6361646F73".toCharArray(), false /* allowSingleChar */); + 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)); + } + + private static String lower(String string) { + return string.toLowerCase(Locale.ROOT); + } + + private static String upper(String string) { + return string.toUpperCase(Locale.ROOT); + } +} |