From f6dc87f43df543bdc761ded7c578ecedc5b39668 Mon Sep 17 00:00:00 2001 From: Nicolas Portmann Date: Sat, 18 Jul 2020 02:12:21 +0200 Subject: [PATCH] Add Convert.ToHexString/FromHexString Add methods to convert between hexadecimal strings and bytes, and converts internal usage to use those APIs when possible, or reuse the shared code as helpers otherwise. --- .../Common/src/System/HexConverter.cs | 114 +++++++++++++++++- .../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 | 21 +--- .../StringParsingHelpers.Connections.cs | 17 +-- ...NetworkInformation.Functional.Tests.csproj | 2 + .../src/Resources/Strings.resx | 9 ++ .../Buffers/Text/Utf8Parser/ParserHelpers.cs | 20 --- .../Utf8Parser.Integer.Unsigned.X.cs | 12 +- .../src/System/Convert.cs | 95 +++++++++++++++ .../System.Private.CoreLib/src/System/Guid.cs | 5 +- .../src/System/Net/WebUtility.cs | 20 +-- .../src/System/Number.Parsing.cs | 42 +++---- .../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 | 48 +------- .../src/System/Xml/BinHexEncoderAsync.cs | 4 +- .../src/System/Xml/Core/XmlTextReaderImpl.cs | 11 +- .../src/System/Xml/XmlConvert.cs | 7 +- .../System.Runtime.Extensions.Tests.csproj | 4 +- .../tests/System/Convert.FromHexString.cs | 106 ++++++++++++++++ .../tests/System/Convert.ToHexString.cs | 66 ++++++++++ .../src/System.Runtime.Numerics.csproj | 2 + .../src/System/Numerics/BigNumber.cs | 19 +-- .../System.Runtime/ref/System.Runtime.cs | 5 + .../tests/System/Text/Unicode/Utf8Tests.cs | 7 +- .../src/Internal/Cryptography/AsnFormatter.cs | 2 +- .../src/Internal/Cryptography/Helpers.cs | 29 +---- .../Cryptography/Xml/SignedXmlDebugLog.cs | 8 +- .../System/Security/Cryptography/Xml/Utils.cs | 33 +---- .../Text/Json/Reader/JsonReaderHelper.cs | 5 +- .../src/System/Web/Util/HttpEncoder.cs | 38 +++--- .../src/System/Web/Util/HttpEncoderUtility.cs | 9 -- .../tests/X509Certificate2UIManualTests.cs | 10 +- 42 files changed, 535 insertions(+), 504 deletions(-) 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 b06ce4b8796eb..d6160a18aa031 100644 --- a/src/libraries/Common/src/System/HexConverter.cs +++ b/src/libraries/Common/src/System/HexConverter.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Diagnostics; using System.Runtime.CompilerServices; namespace System @@ -82,6 +83,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 @@ -112,10 +123,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 @@ -148,5 +156,103 @@ public static char ToCharLower(int value) return (char)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) + { + 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) + break; + + bytes[j++] = (byte)((byteHi << 4) | byteLo); + i += 2; + } + + if (byteLo == 0xFF) + i++; + + charsProcessed = i; + return (byteLo | byteHi) != 0xFF; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int FromChar(int c) + { + return c >= CharToHexLookup.Length ? 0xFF : CharToHexLookup[c]; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int FromUpperChar(int c) + { + return c > 71 ? 0xFF : CharToHexLookup[c]; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int FromLowerChar(int c) + { + if ((uint)(c - '0') <= '9' - '0') + return c - '0'; + + if ((uint)(c - 'a') <= 'f' - 'a') + return 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. + 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 + 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 + }; } } 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 5dea0482483c4..5cb992426c36a 100644 --- a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Activity.cs +++ b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Activity.cs @@ -851,7 +851,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; } @@ -1183,7 +1183,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); } @@ -1584,15 +1584,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) @@ -1603,7 +1602,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; } @@ -1616,13 +1615,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 97ed5149d2f92..35c98f80b66e3 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 @@ -306,20 +306,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 b78a6d1e3f6eb..6def0c00e3b5d 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 @@ -236,17 +236,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 e14f7fc02e13e..8f6ce68838a67 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 6a644810018ff..22f1a1548994b 100644 --- a/src/libraries/System.Net.HttpListener/src/System/Net/HttpListenerRequest.cs +++ b/src/libraries/System.Net.HttpListener/src/System/Net/HttpListenerRequest.cs @@ -430,12 +430,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; @@ -447,10 +447,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; @@ -471,14 +471,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 bdee24168b5e7..f2203d56bfe08 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 @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics.CodeAnalysis; -using System.Text; namespace System.Net.NetworkInformation { @@ -91,7 +90,7 @@ public override bool Equals(object? comparand) public override string ToString() { - return HexConverter.ToString(_address.AsSpan(), HexConverter.Casing.Upper); + return Convert.ToHexString(_address.AsSpan()); } public byte[] GetAddressBytes() @@ -184,20 +183,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) { @@ -208,6 +195,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 1355c470afbd1..5f2548e5102b7 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 @@ -325,22 +325,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 + int result = HexConverter.FromChar(val); + if (result == 0xFF) { throw ExceptionHelper.CreateForParseFailure(); } + + return (byte)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 823110a4403ca..07e4ae778c370 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 @@ -53,6 +53,8 @@ Link="Common\System\IO\StringParser.cs" /> + diff --git a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx index e3e86925a2758..982f0b8e4a210 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. @@ -2224,6 +2227,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/Buffers/Text/Utf8Parser/ParserHelpers.cs b/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Parser/ParserHelpers.cs index 9d4447e260189..46e39795302c3 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 @@ -28,26 +28,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 424568425b79f..543ee68baaecb 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 @@ -16,8 +16,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]; @@ -99,8 +98,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]; @@ -182,8 +180,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]; @@ -265,8 +262,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]; diff --git a/src/libraries/System.Private.CoreLib/src/System/Convert.cs b/src/libraries/System.Private.CoreLib/src/System/Convert.cs index 3144805cefeb7..affc2774e55c9 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Convert.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Convert.cs @@ -2837,5 +2837,100 @@ 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 ((uint)chars.Length % 2 != 0) + throw new FormatException(SR.Format_BadHexLength); + + byte[] result = GC.AllocateUninitializedArray(chars.Length >> 1); + + if (!HexConverter.TryDecodeFromUtf16(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 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) + 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 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. + /// 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 . + /// is too large to be encoded. + 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 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 ArgumentOutOfRangeException(nameof(bytes), SR.ArgumentOutOfRange_InputTooLarge); + + return HexConverter.ToString(bytes, HexConverter.Casing.Upper); + } } // class Convert } // namespace diff --git a/src/libraries/System.Private.CoreLib/src/System/Guid.cs b/src/libraries/System.Private.CoreLib/src/System/Guid.cs index a4f30939b9b05..dd5f5b8de6c0d 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Guid.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Guid.cs @@ -663,13 +663,12 @@ private static bool TryParseHex(ReadOnlySpan guidString, out uint result, for (; i < guidString.Length && guidString[i] == '0'; i++) ; int processedDigits = 0; - ReadOnlySpan charToHexLookup = Number.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 eb97724e78f05..e9b5610304f21 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Net/WebUtility.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Net/WebUtility.cs @@ -510,10 +510,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; @@ -568,10 +568,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; @@ -649,14 +649,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 3c507f573355c..0d7133bc027e6 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Number.Parsing.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Number.Parsing.cs @@ -44,18 +44,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(); @@ -1133,9 +1121,8 @@ private static ParsingStatus TryParseUInt32HexNumberStyle(ReadOnlySpan val bool overflow = false; uint answer = 0; - ReadOnlySpan charToHexLookup = CharToHexLookup; - if ((uint)num < (uint)charToHexLookup.Length && charToHexLookup[num] != 0xFF) + if (HexConverter.IsHexChar(num)) { // Skip past leading zeros. if (num == '0') @@ -1147,12 +1134,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 { @@ -1160,8 +1147,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; @@ -1171,7 +1158,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. @@ -1182,7 +1169,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; } @@ -1462,9 +1449,8 @@ private static ParsingStatus TryParseUInt64HexNumberStyle(ReadOnlySpan val bool overflow = false; ulong answer = 0; - ReadOnlySpan charToHexLookup = CharToHexLookup; - if ((uint)num < (uint)charToHexLookup.Length && charToHexLookup[num] != 0xFF) + if (HexConverter.IsHexChar(num)) { // Skip past leading zeros. if (num == '0') @@ -1476,12 +1462,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 { @@ -1489,8 +1475,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; @@ -1500,7 +1486,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. @@ -1511,7 +1497,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 c391a8d71b4d9..c547a30e50c89 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/AssemblyName.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/AssemblyName.cs @@ -339,7 +339,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++] = '%'; @@ -402,37 +402,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 39ad74402020c..f5b0e46ca1946 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/AssemblyNameFormatter.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/AssemblyNameFormatter.cs @@ -69,10 +69,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 f72f20f8a8402..98814281130ae 100644 --- a/src/libraries/System.Private.DataContractSerialization/src/System/Text/BinHexEncoding.cs +++ b/src/libraries/System.Private.DataContractSerialization/src/System/Text/BinHexEncoding.cs @@ -8,26 +8,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) @@ -65,31 +45,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; @@ -135,22 +94,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 00859845ee6fc..f8357c878503a 100644 --- a/src/libraries/System.Private.DataContractSerialization/src/System/Xml/XmlBufferReader.cs +++ b/src/libraries/System.Private.DataContractSerialization/src/System/Xml/XmlBufferReader.cs @@ -779,14 +779,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 5fd9883c0d53c..daff7bddf4df8 100644 --- a/src/libraries/System.Private.Uri/src/System/Uri.cs +++ b/src/libraries/System.Private.Uri/src/System/Uri.cs @@ -1497,10 +1497,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: @@ -1509,11 +1509,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 91efcdc2c3a30..5069acd553a88 100644 --- a/src/libraries/System.Private.Uri/src/System/UriHelper.cs +++ b/src/libraries/System.Private.Uri/src/System/UriHelper.cs @@ -671,36 +671,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 = @";/?:@&=+$,#[]!'()*"; @@ -782,9 +764,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 7a17ad9b95e20..6c6a6208bc5e6 100644 --- a/src/libraries/System.Private.Xml/src/System/Xml/BinHexDecoder.cs +++ b/src/libraries/System.Private.Xml/src/System/Xml/BinHexDecoder.cs @@ -199,17 +199,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 e09a7be81b162..ee8a5a340e05c 100644 --- a/src/libraries/System.Private.Xml/src/System/Xml/BinHexEncoder.cs +++ b/src/libraries/System.Private.Xml/src/System/Xml/BinHexEncoder.cs @@ -32,8 +32,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; } @@ -41,49 +41,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) - { - 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 2a830bc142054..7bd9b7eaff2c2 100644 --- a/src/libraries/System.Private.Xml/src/System/Xml/BinHexEncoderAsync.cs +++ b/src/libraries/System.Private.Xml/src/System/Xml/BinHexEncoderAsync.cs @@ -32,8 +32,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 75ad0ddeef20e..4a959c251118b 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 @@ -7376,15 +7376,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 62f07e574f084..f777c49e5c626 100644 --- a/src/libraries/System.Private.Xml/src/System/Xml/XmlConvert.cs +++ b/src/libraries/System.Private.Xml/src/System/Xml/XmlConvert.cs @@ -316,12 +316,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.Extensions/tests/System.Runtime.Extensions.Tests.csproj b/src/libraries/System.Runtime.Extensions/tests/System.Runtime.Extensions.Tests.csproj index bac799fc38f18..10f57ac6b1b5e 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 0000000000000..488cda85c3510 --- /dev/null +++ b/src/libraries/System.Runtime.Extensions/tests/System/Convert.FromHexString.cs @@ -0,0 +1,106 @@ +using System.Text; +using Xunit; + +namespace System.Tests +{ + public class ConvertFromHexStringTests + { + [Theory] + [InlineData("000102FDFEFF")] + [InlineData("000102fdfeff")] + [InlineData("000102fDfEfF")] + [InlineData("000102FdFeFf")] + [InlineData("000102FDfeFF")] + public static void KnownByteSequence(string value) + { + byte[] knownSequence = {0x00, 0x01, 0x02, 0xFD, 0xFE, 0xFF}; + TestSequence(knownSequence, value); + } + + [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")); + } + + TestSequence(values, sb.ToString()); + TestSequence(values, sb.ToString().ToLower()); + } + + private static void TestSequence(byte[] expected, string actual) + { + Assert.Equal(expected, Convert.FromHexString(actual)); + } + + [Fact] + public static void InvalidInputString_Null() + { + AssertExtensions.Throws("s", () => 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_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 0000000000000..aea831ffc4063 --- /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() + { + AssertExtensions.Throws("inArray", () => Convert.ToHexString(null)); + AssertExtensions.Throws("inArray", () => Convert.ToHexString(null, 0, 0)); + } + + [Fact] + public static void InvalidOffset() + { + byte[] inputBytes = Convert.FromHexString("000102FDFEFF"); + 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"); + 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] + public static unsafe void InputTooLarge() + { + AssertExtensions.Throws("bytes", () => Convert.ToHexString(new ReadOnlySpan((void*)0, Int32.MaxValue))); + } + } +} 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 9fd7811f61af2..52ae4fe33fc93 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 dc0ee9406517f..f8697ddebbeb2 100644 --- a/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigNumber.cs +++ b/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigNumber.cs @@ -408,21 +408,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; @@ -433,7 +420,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.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs index 447e65bd66d79..75def9c62e075 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; } 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 69f1c9fb2d0aa..d675b3f8431ce 100644 --- a/src/libraries/System.Runtime/tests/System/Text/Unicode/Utf8Tests.cs +++ b/src/libraries/System.Runtime/tests/System/Text/Unicode/Utf8Tests.cs @@ -64,12 +64,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 ed65f0bcfa3f8..b83af59e0d297 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 @@ -13,7 +13,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 f457cca92ef5c..f29792b22aeea 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 @@ -24,24 +24,13 @@ 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) => - 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. @@ -78,7 +67,7 @@ public static byte[] DecodeHexString(this string hexString) } accum <<= 4; - accum |= HexToByte(c); + accum |= (byte)HexConverter.FromChar(c); byteInProgress = !byteInProgress; @@ -102,18 +91,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 19e55efdfcc08..509934612ebbe 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 @@ -196,13 +196,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 55353d710e9c8..123afc5d21056 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 @@ -722,24 +722,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) @@ -750,24 +733,12 @@ internal static byte[] DecodeHexString(string s) int i = 0; for (int index = 0; index < cbHex; index++) { - hex[index] = (byte)((HexToByte(hexString[i]) << 4) | HexToByte(hexString[i + 1])); + hex[index] = (byte)((HexConverter.FromChar(hexString[i]) << 4) | HexConverter.FromChar(hexString[i + 1])); i += 2; } 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 ee4d62a7e9555..fcd7f0d3fb8da 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 @@ -57,10 +57,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 583f0493d96ed..68cc875b29d86 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 @@ -269,10 +269,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); @@ -322,12 +322,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; @@ -339,10 +339,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; @@ -383,12 +383,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; @@ -400,10 +400,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 1d463d7151fd2..4c3f6fe0f999e 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 @@ -8,15 +8,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) { diff --git a/src/libraries/System.Windows.Extensions/tests/X509Certificate2UIManualTests.cs b/src/libraries/System.Windows.Extensions/tests/X509Certificate2UIManualTests.cs index a4f6eee6be04e..3edd4837eb1f8 100644 --- a/src/libraries/System.Windows.Extensions/tests/X509Certificate2UIManualTests.cs +++ b/src/libraries/System.Windows.Extensions/tests/X509Certificate2UIManualTests.cs @@ -71,15 +71,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); } } }