From 2753cb6ea8475ebba05d4a860077a6aec9ab55f1 Mon Sep 17 00:00:00 2001 From: Nicolas Portmann Date: Fri, 5 Jun 2020 17:57:23 +0200 Subject: [PATCH 01/12] Add methods to convert between hexadecimal strings and bytes --- .../Common/src/System/HexConverter.cs | 49 +++++++++ .../src/Resources/Strings.resx | 6 ++ .../src/System/Convert.cs | 92 ++++++++++++++++ .../System.Runtime.Extensions.Tests.csproj | 4 +- .../tests/System/Convert.FromHexString.cs | 101 ++++++++++++++++++ .../tests/System/Convert.ToHexString.cs | 66 ++++++++++++ .../System.Runtime/ref/System.Runtime.cs | 5 + 7 files changed, 322 insertions(+), 1 deletion(-) create mode 100644 src/libraries/System.Runtime.Extensions/tests/System/Convert.FromHexString.cs create mode 100644 src/libraries/System.Runtime.Extensions/tests/System/Convert.ToHexString.cs diff --git a/src/libraries/Common/src/System/HexConverter.cs b/src/libraries/Common/src/System/HexConverter.cs index a1d76cca277335..0a9d378f951f7b 100644 --- a/src/libraries/Common/src/System/HexConverter.cs +++ b/src/libraries/Common/src/System/HexConverter.cs @@ -149,5 +149,54 @@ public static char ToCharLower(int value) return (char)value; } + + public static bool TryDecodeFromUtf16Upper(ReadOnlySpan chars, Span bytes) + { + int i = 0; + int j = 0; + + while (j < bytes.Length) + { + int charHi = chars[i++]; + int charLo = chars[i++]; + + if (charHi > 0xFF) + return false; + if (charLo > 0xFF) + return false; + + int byteHi = DecodingMap[charHi]; + int byteLo = DecodingMap[charLo]; + + if (byteHi == -1) + return false; + if (byteLo == -1) + return false; + + bytes[j++] = (byte)((byteHi << 4) | byteLo); + } + + return true; + } + + private static ReadOnlySpan DecodingMap => new sbyte[] // rely on C# compiler optimization to reference static data + { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, + -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 + }; } } diff --git a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx index 8dbea2ed27172f..b97f00f3bad03f 100644 --- a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx +++ b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx @@ -2224,6 +2224,12 @@ No format specifiers were provided. + + The input is not a valid hex string as it contains a non-hex character. + + + The input is not a valid hex string as its length is not a multiple of 2. + Cannot find a matching quote character for the character '{0}'. diff --git a/src/libraries/System.Private.CoreLib/src/System/Convert.cs b/src/libraries/System.Private.CoreLib/src/System/Convert.cs index ec6b92a96d569b..8f89b0531fcb6d 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Convert.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Convert.cs @@ -2838,5 +2838,97 @@ private static unsafe int FromBase64_ComputeResultLength(char* inputPtr, int inp // Done: return (usefulInputLength / 4) * 3 + padding; } + + /// + /// Converts the specified string, which encodes binary data as hex characters, to an equivalent 8-bit unsigned integer array. + /// + /// The string to convert. + /// An array of 8-bit unsigned integers that is equivalent to . + /// is null. + /// The length of , is not zero or a multiple of 2. + /// The format of is invalid. contains a non-hex character. + public static byte[] FromHexString(string s) + { + if (s == null) + throw new ArgumentNullException(nameof(s)); + + return FromHexString(s.AsSpan()); + } + + /// + /// Converts the span, which encodes binary data as hex characters, to an equivalent 8-bit unsigned integer array. + /// + /// The span to convert. + /// An array of 8-bit unsigned integers that is equivalent to . + /// The length of , is not zero or a multiple of 2. + /// The format of is invalid. contains a non-hex character. + public static byte[] FromHexString(ReadOnlySpan chars) + { + if (chars.Length == 0) + return Array.Empty(); + if (chars.Length % 2 != 0) + throw new FormatException(SR.Format_BadHexLength); + + byte[] result = GC.AllocateUninitializedArray(chars.Length / 2); + + if (!HexConverter.TryDecodeFromUtf16Upper(chars, result)) + throw new FormatException(SR.Format_BadHexChar); + + return result; + } + + /// + /// Converts an array of 8-bit unsigned integers to its equivalent string representation that is encoded with hex characters. + /// + /// An array of 8-bit unsigned integers. + /// The string representation in hex of the elements in . + /// is null. + public static string ToHexString(byte[] inArray) + { + if (inArray == null) + throw new ArgumentNullException(nameof(inArray)); + + return ToHexString(new ReadOnlySpan(inArray)); + } + + /// + /// Converts a subset of an array of 8-bit unsigned integers to its equivalent string representation that is encoded with hex characters. + /// Parameters specify the subset as an offset in the input array and the number of elements in the array to convert. + /// + /// An array of 8-bit unsigned integers. + /// An offset in . + /// The number of elements of to convert. + /// The string representation in hex of elements of , starting at position . + /// is null. + /// or is negative. + /// plus is greater than the length of . + public static string ToHexString(byte[] inArray, int offset, int length) + { + if (inArray == null) + throw new ArgumentNullException(nameof(inArray)); + if (length < 0) + throw new ArgumentOutOfRangeException(nameof(length), SR.ArgumentOutOfRange_Index); + if (offset < 0) + throw new ArgumentOutOfRangeException(nameof(offset), SR.ArgumentOutOfRange_GenericPositive); + if (offset > (inArray.Length - length)) + throw new ArgumentOutOfRangeException(nameof(offset), SR.ArgumentOutOfRange_OffsetLength); + + return ToHexString(new ReadOnlySpan(inArray, offset, length)); + } + + /// + /// Converts a span of 8-bit unsigned integers to its equivalent string representation that is encoded with hex characters. + /// + /// A span of 8-bit unsigned integers. + /// The string representation in hex of the elements in . + public static string ToHexString(ReadOnlySpan bytes) + { + if (bytes.Length == 0) + return string.Empty; + if ((long)bytes.Length * 2 > int.MaxValue) + throw new OutOfMemoryException(); + + return HexConverter.ToString(bytes, HexConverter.Casing.Upper); + } } // class Convert } // namespace diff --git a/src/libraries/System.Runtime.Extensions/tests/System.Runtime.Extensions.Tests.csproj b/src/libraries/System.Runtime.Extensions/tests/System.Runtime.Extensions.Tests.csproj index 968f8f18097fab..de941ae127d527 100644 --- a/src/libraries/System.Runtime.Extensions/tests/System.Runtime.Extensions.Tests.csproj +++ b/src/libraries/System.Runtime.Extensions/tests/System.Runtime.Extensions.Tests.csproj @@ -1,4 +1,4 @@ - + true $(DefineConstants);Unix @@ -39,6 +39,7 @@ + @@ -48,6 +49,7 @@ + diff --git a/src/libraries/System.Runtime.Extensions/tests/System/Convert.FromHexString.cs b/src/libraries/System.Runtime.Extensions/tests/System/Convert.FromHexString.cs new file mode 100644 index 00000000000000..f8fc4f6268dc35 --- /dev/null +++ b/src/libraries/System.Runtime.Extensions/tests/System/Convert.FromHexString.cs @@ -0,0 +1,101 @@ +using System.Text; +using Xunit; + +namespace System.Tests +{ + public class ConvertFromHexStringTests + { + [Fact] + public static void KnownByteSequence() + { + string inputString = "000102FDFEFF"; + Assert.Equal(new byte[] { 0x00, 0x01, 0x02, 0xFD, 0xFE, 0xFF }, Convert.FromHexString(inputString)); + } + + [Fact] + public static void CompleteValueRange() + { + byte[] values = new byte[256]; + StringBuilder sb = new StringBuilder(256); + for (int i = 0; i < values.Length; i++) + { + values[i] = (byte)i; + sb.Append(i.ToString("X2")); + } + + Assert.Equal(values, Convert.FromHexString(sb.ToString())); + } + + [Fact] + public static void InvalidInputString_Null() + { + Assert.Throws(() => Convert.FromHexString(null)); + } + + [Fact] + public static void InvalidInputString_HalfByte() + { + Assert.Throws(() => Convert.FromHexString("ABC")); + } + + [Fact] + public static void InvalidInputString_BadFirstCharacter() + { + Assert.Throws(() => Convert.FromHexString("x0")); + } + + [Fact] + public static void InvalidInputString_BadSecondCharacter() + { + Assert.Throws(() => Convert.FromHexString("0x")); + } + + [Fact] + public static void InvalidInputString_NonAsciiCharacter() + { + Assert.Throws(() => Convert.FromHexString("0\u0308")); + } + + [Fact] + public static void InvalidInputString_Lowercase() + { + Assert.Throws(() => Convert.FromHexString("010a")); + } + + [Fact] + public static void InvalidInputString_ZeroWidthSpace() + { + Assert.Throws(() => Convert.FromHexString("\u200B 000102FDFEFF")); + } + + [Fact] + public static void InvalidInputString_LeadingWhiteSpace() + { + Assert.Throws(() => Convert.FromHexString(" 000102FDFEFF")); + } + + [Fact] + public static void InvalidInputString_TrailingWhiteSpace() + { + Assert.Throws(() => Convert.FromHexString("000102FDFEFF ")); + } + + [Fact] + public static void InvalidInputString_WhiteSpace() + { + Assert.Throws(() => Convert.FromHexString("00 01 02FD FE FF")); + } + + [Fact] + public static void InvalidInputString_Dash() + { + Assert.Throws(() => Convert.FromHexString("01-02-FD-FE-FF")); + } + + [Fact] + public static void ZeroLength() + { + Assert.Same(Array.Empty(), Convert.FromHexString(string.Empty)); + } + } +} diff --git a/src/libraries/System.Runtime.Extensions/tests/System/Convert.ToHexString.cs b/src/libraries/System.Runtime.Extensions/tests/System/Convert.ToHexString.cs new file mode 100644 index 00000000000000..42582d5c6df50b --- /dev/null +++ b/src/libraries/System.Runtime.Extensions/tests/System/Convert.ToHexString.cs @@ -0,0 +1,66 @@ +using System.Text; +using Xunit; + +namespace System.Tests +{ + public class ConvertToHexStringTests + { + [Fact] + public static void KnownByteSequence() + { + byte[] inputBytes = new byte[] { 0x00, 0x01, 0x02, 0xFD, 0xFE, 0xFF }; + Assert.Equal("000102FDFEFF", Convert.ToHexString(inputBytes)); + } + + [Fact] + public static void CompleteValueRange() + { + byte[] values = new byte[256]; + StringBuilder sb = new StringBuilder(256); + for (int i = 0; i < values.Length; i++) + { + values[i] = (byte)i; + sb.Append(i.ToString("X2")); + } + + Assert.Equal(sb.ToString(), Convert.ToHexString(values)); + } + + [Fact] + public static void ZeroLength() + { + byte[] inputBytes = Convert.FromHexString("000102FDFEFF"); + Assert.Same(string.Empty, Convert.ToHexString(inputBytes, 0, 0)); + } + + [Fact] + public static void InvalidInputBuffer() + { + Assert.Throws(() => Convert.ToHexString(null)); + Assert.Throws(() => Convert.ToHexString(null, 0, 0)); + } + + [Fact] + public static void InvalidOffset() + { + byte[] inputBytes = Convert.FromHexString("000102FDFEFF"); + Assert.Throws(() => Convert.ToHexString(inputBytes, -1, inputBytes.Length)); + Assert.Throws(() => Convert.ToHexString(inputBytes, inputBytes.Length, inputBytes.Length)); + } + + [Fact] + public static void InvalidLength() + { + byte[] inputBytes = Convert.FromHexString("000102FDFEFF"); + Assert.Throws(() => Convert.ToHexString(inputBytes, 0, -1)); + Assert.Throws(() => Convert.ToHexString(inputBytes, 0, inputBytes.Length + 1)); + Assert.Throws(() => Convert.ToHexString(inputBytes, 1, inputBytes.Length)); + } + + [Fact] + public static unsafe void InputTooLarge() + { + Assert.Throws(() => Convert.ToHexString(new ReadOnlySpan((void*)0, Int32.MaxValue))); + } + } +} diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs index f48597175b6fb0..f7ace55668f298 100644 --- a/src/libraries/System.Runtime/ref/System.Runtime.cs +++ b/src/libraries/System.Runtime/ref/System.Runtime.cs @@ -988,6 +988,11 @@ public static partial class Convert public static double ToDouble(uint value) { throw null; } [System.CLSCompliantAttribute(false)] public static double ToDouble(ulong value) { throw null; } + public static byte[] FromHexString(string s) { throw null; } + public static byte[] FromHexString(System.ReadOnlySpan chars) { throw null; } + public static string ToHexString(byte[] inArray) { throw null; } + public static string ToHexString(byte[] inArray, int offset, int length) { throw null; } + public static string ToHexString(System.ReadOnlySpan bytes) { throw null; } public static short ToInt16(bool value) { throw null; } public static short ToInt16(byte value) { throw null; } public static short ToInt16(char value) { throw null; } From 3c967566bf105e68bd12e37a2c96df71e317a0eb Mon Sep 17 00:00:00 2001 From: Nicolas Portmann Date: Sun, 7 Jun 2020 18:04:33 +0200 Subject: [PATCH 02/12] Support lowercase decoding --- .../Common/src/System/HexConverter.cs | 4 +-- .../src/System/Convert.cs | 2 +- .../tests/System/Convert.FromHexString.cs | 27 +++++++++++-------- 3 files changed, 19 insertions(+), 14 deletions(-) diff --git a/src/libraries/Common/src/System/HexConverter.cs b/src/libraries/Common/src/System/HexConverter.cs index 0a9d378f951f7b..b97b76f3c44051 100644 --- a/src/libraries/Common/src/System/HexConverter.cs +++ b/src/libraries/Common/src/System/HexConverter.cs @@ -150,7 +150,7 @@ public static char ToCharLower(int value) return (char)value; } - public static bool TryDecodeFromUtf16Upper(ReadOnlySpan chars, Span bytes) + public static bool TryDecodeFromUtf16(ReadOnlySpan chars, Span bytes) { int i = 0; int j = 0; @@ -187,7 +187,7 @@ public static bool TryDecodeFromUtf16Upper(ReadOnlySpan chars, Span 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, diff --git a/src/libraries/System.Private.CoreLib/src/System/Convert.cs b/src/libraries/System.Private.CoreLib/src/System/Convert.cs index 8f89b0531fcb6d..feeed9068a8f4e 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Convert.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Convert.cs @@ -2871,7 +2871,7 @@ public static byte[] FromHexString(ReadOnlySpan chars) byte[] result = GC.AllocateUninitializedArray(chars.Length / 2); - if (!HexConverter.TryDecodeFromUtf16Upper(chars, result)) + if (!HexConverter.TryDecodeFromUtf16(chars, result)) throw new FormatException(SR.Format_BadHexChar); return result; diff --git a/src/libraries/System.Runtime.Extensions/tests/System/Convert.FromHexString.cs b/src/libraries/System.Runtime.Extensions/tests/System/Convert.FromHexString.cs index f8fc4f6268dc35..c8084e0a6617df 100644 --- a/src/libraries/System.Runtime.Extensions/tests/System/Convert.FromHexString.cs +++ b/src/libraries/System.Runtime.Extensions/tests/System/Convert.FromHexString.cs @@ -5,11 +5,16 @@ namespace System.Tests { public class ConvertFromHexStringTests { - [Fact] - public static void KnownByteSequence() + [Theory] + [InlineData("000102FDFEFF")] + [InlineData("000102fdfeff")] + [InlineData("000102fDfEfF")] + [InlineData("000102FdFeFf")] + [InlineData("000102FDfeFF")] + public static void KnownByteSequence(string value) { - string inputString = "000102FDFEFF"; - Assert.Equal(new byte[] { 0x00, 0x01, 0x02, 0xFD, 0xFE, 0xFF }, Convert.FromHexString(inputString)); + byte[] knownSequence = {0x00, 0x01, 0x02, 0xFD, 0xFE, 0xFF}; + TestSequence(knownSequence, value); } [Fact] @@ -23,7 +28,13 @@ public static void CompleteValueRange() sb.Append(i.ToString("X2")); } - Assert.Equal(values, Convert.FromHexString(sb.ToString())); + TestSequence(values, sb.ToString()); + TestSequence(values, sb.ToString().ToLower()); + } + + private static void TestSequence(byte[] expected, string actual) + { + Assert.Equal(expected, Convert.FromHexString(actual)); } [Fact] @@ -56,12 +67,6 @@ public static void InvalidInputString_NonAsciiCharacter() Assert.Throws(() => Convert.FromHexString("0\u0308")); } - [Fact] - public static void InvalidInputString_Lowercase() - { - Assert.Throws(() => Convert.FromHexString("010a")); - } - [Fact] public static void InvalidInputString_ZeroWidthSpace() { From b5463883a2ee11660680cf917975e71416f3cbb1 Mon Sep 17 00:00:00 2001 From: Nicolas Portmann Date: Sun, 7 Jun 2020 18:04:44 +0200 Subject: [PATCH 03/12] Change arithmetics around ROS.Length to help the JIT produce better code --- src/libraries/System.Private.CoreLib/src/System/Convert.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Convert.cs b/src/libraries/System.Private.CoreLib/src/System/Convert.cs index feeed9068a8f4e..1844a2a27323ae 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Convert.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Convert.cs @@ -2866,10 +2866,10 @@ public static byte[] FromHexString(ReadOnlySpan chars) { if (chars.Length == 0) return Array.Empty(); - if (chars.Length % 2 != 0) + if ((uint)chars.Length % 2 != 0) throw new FormatException(SR.Format_BadHexLength); - byte[] result = GC.AllocateUninitializedArray(chars.Length / 2); + byte[] result = GC.AllocateUninitializedArray(chars.Length >> 1); if (!HexConverter.TryDecodeFromUtf16(chars, result)) throw new FormatException(SR.Format_BadHexChar); @@ -2925,7 +2925,7 @@ public static string ToHexString(ReadOnlySpan bytes) { if (bytes.Length == 0) return string.Empty; - if ((long)bytes.Length * 2 > int.MaxValue) + if (bytes.Length > int.MaxValue / 2) throw new OutOfMemoryException(); return HexConverter.ToString(bytes, HexConverter.Casing.Upper); From 3f2b589039a61c2d618ae3dbca3c2fbafda30e39 Mon Sep 17 00:00:00 2001 From: Nicolas Portmann Date: Tue, 30 Jun 2020 19:46:18 +0200 Subject: [PATCH 04/12] Address review feedback --- src/libraries/Common/src/System/HexConverter.cs | 4 ++++ .../tests/System/Convert.FromHexString.cs | 2 +- .../tests/System/Convert.ToHexString.cs | 14 +++++++------- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/libraries/Common/src/System/HexConverter.cs b/src/libraries/Common/src/System/HexConverter.cs index b97b76f3c44051..7f0f319c7b9062 100644 --- a/src/libraries/Common/src/System/HexConverter.cs +++ b/src/libraries/Common/src/System/HexConverter.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System.Diagnostics; using System.Runtime.CompilerServices; namespace System @@ -152,6 +153,9 @@ public static char ToCharLower(int value) public static bool TryDecodeFromUtf16(ReadOnlySpan chars, Span bytes) { + Debug.Assert(chars.Length / 2 == bytes.Length); + Debug.Assert(chars.Length % 2 == 0); + int i = 0; int j = 0; diff --git a/src/libraries/System.Runtime.Extensions/tests/System/Convert.FromHexString.cs b/src/libraries/System.Runtime.Extensions/tests/System/Convert.FromHexString.cs index c8084e0a6617df..488cda85c3510e 100644 --- a/src/libraries/System.Runtime.Extensions/tests/System/Convert.FromHexString.cs +++ b/src/libraries/System.Runtime.Extensions/tests/System/Convert.FromHexString.cs @@ -40,7 +40,7 @@ private static void TestSequence(byte[] expected, string actual) [Fact] public static void InvalidInputString_Null() { - Assert.Throws(() => Convert.FromHexString(null)); + AssertExtensions.Throws("s", () => Convert.FromHexString(null)); } [Fact] diff --git a/src/libraries/System.Runtime.Extensions/tests/System/Convert.ToHexString.cs b/src/libraries/System.Runtime.Extensions/tests/System/Convert.ToHexString.cs index 42582d5c6df50b..9e01fdc0449bec 100644 --- a/src/libraries/System.Runtime.Extensions/tests/System/Convert.ToHexString.cs +++ b/src/libraries/System.Runtime.Extensions/tests/System/Convert.ToHexString.cs @@ -36,25 +36,25 @@ public static void ZeroLength() [Fact] public static void InvalidInputBuffer() { - Assert.Throws(() => Convert.ToHexString(null)); - Assert.Throws(() => Convert.ToHexString(null, 0, 0)); + AssertExtensions.Throws("inArray", () => Convert.ToHexString(null)); + AssertExtensions.Throws("inArray", () => Convert.ToHexString(null, 0, 0)); } [Fact] public static void InvalidOffset() { byte[] inputBytes = Convert.FromHexString("000102FDFEFF"); - Assert.Throws(() => Convert.ToHexString(inputBytes, -1, inputBytes.Length)); - Assert.Throws(() => Convert.ToHexString(inputBytes, inputBytes.Length, inputBytes.Length)); + AssertExtensions.Throws("offset", () => Convert.ToHexString(inputBytes, -1, inputBytes.Length)); + AssertExtensions.Throws("offset", () => Convert.ToHexString(inputBytes, inputBytes.Length, inputBytes.Length)); } [Fact] public static void InvalidLength() { byte[] inputBytes = Convert.FromHexString("000102FDFEFF"); - Assert.Throws(() => Convert.ToHexString(inputBytes, 0, -1)); - Assert.Throws(() => Convert.ToHexString(inputBytes, 0, inputBytes.Length + 1)); - Assert.Throws(() => Convert.ToHexString(inputBytes, 1, inputBytes.Length)); + AssertExtensions.Throws("length", () => Convert.ToHexString(inputBytes, 0, -1)); + AssertExtensions.Throws("offset", () => Convert.ToHexString(inputBytes, 0, inputBytes.Length + 1)); + AssertExtensions.Throws("offset", () => Convert.ToHexString(inputBytes, 1, inputBytes.Length)); } [Fact] From f54ef1ea828fff7f8b4a228168dadbaad8d393b2 Mon Sep 17 00:00:00 2001 From: Nicolas Portmann Date: Thu, 2 Jul 2020 10:58:15 +0200 Subject: [PATCH 05/12] Reuse existing CharToHexLookup table --- .../Common/src/System/HexConverter.cs | 41 +++++++------------ .../System.Private.CoreLib/src/System/Guid.cs | 2 +- .../src/System/Number.Parsing.cs | 16 +------- 3 files changed, 17 insertions(+), 42 deletions(-) diff --git a/src/libraries/Common/src/System/HexConverter.cs b/src/libraries/Common/src/System/HexConverter.cs index 7f0f319c7b9062..8f2be824f31f42 100644 --- a/src/libraries/Common/src/System/HexConverter.cs +++ b/src/libraries/Common/src/System/HexConverter.cs @@ -159,22 +159,17 @@ public static bool TryDecodeFromUtf16(ReadOnlySpan chars, Span bytes int i = 0; int j = 0; + ReadOnlySpan charToHexLookup = CharToHexLookup; while (j < bytes.Length) { + int byteHi; + int byteLo; int charHi = chars[i++]; int charLo = chars[i++]; - if (charHi > 0xFF) + if (charHi >= charToHexLookup.Length || (byteHi = charToHexLookup[charHi]) == 0xFF) return false; - if (charLo > 0xFF) - return false; - - int byteHi = DecodingMap[charHi]; - int byteLo = DecodingMap[charLo]; - - if (byteHi == -1) - return false; - if (byteLo == -1) + if (charLo >= charToHexLookup.Length || (byteLo = charToHexLookup[charLo]) == 0xFF) return false; bytes[j++] = (byte)((byteHi << 4) | byteLo); @@ -183,24 +178,16 @@ public static bool TryDecodeFromUtf16(ReadOnlySpan chars, Span bytes return true; } - private static ReadOnlySpan DecodingMap => new sbyte[] // rely on C# compiler optimization to reference static data + /// Map from an ASCII char to its hex value, e.g. arr['b'] == 11. 0xFF means it's not a hex digit. + internal static ReadOnlySpan CharToHexLookup => new byte[] { - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, - -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 15 + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 31 + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 47 + 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 63 + 0xFF, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 79 + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 95 + 0xFF, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf // 102 }; } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Guid.cs b/src/libraries/System.Private.CoreLib/src/System/Guid.cs index cc54f974d1517d..e8d3cc46c56697 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Guid.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Guid.cs @@ -664,7 +664,7 @@ private static bool TryParseHex(ReadOnlySpan guidString, out uint result, for (; i < guidString.Length && guidString[i] == '0'; i++) ; int processedDigits = 0; - ReadOnlySpan charToHexLookup = Number.CharToHexLookup; + ReadOnlySpan charToHexLookup = HexConverter.CharToHexLookup; uint tmp = 0; for (; i < guidString.Length; i++) { diff --git a/src/libraries/System.Private.CoreLib/src/System/Number.Parsing.cs b/src/libraries/System.Private.CoreLib/src/System/Number.Parsing.cs index fb2949089846bd..fbc4dcc86e7a76 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Number.Parsing.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Number.Parsing.cs @@ -45,18 +45,6 @@ internal static partial class Number private const int HalfMaxExponent = 5; private const int HalfMinExponent = -8; - /// Map from an ASCII char to its hex value, e.g. arr['b'] == 11. 0xFF means it's not a hex digit. - internal static ReadOnlySpan CharToHexLookup => new byte[] - { - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 15 - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 31 - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 47 - 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 63 - 0xFF, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 79 - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 95 - 0xFF, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf // 102 - }; - private static unsafe bool TryNumberToInt32(ref NumberBuffer number, ref int value) { number.CheckConsistency(); @@ -1134,7 +1122,7 @@ private static ParsingStatus TryParseUInt32HexNumberStyle(ReadOnlySpan val bool overflow = false; uint answer = 0; - ReadOnlySpan charToHexLookup = CharToHexLookup; + ReadOnlySpan charToHexLookup = HexConverter.CharToHexLookup; if ((uint)num < (uint)charToHexLookup.Length && charToHexLookup[num] != 0xFF) { @@ -1463,7 +1451,7 @@ private static ParsingStatus TryParseUInt64HexNumberStyle(ReadOnlySpan val bool overflow = false; ulong answer = 0; - ReadOnlySpan charToHexLookup = CharToHexLookup; + ReadOnlySpan charToHexLookup = HexConverter.CharToHexLookup; if ((uint)num < (uint)charToHexLookup.Length && charToHexLookup[num] != 0xFF) { From 044c7103615521072cb5b622751db8584163ca83 Mon Sep 17 00:00:00 2001 From: Nicolas Portmann Date: Sat, 11 Jul 2020 11:59:28 +0200 Subject: [PATCH 06/12] Migrate call sites to new API --- .../System/Security/Cryptography/ByteUtils.cs | 19 ++--------------- .../Net/NetworkInformation/PhysicalAddress.cs | 2 +- .../src/System/Xml/BinHexEncoder.cs | 21 +------------------ .../tests/System/Text/Unicode/Utf8Tests.cs | 7 +------ .../src/Internal/Cryptography/AsnFormatter.cs | 2 +- .../src/Internal/Cryptography/Helpers.cs | 2 +- .../System/Security/Cryptography/Xml/Utils.cs | 19 +---------------- .../tests/X509Certificate2UIManualTests.cs | 10 +-------- 8 files changed, 9 insertions(+), 73 deletions(-) diff --git a/src/libraries/Common/tests/System/Security/Cryptography/ByteUtils.cs b/src/libraries/Common/tests/System/Security/Cryptography/ByteUtils.cs index be8a1d5f9b349d..592d6047df33b3 100644 --- a/src/libraries/Common/tests/System/Security/Cryptography/ByteUtils.cs +++ b/src/libraries/Common/tests/System/Security/Cryptography/ByteUtils.cs @@ -24,15 +24,7 @@ internal static byte[] AsciiBytes(string s) internal static byte[] HexToByteArray(this string hexString) { - byte[] bytes = new byte[hexString.Length / 2]; - - for (int i = 0; i < hexString.Length; i += 2) - { - string s = hexString.Substring(i, 2); - bytes[i / 2] = byte.Parse(s, NumberStyles.HexNumber, null); - } - - return bytes; + return Convert.FromHexString(hexString); } internal static string ByteArrayToHex(this byte[] bytes) @@ -52,14 +44,7 @@ internal static string ByteArrayToHex(this ReadOnlyMemory bytes) internal static string ByteArrayToHex(this ReadOnlySpan bytes) { - StringBuilder builder = new StringBuilder(bytes.Length * 2); - - for (int i = 0; i < bytes.Length; i++) - { - builder.Append(bytes[i].ToString("X2")); - } - - return builder.ToString(); + return Convert.ToHexString(bytes); } internal static byte[] RepeatByte(byte b, int count) diff --git a/src/libraries/System.Net.NetworkInformation/src/System/Net/NetworkInformation/PhysicalAddress.cs b/src/libraries/System.Net.NetworkInformation/src/System/Net/NetworkInformation/PhysicalAddress.cs index 752d1465d2467e..d8c2897999b7c3 100644 --- a/src/libraries/System.Net.NetworkInformation/src/System/Net/NetworkInformation/PhysicalAddress.cs +++ b/src/libraries/System.Net.NetworkInformation/src/System/Net/NetworkInformation/PhysicalAddress.cs @@ -92,7 +92,7 @@ public override bool Equals(object? comparand) public override string ToString() { - return HexConverter.ToString(_address.AsSpan(), HexConverter.Casing.Upper); + return Convert.ToHexString(_address ?? Array.Empty()); } public byte[] GetAddressBytes() diff --git a/src/libraries/System.Private.Xml/src/System/Xml/BinHexEncoder.cs b/src/libraries/System.Private.Xml/src/System/Xml/BinHexEncoder.cs index 1aba445ea8999f..47e58cdeb35807 100644 --- a/src/libraries/System.Private.Xml/src/System/Xml/BinHexEncoder.cs +++ b/src/libraries/System.Private.Xml/src/System/Xml/BinHexEncoder.cs @@ -42,26 +42,7 @@ internal static void Encode(byte[] buffer, int index, int count, XmlWriter write internal static string Encode(byte[] inArray, int offsetIn, int count) { - if (null == inArray) - { - throw new ArgumentNullException(nameof(inArray)); - } - if (0 > offsetIn) - { - throw new ArgumentOutOfRangeException(nameof(offsetIn)); - } - if (0 > count) - { - throw new ArgumentOutOfRangeException(nameof(count)); - } - if (count > inArray.Length - offsetIn) - { - throw new ArgumentOutOfRangeException(nameof(count)); - } - - char[] outArray = new char[2 * count]; - int lenOut = Encode(inArray, offsetIn, count, outArray); - return new string(outArray, 0, lenOut); + return Convert.ToHexString(inArray, offsetIn, count); } private static int Encode(byte[] inArray, int offsetIn, int count, char[] outArray) diff --git a/src/libraries/System.Runtime/tests/System/Text/Unicode/Utf8Tests.cs b/src/libraries/System.Runtime/tests/System/Text/Unicode/Utf8Tests.cs index b633a5053c21cd..889628615288f6 100644 --- a/src/libraries/System.Runtime/tests/System/Text/Unicode/Utf8Tests.cs +++ b/src/libraries/System.Runtime/tests/System/Text/Unicode/Utf8Tests.cs @@ -65,12 +65,7 @@ public static byte[] DecodeHex(ReadOnlySpan inputHex) { Assert.True(Regex.IsMatch(inputHex.ToString(), "^([0-9a-fA-F]{2})*$"), "Input must be an even number of hex characters."); - byte[] retVal = new byte[inputHex.Length / 2]; - for (int i = 0; i < retVal.Length; i++) - { - retVal[i] = byte.Parse(inputHex.Slice(i * 2, 2), NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture); - } - return retVal; + return Convert.FromHexString(inputHex); } // !! IMPORTANT !! diff --git a/src/libraries/System.Security.Cryptography.Encoding/src/Internal/Cryptography/AsnFormatter.cs b/src/libraries/System.Security.Cryptography.Encoding/src/Internal/Cryptography/AsnFormatter.cs index 54f508e3df5c27..ed9b1876300cdd 100644 --- a/src/libraries/System.Security.Cryptography.Encoding/src/Internal/Cryptography/AsnFormatter.cs +++ b/src/libraries/System.Security.Cryptography.Encoding/src/Internal/Cryptography/AsnFormatter.cs @@ -14,7 +14,7 @@ internal abstract partial class AsnFormatter public string Format(Oid? oid, byte[] rawData, bool multiLine) { - return FormatNative(oid, rawData, multiLine) ?? HexConverter.ToString(rawData.AsSpan(), HexConverter.Casing.Upper); + return FormatNative(oid, rawData, multiLine) ?? Convert.ToHexString(rawData); } protected abstract string? FormatNative(Oid? oid, byte[] rawData, bool multiLine); diff --git a/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Helpers.cs b/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Helpers.cs index 99a0f5456976cf..b654d5982de65e 100644 --- a/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Helpers.cs +++ b/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Helpers.cs @@ -42,7 +42,7 @@ private static void ToHexArrayUpper(ReadOnlySpan bytes, Span chars) // Encode a byte array as an upper case hex string. public static string ToHexStringUpper(this byte[] bytes) => - HexConverter.ToString(bytes.AsSpan(), HexConverter.Casing.Upper); + Convert.ToHexString(bytes); // Decode a hex string-encoded byte array passed to various X509 crypto api. // The parsing rules are overly forgiving but for compat reasons, they cannot be tightened. diff --git a/src/libraries/System.Security.Cryptography.Xml/src/System/Security/Cryptography/Xml/Utils.cs b/src/libraries/System.Security.Cryptography.Xml/src/System/Security/Cryptography/Xml/Utils.cs index 0bf48cdd04b0ba..c5bf854a0989f1 100644 --- a/src/libraries/System.Security.Cryptography.Xml/src/System/Security/Cryptography/Xml/Utils.cs +++ b/src/libraries/System.Security.Cryptography.Xml/src/System/Security/Cryptography/Xml/Utils.cs @@ -723,24 +723,7 @@ internal static X509Certificate2Collection BuildBagOfCerts(KeyInfoX509Data keyIn internal static string EncodeHexString(byte[] sArray) { - return EncodeHexString(sArray, 0, (uint)sArray.Length); - } - - internal static string EncodeHexString(byte[] sArray, uint start, uint end) - { - string result = null; - if (sArray != null) - { - char[] hexOrder = new char[(end - start) * 2]; - for (uint i = start, j = 0; i < end; i++) - { - int digit = sArray[i]; - hexOrder[j++] = HexConverter.ToCharUpper(digit >> 4); - hexOrder[j++] = HexConverter.ToCharUpper(digit); - } - result = new string(hexOrder); - } - return result; + return HexConverter.ToString(sArray); } internal static byte[] DecodeHexString(string s) diff --git a/src/libraries/System.Windows.Extensions/tests/X509Certificate2UIManualTests.cs b/src/libraries/System.Windows.Extensions/tests/X509Certificate2UIManualTests.cs index 70d2f8fbcc82e2..15defeea5d8d12 100644 --- a/src/libraries/System.Windows.Extensions/tests/X509Certificate2UIManualTests.cs +++ b/src/libraries/System.Windows.Extensions/tests/X509Certificate2UIManualTests.cs @@ -72,15 +72,7 @@ internal class EccTestData private static byte[] HexToByteArray(string hexString) { - byte[] bytes = new byte[hexString.Length / 2]; - - for (int i = 0; i < hexString.Length; i += 2) - { - string s = hexString.Substring(i, 2); - bytes[i / 2] = byte.Parse(s, NumberStyles.HexNumber, null); - } - - return bytes; + return Convert.FromHexString(hexString); } } } From 300b7251108703504529e80123ef3c21b2de6b7f Mon Sep 17 00:00:00 2001 From: Nicolas Portmann Date: Mon, 13 Jul 2020 16:51:18 +0200 Subject: [PATCH 07/12] Avoid targeting issue (cannot use new API due to net472 target) --- .../System/Security/Cryptography/ByteUtils.cs | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/libraries/Common/tests/System/Security/Cryptography/ByteUtils.cs b/src/libraries/Common/tests/System/Security/Cryptography/ByteUtils.cs index 592d6047df33b3..be8a1d5f9b349d 100644 --- a/src/libraries/Common/tests/System/Security/Cryptography/ByteUtils.cs +++ b/src/libraries/Common/tests/System/Security/Cryptography/ByteUtils.cs @@ -24,7 +24,15 @@ internal static byte[] AsciiBytes(string s) internal static byte[] HexToByteArray(this string hexString) { - return Convert.FromHexString(hexString); + byte[] bytes = new byte[hexString.Length / 2]; + + for (int i = 0; i < hexString.Length; i += 2) + { + string s = hexString.Substring(i, 2); + bytes[i / 2] = byte.Parse(s, NumberStyles.HexNumber, null); + } + + return bytes; } internal static string ByteArrayToHex(this byte[] bytes) @@ -44,7 +52,14 @@ internal static string ByteArrayToHex(this ReadOnlyMemory bytes) internal static string ByteArrayToHex(this ReadOnlySpan bytes) { - return Convert.ToHexString(bytes); + StringBuilder builder = new StringBuilder(bytes.Length * 2); + + for (int i = 0; i < bytes.Length; i++) + { + builder.Append(bytes[i].ToString("X2")); + } + + return builder.ToString(); } internal static byte[] RepeatByte(byte b, int count) From df0392dbab5323eda0bb638dd31a389e7b1b351c Mon Sep 17 00:00:00 2001 From: Nicolas Portmann Date: Mon, 13 Jul 2020 20:08:44 +0200 Subject: [PATCH 08/12] Improve hex decoding method --- .../Common/src/System/HexConverter.cs | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/src/libraries/Common/src/System/HexConverter.cs b/src/libraries/Common/src/System/HexConverter.cs index 8f2be824f31f42..d950372aedc197 100644 --- a/src/libraries/Common/src/System/HexConverter.cs +++ b/src/libraries/Common/src/System/HexConverter.cs @@ -153,31 +153,33 @@ public static char ToCharLower(int value) public static bool TryDecodeFromUtf16(ReadOnlySpan chars, Span bytes) { - Debug.Assert(chars.Length / 2 == bytes.Length); - Debug.Assert(chars.Length % 2 == 0); + Debug.Assert(chars.Length % 2 == 0, "Un-even number of characters provided"); + Debug.Assert(chars.Length / 2 == bytes.Length, "Target buffer not right-sized for provided characters"); int i = 0; int j = 0; - - ReadOnlySpan charToHexLookup = CharToHexLookup; while (j < bytes.Length) { - int byteHi; - int byteLo; - int charHi = chars[i++]; - int charLo = chars[i++]; + int byteLo = FromChar(chars[i + 1]); + int byteHi = FromChar(chars[i]); - if (charHi >= charToHexLookup.Length || (byteHi = charToHexLookup[charHi]) == 0xFF) - return false; - if (charLo >= charToHexLookup.Length || (byteLo = charToHexLookup[charLo]) == 0xFF) + if ((byteLo | byteHi) == 0xFF) return false; bytes[j++] = (byte)((byteHi << 4) | byteLo); + i+=2; } return true; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int FromChar(int c) + { + ReadOnlySpan charToHexLookup = CharToHexLookup; + return c >= charToHexLookup.Length ? 0xFF : charToHexLookup[c]; + } + /// Map from an ASCII char to its hex value, e.g. arr['b'] == 11. 0xFF means it's not a hex digit. internal static ReadOnlySpan CharToHexLookup => new byte[] { From 0dad775eaa01af8bc2fd521d6c3fe84a7e00327b Mon Sep 17 00:00:00 2001 From: Nicolas Portmann Date: Mon, 13 Jul 2020 20:29:08 +0200 Subject: [PATCH 09/12] Address review feedback --- src/libraries/Common/src/System/HexConverter.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/libraries/Common/src/System/HexConverter.cs b/src/libraries/Common/src/System/HexConverter.cs index d950372aedc197..3490bf7612e82a 100644 --- a/src/libraries/Common/src/System/HexConverter.cs +++ b/src/libraries/Common/src/System/HexConverter.cs @@ -163,6 +163,8 @@ public static bool TryDecodeFromUtf16(ReadOnlySpan chars, Span bytes int byteLo = FromChar(chars[i + 1]); int byteHi = FromChar(chars[i]); + // byteHi hasn't been shifted to the high half yet, so the only way the bitwise or produces this pattern + // is if either byteHi or byteLo was not a hex character. if ((byteLo | byteHi) == 0xFF) return false; From 25d222e14822edd4db719ca744b1dfad3d088ce6 Mon Sep 17 00:00:00 2001 From: Nicolas Portmann Date: Tue, 14 Jul 2020 18:29:11 +0200 Subject: [PATCH 10/12] Consolidate hex implementations across the libraries --- .../Common/src/System/HexConverter.cs | 72 ++++++++++++++++--- .../src/System/Diagnostics/Activity.cs | 30 +++----- .../Net/Http/Headers/AltSvcHeaderParser.cs | 17 ++--- .../AuthenticationHelper.Digest.cs | 12 +--- .../src/System.Net.HttpListener.csproj | 2 + .../src/System/Net/HttpListenerRequest.cs | 24 +++---- .../Net/NetworkInformation/PhysicalAddress.cs | 19 ++--- .../StringParsingHelpers.Connections.cs | 17 ++--- ...NetworkInformation.Functional.Tests.csproj | 2 + .../System.Private.CoreLib/src/System/Guid.cs | 5 +- .../src/System/Net/WebUtility.cs | 20 ++---- .../src/System/Number.Parsing.cs | 30 ++++---- .../src/System/Reflection/AssemblyName.cs | 33 +-------- .../Reflection/AssemblyNameFormatter.cs | 5 +- .../src/System/Text/BinHexEncoding.cs | 64 ++--------------- .../src/System/Xml/XmlBufferReader.cs | 10 +-- .../System.Private.Uri/src/System/Uri.cs | 23 +++--- .../src/System/UriHelper.cs | 36 +++------- .../src/System/Xml/BinHexDecoder.cs | 13 +--- .../src/System/Xml/BinHexEncoder.cs | 27 +------ .../src/System/Xml/BinHexEncoderAsync.cs | 4 +- .../src/System/Xml/Core/XmlTextReaderImpl.cs | 11 +-- .../src/System/Xml/XmlConvert.cs | 7 +- .../src/System.Runtime.Numerics.csproj | 2 + .../src/System/Numerics/BigNumber.cs | 19 +---- .../src/Internal/Cryptography/Helpers.cs | 27 +------ .../Cryptography/Xml/SignedXmlDebugLog.cs | 8 +-- .../System/Security/Cryptography/Xml/Utils.cs | 22 +----- .../Text/Json/Reader/JsonReaderHelper.cs | 5 +- .../src/System/Web/Util/HttpEncoder.cs | 38 +++++----- .../src/System/Web/Util/HttpEncoderUtility.cs | 9 --- 31 files changed, 194 insertions(+), 419 deletions(-) diff --git a/src/libraries/Common/src/System/HexConverter.cs b/src/libraries/Common/src/System/HexConverter.cs index 3490bf7612e82a..425ee6ef081984 100644 --- a/src/libraries/Common/src/System/HexConverter.cs +++ b/src/libraries/Common/src/System/HexConverter.cs @@ -84,6 +84,16 @@ public static void ToCharsBuffer(byte value, Span buffer, int startingInde buffer[startingIndex] = (char)(packedResult >> 8); } + public static void EncodeToUtf16(ReadOnlySpan bytes, Span chars, Casing casing = Casing.Upper) + { + Debug.Assert(chars.Length >= bytes.Length * 2); + + for (int pos = 0; pos < bytes.Length; ++pos) + { + ToCharsBuffer(bytes[pos], chars, pos * 2, casing); + } + } + #if ALLOW_PARTIALLY_TRUSTED_CALLERS [System.Security.SecuritySafeCriticalAttribute] #endif @@ -114,10 +124,7 @@ public static unsafe string ToString(ReadOnlySpan bytes, Casing casing = C return string.Create(bytes.Length * 2, (Ptr: (IntPtr)bytesPtr, bytes.Length, casing), (chars, args) => { var ros = new ReadOnlySpan((byte*)args.Ptr, args.Length); - for (int pos = 0; pos < ros.Length; ++pos) - { - ToCharsBuffer(ros[pos], chars, pos * 2, args.casing); - } + EncodeToUtf16(ros, chars, args.casing); }); } #endif @@ -152,27 +159,38 @@ public static char ToCharLower(int value) } public static bool TryDecodeFromUtf16(ReadOnlySpan chars, Span bytes) + { + return TryDecodeFromUtf16(chars, bytes, out _); + } + + public static bool TryDecodeFromUtf16(ReadOnlySpan chars, Span bytes, out int charsProcessed) { Debug.Assert(chars.Length % 2 == 0, "Un-even number of characters provided"); Debug.Assert(chars.Length / 2 == bytes.Length, "Target buffer not right-sized for provided characters"); int i = 0; int j = 0; + int byteLo = 0; + int byteHi = 0; while (j < bytes.Length) { - int byteLo = FromChar(chars[i + 1]); - int byteHi = FromChar(chars[i]); + byteLo = FromChar(chars[i + 1]); + byteHi = FromChar(chars[i]); // byteHi hasn't been shifted to the high half yet, so the only way the bitwise or produces this pattern // is if either byteHi or byteLo was not a hex character. if ((byteLo | byteHi) == 0xFF) - return false; + break; bytes[j++] = (byte)((byteHi << 4) | byteLo); i+=2; } - return true; + if (byteLo == 0xFF) + i++; + + charsProcessed = i; + return (byteLo | byteHi) != 0xFF; } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -182,8 +200,44 @@ public static int FromChar(int c) return c >= charToHexLookup.Length ? 0xFF : charToHexLookup[c]; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int FromUpperChar(int c) + { + ReadOnlySpan charToHexLookup = CharToHexLookup; + return c > 71 ? 0xFF : charToHexLookup[c]; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int FromLowerChar(int c) + { + if ('0' <= c && c <= '9') + return (byte)(c - '0'); + if ('a' <= c && c <= 'f') + return (byte)(c - ('a' - 10)); + + return 0xFF; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsHexChar(int c) + { + return FromChar(c) != 0xFF; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsHexUpperChar(int c) + { + return (uint)(c - '0') <= 9 || (uint)(c - 'A') <= ('F' - 'A'); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsHexLowerChar(int c) + { + return (uint)(c - '0') <= 9 || (uint)(c - 'a') <= ('f' - 'a'); + } + /// Map from an ASCII char to its hex value, e.g. arr['b'] == 11. 0xFF means it's not a hex digit. - internal static ReadOnlySpan CharToHexLookup => new byte[] + private static ReadOnlySpan CharToHexLookup => new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 15 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 31 diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Activity.cs b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Activity.cs index eb4a2390a8330a..f39d561ec22dbe 100644 --- a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Activity.cs +++ b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Activity.cs @@ -804,7 +804,7 @@ internal static bool TryConvertIdToContext(string id, out ActivityContext contex ReadOnlySpan spanIdSpan = id.AsSpan(36, 16); if (!ActivityTraceId.IsLowerCaseHexAndNotAllZeros(traceIdSpan) || !ActivityTraceId.IsLowerCaseHexAndNotAllZeros(spanIdSpan) || - !ActivityTraceId.IsHexadecimalLowercaseChar(id[53]) || !ActivityTraceId.IsHexadecimalLowercaseChar(id[54])) + !HexConverter.IsHexLowerChar(id[53]) || !HexConverter.IsHexLowerChar(id[54])) { return false; } @@ -1136,7 +1136,7 @@ private void TrySetTraceFlagsFromParent() } else if (_parentId != null && IsW3CId(_parentId)) { - if (ActivityTraceId.IsHexadecimalLowercaseChar(_parentId[53]) && ActivityTraceId.IsHexadecimalLowercaseChar(_parentId[54])) + if (HexConverter.IsHexLowerChar(_parentId[53]) && HexConverter.IsHexLowerChar(_parentId[54])) { _w3CIdFlags = (byte)(ActivityTraceId.HexByteFromChars(_parentId[53], _parentId[54]) | ActivityTraceFlagsIsSet); } @@ -1408,15 +1408,14 @@ internal static void SetSpanFromHexChars(ReadOnlySpan charData, Span } internal static byte HexByteFromChars(char char1, char char2) { - return (byte)(HexDigitToBinary(char1) * 16 + HexDigitToBinary(char2)); - } - private static byte HexDigitToBinary(char c) - { - if ('0' <= c && c <= '9') - return (byte)(c - '0'); - if ('a' <= c && c <= 'f') - return (byte)(c - ('a' - 10)); - throw new ArgumentOutOfRangeException("idData"); + int hi = HexConverter.FromLowerChar(char1); + int lo = HexConverter.FromLowerChar(char2); + if ((hi | lo) == 0xFF) + { + throw new ArgumentOutOfRangeException("idData"); + } + + return (byte)((hi << 4) | lo); } internal static bool IsLowerCaseHexAndNotAllZeros(ReadOnlySpan idData) @@ -1427,7 +1426,7 @@ internal static bool IsLowerCaseHexAndNotAllZeros(ReadOnlySpan idData) for (; i < idData.Length; i++) { char c = idData[i]; - if (!IsHexadecimalLowercaseChar(c)) + if (!HexConverter.IsHexLowerChar(c)) { return false; } @@ -1440,13 +1439,6 @@ internal static bool IsLowerCaseHexAndNotAllZeros(ReadOnlySpan idData) return isNonZero; } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static bool IsHexadecimalLowercaseChar(char c) - { - // Between 0 - 9 or lowercased between a - f - return (uint)(c - '0') <= 9 || (uint)(c - 'a') <= ('f' - 'a'); - } } /// diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/Headers/AltSvcHeaderParser.cs b/src/libraries/System.Net.Http/src/System/Net/Http/Headers/AltSvcHeaderParser.cs index 9990f4824251c1..59d561cb06b2ec 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/Headers/AltSvcHeaderParser.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/Headers/AltSvcHeaderParser.cs @@ -307,20 +307,15 @@ private static bool TryReadUnknownPercentEncodedAlpnProtocolName(ReadOnlySpan private static bool TryReadAlpnHexDigit(char ch, out int nibble) { - if ((uint)(ch - '0') <= '9' - '0') // ch >= '0' && ch <= '9' + int result = HexConverter.FromUpperChar(ch); + if (result == 0xFF) { - nibble = ch - '0'; - return true; - } - - if ((uint)(ch - 'A') <= 'F' - 'A') // ch >= 'A' && ch <= 'F' - { - nibble = ch - 'A' + 10; - return true; + nibble = 0; + return false; } - nibble = 0; - return false; + nibble = result; + return true; } private static bool TryReadQuotedAltAuthority(string value, int startIndex, out string? host, out int port, out int readLength) diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/AuthenticationHelper.Digest.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/AuthenticationHelper.Digest.cs index 7f6787741375e3..bc7ad02ab70ad3 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/AuthenticationHelper.Digest.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/AuthenticationHelper.Digest.cs @@ -237,17 +237,7 @@ private static string ComputeHash(string data, string algorithm) bool hashComputed = hash.TryComputeHash(Encoding.UTF8.GetBytes(data), result, out int bytesWritten); Debug.Assert(hashComputed && bytesWritten == result.Length); - StringBuilder sb = StringBuilderCache.Acquire(result.Length * 2); - - Span byteX2 = stackalloc char[2]; - for (int i = 0; i < result.Length; i++) - { - bool formatted = result[i].TryFormat(byteX2, out int charsWritten, "x2"); - Debug.Assert(formatted && charsWritten == 2); - sb.Append(byteX2); - } - - return StringBuilderCache.GetStringAndRelease(sb); + return HexConverter.ToString(result, HexConverter.Casing.Lower); } } diff --git a/src/libraries/System.Net.HttpListener/src/System.Net.HttpListener.csproj b/src/libraries/System.Net.HttpListener/src/System.Net.HttpListener.csproj index b4ed96e663a885..0c2b62f4068240 100644 --- a/src/libraries/System.Net.HttpListener/src/System.Net.HttpListener.csproj +++ b/src/libraries/System.Net.HttpListener/src/System.Net.HttpListener.csproj @@ -83,6 +83,8 @@ + diff --git a/src/libraries/System.Net.HttpListener/src/System/Net/HttpListenerRequest.cs b/src/libraries/System.Net.HttpListener/src/System/Net/HttpListenerRequest.cs index 9237643e38d056..a00abcabaeb9c6 100644 --- a/src/libraries/System.Net.HttpListener/src/System/Net/HttpListenerRequest.cs +++ b/src/libraries/System.Net.HttpListener/src/System/Net/HttpListenerRequest.cs @@ -438,12 +438,12 @@ private static string UrlDecodeStringFromStringInternal(string s, Encoding e) { if (s[pos + 1] == 'u' && pos < count - 5) { - int h1 = HexToInt(s[pos + 2]); - int h2 = HexToInt(s[pos + 3]); - int h3 = HexToInt(s[pos + 4]); - int h4 = HexToInt(s[pos + 5]); + int h1 = HexConverter.FromChar(s[pos + 2]); + int h2 = HexConverter.FromChar(s[pos + 3]); + int h3 = HexConverter.FromChar(s[pos + 4]); + int h4 = HexConverter.FromChar(s[pos + 5]); - if (h1 >= 0 && h2 >= 0 && h3 >= 0 && h4 >= 0) + if ((h1 | h2 | h3 | h4) != 0xFF) { // valid 4 hex chars ch = (char)((h1 << 12) | (h2 << 8) | (h3 << 4) | h4); pos += 5; @@ -455,10 +455,10 @@ private static string UrlDecodeStringFromStringInternal(string s, Encoding e) } else { - int h1 = HexToInt(s[pos + 1]); - int h2 = HexToInt(s[pos + 2]); + int h1 = HexConverter.FromChar(s[pos + 1]); + int h2 = HexConverter.FromChar(s[pos + 2]); - if (h1 >= 0 && h2 >= 0) + if ((h1 | h2) != 0xFF) { // valid 2 hex chars byte b = (byte)((h1 << 4) | h2); pos += 2; @@ -479,14 +479,6 @@ private static string UrlDecodeStringFromStringInternal(string s, Encoding e) return helper.GetString(); } - private static int HexToInt(char h) - { - return (h >= '0' && h <= '9') ? h - '0' : - (h >= 'a' && h <= 'f') ? h - 'a' + 10 : - (h >= 'A' && h <= 'F') ? h - 'A' + 10 : - -1; - } - private class UrlDecoder { private readonly int _bufferSize; diff --git a/src/libraries/System.Net.NetworkInformation/src/System/Net/NetworkInformation/PhysicalAddress.cs b/src/libraries/System.Net.NetworkInformation/src/System/Net/NetworkInformation/PhysicalAddress.cs index d8c2897999b7c3..5bd86e6a807ccd 100644 --- a/src/libraries/System.Net.NetworkInformation/src/System/Net/NetworkInformation/PhysicalAddress.cs +++ b/src/libraries/System.Net.NetworkInformation/src/System/Net/NetworkInformation/PhysicalAddress.cs @@ -3,7 +3,6 @@ // See the LICENSE file in the project root for more information. using System.Diagnostics.CodeAnalysis; -using System.Text; namespace System.Net.NetworkInformation { @@ -185,20 +184,8 @@ public static bool TryParse(ReadOnlySpan address, [NotNullWhen(true)] out for (int i = 0; i < address.Length; i++) { int character = address[i]; - - if (character >= '0' && character <= '9') - { - character -= '0'; - } - else if (character >= 'A' && character <= 'F') - { - character -= ('A' - 10); - } - else if (character >= 'a' && character <= 'f') - { - character -= ('a' - 10); - } - else + int tmp; + if ((tmp = HexConverter.FromChar(character)) == 0xFF) { if (delimiter == character && validCount == validSegmentLength) { @@ -209,6 +196,8 @@ public static bool TryParse(ReadOnlySpan address, [NotNullWhen(true)] out return false; } + character = tmp; + // we had too many characters after the last delimiter if (validCount >= validSegmentLength) { diff --git a/src/libraries/System.Net.NetworkInformation/src/System/Net/NetworkInformation/StringParsingHelpers.Connections.cs b/src/libraries/System.Net.NetworkInformation/src/System/Net/NetworkInformation/StringParsingHelpers.Connections.cs index 4903e3610f0eeb..0a66b7caddb1e3 100644 --- a/src/libraries/System.Net.NetworkInformation/src/System/Net/NetworkInformation/StringParsingHelpers.Connections.cs +++ b/src/libraries/System.Net.NetworkInformation/src/System/Net/NetworkInformation/StringParsingHelpers.Connections.cs @@ -326,22 +326,13 @@ private static IPAddress ParseIPv6HexString(string hexAddress, bool isNetworkOrd private static byte HexToByte(char val) { - if (val <= '9' && val >= '0') - { - return (byte)(val - '0'); - } - else if (val >= 'a' && val <= 'f') - { - return (byte)((val - 'a') + 10); - } - else if (val >= 'A' && val <= 'F') - { - return (byte)((val - 'A') + 10); - } - else + byte result = (byte)HexConverter.FromChar(val); + if (result == 0xFF) { throw ExceptionHelper.CreateForParseFailure(); } + + return result; } } } diff --git a/src/libraries/System.Net.NetworkInformation/tests/FunctionalTests/System.Net.NetworkInformation.Functional.Tests.csproj b/src/libraries/System.Net.NetworkInformation/tests/FunctionalTests/System.Net.NetworkInformation.Functional.Tests.csproj index f9b964b19b5dac..17b11f0fb03833 100644 --- a/src/libraries/System.Net.NetworkInformation/tests/FunctionalTests/System.Net.NetworkInformation.Functional.Tests.csproj +++ b/src/libraries/System.Net.NetworkInformation/tests/FunctionalTests/System.Net.NetworkInformation.Functional.Tests.csproj @@ -51,6 +51,8 @@ Link="Common\System\IO\StringParser.cs" /> + diff --git a/src/libraries/System.Private.CoreLib/src/System/Guid.cs b/src/libraries/System.Private.CoreLib/src/System/Guid.cs index e8d3cc46c56697..69f6c3956c1458 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Guid.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Guid.cs @@ -664,13 +664,12 @@ private static bool TryParseHex(ReadOnlySpan guidString, out uint result, for (; i < guidString.Length && guidString[i] == '0'; i++) ; int processedDigits = 0; - ReadOnlySpan charToHexLookup = HexConverter.CharToHexLookup; uint tmp = 0; for (; i < guidString.Length; i++) { - int numValue; char c = guidString[i]; - if (c >= (uint)charToHexLookup.Length || (numValue = charToHexLookup[c]) == 0xFF) + int numValue = HexConverter.FromChar(c); + if (numValue == 0xFF) { if (processedDigits > 8) overflow = true; result = 0; diff --git a/src/libraries/System.Private.CoreLib/src/System/Net/WebUtility.cs b/src/libraries/System.Private.CoreLib/src/System/Net/WebUtility.cs index a7b7169b710f04..3e1e96834c4ff6 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Net/WebUtility.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Net/WebUtility.cs @@ -511,10 +511,10 @@ private static void GetEncodedBytes(byte[] originalBytes, int offset, int count, } else if (ch == '%' && pos < count - 2) { - int h1 = HexToInt(value[pos + 1]); - int h2 = HexToInt(value[pos + 2]); + int h1 = HexConverter.FromChar(value[pos + 1]); + int h2 = HexConverter.FromChar(value[pos + 2]); - if (h1 >= 0 && h2 >= 0) + if ((h1 | h2) != 0xFF) { // valid 2 hex chars byte b = (byte)((h1 << 4) | h2); pos += 2; @@ -569,10 +569,10 @@ private static void GetEncodedBytes(byte[] originalBytes, int offset, int count, } else if (b == '%' && i < count - 2) { - int h1 = HexToInt((char)bytes[pos + 1]); - int h2 = HexToInt((char)bytes[pos + 2]); + int h1 = HexConverter.FromChar(bytes[pos + 1]); + int h2 = HexConverter.FromChar(bytes[pos + 2]); - if (h1 >= 0 && h2 >= 0) + if ((h1 | h2) != 0xFF) { // valid 2 hex chars b = (byte)((h1 << 4) | h2); i += 2; @@ -650,14 +650,6 @@ private static int GetNextUnicodeScalarValueFromUtf16Surrogate(ReadOnlySpan= '0' && h <= '9') ? h - '0' : - (h >= 'a' && h <= 'f') ? h - 'a' + 10 : - (h >= 'A' && h <= 'F') ? h - 'A' + 10 : - -1; - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] private static bool IsUrlSafeChar(char ch) { diff --git a/src/libraries/System.Private.CoreLib/src/System/Number.Parsing.cs b/src/libraries/System.Private.CoreLib/src/System/Number.Parsing.cs index fbc4dcc86e7a76..cf25b62a967715 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Number.Parsing.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Number.Parsing.cs @@ -1122,9 +1122,8 @@ private static ParsingStatus TryParseUInt32HexNumberStyle(ReadOnlySpan val bool overflow = false; uint answer = 0; - ReadOnlySpan charToHexLookup = HexConverter.CharToHexLookup; - if ((uint)num < (uint)charToHexLookup.Length && charToHexLookup[num] != 0xFF) + if (HexConverter.IsHexChar(num)) { // Skip past leading zeros. if (num == '0') @@ -1136,12 +1135,12 @@ private static ParsingStatus TryParseUInt32HexNumberStyle(ReadOnlySpan val goto DoneAtEnd; num = value[index]; } while (num == '0'); - if ((uint)num >= (uint)charToHexLookup.Length || charToHexLookup[num] == 0xFF) + if (!HexConverter.IsHexChar(num)) goto HasTrailingChars; } // Parse up through 8 digits, as no overflow is possible - answer = charToHexLookup[num]; // first digit + answer = (uint)HexConverter.FromChar(num); // first digit index++; for (int i = 0; i < 7; i++) // next 7 digits can't overflow { @@ -1149,8 +1148,8 @@ private static ParsingStatus TryParseUInt32HexNumberStyle(ReadOnlySpan val goto DoneAtEnd; num = value[index]; - uint numValue; - if ((uint)num >= (uint)charToHexLookup.Length || (numValue = charToHexLookup[num]) == 0xFF) + uint numValue = (uint)HexConverter.FromChar(num); + if (numValue == 0xFF) goto HasTrailingChars; index++; answer = 16 * answer + numValue; @@ -1160,7 +1159,7 @@ private static ParsingStatus TryParseUInt32HexNumberStyle(ReadOnlySpan val if ((uint)index >= (uint)value.Length) goto DoneAtEnd; num = value[index]; - if ((uint)num >= (uint)charToHexLookup.Length || charToHexLookup[num] == 0xFF) + if (!HexConverter.IsHexChar(num)) goto HasTrailingChars; // At this point, we're either overflowing or hitting a formatting error. @@ -1171,7 +1170,7 @@ private static ParsingStatus TryParseUInt32HexNumberStyle(ReadOnlySpan val if ((uint)index >= (uint)value.Length) goto OverflowExit; num = value[index]; - } while ((uint)num < (uint)charToHexLookup.Length && charToHexLookup[num] != 0xFF); + } while (HexConverter.IsHexChar(num)); overflow = true; goto HasTrailingChars; } @@ -1451,9 +1450,8 @@ private static ParsingStatus TryParseUInt64HexNumberStyle(ReadOnlySpan val bool overflow = false; ulong answer = 0; - ReadOnlySpan charToHexLookup = HexConverter.CharToHexLookup; - if ((uint)num < (uint)charToHexLookup.Length && charToHexLookup[num] != 0xFF) + if (HexConverter.IsHexChar(num)) { // Skip past leading zeros. if (num == '0') @@ -1465,12 +1463,12 @@ private static ParsingStatus TryParseUInt64HexNumberStyle(ReadOnlySpan val goto DoneAtEnd; num = value[index]; } while (num == '0'); - if ((uint)num >= (uint)charToHexLookup.Length || charToHexLookup[num] == 0xFF) + if (!HexConverter.IsHexChar(num)) goto HasTrailingChars; } // Parse up through 16 digits, as no overflow is possible - answer = charToHexLookup[num]; // first digit + answer = (uint)HexConverter.FromChar(num); // first digit index++; for (int i = 0; i < 15; i++) // next 15 digits can't overflow { @@ -1478,8 +1476,8 @@ private static ParsingStatus TryParseUInt64HexNumberStyle(ReadOnlySpan val goto DoneAtEnd; num = value[index]; - uint numValue; - if ((uint)num >= (uint)charToHexLookup.Length || (numValue = charToHexLookup[num]) == 0xFF) + uint numValue = (uint)HexConverter.FromChar(num); + if (numValue == 0xFF) goto HasTrailingChars; index++; answer = 16 * answer + numValue; @@ -1489,7 +1487,7 @@ private static ParsingStatus TryParseUInt64HexNumberStyle(ReadOnlySpan val if ((uint)index >= (uint)value.Length) goto DoneAtEnd; num = value[index]; - if ((uint)num >= (uint)charToHexLookup.Length || charToHexLookup[num] == 0xFF) + if (!HexConverter.IsHexChar(num)) goto HasTrailingChars; // At this point, we're either overflowing or hitting a formatting error. @@ -1500,7 +1498,7 @@ private static ParsingStatus TryParseUInt64HexNumberStyle(ReadOnlySpan val if ((uint)index >= (uint)value.Length) goto OverflowExit; num = value[index]; - } while ((uint)num < (uint)charToHexLookup.Length && charToHexLookup[num] != 0xFF); + } while (HexConverter.IsHexChar(num)); overflow = true; goto HasTrailingChars; } diff --git a/src/libraries/System.Private.CoreLib/src/System/Reflection/AssemblyName.cs b/src/libraries/System.Private.CoreLib/src/System/Reflection/AssemblyName.cs index 898a9a1524fb76..b7c59c7ccb98fe 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/AssemblyName.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/AssemblyName.cs @@ -340,7 +340,7 @@ internal static string EscapeCodeBase(string? codebase) // Means we don't reEncode '%' but check for the possible escaped sequence dest = EnsureDestinationSize(pStr, dest, i, c_EncodedCharsPerByte, c_MaxAsciiCharsReallocate * c_EncodedCharsPerByte, ref destPos, prevInputPos); - if (i + 2 < end && EscapedAscii(pStr[i + 1], pStr[i + 2]) != c_DummyChar) + if (i + 2 < end && HexConverter.IsHexChar(pStr[i + 1]) && HexConverter.IsHexChar(pStr[i + 2])) { // leave it escaped dest[destPos++] = '%'; @@ -403,37 +403,6 @@ internal static void EscapeAsciiChar(char ch, char[] to, ref int pos) to[pos++] = HexConverter.ToCharUpper(ch); } - internal static char EscapedAscii(char digit, char next) - { - if (!(((digit >= '0') && (digit <= '9')) - || ((digit >= 'A') && (digit <= 'F')) - || ((digit >= 'a') && (digit <= 'f')))) - { - return c_DummyChar; - } - - int res = (digit <= '9') - ? ((int)digit - (int)'0') - : (((digit <= 'F') - ? ((int)digit - (int)'A') - : ((int)digit - (int)'a')) - + 10); - - if (!(((next >= '0') && (next <= '9')) - || ((next >= 'A') && (next <= 'F')) - || ((next >= 'a') && (next <= 'f')))) - { - return c_DummyChar; - } - - return (char)((res << 4) + ((next <= '9') - ? ((int)next - (int)'0') - : (((next <= 'F') - ? ((int)next - (int)'A') - : ((int)next - (int)'a')) - + 10))); - } - private static bool IsReservedUnreservedOrHash(char c) { if (IsUnreserved(c)) diff --git a/src/libraries/System.Private.CoreLib/src/System/Reflection/AssemblyNameFormatter.cs b/src/libraries/System.Private.CoreLib/src/System/Reflection/AssemblyNameFormatter.cs index 3ead4ce8e87f39..1890753f418967 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/AssemblyNameFormatter.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/AssemblyNameFormatter.cs @@ -70,10 +70,7 @@ public static string ComputeDisplayName(string? name, Version? version, string? sb.Append("null"); else { - foreach (byte b in pkt) - { - sb.Append(b.ToString("x2", CultureInfo.InvariantCulture)); - } + sb.Append(HexConverter.ToString(pkt, HexConverter.Casing.Lower)); } } diff --git a/src/libraries/System.Private.DataContractSerialization/src/System/Text/BinHexEncoding.cs b/src/libraries/System.Private.DataContractSerialization/src/System/Text/BinHexEncoding.cs index 28cf64e13094f6..347e1e35a66eca 100644 --- a/src/libraries/System.Private.DataContractSerialization/src/System/Text/BinHexEncoding.cs +++ b/src/libraries/System.Private.DataContractSerialization/src/System/Text/BinHexEncoding.cs @@ -9,26 +9,6 @@ namespace System.Text { internal class BinHexEncoding : Encoding { - private static ReadOnlySpan Char2val => new byte[128] // rely on C# compiler optimization to eliminate allocation - { - /* 0-15 */ - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - /* 16-31 */ - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - /* 32-47 */ - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - /* 48-63 */ - 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - /* 64-79 */ - 0xFF, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - /* 80-95 */ - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - /* 96-111 */ - 0xFF, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - /* 112-127 */ - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - }; - public override int GetMaxByteCount(int charCount) { if (charCount < 0) @@ -66,31 +46,10 @@ public unsafe override int GetBytes(char[] chars, int charIndex, int charCount, throw new ArgumentException(SR.XmlArrayTooSmall, nameof(bytes)); if (charCount > 0) { - fixed (byte* _char2val = &Char2val[0]) + if (!HexConverter.TryDecodeFromUtf16(chars.AsSpan(charIndex, charCount), bytes.AsSpan(byteIndex, byteCount), out int charsProcessed)) { - fixed (byte* _bytes = &bytes[byteIndex]) - { - fixed (char* _chars = &chars[charIndex]) - { - char* pch = _chars; - char* pchMax = _chars + charCount; - byte* pb = _bytes; - while (pch < pchMax) - { - char pch0 = pch[0]; - char pch1 = pch[1]; - if ((pch0 | pch1) >= 128) - throw new FormatException(SR.Format(SR.XmlInvalidBinHexSequence, new string(pch, 0, 2), charIndex + (int)(pch - _chars))); - byte d1 = _char2val[pch0]; - byte d2 = _char2val[pch1]; - if ((d1 | d2) == 0xFF) - throw new FormatException(SR.Format(SR.XmlInvalidBinHexSequence, new string(pch, 0, 2), charIndex + (int)(pch - _chars))); - pb[0] = (byte)((d1 << 4) + d2); - pch += 2; - pb++; - } - } - } + int error = charsProcessed + charIndex; + throw new FormatException(SR.Format(SR.XmlInvalidBinHexSequence, new string(chars, error, 2), error)); } } return byteCount; @@ -136,22 +95,7 @@ public unsafe override int GetChars(byte[] bytes, int byteIndex, int byteCount, throw new ArgumentException(SR.XmlArrayTooSmall, nameof(chars)); if (byteCount > 0) { - fixed (byte* _bytes = &bytes[byteIndex]) - { - fixed (char* _chars = &chars[charIndex]) - { - char* pch = _chars; - byte* pb = _bytes; - byte* pbMax = _bytes + byteCount; - while (pb < pbMax) - { - pch[0] = HexConverter.ToCharUpper(pb[0] >> 4); - pch[1] = HexConverter.ToCharUpper(pb[0]); - pb++; - pch += 2; - } - } - } + HexConverter.EncodeToUtf16(bytes.AsSpan(byteIndex, byteCount), chars.AsSpan(charIndex)); } return charCount; } diff --git a/src/libraries/System.Private.DataContractSerialization/src/System/Xml/XmlBufferReader.cs b/src/libraries/System.Private.DataContractSerialization/src/System/Xml/XmlBufferReader.cs index 43d74a87879fb7..efb0369735c5d8 100644 --- a/src/libraries/System.Private.DataContractSerialization/src/System/Xml/XmlBufferReader.cs +++ b/src/libraries/System.Private.DataContractSerialization/src/System/Xml/XmlBufferReader.cs @@ -780,14 +780,8 @@ private int GetHexCharEntity(int offset, int length) for (int i = 3; i < length - 1; i++) { byte ch = buffer[offset + i]; - int digit = 0; - if (ch >= '0' && ch <= '9') - digit = (ch - '0'); - else if (ch >= 'a' && ch <= 'f') - digit = 10 + (ch - 'a'); - else if (ch >= 'A' && ch <= 'F') - digit = 10 + (ch - 'A'); - else + int digit = HexConverter.FromChar(ch); + if (digit == 0xFF) XmlExceptionHelper.ThrowInvalidCharRef(_reader); DiagnosticUtility.DebugAssert(digit >= 0 && digit < 16, ""); value = value * 16 + digit; diff --git a/src/libraries/System.Private.Uri/src/System/Uri.cs b/src/libraries/System.Private.Uri/src/System/Uri.cs index 55af25a5ea892f..9afc414783ebdc 100644 --- a/src/libraries/System.Private.Uri/src/System/Uri.cs +++ b/src/libraries/System.Private.Uri/src/System/Uri.cs @@ -1498,10 +1498,10 @@ public static bool CheckSchemeName(string? schemeName) // Throws: // Nothing // - public static bool IsHexDigit(char character) => - (uint)(character - '0') <= '9' - '0' || - (uint)(character - 'A') <= 'F' - 'A' || - (uint)(character - 'a') <= 'f' - 'a'; + public static bool IsHexDigit(char character) + { + return HexConverter.IsHexChar(character); + } // // Returns: @@ -1510,11 +1510,16 @@ public static bool IsHexDigit(char character) => // Throws: // ArgumentException // - public static int FromHex(char digit) => - (uint)(digit - '0') <= '9' - '0' ? digit - '0' : - (uint)(digit - 'A') <= 'F' - 'A' ? digit - 'A' + 10 : - (uint)(digit - 'a') <= 'f' - 'a' ? digit - 'a' + 10 : - throw new ArgumentException(null, nameof(digit)); + public static int FromHex(char digit) + { + int result = HexConverter.FromChar(digit); + if (result == 0xFF) + { + throw new ArgumentException(null, nameof(digit)); + } + + return result; + } public override int GetHashCode() { diff --git a/src/libraries/System.Private.Uri/src/System/UriHelper.cs b/src/libraries/System.Private.Uri/src/System/UriHelper.cs index 668f6b8568747f..22e89c60ec7d92 100644 --- a/src/libraries/System.Private.Uri/src/System/UriHelper.cs +++ b/src/libraries/System.Private.Uri/src/System/UriHelper.cs @@ -672,36 +672,18 @@ internal static void EscapeAsciiChar(byte b, ref ValueStringBuilder to) /// Converts 2 hex chars to a byte (returned in a char), e.g, "0a" becomes (char)0x0A. /// If either char is not hex, returns . /// - internal static char DecodeHexChars(uint first, uint second) + internal static char DecodeHexChars(int first, int second) { - first -= '0'; + int a = HexConverter.FromChar(first); + int b = HexConverter.FromChar(second); - if (first <= 9) + if ((a | b) == 0xFF) { - // first is already [0, 9] + // either a or b is 0xFF (invalid) + return Uri.c_DummyChar; } - else if ((uint)((first - ('A' - '0')) & ~0x20) <= ('F' - 'A')) - { - first = ((first + '0') | 0x20) - 'a' + 10; - } - else goto Invalid; - - second -= '0'; - - if (second <= 9) - { - // second is already [0, 9] - } - else if ((uint)((second - ('A' - '0')) & ~0x20) <= ('F' - 'A')) - { - second = ((second + '0') | 0x20) - 'a' + 10; - } - else goto Invalid; - return (char)((first << 4) | second); - - Invalid: - return Uri.c_DummyChar; + return (char)((a << 4) | b); } internal const string RFC3986ReservedMarks = @";/?:@&=+$,#[]!'()*"; @@ -783,9 +765,7 @@ internal static bool IsAsciiLetterOrDigit(char character) => ((((uint)character - 'A') & ~0x20) < 26) || (((uint)character - '0') < 10); - internal static bool IsHexDigit(char character) => - ((((uint)character - 'A') & ~0x20) < 6) || - (((uint)character - '0') < 10); + internal static bool IsHexDigit(char character) => HexConverter.IsHexChar(character); // // Is this a Bidirectional control char.. These get stripped diff --git a/src/libraries/System.Private.Xml/src/System/Xml/BinHexDecoder.cs b/src/libraries/System.Private.Xml/src/System/Xml/BinHexDecoder.cs index bed87b0403b818..7f5b0e87db6d00 100644 --- a/src/libraries/System.Private.Xml/src/System/Xml/BinHexDecoder.cs +++ b/src/libraries/System.Private.Xml/src/System/Xml/BinHexDecoder.cs @@ -200,17 +200,10 @@ private static unsafe void Decode(char* pChars, char* pCharsEndPos, byte halfByte; char ch = *pChar++; - if (ch >= 'a' && ch <= 'f') + int val = HexConverter.FromChar(ch); + if (val != 0xFF) { - halfByte = (byte)(ch - 'a' + 10); - } - else if (ch >= 'A' && ch <= 'F') - { - halfByte = (byte)(ch - 'A' + 10); - } - else if (ch >= '0' && ch <= '9') - { - halfByte = (byte)(ch - '0'); + halfByte = (byte)val; } else if (xmlCharType.IsWhiteSpace(ch)) { diff --git a/src/libraries/System.Private.Xml/src/System/Xml/BinHexEncoder.cs b/src/libraries/System.Private.Xml/src/System/Xml/BinHexEncoder.cs index 47e58cdeb35807..54e33425bf2935 100644 --- a/src/libraries/System.Private.Xml/src/System/Xml/BinHexEncoder.cs +++ b/src/libraries/System.Private.Xml/src/System/Xml/BinHexEncoder.cs @@ -33,8 +33,8 @@ internal static void Encode(byte[] buffer, int index, int count, XmlWriter write while (index < endIndex) { int cnt = (count < CharsChunkSize / 2) ? count : CharsChunkSize / 2; - int charCount = Encode(buffer, index, cnt, chars); - writer.WriteRaw(chars, 0, charCount); + HexConverter.EncodeToUtf16(buffer.AsSpan(index, cnt), chars); + writer.WriteRaw(chars, 0, cnt * 2); index += cnt; count -= cnt; } @@ -44,28 +44,5 @@ internal static string Encode(byte[] inArray, int offsetIn, int count) { return Convert.ToHexString(inArray, offsetIn, count); } - - private static int Encode(byte[] inArray, int offsetIn, int count, char[] outArray) - { - int curOffsetOut = 0, offsetOut = 0; - byte b; - int lengthOut = outArray.Length; - - for (int j = 0; j < count; j++) - { - b = inArray[offsetIn++]; - outArray[curOffsetOut++] = HexConverter.ToCharUpper(b >> 4); - if (curOffsetOut == lengthOut) - { - break; - } - outArray[curOffsetOut++] = HexConverter.ToCharUpper(b); - if (curOffsetOut == lengthOut) - { - break; - } - } - return curOffsetOut - offsetOut; - } // function } // class } // namespace diff --git a/src/libraries/System.Private.Xml/src/System/Xml/BinHexEncoderAsync.cs b/src/libraries/System.Private.Xml/src/System/Xml/BinHexEncoderAsync.cs index 26b4cb56048333..944c303a31ee85 100644 --- a/src/libraries/System.Private.Xml/src/System/Xml/BinHexEncoderAsync.cs +++ b/src/libraries/System.Private.Xml/src/System/Xml/BinHexEncoderAsync.cs @@ -33,8 +33,8 @@ internal static async Task EncodeAsync(byte[] buffer, int index, int count, XmlW while (index < endIndex) { int cnt = (count < CharsChunkSize / 2) ? count : CharsChunkSize / 2; - int charCount = Encode(buffer, index, cnt, chars); - await writer.WriteRawAsync(chars, 0, charCount).ConfigureAwait(false); + HexConverter.EncodeToUtf16(buffer.AsSpan(index, cnt), chars); + await writer.WriteRawAsync(chars, 0, cnt * 2).ConfigureAwait(false); index += cnt; count -= cnt; } diff --git a/src/libraries/System.Private.Xml/src/System/Xml/Core/XmlTextReaderImpl.cs b/src/libraries/System.Private.Xml/src/System/Xml/Core/XmlTextReaderImpl.cs index 3957d7c39222da..ee11584856e46e 100644 --- a/src/libraries/System.Private.Xml/src/System/Xml/Core/XmlTextReaderImpl.cs +++ b/src/libraries/System.Private.Xml/src/System/Xml/Core/XmlTextReaderImpl.cs @@ -7432,15 +7432,10 @@ private int ParseNumericCharRefInline(int startPos, bool expand, StringBuilder? badDigitExceptionString = SR.Xml_BadHexEntity; while (true) { - char ch = chars[pos]; - if (ch >= '0' && ch <= '9') - val = checked(val * 16 + ch - '0'); - else if (ch >= 'a' && ch <= 'f') - val = checked(val * 16 + 10 + ch - 'a'); - else if (ch >= 'A' && ch <= 'F') - val = checked(val * 16 + 10 + ch - 'A'); - else + int ch = HexConverter.FromChar(chars[pos]); + if (ch == 0xFF) break; + val = checked(val * 16 + ch); pos++; } entityType = EntityType.CharacterHex; diff --git a/src/libraries/System.Private.Xml/src/System/Xml/XmlConvert.cs b/src/libraries/System.Private.Xml/src/System/Xml/XmlConvert.cs index 2d686e6920b2a2..8e9fcaeb144258 100644 --- a/src/libraries/System.Private.Xml/src/System/Xml/XmlConvert.cs +++ b/src/libraries/System.Private.Xml/src/System/Xml/XmlConvert.cs @@ -317,12 +317,7 @@ public class XmlConvert private static volatile Regex? s_decodeCharPattern; private static int FromHex(char digit) { - return (digit <= '9') - ? ((int)digit - (int)'0') - : (((digit <= 'F') - ? ((int)digit - (int)'A') - : ((int)digit - (int)'a')) - + 10); + return HexConverter.FromChar(digit); } internal static byte[] FromBinHexString(string s) diff --git a/src/libraries/System.Runtime.Numerics/src/System.Runtime.Numerics.csproj b/src/libraries/System.Runtime.Numerics/src/System.Runtime.Numerics.csproj index 9fd7811f61af25..52ae4fe33fc938 100644 --- a/src/libraries/System.Runtime.Numerics/src/System.Runtime.Numerics.csproj +++ b/src/libraries/System.Runtime.Numerics/src/System.Runtime.Numerics.csproj @@ -24,6 +24,8 @@ Link="System\Globalization\FormatProvider.Number.cs" /> + diff --git a/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigNumber.cs b/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigNumber.cs index e2a57da28bbc0d..cef0d816a00226 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigNumber.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigNumber.cs @@ -409,21 +409,8 @@ private static unsafe bool HexNumberToBigInteger(ref BigNumberBuffer number, ref for (int i = len - 1; i > -1; i--) { char c = number.digits[i]; - - byte b; - if (c >= '0' && c <= '9') - { - b = (byte)(c - '0'); - } - else if (c >= 'A' && c <= 'F') - { - b = (byte)((c - 'A') + 10); - } - else - { - Debug.Assert(c >= 'a' && c <= 'f'); - b = (byte)((c - 'a') + 10); - } + int b = HexConverter.FromChar(c); + Debug.Assert(b != 0xFF); if (i == 0 && (b & 0x08) == 0x08) isNegative = true; @@ -434,7 +421,7 @@ private static unsafe bool HexNumberToBigInteger(ref BigNumberBuffer number, ref } else { - bits[bitIndex] = isNegative ? (byte)(b | 0xF0) : (b); + bits[bitIndex] = (byte)(isNegative ? (b | 0xF0) : (b)); } shift = !shift; } diff --git a/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Helpers.cs b/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Helpers.cs index b654d5982de65e..9911ae8d33bcf1 100644 --- a/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Helpers.cs +++ b/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Helpers.cs @@ -25,21 +25,10 @@ internal static ReadOnlySpan AsSpanParameter(this byte[] array, string par public static char[] ToHexArrayUpper(this byte[] bytes) { char[] chars = new char[bytes.Length * 2]; - ToHexArrayUpper(bytes, chars); + HexConverter.EncodeToUtf16(bytes, chars); return chars; } - private static void ToHexArrayUpper(ReadOnlySpan bytes, Span chars) - { - Debug.Assert(chars.Length >= bytes.Length * 2); - int i = 0; - foreach (byte b in bytes) - { - HexConverter.ToCharsBuffer(b, chars, i, HexConverter.Casing.Upper); - i += 2; - } - } - // Encode a byte array as an upper case hex string. public static string ToHexStringUpper(this byte[] bytes) => Convert.ToHexString(bytes); @@ -79,7 +68,7 @@ public static byte[] DecodeHexString(this string hexString) } accum <<= 4; - accum |= HexToByte(c); + accum |= (byte)HexConverter.FromChar(c); byteInProgress = !byteInProgress; @@ -103,18 +92,6 @@ public static byte[] DecodeHexString(this string hexString) return hex; } - private static byte HexToByte(char val) - { - if (val <= '9' && val >= '0') - return (byte)(val - '0'); - else if (val >= 'a' && val <= 'f') - return (byte)((val - 'a') + 10); - else if (val >= 'A' && val <= 'F') - return (byte)((val - 'A') + 10); - else - return 0xFF; - } - public static bool ContentsEqual(this byte[]? a1, byte[]? a2) { if (a1 == null) diff --git a/src/libraries/System.Security.Cryptography.Xml/src/System/Security/Cryptography/Xml/SignedXmlDebugLog.cs b/src/libraries/System.Security.Cryptography.Xml/src/System/Security/Cryptography/Xml/SignedXmlDebugLog.cs index a8e18bed887a84..1075dbd397709d 100644 --- a/src/libraries/System.Security.Cryptography.Xml/src/System/Security/Cryptography/Xml/SignedXmlDebugLog.cs +++ b/src/libraries/System.Security.Cryptography.Xml/src/System/Security/Cryptography/Xml/SignedXmlDebugLog.cs @@ -197,13 +197,7 @@ private static string FormatBytes(byte[] bytes) if (bytes == null) return NullString; - StringBuilder builder = new StringBuilder(bytes.Length * 2); - foreach (byte b in bytes) - { - builder.Append(b.ToString("x2", CultureInfo.InvariantCulture)); - } - - return builder.ToString(); + return HexConverter.ToString(bytes, HexConverter.Casing.Lower); } /// diff --git a/src/libraries/System.Security.Cryptography.Xml/src/System/Security/Cryptography/Xml/Utils.cs b/src/libraries/System.Security.Cryptography.Xml/src/System/Security/Cryptography/Xml/Utils.cs index c5bf854a0989f1..c77ea9cce549fe 100644 --- a/src/libraries/System.Security.Cryptography.Xml/src/System/Security/Cryptography/Xml/Utils.cs +++ b/src/libraries/System.Security.Cryptography.Xml/src/System/Security/Cryptography/Xml/Utils.cs @@ -729,29 +729,11 @@ internal static string EncodeHexString(byte[] sArray) internal static byte[] DecodeHexString(string s) { string hexString = Utils.DiscardWhiteSpaces(s); - uint cbHex = (uint)hexString.Length / 2; - byte[] hex = new byte[cbHex]; - int i = 0; - for (int index = 0; index < cbHex; index++) - { - hex[index] = (byte)((HexToByte(hexString[i]) << 4) | HexToByte(hexString[i + 1])); - i += 2; - } + byte[] hex = new byte[hexString.Length / 2]; + HexConverter.TryDecodeFromUtf16(hexString.AsSpan(0, hex.Length * 2), hex); return hex; } - internal static byte HexToByte(char val) - { - if (val <= '9' && val >= '0') - return (byte)(val - '0'); - else if (val >= 'a' && val <= 'f') - return (byte)((val - 'a') + 10); - else if (val >= 'A' && val <= 'F') - return (byte)((val - 'A') + 10); - else - return 0xFF; - } - internal static bool IsSelfSigned(X509Chain chain) { X509ChainElementCollection elements = chain.ChainElements; diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Reader/JsonReaderHelper.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Reader/JsonReaderHelper.cs index 002826694e650f..a8ae579aad3174 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Reader/JsonReaderHelper.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Reader/JsonReaderHelper.cs @@ -58,10 +58,7 @@ public static bool IsTokenTypePrimitive(JsonTokenType tokenType) => // A hex digit is valid if it is in the range: [0..9] | [A..F] | [a..f] // Otherwise, return false. - public static bool IsHexDigit(byte nextByte) => - (uint)(nextByte - '0') <= '9' - '0' || - (uint)(nextByte - 'A') <= 'F' - 'A' || - (uint)(nextByte - 'a') <= 'f' - 'a'; + public static bool IsHexDigit(byte nextByte) => HexConverter.IsHexChar(nextByte); // https://tools.ietf.org/html/rfc8259 // Does the span contain '"', '\', or any control characters (i.e. 0 to 31) diff --git a/src/libraries/System.Web.HttpUtility/src/System/Web/Util/HttpEncoder.cs b/src/libraries/System.Web.HttpUtility/src/System/Web/Util/HttpEncoder.cs index 92f5cf9fa6b14a..0dad08571bf84f 100644 --- a/src/libraries/System.Web.HttpUtility/src/System/Web/Util/HttpEncoder.cs +++ b/src/libraries/System.Web.HttpUtility/src/System/Web/Util/HttpEncoder.cs @@ -270,10 +270,10 @@ internal static string JavaScriptStringEncode(string? value) } else if (b == '%' && i < count - 2) { - int h1 = HttpEncoderUtility.HexToInt((char)bytes[pos + 1]); - int h2 = HttpEncoderUtility.HexToInt((char)bytes[pos + 2]); + int h1 = HexConverter.FromChar(bytes[pos + 1]); + int h2 = HexConverter.FromChar(bytes[pos + 2]); - if (h1 >= 0 && h2 >= 0) + if ((h1 | h2) != 0xFF) { // valid 2 hex chars b = (byte)((h1 << 4) | h2); @@ -323,12 +323,12 @@ internal static string JavaScriptStringEncode(string? value) { if (bytes[pos + 1] == 'u' && i < count - 5) { - int h1 = HttpEncoderUtility.HexToInt((char)bytes[pos + 2]); - int h2 = HttpEncoderUtility.HexToInt((char)bytes[pos + 3]); - int h3 = HttpEncoderUtility.HexToInt((char)bytes[pos + 4]); - int h4 = HttpEncoderUtility.HexToInt((char)bytes[pos + 5]); + int h1 = HexConverter.FromChar(bytes[pos + 2]); + int h2 = HexConverter.FromChar(bytes[pos + 3]); + int h3 = HexConverter.FromChar(bytes[pos + 4]); + int h4 = HexConverter.FromChar(bytes[pos + 5]); - if (h1 >= 0 && h2 >= 0 && h3 >= 0 && h4 >= 0) + if ((h1 | h2 | h3 | h4) != 0xFF) { // valid 4 hex chars char ch = (char)((h1 << 12) | (h2 << 8) | (h3 << 4) | h4); i += 5; @@ -340,10 +340,10 @@ internal static string JavaScriptStringEncode(string? value) } else { - int h1 = HttpEncoderUtility.HexToInt((char)bytes[pos + 1]); - int h2 = HttpEncoderUtility.HexToInt((char)bytes[pos + 2]); + int h1 = HexConverter.FromChar(bytes[pos + 1]); + int h2 = HexConverter.FromChar(bytes[pos + 2]); - if (h1 >= 0 && h2 >= 0) + if ((h1 | h2) != 0xFF) { // valid 2 hex chars b = (byte)((h1 << 4) | h2); i += 2; @@ -384,12 +384,12 @@ internal static string JavaScriptStringEncode(string? value) { if (value[pos + 1] == 'u' && pos < count - 5) { - int h1 = HttpEncoderUtility.HexToInt(value[pos + 2]); - int h2 = HttpEncoderUtility.HexToInt(value[pos + 3]); - int h3 = HttpEncoderUtility.HexToInt(value[pos + 4]); - int h4 = HttpEncoderUtility.HexToInt(value[pos + 5]); + int h1 = HexConverter.FromChar(value[pos + 2]); + int h2 = HexConverter.FromChar(value[pos + 3]); + int h3 = HexConverter.FromChar(value[pos + 4]); + int h4 = HexConverter.FromChar(value[pos + 5]); - if (h1 >= 0 && h2 >= 0 && h3 >= 0 && h4 >= 0) + if ((h1 | h2 | h3 | h4) != 0xFF) { // valid 4 hex chars ch = (char)((h1 << 12) | (h2 << 8) | (h3 << 4) | h4); pos += 5; @@ -401,10 +401,10 @@ internal static string JavaScriptStringEncode(string? value) } else { - int h1 = HttpEncoderUtility.HexToInt(value[pos + 1]); - int h2 = HttpEncoderUtility.HexToInt(value[pos + 2]); + int h1 = HexConverter.FromChar(value[pos + 1]); + int h2 = HexConverter.FromChar(value[pos + 2]); - if (h1 >= 0 && h2 >= 0) + if ((h1 | h2) != 0xFF) { // valid 2 hex chars byte b = (byte)((h1 << 4) | h2); pos += 2; diff --git a/src/libraries/System.Web.HttpUtility/src/System/Web/Util/HttpEncoderUtility.cs b/src/libraries/System.Web.HttpUtility/src/System/Web/Util/HttpEncoderUtility.cs index bc07b63f48a23c..87012d7862ca3a 100644 --- a/src/libraries/System.Web.HttpUtility/src/System/Web/Util/HttpEncoderUtility.cs +++ b/src/libraries/System.Web.HttpUtility/src/System/Web/Util/HttpEncoderUtility.cs @@ -9,15 +9,6 @@ namespace System.Web.Util { internal static class HttpEncoderUtility { - public static int HexToInt(char h) => - h >= '0' && h <= '9' - ? h - '0' - : h >= 'a' && h <= 'f' - ? h - 'a' + 10 - : h >= 'A' && h <= 'F' - ? h - 'A' + 10 - : -1; - // Set of safe chars, from RFC 1738.4 minus '+' public static bool IsUrlSafeChar(char ch) { From de2085b12951f29d1985a015dee57d031e09ea07 Mon Sep 17 00:00:00 2001 From: Nicolas Portmann Date: Wed, 15 Jul 2020 09:58:51 +0200 Subject: [PATCH 11/12] Address review feedback --- src/libraries/Common/src/System/HexConverter.cs | 17 ++++++++--------- .../Net/NetworkInformation/PhysicalAddress.cs | 2 +- .../StringParsingHelpers.Connections.cs | 4 ++-- .../src/Resources/Strings.resx | 3 +++ .../src/System/Convert.cs | 11 +++++++---- .../tests/System/Convert.ToHexString.cs | 2 +- .../System/Security/Cryptography/Xml/Utils.cs | 10 ++++++++-- 7 files changed, 30 insertions(+), 19 deletions(-) diff --git a/src/libraries/Common/src/System/HexConverter.cs b/src/libraries/Common/src/System/HexConverter.cs index 425ee6ef081984..4c9f94e110b353 100644 --- a/src/libraries/Common/src/System/HexConverter.cs +++ b/src/libraries/Common/src/System/HexConverter.cs @@ -183,7 +183,7 @@ public static bool TryDecodeFromUtf16(ReadOnlySpan chars, Span bytes break; bytes[j++] = (byte)((byteHi << 4) | byteLo); - i+=2; + i += 2; } if (byteLo == 0xFF) @@ -196,24 +196,23 @@ public static bool TryDecodeFromUtf16(ReadOnlySpan chars, Span bytes [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int FromChar(int c) { - ReadOnlySpan charToHexLookup = CharToHexLookup; - return c >= charToHexLookup.Length ? 0xFF : charToHexLookup[c]; + return c >= CharToHexLookup.Length ? 0xFF : CharToHexLookup[c]; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int FromUpperChar(int c) { - ReadOnlySpan charToHexLookup = CharToHexLookup; - return c > 71 ? 0xFF : charToHexLookup[c]; + return c > 71 ? 0xFF : CharToHexLookup[c]; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int FromLowerChar(int c) { - if ('0' <= c && c <= '9') - return (byte)(c - '0'); - if ('a' <= c && c <= 'f') - return (byte)(c - ('a' - 10)); + if ((uint)(c - '0') <= '9' - '0') + return c - '0'; + + if ((uint)(c - 'a') <= 'f' - 'a') + return c - 'a' + 10; return 0xFF; } diff --git a/src/libraries/System.Net.NetworkInformation/src/System/Net/NetworkInformation/PhysicalAddress.cs b/src/libraries/System.Net.NetworkInformation/src/System/Net/NetworkInformation/PhysicalAddress.cs index 5bd86e6a807ccd..46984ccb4604f7 100644 --- a/src/libraries/System.Net.NetworkInformation/src/System/Net/NetworkInformation/PhysicalAddress.cs +++ b/src/libraries/System.Net.NetworkInformation/src/System/Net/NetworkInformation/PhysicalAddress.cs @@ -91,7 +91,7 @@ public override bool Equals(object? comparand) public override string ToString() { - return Convert.ToHexString(_address ?? Array.Empty()); + return Convert.ToHexString(_address.AsSpan()); } public byte[] GetAddressBytes() diff --git a/src/libraries/System.Net.NetworkInformation/src/System/Net/NetworkInformation/StringParsingHelpers.Connections.cs b/src/libraries/System.Net.NetworkInformation/src/System/Net/NetworkInformation/StringParsingHelpers.Connections.cs index 0a66b7caddb1e3..d00f3a8fe8ced1 100644 --- a/src/libraries/System.Net.NetworkInformation/src/System/Net/NetworkInformation/StringParsingHelpers.Connections.cs +++ b/src/libraries/System.Net.NetworkInformation/src/System/Net/NetworkInformation/StringParsingHelpers.Connections.cs @@ -326,13 +326,13 @@ private static IPAddress ParseIPv6HexString(string hexAddress, bool isNetworkOrd private static byte HexToByte(char val) { - byte result = (byte)HexConverter.FromChar(val); + int result = HexConverter.FromChar(val); if (result == 0xFF) { throw ExceptionHelper.CreateForParseFailure(); } - return result; + return (byte)result; } } } diff --git a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx index b97f00f3bad03f..cd63cb1ea6c823 100644 --- a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx +++ b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx @@ -1756,6 +1756,9 @@ Index was out of range. Must be non-negative and less than the length of the string. + + Input is too large to be processed. + Era value was not valid. diff --git a/src/libraries/System.Private.CoreLib/src/System/Convert.cs b/src/libraries/System.Private.CoreLib/src/System/Convert.cs index 1844a2a27323ae..0a821f4e6fee74 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Convert.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Convert.cs @@ -2878,11 +2878,12 @@ public static byte[] FromHexString(ReadOnlySpan chars) } /// - /// Converts an array of 8-bit unsigned integers to its equivalent string representation that is encoded with hex characters. + /// Converts an array of 8-bit unsigned integers to its equivalent string representation that is encoded with uppercase hex characters. /// /// An array of 8-bit unsigned integers. /// The string representation in hex of the elements in . /// is null. + /// is too large to be encoded. public static string ToHexString(byte[] inArray) { if (inArray == null) @@ -2892,7 +2893,7 @@ public static string ToHexString(byte[] inArray) } /// - /// Converts a subset of an array of 8-bit unsigned integers to its equivalent string representation that is encoded with hex characters. + /// Converts a subset of an array of 8-bit unsigned integers to its equivalent string representation that is encoded with uppercase hex characters. /// Parameters specify the subset as an offset in the input array and the number of elements in the array to convert. /// /// An array of 8-bit unsigned integers. @@ -2902,6 +2903,7 @@ public static string ToHexString(byte[] inArray) /// is null. /// or is negative. /// plus is greater than the length of . + /// is too large to be encoded. public static string ToHexString(byte[] inArray, int offset, int length) { if (inArray == null) @@ -2917,16 +2919,17 @@ public static string ToHexString(byte[] inArray, int offset, int length) } /// - /// Converts a span of 8-bit unsigned integers to its equivalent string representation that is encoded with hex characters. + /// Converts a span of 8-bit unsigned integers to its equivalent string representation that is encoded with uppercase hex characters. /// /// A span of 8-bit unsigned integers. /// The string representation in hex of the elements in . + /// is too large to be encoded. public static string ToHexString(ReadOnlySpan bytes) { if (bytes.Length == 0) return string.Empty; if (bytes.Length > int.MaxValue / 2) - throw new OutOfMemoryException(); + throw new ArgumentOutOfRangeException(nameof(bytes), SR.ArgumentOutOfRange_InputTooLarge); return HexConverter.ToString(bytes, HexConverter.Casing.Upper); } diff --git a/src/libraries/System.Runtime.Extensions/tests/System/Convert.ToHexString.cs b/src/libraries/System.Runtime.Extensions/tests/System/Convert.ToHexString.cs index 9e01fdc0449bec..aea831ffc40634 100644 --- a/src/libraries/System.Runtime.Extensions/tests/System/Convert.ToHexString.cs +++ b/src/libraries/System.Runtime.Extensions/tests/System/Convert.ToHexString.cs @@ -60,7 +60,7 @@ public static void InvalidLength() [Fact] public static unsafe void InputTooLarge() { - Assert.Throws(() => Convert.ToHexString(new ReadOnlySpan((void*)0, Int32.MaxValue))); + AssertExtensions.Throws("bytes", () => Convert.ToHexString(new ReadOnlySpan((void*)0, Int32.MaxValue))); } } } diff --git a/src/libraries/System.Security.Cryptography.Xml/src/System/Security/Cryptography/Xml/Utils.cs b/src/libraries/System.Security.Cryptography.Xml/src/System/Security/Cryptography/Xml/Utils.cs index c77ea9cce549fe..7797699d7187d1 100644 --- a/src/libraries/System.Security.Cryptography.Xml/src/System/Security/Cryptography/Xml/Utils.cs +++ b/src/libraries/System.Security.Cryptography.Xml/src/System/Security/Cryptography/Xml/Utils.cs @@ -729,8 +729,14 @@ internal static string EncodeHexString(byte[] sArray) internal static byte[] DecodeHexString(string s) { string hexString = Utils.DiscardWhiteSpaces(s); - byte[] hex = new byte[hexString.Length / 2]; - HexConverter.TryDecodeFromUtf16(hexString.AsSpan(0, hex.Length * 2), hex); + uint cbHex = (uint)hexString.Length / 2; + byte[] hex = new byte[cbHex]; + int i = 0; + for (int index = 0; index < cbHex; index++) + { + hex[index] = (byte)((HexConverter.FromChar(hexString[i]) << 4) | HexConverter.FromChar(hexString[i + 1])); + i += 2; + } return hex; } From 7917e6dbb41027fe3a808c61cf89b608e18fa30d Mon Sep 17 00:00:00 2001 From: Nicolas Portmann Date: Wed, 15 Jul 2020 11:19:37 +0200 Subject: [PATCH 12/12] Remove another duplicate hex lookup table --- .../Common/src/System/HexConverter.cs | 13 ++++++++++-- .../Buffers/Text/Utf8Parser/ParserHelpers.cs | 20 ------------------- .../Utf8Parser.Integer.Unsigned.X.cs | 12 ++++------- 3 files changed, 15 insertions(+), 30 deletions(-) diff --git a/src/libraries/Common/src/System/HexConverter.cs b/src/libraries/Common/src/System/HexConverter.cs index 4c9f94e110b353..2c54a2df5d0660 100644 --- a/src/libraries/Common/src/System/HexConverter.cs +++ b/src/libraries/Common/src/System/HexConverter.cs @@ -236,7 +236,7 @@ public static bool IsHexLowerChar(int c) } /// Map from an ASCII char to its hex value, e.g. arr['b'] == 11. 0xFF means it's not a hex digit. - private static ReadOnlySpan CharToHexLookup => new byte[] + public static ReadOnlySpan CharToHexLookup => new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 15 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 31 @@ -244,7 +244,16 @@ public static bool IsHexLowerChar(int c) 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 63 0xFF, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 79 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 95 - 0xFF, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf // 102 + 0xFF, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 111 + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 127 + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 143 + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 159 + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 175 + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 191 + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 207 + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 223 + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 239 + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF // 255 }; } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Parser/ParserHelpers.cs b/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Parser/ParserHelpers.cs index 5d0562e9d04b60..51c6ebd0f12b7f 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Parser/ParserHelpers.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Parser/ParserHelpers.cs @@ -29,26 +29,6 @@ internal static class ParserHelpers public const int Int64OverflowLength = 19; public const int Int64OverflowLengthHex = 16; - public static ReadOnlySpan HexLookup => new byte[] // rely on C# compiler optimization to reference static data - { - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 15 - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 31 - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 47 - 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 63 - 0xFF, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 79 - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 95 - 0xFF, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 111 - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 127 - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 143 - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 159 - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 175 - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 191 - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 207 - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 223 - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 239 - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF // 255 - }; - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool IsDigit(int i) { diff --git a/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Parser/Utf8Parser.Integer.Unsigned.X.cs b/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Parser/Utf8Parser.Integer.Unsigned.X.cs index 1d07e9ef7debd2..76075ab149178e 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Parser/Utf8Parser.Integer.Unsigned.X.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Parser/Utf8Parser.Integer.Unsigned.X.cs @@ -17,8 +17,7 @@ private static bool TryParseByteX(ReadOnlySpan source, out byte value, out byte nextCharacter; byte nextDigit; - // Cache Parsers.s_HexLookup in order to avoid static constructor checks - ReadOnlySpan hexLookup = ParserHelpers.HexLookup; + ReadOnlySpan hexLookup = HexConverter.CharToHexLookup; // Parse the first digit separately. If invalid here, we need to return false. nextCharacter = source[0]; @@ -100,8 +99,7 @@ private static bool TryParseUInt16X(ReadOnlySpan source, out ushort value, byte nextCharacter; byte nextDigit; - // Cache Parsers.s_HexLookup in order to avoid static constructor checks - ReadOnlySpan hexLookup = ParserHelpers.HexLookup; + ReadOnlySpan hexLookup = HexConverter.CharToHexLookup; // Parse the first digit separately. If invalid here, we need to return false. nextCharacter = source[0]; @@ -183,8 +181,7 @@ private static bool TryParseUInt32X(ReadOnlySpan source, out uint value, o byte nextCharacter; byte nextDigit; - // Cache Parsers.s_HexLookup in order to avoid static constructor checks - ReadOnlySpan hexLookup = ParserHelpers.HexLookup; + ReadOnlySpan hexLookup = HexConverter.CharToHexLookup; // Parse the first digit separately. If invalid here, we need to return false. nextCharacter = source[0]; @@ -266,8 +263,7 @@ private static bool TryParseUInt64X(ReadOnlySpan source, out ulong value, byte nextCharacter; byte nextDigit; - // Cache Parsers.s_HexLookup in order to avoid static constructor checks - ReadOnlySpan hexLookup = ParserHelpers.HexLookup; + ReadOnlySpan hexLookup = HexConverter.CharToHexLookup; // Parse the first digit separately. If invalid here, we need to return false. nextCharacter = source[0];