diff --git a/src/libraries/Common/src/System/HexConverter.cs b/src/libraries/Common/src/System/HexConverter.cs new file mode 100644 index 0000000000000..a1d76cca27733 --- /dev/null +++ b/src/libraries/Common/src/System/HexConverter.cs @@ -0,0 +1,153 @@ +// Licensed to the .NET Foundation under one or more agreements. +// 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.Runtime.CompilerServices; + +namespace System +{ + internal static class HexConverter + { + public enum Casing : uint + { + // Output [ '0' .. '9' ] and [ 'A' .. 'F' ]. + Upper = 0, + + // Output [ '0' .. '9' ] and [ 'a' .. 'f' ]. + // This works because values in the range [ 0x30 .. 0x39 ] ([ '0' .. '9' ]) + // already have the 0x20 bit set, so ORing them with 0x20 is a no-op, + // while outputs in the range [ 0x41 .. 0x46 ] ([ 'A' .. 'F' ]) + // don't have the 0x20 bit set, so ORing them maps to + // [ 0x61 .. 0x66 ] ([ 'a' .. 'f' ]), which is what we want. + Lower = 0x2020U, + } + + // We want to pack the incoming byte into a single integer [ 0000 HHHH 0000 LLLL ], + // where HHHH and LLLL are the high and low nibbles of the incoming byte. Then + // subtract this integer from a constant minuend as shown below. + // + // [ 1000 1001 1000 1001 ] + // - [ 0000 HHHH 0000 LLLL ] + // ========================= + // [ *YYY **** *ZZZ **** ] + // + // The end result of this is that YYY is 0b000 if HHHH <= 9, and YYY is 0b111 if HHHH >= 10. + // Similarly, ZZZ is 0b000 if LLLL <= 9, and ZZZ is 0b111 if LLLL >= 10. + // (We don't care about the value of asterisked bits.) + // + // To turn a nibble in the range [ 0 .. 9 ] into hex, we calculate hex := nibble + 48 (ascii '0'). + // To turn a nibble in the range [ 10 .. 15 ] into hex, we calculate hex := nibble - 10 + 65 (ascii 'A'). + // => hex := nibble + 55. + // The difference in the starting ASCII offset is (55 - 48) = 7, depending on whether the nibble is <= 9 or >= 10. + // Since 7 is 0b111, this conveniently matches the YYY or ZZZ value computed during the earlier subtraction. + + // The commented out code below is code that directly implements the logic described above. + + // uint packedOriginalValues = (((uint)value & 0xF0U) << 4) + ((uint)value & 0x0FU); + // uint difference = 0x8989U - packedOriginalValues; + // uint add7Mask = (difference & 0x7070U) >> 4; // line YYY and ZZZ back up with the packed values + // uint packedResult = packedOriginalValues + add7Mask + 0x3030U /* ascii '0' */; + + // The code below is equivalent to the commented out code above but has been tweaked + // to allow codegen to make some extra optimizations. + + // The low byte of the packed result contains the hex representation of the incoming byte's low nibble. + // The adjacent byte of the packed result contains the hex representation of the incoming byte's high nibble. + + // Finally, write to the output buffer starting with the *highest* index so that codegen can + // elide all but the first bounds check. (This only works if 'startingIndex' is a compile-time constant.) + + // The JIT can elide bounds checks if 'startingIndex' is constant and if the caller is + // writing to a span of known length (or the caller has already checked the bounds of the + // furthest access). + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ToBytesBuffer(byte value, Span buffer, int startingIndex = 0, Casing casing = Casing.Upper) + { + uint difference = (((uint)value & 0xF0U) << 4) + ((uint)value & 0x0FU) - 0x8989U; + uint packedResult = ((((uint)(-(int)difference) & 0x7070U) >> 4) + difference + 0xB9B9U) | (uint)casing; + + buffer[startingIndex + 1] = (byte)packedResult; + buffer[startingIndex] = (byte)(packedResult >> 8); + } + +#if ALLOW_PARTIALLY_TRUSTED_CALLERS + [System.Security.SecuritySafeCriticalAttribute] +#endif + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ToCharsBuffer(byte value, Span buffer, int startingIndex = 0, Casing casing = Casing.Upper) + { + uint difference = (((uint)value & 0xF0U) << 4) + ((uint)value & 0x0FU) - 0x8989U; + uint packedResult = ((((uint)(-(int)difference) & 0x7070U) >> 4) + difference + 0xB9B9U) | (uint)casing; + + buffer[startingIndex + 1] = (char)(packedResult & 0xFF); + buffer[startingIndex] = (char)(packedResult >> 8); + } + +#if ALLOW_PARTIALLY_TRUSTED_CALLERS + [System.Security.SecuritySafeCriticalAttribute] +#endif + public static unsafe string ToString(ReadOnlySpan bytes, Casing casing = Casing.Upper) + { +#if NET45 || NET46 || NET461 || NET462 || NET47 || NET471 || NET472 || NETSTANDARD1_0 || NETSTANDARD1_3 || NETSTANDARD2_0 + Span result = stackalloc char[0]; + if (bytes.Length > 16) + { + var array = new char[bytes.Length * 2]; + result = array.AsSpan(); + } + else + { + result = stackalloc char[bytes.Length * 2]; + } + + int pos = 0; + foreach (byte b in bytes) + { + ToCharsBuffer(b, result, pos, casing); + pos += 2; + } + return result.ToString(); +#else + fixed (byte* bytesPtr = bytes) + { + 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); + } + }); + } +#endif + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static char ToCharUpper(int value) + { + value &= 0xF; + value += '0'; + + if (value > '9') + { + value += ('A' - ('9' + 1)); + } + + return (char)value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static char ToCharLower(int value) + { + value &= 0xF; + value += '0'; + + if (value > '9') + { + value += ('a' - ('9' + 1)); + } + + return (char)value; + } + } +} diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/src/System.Diagnostics.DiagnosticSource.csproj b/src/libraries/System.Diagnostics.DiagnosticSource/src/System.Diagnostics.DiagnosticSource.csproj index 642872239c6a5..e1b6deee725f6 100644 --- a/src/libraries/System.Diagnostics.DiagnosticSource/src/System.Diagnostics.DiagnosticSource.csproj +++ b/src/libraries/System.Diagnostics.DiagnosticSource/src/System.Diagnostics.DiagnosticSource.csproj @@ -28,6 +28,9 @@ + + Common\System\HexConverter.cs + 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 7dcf650c848f5..74b5308e034b6 100644 --- a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Activity.cs +++ b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Activity.cs @@ -131,7 +131,7 @@ public string? Id { // Convert flags to binary. Span flagsChars = stackalloc char[2]; - ActivityTraceId.ByteToHexDigits(flagsChars, (byte)((~ActivityTraceFlagsIsSet) & _w3CIdFlags)); + HexConverter.ToCharsBuffer((byte)((~ActivityTraceFlagsIsSet) & _w3CIdFlags), flagsChars, 0, HexConverter.Casing.Lower); string id = "00-" + _traceId + "-" + _spanId + "-" + flagsChars.ToString(); Interlocked.CompareExchange(ref _id, id, null); @@ -1018,7 +1018,7 @@ public static ActivityTraceId CreateFromBytes(ReadOnlySpan idData) if (idData.Length != 16) throw new ArgumentOutOfRangeException(nameof(idData)); - return new ActivityTraceId(SpanToHexString(idData)); + return new ActivityTraceId(HexConverter.ToString(idData, HexConverter.Casing.Lower)); } public static ActivityTraceId CreateFromUtf8String(ReadOnlySpan idData) => new ActivityTraceId(idData); @@ -1097,7 +1097,7 @@ private ActivityTraceId(ReadOnlySpan idData) span[1] = BinaryPrimitives.ReverseEndianness(span[1]); } - _hexString = ActivityTraceId.SpanToHexString(MemoryMarshal.AsBytes(span)); + _hexString = HexConverter.ToString(MemoryMarshal.AsBytes(span), HexConverter.Casing.Lower); } /// @@ -1120,26 +1120,6 @@ internal static unsafe void SetToRandomBytes(Span outBytes) guidBytes.Slice(0, outBytes.Length).CopyTo(outBytes); } - // CONVERSION binary spans to hex spans, and hex spans to binary spans - /* It would be nice to use generic Hex number conversion routines, but there - * is nothing that is exposed publicly and efficient */ - /// - /// Converts each byte in 'bytes' to hex (thus two characters) and concatenates them - /// and returns the resulting string. - /// - internal static string SpanToHexString(ReadOnlySpan bytes) - { - Debug.Assert(bytes.Length <= 16); // We want it to not be very big - Span result = stackalloc char[bytes.Length * 2]; - int pos = 0; - foreach (byte b in bytes) - { - result[pos++] = BinaryToHexDigit(b >> 4); - result[pos++] = BinaryToHexDigit(b); - } - return result.ToString(); - } - /// /// Converts 'idData' which is assumed to be HEX Unicode characters to binary /// puts it in 'outBytes' @@ -1162,20 +1142,6 @@ private static byte HexDigitToBinary(char c) return (byte)(c - ('a' - 10)); throw new ArgumentOutOfRangeException("idData"); } - private static char BinaryToHexDigit(int val) - { - val &= 0xF; - if (val <= 9) - return (char)('0' + val); - return (char)(('a' - 10) + val); - } - - internal static void ByteToHexDigits(Span outChars, byte val) - { - Debug.Assert(outChars.Length == 2); - outChars[0] = BinaryToHexDigit((val >> 4) & 0xF); - outChars[1] = BinaryToHexDigit(val & 0xF); - } internal static bool IsLowerCaseHexAndNotAllZeros(ReadOnlySpan idData) { @@ -1232,14 +1198,14 @@ public static unsafe ActivitySpanId CreateRandom() { ulong id; ActivityTraceId.SetToRandomBytes(new Span(&id, sizeof(ulong))); - return new ActivitySpanId(ActivityTraceId.SpanToHexString(new ReadOnlySpan(&id, sizeof(ulong)))); + return new ActivitySpanId(HexConverter.ToString(new ReadOnlySpan(&id, sizeof(ulong)), HexConverter.Casing.Lower)); } public static ActivitySpanId CreateFromBytes(ReadOnlySpan idData) { if (idData.Length != 8) throw new ArgumentOutOfRangeException(nameof(idData)); - return new ActivitySpanId(ActivityTraceId.SpanToHexString(idData)); + return new ActivitySpanId(HexConverter.ToString(idData, HexConverter.Casing.Lower)); } public static ActivitySpanId CreateFromUtf8String(ReadOnlySpan idData) => new ActivitySpanId(idData); @@ -1307,7 +1273,7 @@ private unsafe ActivitySpanId(ReadOnlySpan idData) id = BinaryPrimitives.ReverseEndianness(id); } - _hexString = ActivityTraceId.SpanToHexString(new ReadOnlySpan(&id, sizeof(ulong))); + _hexString = HexConverter.ToString(new ReadOnlySpan(&id, sizeof(ulong)), HexConverter.Casing.Lower); } /// diff --git a/src/libraries/System.Net.Http/src/System.Net.Http.csproj b/src/libraries/System.Net.Http/src/System.Net.Http.csproj index 08aa292949bde..1fd652a819ba9 100644 --- a/src/libraries/System.Net.Http/src/System.Net.Http.csproj +++ b/src/libraries/System.Net.Http/src/System.Net.Http.csproj @@ -119,6 +119,9 @@ Common\System\Text\SimpleRegex.cs + + Common\System\HexConverter.cs + Common\System\Net\ArrayBuffer.cs diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/Headers/HeaderUtilities.cs b/src/libraries/System.Net.Http/src/System/Net/Http/Headers/HeaderUtilities.cs index 5e12307796d09..a8183f11912f3 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/Headers/HeaderUtilities.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/Headers/HeaderUtilities.cs @@ -27,8 +27,6 @@ internal static class HeaderUtilities // Validator internal static readonly Action, string> TokenValidator = ValidateToken; - private static readonly char[] s_hexUpperChars = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; - internal static void SetQuality(ObjectCollection parameters, double? value) { Debug.Assert(parameters != null); @@ -125,8 +123,8 @@ private static void AddHexEscaped(byte c, StringBuilder destination) Debug.Assert(destination != null); destination.Append('%'); - destination.Append(s_hexUpperChars[(c & 0xf0) >> 4]); - destination.Append(s_hexUpperChars[c & 0xf]); + destination.Append(HexConverter.ToCharUpper(c >> 4)); + destination.Append(HexConverter.ToCharUpper(c)); } internal static double? GetQuality(ObjectCollection parameters) diff --git a/src/libraries/System.Net.Http/tests/UnitTests/System.Net.Http.Unit.Tests.csproj b/src/libraries/System.Net.Http/tests/UnitTests/System.Net.Http.Unit.Tests.csproj index dad493b7b92c5..424fe2fa99fe1 100644 --- a/src/libraries/System.Net.Http/tests/UnitTests/System.Net.Http.Unit.Tests.csproj +++ b/src/libraries/System.Net.Http/tests/UnitTests/System.Net.Http.Unit.Tests.csproj @@ -11,6 +11,9 @@ + + Common\System\HexConverter.cs + ProductionCode\Common\System\NotImplemented.cs diff --git a/src/libraries/System.Net.Mail/src/System.Net.Mail.csproj b/src/libraries/System.Net.Mail/src/System.Net.Mail.csproj index 0b934c6116c22..fe0aadd800681 100644 --- a/src/libraries/System.Net.Mail/src/System.Net.Mail.csproj +++ b/src/libraries/System.Net.Mail/src/System.Net.Mail.csproj @@ -143,6 +143,9 @@ Common\System\Net\Security\NetEventSource.Security.cs + + Common\System\HexConverter.cs + diff --git a/src/libraries/System.Net.Mail/src/System/Net/Mime/QEncodedStream.cs b/src/libraries/System.Net.Mail/src/System/Net/Mime/QEncodedStream.cs index 12571735e0d4f..d6b744474192f 100644 --- a/src/libraries/System.Net.Mail/src/System/Net/Mime/QEncodedStream.cs +++ b/src/libraries/System.Net.Mail/src/System/Net/Mime/QEncodedStream.cs @@ -40,9 +40,6 @@ internal class QEncodedStream : DelegatedStream, IEncodableStream 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, // F }; - //bytes that correspond to the hex char representations in ASCII (0-9, A-F) - private static readonly byte[] s_hexEncodeMap = new byte[] { 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 65, 66, 67, 68, 69, 70 }; - private ReadStateInfo _readState; private readonly WriteStateInfoBase _writeState; @@ -248,9 +245,9 @@ public int EncodeBytes(byte[] buffer, int offset, int count) //append an = to indicate an encoded character WriteState.Append((byte)'='); //shift 4 to get the first four bytes only and look up the hex digit - WriteState.Append(s_hexEncodeMap[buffer[cur] >> 4]); + WriteState.Append((byte)HexConverter.ToCharUpper(buffer[cur] >> 4)); //clear the first four bytes to get the last four and look up the hex digit - WriteState.Append(s_hexEncodeMap[buffer[cur] & 0xF]); + WriteState.Append((byte)HexConverter.ToCharUpper(buffer[cur])); } } WriteState.AppendFooter(); diff --git a/src/libraries/System.Net.Mail/tests/Unit/System.Net.Mail.Unit.Tests.csproj b/src/libraries/System.Net.Mail/tests/Unit/System.Net.Mail.Unit.Tests.csproj index f1930d80ff091..1312f99b7b593 100644 --- a/src/libraries/System.Net.Mail/tests/Unit/System.Net.Mail.Unit.Tests.csproj +++ b/src/libraries/System.Net.Mail/tests/Unit/System.Net.Mail.Unit.Tests.csproj @@ -219,6 +219,9 @@ Common\System\Net\Security\NetEventSource.Security.cs + + Common\System\HexConverter.cs + diff --git a/src/libraries/System.Net.NetworkInformation/src/System.Net.NetworkInformation.csproj b/src/libraries/System.Net.NetworkInformation/src/System.Net.NetworkInformation.csproj index d80dcfad37677..4a65f9ef2a5e6 100644 --- a/src/libraries/System.Net.NetworkInformation/src/System.Net.NetworkInformation.csproj +++ b/src/libraries/System.Net.NetworkInformation/src/System.Net.NetworkInformation.csproj @@ -52,6 +52,9 @@ Common\System\Net\NetworkInformation\NetworkInformationException.cs + + Common\System\HexConverter.cs + 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 5e44cc1878794..5b0f037009442 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,16 +91,7 @@ public override bool Equals(object comparand) public override string ToString() { - return string.Create(_address.Length * 2, _address, (span, addr) => - { - int p = 0; - foreach (byte value in addr) - { - byte upper = (byte)(value >> 4), lower = (byte)(value & 0xF); - span[p++] = (char)(upper + (upper < 10 ? '0' : 'A' - 10)); - span[p++] = (char)(lower + (lower < 10 ? '0' : 'A' - 10)); - } - }); + return HexConverter.ToString(_address.AsSpan(), HexConverter.Casing.Upper); } public byte[] GetAddressBytes() diff --git a/src/libraries/System.Net.NetworkInformation/tests/FunctionalTests/PhysicalAddressTest.cs b/src/libraries/System.Net.NetworkInformation/tests/FunctionalTests/PhysicalAddressTest.cs index db10cb30d29c8..cf891ce0b20a8 100644 --- a/src/libraries/System.Net.NetworkInformation/tests/FunctionalTests/PhysicalAddressTest.cs +++ b/src/libraries/System.Net.NetworkInformation/tests/FunctionalTests/PhysicalAddressTest.cs @@ -256,10 +256,10 @@ public void TryParseSpan_Invalid_ReturnsFalse(string address) } [Fact] - public void ToString_NullAddress_NullReferenceException() + public void ToString_NullAddress_EmptyString() { var pa = new PhysicalAddress(null); - Assert.Throws(() => pa.ToString()); + Assert.Equal("", pa.ToString()); } [Theory] diff --git a/src/libraries/System.Net.Primitives/src/System.Net.Primitives.csproj b/src/libraries/System.Net.Primitives/src/System.Net.Primitives.csproj index 1f95508c7fe39..48e6f7753831e 100644 --- a/src/libraries/System.Net.Primitives/src/System.Net.Primitives.csproj +++ b/src/libraries/System.Net.Primitives/src/System.Net.Primitives.csproj @@ -94,6 +94,9 @@ Common\System\Runtime\CompilerServices\PreserveDependencyAttribute.cs + + Common\System\HexConverter.cs + Common\System\Net\Logging\NetEventSource.Common.cs diff --git a/src/libraries/System.Net.Primitives/src/System/Net/IPAddressParser.cs b/src/libraries/System.Net.Primitives/src/System/Net/IPAddressParser.cs index 72f526638ef95..2e53dfaf4c9e3 100644 --- a/src/libraries/System.Net.Primitives/src/System/Net/IPAddressParser.cs +++ b/src/libraries/System.Net.Primitives/src/System/Net/IPAddressParser.cs @@ -279,22 +279,18 @@ private static void AppendSections(ushort[] address, int fromInclusive, int toEx } /// Appends a number as hexadecimal (without the leading "0x") to the StringBuilder. - private static unsafe void AppendHex(ushort value, StringBuilder buffer) + private static void AppendHex(ushort value, StringBuilder buffer) { - const int MaxLength = sizeof(ushort) * 2; // two hex chars per byte - char* chars = stackalloc char[MaxLength]; - int pos = MaxLength; + if ((value & 0xF000) != 0) + buffer.Append(HexConverter.ToCharLower(value >> 12)); - do - { - int rem = value % 16; - value /= 16; - chars[--pos] = rem < 10 ? (char)('0' + rem) : (char)('a' + (rem - 10)); - Debug.Assert(pos >= 0); - } - while (value != 0); + if ((value & 0xFF00) != 0) + buffer.Append(HexConverter.ToCharLower(value >> 8)); + + if ((value & 0xFFF0) != 0) + buffer.Append(HexConverter.ToCharLower(value >> 4)); - buffer.Append(chars + pos, MaxLength - pos); + buffer.Append(HexConverter.ToCharLower(value)); } /// Extracts the IPv4 address from the end of the IPv6 address byte array. diff --git a/src/libraries/System.Net.Primitives/tests/PalTests/System.Net.Primitives.Pal.Tests.csproj b/src/libraries/System.Net.Primitives/tests/PalTests/System.Net.Primitives.Pal.Tests.csproj index 840e160f65b5f..2cf3d34ae653c 100644 --- a/src/libraries/System.Net.Primitives/tests/PalTests/System.Net.Primitives.Pal.Tests.csproj +++ b/src/libraries/System.Net.Primitives/tests/PalTests/System.Net.Primitives.Pal.Tests.csproj @@ -47,6 +47,9 @@ ProductionCode\Common\System\Text\StringBuilderCache.cs + + Common\System\HexConverter.cs + diff --git a/src/libraries/System.Net.Primitives/tests/UnitTests/System.Net.Primitives.UnitTests.Tests.csproj b/src/libraries/System.Net.Primitives/tests/UnitTests/System.Net.Primitives.UnitTests.Tests.csproj index fdbb9b7eb7ee9..23430eac94c22 100644 --- a/src/libraries/System.Net.Primitives/tests/UnitTests/System.Net.Primitives.UnitTests.Tests.csproj +++ b/src/libraries/System.Net.Primitives/tests/UnitTests/System.Net.Primitives.UnitTests.Tests.csproj @@ -62,6 +62,9 @@ Common\System\Runtime\CompilerServices\PreserveDependencyAttribute.cs + + Common\System\HexConverter.cs + diff --git a/src/libraries/System.Net.Security/src/System.Net.Security.csproj b/src/libraries/System.Net.Security/src/System.Net.Security.csproj index f923d7e1a3918..13c4150d3ab2a 100644 --- a/src/libraries/System.Net.Security/src/System.Net.Security.csproj +++ b/src/libraries/System.Net.Security/src/System.Net.Security.csproj @@ -100,6 +100,9 @@ Common\System\Net\SecurityStatusPal.cs + + Common\System\HexConverter.cs + diff --git a/src/libraries/System.Net.Security/src/System/Net/Security/SslApplicationProtocol.cs b/src/libraries/System.Net.Security/src/System/Net/Security/SslApplicationProtocol.cs index 7176874565e1c..f15e0c8ca6fcb 100644 --- a/src/libraries/System.Net.Security/src/System/Net/Security/SslApplicationProtocol.cs +++ b/src/libraries/System.Net.Security/src/System/Net/Security/SslApplicationProtocol.cs @@ -95,14 +95,12 @@ public override string ToString() byte b = arr[index++]; byteChars[i] = '0'; byteChars[i + 1] = 'x'; - byteChars[i + 2] = GetHexValue(Math.DivRem(b, 16, out int rem)); - byteChars[i + 3] = GetHexValue(rem); + byteChars[i + 2] = HexConverter.ToCharLower(b >> 4); + byteChars[i + 3] = HexConverter.ToCharLower(b); byteChars[i + 4] = ' '; } return new string(byteChars, 0, byteChars.Length - 1); - - static char GetHexValue(int i) => (char)(i < 10 ? i + '0' : i - 10 + 'a'); } } diff --git a/src/libraries/System.Net.Security/tests/UnitTests/System.Net.Security.Unit.Tests.csproj b/src/libraries/System.Net.Security/tests/UnitTests/System.Net.Security.Unit.Tests.csproj index 0462dfb345959..be0e5019e2143 100644 --- a/src/libraries/System.Net.Security/tests/UnitTests/System.Net.Security.Unit.Tests.csproj +++ b/src/libraries/System.Net.Security/tests/UnitTests/System.Net.Security.Unit.Tests.csproj @@ -28,6 +28,9 @@ CommonTest\System\Net\SslProtocolSupport.cs + + Common\System\HexConverter.cs + diff --git a/src/libraries/System.Net.WebClient/src/System.Net.WebClient.csproj b/src/libraries/System.Net.WebClient/src/System.Net.WebClient.csproj index 61e9086c63e0f..8ef6982edcda6 100644 --- a/src/libraries/System.Net.WebClient/src/System.Net.WebClient.csproj +++ b/src/libraries/System.Net.WebClient/src/System.Net.WebClient.csproj @@ -20,6 +20,9 @@ Common\System\Net\HttpKnownHeaderNames.cs + + Common\System\HexConverter.cs + diff --git a/src/libraries/System.Net.WebClient/src/System/Net/WebClient.cs b/src/libraries/System.Net.WebClient/src/System/Net/WebClient.cs index 31d33e733d8b6..23fabd15f0121 100644 --- a/src/libraries/System.Net.WebClient/src/System/Net/WebClient.cs +++ b/src/libraries/System.Net.WebClient/src/System/Net/WebClient.cs @@ -1235,26 +1235,14 @@ private static byte[] UrlEncodeBytesToBytesInternal(byte[] bytes, int offset, in else { expandedBytes[pos++] = (byte)'%'; - expandedBytes[pos++] = (byte)IntToHex((b >> 4) & 0xf); - expandedBytes[pos++] = (byte)IntToHex(b & 0x0f); + expandedBytes[pos++] = (byte)HexConverter.ToCharLower(b >> 4); + expandedBytes[pos++] = (byte)HexConverter.ToCharLower(b); } } return expandedBytes; } - private static char IntToHex(int n) - { - Debug.Assert(n < 0x10); - - if (n <= 9) - { - return (char)(n + (int)'0'); - } - - return (char)(n - 10 + (int)'a'); - } - private static bool IsSafe(char ch) { if (ch >= 'a' && ch <= 'z' || ch >= 'A' && ch <= 'Z' || ch >= '0' && ch <= '9') diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems index 3998106965139..4c42870c7db34 100644 --- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems +++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems @@ -1018,6 +1018,11 @@ + + + Common\System\HexConverter.cs + + True diff --git a/src/libraries/System.Private.CoreLib/src/System/BitConverter.cs b/src/libraries/System.Private.CoreLib/src/System/BitConverter.cs index 8085e9cb38a76..25719466ddb09 100644 --- a/src/libraries/System.Private.CoreLib/src/System/BitConverter.cs +++ b/src/libraries/System.Private.CoreLib/src/System/BitConverter.cs @@ -383,23 +383,21 @@ public static string ToString(byte[] value, int startIndex, int length) return string.Create(length * 3 - 1, (value, startIndex, length), (dst, state) => { - const string HexValues = "0123456789ABCDEF"; - var src = new ReadOnlySpan(state.value, state.startIndex, state.length); int i = 0; int j = 0; byte b = src[i++]; - dst[j++] = HexValues[b >> 4]; - dst[j++] = HexValues[b & 0xF]; + dst[j++] = HexConverter.ToCharUpper(b >> 4); + dst[j++] = HexConverter.ToCharUpper(b); while (i < src.Length) { b = src[i++]; dst[j++] = '-'; - dst[j++] = HexValues[b >> 4]; - dst[j++] = HexValues[b & 0xF]; + dst[j++] = HexConverter.ToCharUpper(b >> 4); + dst[j++] = HexConverter.ToCharUpper(b); } }); } diff --git a/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Formatter/FormattingHelpers.cs b/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Formatter/FormattingHelpers.cs index 16480d08f7366..0cdebc40342b4 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Formatter/FormattingHelpers.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Formatter/FormattingHelpers.cs @@ -12,11 +12,6 @@ namespace System.Buffers.Text // code must have already done all the necessary validation. internal static partial class FormattingHelpers { - // A simple lookup table for converting numbers to hex. - internal const string HexTableLower = "0123456789abcdef"; - - internal const string HexTableUpper = "0123456789ABCDEF"; - /// /// Returns the symbol contained within the standard format. If the standard format /// has not been initialized, returns the provided fallback symbol. @@ -52,68 +47,6 @@ public static void FillWithAsciiZeros(Span buffer) } } - public enum HexCasing : uint - { - // Output [ '0' .. '9' ] and [ 'A' .. 'F' ]. - Uppercase = 0, - - // Output [ '0' .. '9' ] and [ 'a' .. 'f' ]. - // This works because values in the range [ 0x30 .. 0x39 ] ([ '0' .. '9' ]) - // already have the 0x20 bit set, so ORing them with 0x20 is a no-op, - // while outputs in the range [ 0x41 .. 0x46 ] ([ 'A' .. 'F' ]) - // don't have the 0x20 bit set, so ORing them maps to - // [ 0x61 .. 0x66 ] ([ 'a' .. 'f' ]), which is what we want. - Lowercase = 0x2020U, - } - - // The JIT can elide bounds checks if 'startingIndex' is constant and if the caller is - // writing to a span of known length (or the caller has already checked the bounds of the - // furthest access). - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void WriteHexByte(byte value, Span buffer, int startingIndex = 0, HexCasing casing = HexCasing.Uppercase) - { - // We want to pack the incoming byte into a single integer [ 0000 HHHH 0000 LLLL ], - // where HHHH and LLLL are the high and low nibbles of the incoming byte. Then - // subtract this integer from a constant minuend as shown below. - // - // [ 1000 1001 1000 1001 ] - // - [ 0000 HHHH 0000 LLLL ] - // ========================= - // [ *YYY **** *ZZZ **** ] - // - // The end result of this is that YYY is 0b000 if HHHH <= 9, and YYY is 0b111 if HHHH >= 10. - // Similarly, ZZZ is 0b000 if LLLL <= 9, and ZZZ is 0b111 if LLLL >= 10. - // (We don't care about the value of asterisked bits.) - // - // To turn a nibble in the range [ 0 .. 9 ] into hex, we calculate hex := nibble + 48 (ascii '0'). - // To turn a nibble in the range [ 10 .. 15 ] into hex, we calculate hex := nibble - 10 + 65 (ascii 'A'). - // => hex := nibble + 55. - // The difference in the starting ASCII offset is (55 - 48) = 7, depending on whether the nibble is <= 9 or >= 10. - // Since 7 is 0b111, this conveniently matches the YYY or ZZZ value computed during the earlier subtraction. - - // The commented out code below is code that directly implements the logic described above. - - // uint packedOriginalValues = (((uint)value & 0xF0U) << 4) + ((uint)value & 0x0FU); - // uint difference = 0x8989U - packedOriginalValues; - // uint add7Mask = (difference & 0x7070U) >> 4; // line YYY and ZZZ back up with the packed values - // uint packedResult = packedOriginalValues + add7Mask + 0x3030U /* ascii '0' */; - - // The code below is equivalent to the commented out code above but has been tweaked - // to allow codegen to make some extra optimizations. - - uint difference = (((uint)value & 0xF0U) << 4) + ((uint)value & 0x0FU) - 0x8989U; - uint packedResult = ((((uint)(-(int)difference) & 0x7070U) >> 4) + difference + 0xB9B9U) | (uint)casing; - - // The low byte of the packed result contains the hex representation of the incoming byte's low nibble. - // The adjacent byte of the packed result contains the hex representation of the incoming byte's high nibble. - - // Finally, write to the output buffer starting with the *highest* index so that codegen can - // elide all but the first bounds check. (This only works if 'startingIndex' is a compile-time constant.) - - buffer[startingIndex + 1] = (byte)(packedResult); - buffer[startingIndex] = (byte)(packedResult >> 8); - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteDigits(ulong value, Span buffer) { diff --git a/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Guid.cs b/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Guid.cs index 63a9fa248ab0a..fa74f7a8e886f 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Guid.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Guid.cs @@ -118,10 +118,10 @@ public static bool TryFormat(Guid value, Span destination, out int bytesWr // We use 8 instead of 7 so that we also capture the dash if we're asked to insert one. { _ = destination[8]; } - FormattingHelpers.WriteHexByte(guidAsBytes.Byte03, destination, 0, FormattingHelpers.HexCasing.Lowercase); - FormattingHelpers.WriteHexByte(guidAsBytes.Byte02, destination, 2, FormattingHelpers.HexCasing.Lowercase); - FormattingHelpers.WriteHexByte(guidAsBytes.Byte01, destination, 4, FormattingHelpers.HexCasing.Lowercase); - FormattingHelpers.WriteHexByte(guidAsBytes.Byte00, destination, 6, FormattingHelpers.HexCasing.Lowercase); + HexConverter.ToBytesBuffer(guidAsBytes.Byte03, destination, 0, HexConverter.Casing.Lower); + HexConverter.ToBytesBuffer(guidAsBytes.Byte02, destination, 2, HexConverter.Casing.Lower); + HexConverter.ToBytesBuffer(guidAsBytes.Byte01, destination, 4, HexConverter.Casing.Lower); + HexConverter.ToBytesBuffer(guidAsBytes.Byte00, destination, 6, HexConverter.Casing.Lower); if (flags < 0 /* use dash? */) { @@ -134,8 +134,8 @@ public static bool TryFormat(Guid value, Span destination, out int bytesWr } { _ = destination[4]; } - FormattingHelpers.WriteHexByte(guidAsBytes.Byte05, destination, 0, FormattingHelpers.HexCasing.Lowercase); - FormattingHelpers.WriteHexByte(guidAsBytes.Byte04, destination, 2, FormattingHelpers.HexCasing.Lowercase); + HexConverter.ToBytesBuffer(guidAsBytes.Byte05, destination, 0, HexConverter.Casing.Lower); + HexConverter.ToBytesBuffer(guidAsBytes.Byte04, destination, 2, HexConverter.Casing.Lower); if (flags < 0 /* use dash? */) { @@ -148,8 +148,8 @@ public static bool TryFormat(Guid value, Span destination, out int bytesWr } { _ = destination[4]; } - FormattingHelpers.WriteHexByte(guidAsBytes.Byte07, destination, 0, FormattingHelpers.HexCasing.Lowercase); - FormattingHelpers.WriteHexByte(guidAsBytes.Byte06, destination, 2, FormattingHelpers.HexCasing.Lowercase); + HexConverter.ToBytesBuffer(guidAsBytes.Byte07, destination, 0, HexConverter.Casing.Lower); + HexConverter.ToBytesBuffer(guidAsBytes.Byte06, destination, 2, HexConverter.Casing.Lower); if (flags < 0 /* use dash? */) { @@ -162,8 +162,8 @@ public static bool TryFormat(Guid value, Span destination, out int bytesWr } { _ = destination[4]; } - FormattingHelpers.WriteHexByte(guidAsBytes.Byte08, destination, 0, FormattingHelpers.HexCasing.Lowercase); - FormattingHelpers.WriteHexByte(guidAsBytes.Byte09, destination, 2, FormattingHelpers.HexCasing.Lowercase); + HexConverter.ToBytesBuffer(guidAsBytes.Byte08, destination, 0, HexConverter.Casing.Lower); + HexConverter.ToBytesBuffer(guidAsBytes.Byte09, destination, 2, HexConverter.Casing.Lower); if (flags < 0 /* use dash? */) { @@ -176,12 +176,12 @@ public static bool TryFormat(Guid value, Span destination, out int bytesWr } { _ = destination[11]; } // can't hoist bounds check on the final brace (if exists) - FormattingHelpers.WriteHexByte(guidAsBytes.Byte10, destination, 0, FormattingHelpers.HexCasing.Lowercase); - FormattingHelpers.WriteHexByte(guidAsBytes.Byte11, destination, 2, FormattingHelpers.HexCasing.Lowercase); - FormattingHelpers.WriteHexByte(guidAsBytes.Byte12, destination, 4, FormattingHelpers.HexCasing.Lowercase); - FormattingHelpers.WriteHexByte(guidAsBytes.Byte13, destination, 6, FormattingHelpers.HexCasing.Lowercase); - FormattingHelpers.WriteHexByte(guidAsBytes.Byte14, destination, 8, FormattingHelpers.HexCasing.Lowercase); - FormattingHelpers.WriteHexByte(guidAsBytes.Byte15, destination, 10, FormattingHelpers.HexCasing.Lowercase); + HexConverter.ToBytesBuffer(guidAsBytes.Byte10, destination, 0, HexConverter.Casing.Lower); + HexConverter.ToBytesBuffer(guidAsBytes.Byte11, destination, 2, HexConverter.Casing.Lower); + HexConverter.ToBytesBuffer(guidAsBytes.Byte12, destination, 4, HexConverter.Casing.Lower); + HexConverter.ToBytesBuffer(guidAsBytes.Byte13, destination, 6, HexConverter.Casing.Lower); + HexConverter.ToBytesBuffer(guidAsBytes.Byte14, destination, 8, HexConverter.Casing.Lower); + HexConverter.ToBytesBuffer(guidAsBytes.Byte15, destination, 10, HexConverter.Casing.Lower); if ((byte)flags != 0) { diff --git a/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Integer.Unsigned.X.cs b/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Integer.Unsigned.X.cs index 4cf4d52b5c87e..42cfd0160b865 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Integer.Unsigned.X.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Integer.Unsigned.X.cs @@ -23,7 +23,6 @@ private static bool TryFormatUInt64X(ulong value, byte precision, bool useLower, } bytesWritten = computedOutputLength; - string hexTable = (useLower) ? FormattingHelpers.HexTableLower : FormattingHelpers.HexTableUpper; // Writing the output backward in this manner allows the JIT to elide // bounds checking on the output buffer. The JIT won't elide the bounds @@ -35,11 +34,23 @@ private static bool TryFormatUInt64X(ulong value, byte precision, bool useLower, // casing output lengths of 2, 4, 8, and 16 and running them down optimized // code paths. - while ((uint)(--computedOutputLength) < (uint)destination.Length) + if (useLower) { - destination[computedOutputLength] = (byte)hexTable[(int)value & 0xf]; - value >>= 4; + while ((uint)(--computedOutputLength) < (uint)destination.Length) + { + destination[computedOutputLength] = (byte)HexConverter.ToCharLower((int)value); + value >>= 4; + } } + else + { + while ((uint)(--computedOutputLength) < (uint)destination.Length) + { + destination[computedOutputLength] = (byte)HexConverter.ToCharUpper((int)value); + value >>= 4; + } + } + return true; } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Guid.cs b/src/libraries/System.Private.CoreLib/src/System/Guid.cs index 65370c0556e62..3d68e4503d5f1 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Guid.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Guid.cs @@ -957,20 +957,13 @@ public string ToString(string? format) return ToString(format, null); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static char HexToChar(int a) - { - a &= 0xf; - return (char)((a > 9) ? a - 10 + 0x61 : a + 0x30); - } - private static unsafe int HexsToChars(char* guidChars, int a, int b) { - guidChars[0] = HexToChar(a >> 4); - guidChars[1] = HexToChar(a); + guidChars[0] = HexConverter.ToCharLower(a >> 4); + guidChars[1] = HexConverter.ToCharLower(a); - guidChars[2] = HexToChar(b >> 4); - guidChars[3] = HexToChar(b); + guidChars[2] = HexConverter.ToCharLower(b >> 4); + guidChars[3] = HexConverter.ToCharLower(b); return 4; } @@ -980,15 +973,15 @@ private static unsafe int HexsToCharsHexOutput(char* guidChars, int a, int b) guidChars[0] = '0'; guidChars[1] = 'x'; - guidChars[2] = HexToChar(a >> 4); - guidChars[3] = HexToChar(a); + guidChars[2] = HexConverter.ToCharLower(a >> 4); + guidChars[3] = HexConverter.ToCharLower(a); guidChars[4] = ','; guidChars[5] = '0'; guidChars[6] = 'x'; - guidChars[7] = HexToChar(b >> 4); - guidChars[8] = HexToChar(b); + guidChars[7] = HexConverter.ToCharLower(b >> 4); + guidChars[8] = HexConverter.ToCharLower(b); return 9; } 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 f1550fb43d52b..898a9a1524fb7 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/AssemblyName.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/AssemblyName.cs @@ -399,8 +399,8 @@ private static unsafe char[] EnsureDestinationSize(char* pStr, char[]? dest, int internal static void EscapeAsciiChar(char ch, char[] to, ref int pos) { to[pos++] = '%'; - to[pos++] = s_hexUpperChars[(ch & 0xf0) >> 4]; - to[pos++] = s_hexUpperChars[ch & 0xf]; + to[pos++] = HexConverter.ToCharUpper(ch >> 4); + to[pos++] = HexConverter.ToCharUpper(ch); } internal static char EscapedAscii(char digit, char next) @@ -464,9 +464,6 @@ internal static bool IsAsciiLetterOrDigit(char character) return IsAsciiLetter(character) || (character >= '0' && character <= '9'); } - private static readonly char[] s_hexUpperChars = { - '0', '1', '2', '3', '4', '5', '6', '7', - '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; internal const char c_DummyChar = (char)0xFFFF; // An Invalid Unicode character used as a dummy char passed into the parameter private const short c_MaxAsciiCharsReallocate = 40; private const short c_MaxUnicodeCharsReallocate = 40; diff --git a/src/libraries/System.Private.DataContractSerialization/src/System.Private.DataContractSerialization.csproj b/src/libraries/System.Private.DataContractSerialization/src/System.Private.DataContractSerialization.csproj index 7389c0f1d47c3..7358d5d33c087 100644 --- a/src/libraries/System.Private.DataContractSerialization/src/System.Private.DataContractSerialization.csproj +++ b/src/libraries/System.Private.DataContractSerialization/src/System.Private.DataContractSerialization.csproj @@ -22,6 +22,9 @@ Common\System\NotImplemented.cs + + Common\System\HexConverter.cs + 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 6cd16af1864b1..95308db850203 100644 --- a/src/libraries/System.Private.DataContractSerialization/src/System/Text/BinHexEncoding.cs +++ b/src/libraries/System.Private.DataContractSerialization/src/System/Text/BinHexEncoding.cs @@ -29,8 +29,6 @@ internal class BinHexEncoding : Encoding 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, }; - private const string Val2Char = "0123456789ABCDEF"; - public override int GetMaxByteCount(int charCount) { if (charCount < 0) @@ -138,8 +136,6 @@ public unsafe override int GetChars(byte[] bytes, int byteIndex, int byteCount, throw new ArgumentException(SR.XmlArrayTooSmall, nameof(chars)); if (byteCount > 0) { - fixed (char* _val2char = Val2Char) - { fixed (byte* _bytes = &bytes[byteIndex]) { fixed (char* _chars = &chars[charIndex]) @@ -149,14 +145,13 @@ public unsafe override int GetChars(byte[] bytes, int byteIndex, int byteCount, byte* pbMax = _bytes + byteCount; while (pb < pbMax) { - pch[0] = _val2char[pb[0] >> 4]; - pch[1] = _val2char[pb[0] & 0x0F]; + pch[0] = HexConverter.ToCharUpper(pb[0] >> 4); + pch[1] = HexConverter.ToCharUpper(pb[0]); pb++; pch += 2; } } } - } } return charCount; } diff --git a/src/libraries/System.Private.DataContractSerialization/src/System/Xml/UniqueId.cs b/src/libraries/System.Private.DataContractSerialization/src/System/Xml/UniqueId.cs index 5ba6aa5b1bb8b..4718b6039c475 100644 --- a/src/libraries/System.Private.DataContractSerialization/src/System/Xml/UniqueId.cs +++ b/src/libraries/System.Private.DataContractSerialization/src/System/Xml/UniqueId.cs @@ -49,8 +49,6 @@ public class UniqueId 0x100, 0x100, 0x100, 0x100, 0x100, 0x100, 0x100, 0x100, 0x100, 0x100, 0x100, 0x100, 0x100, 0x100, 0x100, 0x100, }; - private const string val2char = "0123456789abcdef"; - public UniqueId() : this(Guid.NewGuid()) { } @@ -137,10 +135,10 @@ private unsafe int UnsafeDecode(short* char2val, char ch1, char ch2) return char2val[ch1] | char2val[0x80 + ch2]; } - private unsafe void UnsafeEncode(char* val2char, byte b, char* pch) + private static unsafe void UnsafeEncode(byte b, char* pch) { - pch[0] = val2char[b >> 4]; - pch[1] = val2char[b & 0x0F]; + pch[0] = HexConverter.ToCharLower(b >> 4); + pch[1] = HexConverter.ToCharLower(b); } public bool IsGuid => ((_idLow | _idHigh) != 0); @@ -241,26 +239,22 @@ public unsafe int ToCharArray(char[] chars, int offset) pch[27] = '-'; pch[32] = '-'; - fixed (char* ps = val2char) - { - char* _val2char = ps; - UnsafeEncode(_val2char, bytes[0], &pch[15]); - UnsafeEncode(_val2char, bytes[1], &pch[13]); - UnsafeEncode(_val2char, bytes[2], &pch[11]); - UnsafeEncode(_val2char, bytes[3], &pch[9]); - UnsafeEncode(_val2char, bytes[4], &pch[20]); - UnsafeEncode(_val2char, bytes[5], &pch[18]); - UnsafeEncode(_val2char, bytes[6], &pch[25]); - UnsafeEncode(_val2char, bytes[7], &pch[23]); - UnsafeEncode(_val2char, bytes[8], &pch[28]); - UnsafeEncode(_val2char, bytes[9], &pch[30]); - UnsafeEncode(_val2char, bytes[10], &pch[33]); - UnsafeEncode(_val2char, bytes[11], &pch[35]); - UnsafeEncode(_val2char, bytes[12], &pch[37]); - UnsafeEncode(_val2char, bytes[13], &pch[39]); - UnsafeEncode(_val2char, bytes[14], &pch[41]); - UnsafeEncode(_val2char, bytes[15], &pch[43]); - } + UnsafeEncode(bytes[0], &pch[15]); + UnsafeEncode(bytes[1], &pch[13]); + UnsafeEncode(bytes[2], &pch[11]); + UnsafeEncode(bytes[3], &pch[9]); + UnsafeEncode(bytes[4], &pch[20]); + UnsafeEncode(bytes[5], &pch[18]); + UnsafeEncode(bytes[6], &pch[25]); + UnsafeEncode(bytes[7], &pch[23]); + UnsafeEncode(bytes[8], &pch[28]); + UnsafeEncode(bytes[9], &pch[30]); + UnsafeEncode(bytes[10], &pch[33]); + UnsafeEncode(bytes[11], &pch[35]); + UnsafeEncode(bytes[12], &pch[37]); + UnsafeEncode(bytes[13], &pch[39]); + UnsafeEncode(bytes[14], &pch[41]); + UnsafeEncode(bytes[15], &pch[43]); } } diff --git a/src/libraries/System.Private.Uri/src/System.Private.Uri.csproj b/src/libraries/System.Private.Uri/src/System.Private.Uri.csproj index d2113746010c2..1f88c208e95f0 100644 --- a/src/libraries/System.Private.Uri/src/System.Private.Uri.csproj +++ b/src/libraries/System.Private.Uri/src/System.Private.Uri.csproj @@ -9,6 +9,9 @@ Common\System\Collections\Generic\ArrayBuilder.cs + + Common\System\HexConverter.cs + diff --git a/src/libraries/System.Private.Uri/src/System/IriHelper.cs b/src/libraries/System.Private.Uri/src/System/IriHelper.cs index ea8e48ab2aec9..5b821405717b0 100644 --- a/src/libraries/System.Private.Uri/src/System/IriHelper.cs +++ b/src/libraries/System.Private.Uri/src/System/IriHelper.cs @@ -273,7 +273,7 @@ internal static unsafe string EscapeUnescapeIri(char* pInput, int start, int end for (int count = 0; count < encodedBytesCount; ++count) { - UriHelper.EscapeAsciiChar((char)encodedBytes[count], ref dest); + UriHelper.EscapeAsciiChar(encodedBytes[count], ref dest); } } } diff --git a/src/libraries/System.Private.Uri/src/System/Uri.cs b/src/libraries/System.Private.Uri/src/System/Uri.cs index 808cf4f6c4ba7..474375ec956c3 100644 --- a/src/libraries/System.Private.Uri/src/System/Uri.cs +++ b/src/libraries/System.Private.Uri/src/System/Uri.cs @@ -1369,11 +1369,10 @@ public static string HexEscape(char character) throw new ArgumentOutOfRangeException(nameof(character)); } - return string.Create(3, character, (Span chars, char c) => + return string.Create(3, (byte)character, (Span chars, byte b) => { chars[0] = '%'; - chars[1] = (char)UriHelper.HexUpperChars[(c & 0xf0) >> 4]; - chars[2] = (char)UriHelper.HexUpperChars[c & 0xf]; + HexConverter.ToCharsBuffer(b, chars, 1, HexConverter.Casing.Upper); }); } diff --git a/src/libraries/System.Private.Uri/src/System/UriHelper.cs b/src/libraries/System.Private.Uri/src/System/UriHelper.cs index d9fb6d2dd5c61..915931fb620c2 100644 --- a/src/libraries/System.Private.Uri/src/System/UriHelper.cs +++ b/src/libraries/System.Private.Uri/src/System/UriHelper.cs @@ -10,12 +10,6 @@ namespace System { internal static class UriHelper { - internal static ReadOnlySpan HexUpperChars => new byte[16] - { - (byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', (byte)'5', (byte)'6', (byte)'7', - (byte)'8', (byte)'9', (byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F' - }; - internal static readonly Encoding s_noFallbackCharUTF8 = Encoding.GetEncoding( Encoding.UTF8.CodePage, new EncoderReplacementFallback(""), new DecoderReplacementFallback("")); @@ -250,8 +244,7 @@ private static void EscapeStringToBuilder( foreach (byte b in utf8Bytes.Slice(0, bytesWritten)) { vsb.Append('%'); - vsb.Append((char)HexUpperChars[(b & 0xf0) >> 4]); - vsb.Append((char)HexUpperChars[b & 0xf]); + HexConverter.ToCharsBuffer(b, vsb.AppendSpan(2), 0, HexConverter.Casing.Upper); } continue; } @@ -293,8 +286,7 @@ private static void EscapeStringToBuilder( // Otherwise, append the escaped character. vsb.Append('%'); - vsb.Append((char)HexUpperChars[(value & 0xf0) >> 4]); - vsb.Append((char)HexUpperChars[value & 0xf]); + HexConverter.ToCharsBuffer(value, vsb.AppendSpan(2), 0, HexConverter.Casing.Upper); } } @@ -484,7 +476,7 @@ internal static unsafe void UnescapeString(char* pStr, int start, int end, ref V if (escapeReserved) { //escape that char - EscapeAsciiChar(pStr[next], ref dest); + EscapeAsciiChar((byte)pStr[next], ref dest); escapeReserved = false; start = ++next; continue; @@ -597,7 +589,7 @@ internal static unsafe void MatchUTF8Sequence(ref ValueStringBuilder dest, Span< // Escape any invalid bytes that were before this character while (bytes[count] != encodedBytes[0]) { - EscapeAsciiChar((char)bytes[count++], ref dest); + EscapeAsciiChar(bytes[count++], ref dest); } // check if all bytes match @@ -622,7 +614,7 @@ internal static unsafe void MatchUTF8Sequence(ref ValueStringBuilder dest, Span< // need to keep chars not allowed as escaped for (int l = 0; l < encodedBytes.Length; ++l) { - EscapeAsciiChar((char)encodedBytes[l], ref dest); + EscapeAsciiChar(encodedBytes[l], ref dest); } } else @@ -653,7 +645,7 @@ internal static unsafe void MatchUTF8Sequence(ref ValueStringBuilder dest, Span< // copy bytes till place where bytes don't match for (int l = 0; l < k; ++l) { - EscapeAsciiChar((char)bytes[count++], ref dest); + EscapeAsciiChar(bytes[count++], ref dest); } } } @@ -665,15 +657,14 @@ internal static unsafe void MatchUTF8Sequence(ref ValueStringBuilder dest, Span< // Include any trailing invalid sequences while (count < byteCount) { - EscapeAsciiChar((char)bytes[count++], ref dest); + EscapeAsciiChar(bytes[count++], ref dest); } } - internal static void EscapeAsciiChar(char ch, ref ValueStringBuilder to) + internal static void EscapeAsciiChar(byte b, ref ValueStringBuilder to) { to.Append('%'); - to.Append((char)HexUpperChars[(ch & 0xf0) >> 4]); - to.Append((char)HexUpperChars[ch & 0xf]); + HexConverter.ToCharsBuffer(b, to.AppendSpan(2), 0, HexConverter.Casing.Upper); } internal static char EscapedAscii(char digit, char next) diff --git a/src/libraries/System.Private.Uri/tests/UnitTests/System.Private.Uri.Unit.Tests.csproj b/src/libraries/System.Private.Uri/tests/UnitTests/System.Private.Uri.Unit.Tests.csproj index fe4bee8165668..f1f97bbd7e89e 100644 --- a/src/libraries/System.Private.Uri/tests/UnitTests/System.Private.Uri.Unit.Tests.csproj +++ b/src/libraries/System.Private.Uri/tests/UnitTests/System.Private.Uri.Unit.Tests.csproj @@ -18,5 +18,8 @@ Common\System\Text\ValueStringBuilder.cs + + Common\System\HexConverter.cs + \ No newline at end of file diff --git a/src/libraries/System.Private.Xml/src/System.Private.Xml.csproj b/src/libraries/System.Private.Xml/src/System.Private.Xml.csproj index a2a3f1b5b45f8..c0215be859644 100644 --- a/src/libraries/System.Private.Xml/src/System.Private.Xml.csproj +++ b/src/libraries/System.Private.Xml/src/System.Private.Xml.csproj @@ -14,6 +14,9 @@ Common\System\Marvin.cs + + Common\System\HexConverter.cs + 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 d6a74c7686d0b..45125839c4c0e 100644 --- a/src/libraries/System.Private.Xml/src/System/Xml/BinHexEncoder.cs +++ b/src/libraries/System.Private.Xml/src/System/Xml/BinHexEncoder.cs @@ -6,7 +6,6 @@ namespace System.Xml { internal static partial class BinHexEncoder { - private const string s_hexDigits = "0123456789ABCDEF"; private const int CharsChunkSize = 128; internal static void Encode(byte[] buffer, int index, int count, XmlWriter writer) @@ -73,12 +72,12 @@ private static int Encode(byte[] inArray, int offsetIn, int count, char[] outArr for (int j = 0; j < count; j++) { b = inArray[offsetIn++]; - outArray[curOffsetOut++] = s_hexDigits[b >> 4]; + outArray[curOffsetOut++] = HexConverter.ToCharUpper(b >> 4); if (curOffsetOut == lengthOut) { break; } - outArray[curOffsetOut++] = s_hexDigits[b & 0xF]; + outArray[curOffsetOut++] = HexConverter.ToCharUpper(b); if (curOffsetOut == lengthOut) { break; diff --git a/src/libraries/System.Private.Xml/src/System/Xml/Core/HtmlEncodedRawTextWriter.cs b/src/libraries/System.Private.Xml/src/System/Xml/Core/HtmlEncodedRawTextWriter.cs index d25a52ea2a042..87b9c26e81273 100644 --- a/src/libraries/System.Private.Xml/src/System/Xml/Core/HtmlEncodedRawTextWriter.cs +++ b/src/libraries/System.Private.Xml/src/System/Xml/Core/HtmlEncodedRawTextWriter.cs @@ -715,7 +715,6 @@ private unsafe void WriteUriAttributeText(char* pSrc, char* pSrcEnd) pDst = LineFeedEntity(pDst); break; default: - const string hexDigits = "0123456789ABCDEF"; Debug.Assert(_uriEscapingBuffer?.Length > 0); fixed (byte* pUriEscapingBuffer = _uriEscapingBuffer) { @@ -727,8 +726,8 @@ private unsafe void WriteUriAttributeText(char* pSrc, char* pSrcEnd) while (pByte < pEnd) { *pDst++ = (char)'%'; - *pDst++ = (char)hexDigits[*pByte >> 4]; - *pDst++ = (char)hexDigits[*pByte & 0xF]; + *pDst++ = (char)HexConverter.ToCharUpper(*pByte >> 4); + *pDst++ = (char)HexConverter.ToCharUpper(*pByte); pByte++; } } diff --git a/src/libraries/System.Private.Xml/src/System/Xml/Core/HtmlRawTextWriterGenerator.ttinclude b/src/libraries/System.Private.Xml/src/System/Xml/Core/HtmlRawTextWriterGenerator.ttinclude index d9e842842fee5..f8101cbe653ae 100644 --- a/src/libraries/System.Private.Xml/src/System/Xml/Core/HtmlRawTextWriterGenerator.ttinclude +++ b/src/libraries/System.Private.Xml/src/System/Xml/Core/HtmlRawTextWriterGenerator.ttinclude @@ -723,7 +723,6 @@ namespace System.Xml pDst = LineFeedEntity(pDst); break; default: - const string hexDigits = "0123456789ABCDEF"; Debug.Assert(_uriEscapingBuffer?.Length > 0); fixed (byte* pUriEscapingBuffer = _uriEscapingBuffer) { @@ -735,8 +734,8 @@ namespace System.Xml while (pByte < pEnd) { *pDst++ = (<#= BufferType #>)'%'; - *pDst++ = (<#= BufferType #>)hexDigits[*pByte >> 4]; - *pDst++ = (<#= BufferType #>)hexDigits[*pByte & 0xF]; + *pDst++ = (<#= BufferType #>)HexConverter.ToCharUpper(*pByte >> 4); + *pDst++ = (<#= BufferType #>)HexConverter.ToCharUpper(*pByte); pByte++; } } diff --git a/src/libraries/System.Private.Xml/src/System/Xml/Core/HtmlUtf8RawTextWriter.cs b/src/libraries/System.Private.Xml/src/System/Xml/Core/HtmlUtf8RawTextWriter.cs index 2ba8e9cae42be..f9934ec9548d1 100644 --- a/src/libraries/System.Private.Xml/src/System/Xml/Core/HtmlUtf8RawTextWriter.cs +++ b/src/libraries/System.Private.Xml/src/System/Xml/Core/HtmlUtf8RawTextWriter.cs @@ -692,7 +692,6 @@ private unsafe void WriteUriAttributeText(char* pSrc, char* pSrcEnd) pDst = LineFeedEntity(pDst); break; default: - const string hexDigits = "0123456789ABCDEF"; Debug.Assert(_uriEscapingBuffer?.Length > 0); fixed (byte* pUriEscapingBuffer = _uriEscapingBuffer) { @@ -704,8 +703,8 @@ private unsafe void WriteUriAttributeText(char* pSrc, char* pSrcEnd) while (pByte < pEnd) { *pDst++ = (byte)'%'; - *pDst++ = (byte)hexDigits[*pByte >> 4]; - *pDst++ = (byte)hexDigits[*pByte & 0xF]; + *pDst++ = (byte)HexConverter.ToCharUpper(*pByte >> 4); + *pDst++ = (byte)HexConverter.ToCharUpper(*pByte); pByte++; } } diff --git a/src/libraries/System.Private.Xml/src/System/Xml/Serialization/XmlSerializationWriter.cs b/src/libraries/System.Private.Xml/src/System/Xml/Serialization/XmlSerializationWriter.cs index 9c7745311590b..805170825a2ba 100644 --- a/src/libraries/System.Private.Xml/src/System/Xml/Serialization/XmlSerializationWriter.cs +++ b/src/libraries/System.Private.Xml/src/System/Xml/Serialization/XmlSerializationWriter.cs @@ -1527,7 +1527,6 @@ internal static string GetName(Assembly a) internal class ReflectionAwareCodeGen { - private const string hexDigits = "0123456789ABCDEF"; private const string arrayMemberKey = "0"; // reflectionVariables holds mapping between a reflection entity // referenced in the generated code (such as TypeInfo, @@ -2110,8 +2109,8 @@ internal static void WriteQuotedCSharpString(IndentedWriter writer, string value { byte b = (byte)ch; writer.Write("\\x"); - writer.Write(hexDigits[b >> 4]); - writer.Write(hexDigits[b & 0xF]); + writer.Write(HexConverter.ToCharUpper(b >> 4)); + writer.Write(HexConverter.ToCharUpper(b)); } } else if (ch == '\"') diff --git a/src/libraries/System.Private.Xml/src/System/Xml/Serialization/XmlSerializationWriterILGen.cs b/src/libraries/System.Private.Xml/src/System/Xml/Serialization/XmlSerializationWriterILGen.cs index 10e75fc31d65c..c087b9b022a04 100644 --- a/src/libraries/System.Private.Xml/src/System/Xml/Serialization/XmlSerializationWriterILGen.cs +++ b/src/libraries/System.Private.Xml/src/System/Xml/Serialization/XmlSerializationWriterILGen.cs @@ -2292,7 +2292,6 @@ private string FindChoiceEnumValue(ElementAccessor element, EnumMapping choiceMa internal class ReflectionAwareILGen { - private const string hexDigits = "0123456789ABCDEF"; // reflectionVariables holds mapping between a reflection entity // referenced in the generated code (such as TypeInfo, // FieldInfo) and the variable which represent the entity (and @@ -2614,8 +2613,8 @@ internal static string GetCSharpString(string value) { byte b = (byte)ch; writer.Append("\\x"); - writer.Append(hexDigits[b >> 4]); - writer.Append(hexDigits[b & 0xF]); + writer.Append(HexConverter.ToCharUpper(b >> 4)); + writer.Append(HexConverter.ToCharUpper(b)); } } else if (ch == '\"') diff --git a/src/libraries/System.Runtime.Extensions/src/System.Runtime.Extensions.csproj b/src/libraries/System.Runtime.Extensions/src/System.Runtime.Extensions.csproj index 58106e4e5506a..111eae03677c5 100644 --- a/src/libraries/System.Runtime.Extensions/src/System.Runtime.Extensions.csproj +++ b/src/libraries/System.Runtime.Extensions/src/System.Runtime.Extensions.csproj @@ -51,6 +51,9 @@ Common\Interop\Windows\Interop.BOOL.cs + + Common\System\HexConverter.cs + diff --git a/src/libraries/System.Runtime.Extensions/src/System/ApplicationId.cs b/src/libraries/System.Runtime.Extensions/src/System/ApplicationId.cs index aa5fce4c36bac..bb60d113fea9f 100644 --- a/src/libraries/System.Runtime.Extensions/src/System/ApplicationId.cs +++ b/src/libraries/System.Runtime.Extensions/src/System/ApplicationId.cs @@ -69,15 +69,8 @@ private static void EncodeHexString(byte[] sArray, ref ValueStringBuilder string { for (int i = 0; i < sArray.Length; i++) { - int digit = (sArray[i] & 0xf0) >> 4; - stringBuilder.Append(HexDigit(digit)); - - digit = sArray[i] & 0x0f; - stringBuilder.Append(HexDigit(digit)); + HexConverter.ToCharsBuffer(sArray[i], stringBuilder.AppendSpan(2), 0, HexConverter.Casing.Upper); } - - static char HexDigit(int num) => - (char)((num < 10) ? (num + '0') : (num + ('A' - 10))); } public override bool Equals(object? o) diff --git a/src/libraries/System.Runtime.Extensions/src/System/Net/WebUtility.cs b/src/libraries/System.Runtime.Extensions/src/System/Net/WebUtility.cs index e2d1e1831117c..845b0afeb5c8f 100644 --- a/src/libraries/System.Runtime.Extensions/src/System/Net/WebUtility.cs +++ b/src/libraries/System.Runtime.Extensions/src/System/Net/WebUtility.cs @@ -394,8 +394,8 @@ private static void GetEncodedBytes(byte[] originalBytes, int offset, int count, else { expandedBytes[pos++] = (byte)'%'; - expandedBytes[pos++] = (byte)IntToHex((b >> 4) & 0xf); - expandedBytes[pos++] = (byte)IntToHex(b & 0x0f); + expandedBytes[pos++] = (byte)HexConverter.ToCharUpper(b >> 4); + expandedBytes[pos++] = (byte)HexConverter.ToCharUpper(b); } } } @@ -670,16 +670,6 @@ private static int HexToInt(char h) -1; } - private static char IntToHex(int n) - { - Debug.Assert(n < 0x10); - - if (n <= 9) - return (char)(n + (int)'0'); - else - return (char)(n - 10 + (int)'A'); - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] private static bool IsUrlSafeChar(char ch) { diff --git a/src/libraries/System.Runtime.WindowsRuntime/src/System.Runtime.WindowsRuntime.csproj b/src/libraries/System.Runtime.WindowsRuntime/src/System.Runtime.WindowsRuntime.csproj index b05c8ec51d274..bf25700f883d4 100644 --- a/src/libraries/System.Runtime.WindowsRuntime/src/System.Runtime.WindowsRuntime.csproj +++ b/src/libraries/System.Runtime.WindowsRuntime/src/System.Runtime.WindowsRuntime.csproj @@ -98,6 +98,9 @@ Common\System\IO\Win32Marshal.cs + + Common\System\HexConverter.cs + diff --git a/src/libraries/System.Runtime.WindowsRuntime/src/System/Resources/WindowsRuntimeResourceManager.cs b/src/libraries/System.Runtime.WindowsRuntime/src/System/Resources/WindowsRuntimeResourceManager.cs index 1b395834f0b97..11ef9b7d7847d 100644 --- a/src/libraries/System.Runtime.WindowsRuntime/src/System/Resources/WindowsRuntimeResourceManager.cs +++ b/src/libraries/System.Runtime.WindowsRuntime/src/System/Resources/WindowsRuntimeResourceManager.cs @@ -620,8 +620,8 @@ public static string UriEncode(string str) else { expandedBytes[pos++] = (byte)'%'; - expandedBytes[pos++] = (byte)IntToHex((b >> 4) & 0xf); - expandedBytes[pos++] = (byte)IntToHex(b & 0xf); + expandedBytes[pos++] = (byte)HexConverter.ToCharLower(b >> 4); + expandedBytes[pos++] = (byte)HexConverter.ToCharLower(b); } } } @@ -630,15 +630,6 @@ public static string UriEncode(string str) return Encoding.UTF8.GetString(expandedBytes); } - // Adapted from IntToHex originally in file:ndp\fx\src\xsp\system\web\util\httpencoderutility.cs - private static char IntToHex(int n) - { - if (n <= 9) - return (char)(n + (int)'0'); - else - return (char)(n - 10 + (int)'a'); - } - // Modified from IsUrlSafeChar originally in file:ndp\fx\src\xsp\system\web\util\httpencoderutility.cs // Set of unreserved characters from RFC 3986 private static bool IsUriUnreservedChar(char ch) 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 68e5bde377764..2ac6572cbd0cb 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 @@ -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; using System.Diagnostics.CodeAnalysis; using System.Security.Cryptography; @@ -9,14 +10,11 @@ namespace Internal.Cryptography { internal abstract partial class AsnFormatter { - private static readonly char[] s_hexValues = - { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; - internal static AsnFormatter Instance { get { return s_instance; } } public string Format(Oid? oid, byte[] rawData, bool multiLine) { - return FormatNative(oid, rawData, multiLine) ?? EncodeHexString(rawData); + return FormatNative(oid, rawData, multiLine) ?? HexConverter.ToString(rawData.AsSpan(), HexConverter.Casing.Upper); } protected abstract string? FormatNative(Oid? oid, byte[] rawData, bool multiLine); @@ -50,11 +48,9 @@ protected static string EncodeHexString(byte[] sArray, bool spaceSeparated = fal hexOrder[j++] = ' '; } - uint digit = (uint)((sArray[i] & 0xf0) >> 4); - hexOrder[j++] = s_hexValues[digit]; - - digit = (uint)(sArray[i] & 0x0f); - hexOrder[j++] = s_hexValues[digit]; + int digit = sArray[i]; + hexOrder[j++] = HexConverter.ToCharUpper(digit >> 4); + hexOrder[j++] = HexConverter.ToCharUpper(digit); } result = new string(hexOrder); diff --git a/src/libraries/System.Security.Cryptography.Encoding/src/System.Security.Cryptography.Encoding.csproj b/src/libraries/System.Security.Cryptography.Encoding/src/System.Security.Cryptography.Encoding.csproj index b249576dc3709..0c8b8432bdb10 100644 --- a/src/libraries/System.Security.Cryptography.Encoding/src/System.Security.Cryptography.Encoding.csproj +++ b/src/libraries/System.Security.Cryptography.Encoding/src/System.Security.Cryptography.Encoding.csproj @@ -24,6 +24,9 @@ System\Security\Cryptography\CryptoPool.cs + + Common\System\HexConverter.cs + diff --git a/src/libraries/System.Security.Cryptography.Pkcs/src/Internal/Cryptography/PkcsHelpers.cs b/src/libraries/System.Security.Cryptography.Pkcs/src/Internal/Cryptography/PkcsHelpers.cs index cb330c88c9a3b..c696db23d5c04 100644 --- a/src/libraries/System.Security.Cryptography.Pkcs/src/Internal/Cryptography/PkcsHelpers.cs +++ b/src/libraries/System.Security.Cryptography.Pkcs/src/Internal/Cryptography/PkcsHelpers.cs @@ -325,21 +325,9 @@ public static string ToSerialString(this byte[] serialBytes) } #if NETCOREAPP || NETSTANDARD2_1 - private static unsafe string ToUpperHexString(ReadOnlySpan ba) + private static string ToUpperHexString(ReadOnlySpan ba) { - fixed (byte* baPtr = ba) - { - return string.Create(ba.Length * 2, (Ptr: new IntPtr(baPtr), ba.Length), (span, args) => - { - const string HexValues = "0123456789ABCDEF"; - int p = 0; - foreach (byte b in new ReadOnlySpan((byte*)args.Ptr, args.Length)) - { - span[p++] = HexValues[b >> 4]; - span[p++] = HexValues[b & 0xF]; - } - }); - } + return HexConverter.ToString(ba, HexConverter.Casing.Upper); } #else private static string ToUpperHexString(ReadOnlySpan ba) diff --git a/src/libraries/System.Security.Cryptography.Pkcs/src/System.Security.Cryptography.Pkcs.csproj b/src/libraries/System.Security.Cryptography.Pkcs/src/System.Security.Cryptography.Pkcs.csproj index f440cdf0368c6..573042a62d9ce 100644 --- a/src/libraries/System.Security.Cryptography.Pkcs/src/System.Security.Cryptography.Pkcs.csproj +++ b/src/libraries/System.Security.Cryptography.Pkcs/src/System.Security.Cryptography.Pkcs.csproj @@ -59,6 +59,9 @@ Internal\Cryptography\Helpers.cs + + Common\System\HexConverter.cs + 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 dc820600bab24..b96f9c3cdbdde 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 @@ -27,14 +27,14 @@ private static void ToHexArrayUpper(ReadOnlySpan bytes, Span chars) int i = 0; foreach (byte b in bytes) { - chars[i++] = NibbleToHex((byte)(b >> 4)); - chars[i++] = NibbleToHex((byte)(b & 0xF)); + 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) => - string.Create(bytes.Length * 2, bytes, (chars, src) => ToHexArrayUpper(src, chars)); + HexConverter.ToString(bytes.AsSpan(), HexConverter.Casing.Upper); // 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. @@ -107,14 +107,6 @@ private static byte HexToByte(char val) return 0xFF; } - private static char NibbleToHex(byte b) - { - Debug.Assert(b >= 0 && b <= 15); - return (char)(b >= 0 && b <= 9 ? - '0' + b : - 'A' + (b - 10)); - } - public static bool ContentsEqual(this byte[] a1, byte[] a2) { if (a1.Length != a2.Length) diff --git a/src/libraries/System.Security.Cryptography.X509Certificates/src/System.Security.Cryptography.X509Certificates.csproj b/src/libraries/System.Security.Cryptography.X509Certificates/src/System.Security.Cryptography.X509Certificates.csproj index 2424b58873d8d..d9e9ffcff53b8 100644 --- a/src/libraries/System.Security.Cryptography.X509Certificates/src/System.Security.Cryptography.X509Certificates.csproj +++ b/src/libraries/System.Security.Cryptography.X509Certificates/src/System.Security.Cryptography.X509Certificates.csproj @@ -110,6 +110,9 @@ Internal\Cryptography\Helpers.cs + + Common\System\HexConverter.cs + diff --git a/src/libraries/System.Security.Cryptography.Xml/src/System.Security.Cryptography.Xml.csproj b/src/libraries/System.Security.Cryptography.Xml/src/System.Security.Cryptography.Xml.csproj index 79e985301576a..44784da1fb405 100644 --- a/src/libraries/System.Security.Cryptography.Xml/src/System.Security.Cryptography.Xml.csproj +++ b/src/libraries/System.Security.Cryptography.Xml/src/System.Security.Cryptography.Xml.csproj @@ -83,6 +83,9 @@ + + Common\System\HexConverter.cs + @@ -105,6 +108,7 @@ + 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 7067873b925b2..af4b92f0345d4 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 @@ -721,7 +721,6 @@ internal static X509Certificate2Collection BuildBagOfCerts(KeyInfoX509Data keyIn return collection; } - private static readonly char[] s_hexValues = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; internal static string EncodeHexString(byte[] sArray) { return EncodeHexString(sArray, 0, (uint)sArray.Length); @@ -733,13 +732,11 @@ internal static string EncodeHexString(byte[] sArray, uint start, uint end) if (sArray != null) { char[] hexOrder = new char[(end - start) * 2]; - uint digit; for (uint i = start, j = 0; i < end; i++) { - digit = (uint)((sArray[i] & 0xf0) >> 4); - hexOrder[j++] = s_hexValues[digit]; - digit = (uint)(sArray[i] & 0x0f); - hexOrder[j++] = s_hexValues[digit]; + int digit = sArray[i]; + hexOrder[j++] = HexConverter.ToCharUpper(digit >> 4); + hexOrder[j++] = HexConverter.ToCharUpper(digit); } result = new string(hexOrder); } diff --git a/src/libraries/System.Text.Encodings.Web/src/System.Text.Encodings.Web.csproj b/src/libraries/System.Text.Encodings.Web/src/System.Text.Encodings.Web.csproj index bba454968ada0..176198fafdd57 100644 --- a/src/libraries/System.Text.Encodings.Web/src/System.Text.Encodings.Web.csproj +++ b/src/libraries/System.Text.Encodings.Web/src/System.Text.Encodings.Web.csproj @@ -8,7 +8,6 @@ - @@ -34,7 +33,10 @@ System\Text\UnicodeUtility.cs - + + Common\System\HexConverter.cs + + diff --git a/src/libraries/System.Text.Encodings.Web/src/System/Text/Encodings/Web/HexUtil.cs b/src/libraries/System.Text.Encodings.Web/src/System/Text/Encodings/Web/HexUtil.cs deleted file mode 100644 index 37fdb001c6191..0000000000000 --- a/src/libraries/System.Text.Encodings.Web/src/System/Text/Encodings/Web/HexUtil.cs +++ /dev/null @@ -1,46 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// 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; -using System.Diagnostics; -using System.Runtime.CompilerServices; - -namespace System.Text.Encodings.Web -{ - /// - /// Contains helpers for dealing with byte-hex char conversions. - /// - internal static class HexUtil - { - /// - /// Converts a number 0 - 15 to its associated hex character '0' - 'F'. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static char UInt32LsbToHexDigit(uint value) - { - Debug.Assert(value < 16); - return (value < 10) ? (char)('0' + value) : (char)('A' + (value - 10)); - } - - /// - /// Converts a number 0 - 15 to its associated hex character '0' - 'F'. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static char Int32LsbToHexDigit(int value) - { - Debug.Assert(value < 16); - return (char)((value < 10) ? ('0' + value) : ('A' + (value - 10))); - } - - /// - /// Gets the uppercase hex-encoded form of a byte. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static void ByteToHexDigits(byte value, out char firstHexChar, out char secondHexChar) - { - firstHexChar = UInt32LsbToHexDigit((uint)value >> 4); - secondHexChar = UInt32LsbToHexDigit((uint)value & 0xFU); - } - } -} diff --git a/src/libraries/System.Text.Encodings.Web/src/System/Text/Encodings/Web/HtmlEncoder.cs b/src/libraries/System.Text.Encodings.Web/src/System/Text/Encodings/Web/HtmlEncoder.cs index 2196ece7a80f0..f819446599520 100644 --- a/src/libraries/System.Text.Encodings.Web/src/System/Text/Encodings/Web/HtmlEncoder.cs +++ b/src/libraries/System.Text.Encodings.Web/src/System/Text/Encodings/Web/HtmlEncoder.cs @@ -153,7 +153,7 @@ private static unsafe bool TryWriteEncodedScalarAsNumericEntity(int unicodeScala buffer += numberOfHexCharacters; do { - *buffer = HexUtil.Int32LsbToHexDigit(unicodeScalar & 0xF); + *buffer = HexConverter.ToCharUpper(unicodeScalar); unicodeScalar >>= nibbleSize; buffer--; } diff --git a/src/libraries/System.Text.Encodings.Web/src/System/Text/Encodings/Web/JavaScriptEncoderHelper.cs b/src/libraries/System.Text.Encodings.Web/src/System/Text/Encodings/Web/JavaScriptEncoderHelper.cs index 7c8bffa4f54af..d94a21f3828a2 100644 --- a/src/libraries/System.Text.Encodings.Web/src/System/Text/Encodings/Web/JavaScriptEncoderHelper.cs +++ b/src/libraries/System.Text.Encodings.Web/src/System/Text/Encodings/Web/JavaScriptEncoderHelper.cs @@ -54,13 +54,13 @@ private static unsafe bool TryWriteEncodedSingleCharacter(int unicodeScalar, cha buffer++; *buffer = 'u'; buffer++; - *buffer = HexUtil.Int32LsbToHexDigit(unicodeScalar >> 12); + *buffer = HexConverter.ToCharUpper(unicodeScalar >> 12); buffer++; - *buffer = HexUtil.Int32LsbToHexDigit((int)((unicodeScalar >> 8) & 0xFU)); + *buffer = HexConverter.ToCharUpper(unicodeScalar >> 8); buffer++; - *buffer = HexUtil.Int32LsbToHexDigit((int)((unicodeScalar >> 4) & 0xFU)); + *buffer = HexConverter.ToCharUpper(unicodeScalar >> 4); buffer++; - *buffer = HexUtil.Int32LsbToHexDigit((int)(unicodeScalar & 0xFU)); + *buffer = HexConverter.ToCharUpper(unicodeScalar); numberOfCharactersWritten = 6; return true; diff --git a/src/libraries/System.Text.Encodings.Web/src/System/Text/Encodings/Web/UrlEncoder.cs b/src/libraries/System.Text.Encodings.Web/src/System/Text/Encodings/Web/UrlEncoder.cs index 207b3c0c82166..876c05b2c7dd8 100644 --- a/src/libraries/System.Text.Encodings.Web/src/System/Text/Encodings/Web/UrlEncoder.cs +++ b/src/libraries/System.Text.Encodings.Web/src/System/Text/Encodings/Web/UrlEncoder.cs @@ -165,12 +165,10 @@ public unsafe override bool TryEncodeUnicodeScalar(int unicodeScalar, char* buff if (!WillEncode(unicodeScalar)) { return TryWriteScalarAsChar(unicodeScalar, buffer, bufferLength, out numberOfCharactersWritten); } numberOfCharactersWritten = 0; + uint asUtf8 = unchecked((uint)UnicodeHelpers.GetUtf8RepresentationForScalarValue((uint)unicodeScalar)); do { - char highNibble, lowNibble; - HexUtil.ByteToHexDigits(unchecked((byte)asUtf8), out highNibble, out lowNibble); - if (numberOfCharactersWritten + 3 > bufferLength) { numberOfCharactersWritten = 0; @@ -178,8 +176,8 @@ public unsafe override bool TryEncodeUnicodeScalar(int unicodeScalar, char* buff } *buffer = '%'; buffer++; - *buffer = highNibble; buffer++; - *buffer = lowNibble; buffer++; + *buffer = HexConverter.ToCharUpper((int)asUtf8 >> 4); buffer++; + *buffer = HexConverter.ToCharUpper((int)asUtf8); buffer++; numberOfCharactersWritten += 3; } diff --git a/src/libraries/System.Text.Encodings.Web/tests/System.Text.Encodings.Web.Tests.csproj b/src/libraries/System.Text.Encodings.Web/tests/System.Text.Encodings.Web.Tests.csproj index 147164dccd438..1fe386e88926a 100644 --- a/src/libraries/System.Text.Encodings.Web/tests/System.Text.Encodings.Web.Tests.csproj +++ b/src/libraries/System.Text.Encodings.Web/tests/System.Text.Encodings.Web.Tests.csproj @@ -14,7 +14,6 @@ - @@ -57,5 +56,8 @@ System\Text\UnicodeUtility.cs + + Common\System\HexConverter.cs + \ No newline at end of file diff --git a/src/libraries/System.Text.Encodings.Web/tests/TemporaryInternalTypes.cs b/src/libraries/System.Text.Encodings.Web/tests/TemporaryInternalTypes.cs index 9fc8ef5786416..93afca9a81873 100644 --- a/src/libraries/System.Text.Encodings.Web/tests/TemporaryInternalTypes.cs +++ b/src/libraries/System.Text.Encodings.Web/tests/TemporaryInternalTypes.cs @@ -153,11 +153,11 @@ protected override void WriteEncodedScalar(ref Writer writer, uint value) else if (value == (uint)'&') { writer.Write("&"); } else if (value == (uint)'<') { writer.Write("<"); } else if (value == (uint)'>') { writer.Write(">"); } - else { WriteEncodedScalarAsNumericEntity(ref writer, value); } + else { WriteEncodedScalarAsNumericEntity(ref writer, (int)value); } } // Writes a scalar value as an HTML-encoded numeric entity. - private static void WriteEncodedScalarAsNumericEntity(ref Writer writer, uint value) + private static void WriteEncodedScalarAsNumericEntity(ref Writer writer, int value) { // We're building the characters up in reverse char* chars = stackalloc char[8 /* "FFFFFFFF" */]; @@ -166,7 +166,7 @@ private static void WriteEncodedScalarAsNumericEntity(ref Writer writer, uint va { Debug.Assert(numCharsWritten < 8, "Couldn't have written 8 characters out by this point."); // Pop off the last nibble - chars[numCharsWritten++] = HexUtil.UInt32LsbToHexDigit(value & 0xFU); + chars[numCharsWritten++] = HexConverter.ToCharLower(value); value >>= 4; } while (value != 0); @@ -343,17 +343,17 @@ protected override void WriteEncodedScalar(ref Writer writer, uint value) else if (value == (uint)'\r') { writer.Write(@"\r"); } else if (value == (uint)'/') { writer.Write(@"\/"); } else if (value == (uint)'\\') { writer.Write(@"\\"); } - else { WriteEncodedScalarAsNumericEntity(ref writer, value); } + else { WriteEncodedScalarAsNumericEntity(ref writer, (int)value); } } // Writes a scalar value as an JavaScript-escaped character (or sequence of characters). - private static void WriteEncodedScalarAsNumericEntity(ref Writer writer, uint value) + private static void WriteEncodedScalarAsNumericEntity(ref Writer writer, int value) { - if (UnicodeHelpers.IsSupplementaryCodePoint((int)value)) + if (UnicodeHelpers.IsSupplementaryCodePoint(value)) { // Convert this back to UTF-16 and write out both characters. char leadingSurrogate, trailingSurrogate; - UnicodeHelpers.GetUtf16SurrogatePairFromAstralScalarValue((int)value, out leadingSurrogate, out trailingSurrogate); + UnicodeHelpers.GetUtf16SurrogatePairFromAstralScalarValue(value, out leadingSurrogate, out trailingSurrogate); WriteEncodedSingleCharacter(ref writer, leadingSurrogate); WriteEncodedSingleCharacter(ref writer, trailingSurrogate); } @@ -365,17 +365,17 @@ private static void WriteEncodedScalarAsNumericEntity(ref Writer writer, uint va } // Writes an encoded scalar value (in the BMP) as a JavaScript-escaped character. - private static void WriteEncodedSingleCharacter(ref Writer writer, uint value) + private static void WriteEncodedSingleCharacter(ref Writer writer, int value) { - Debug.Assert(!UnicodeHelpers.IsSupplementaryCodePoint((int)value), "The incoming value should've been in the BMP."); + Debug.Assert(!UnicodeHelpers.IsSupplementaryCodePoint(value), "The incoming value should've been in the BMP."); // Encode this as 6 chars "\uFFFF". writer.Write('\\'); writer.Write('u'); - writer.Write(HexUtil.UInt32LsbToHexDigit(value >> 12)); - writer.Write(HexUtil.UInt32LsbToHexDigit((value >> 8) & 0xFU)); - writer.Write(HexUtil.UInt32LsbToHexDigit((value >> 4) & 0xFU)); - writer.Write(HexUtil.UInt32LsbToHexDigit(value & 0xFU)); + writer.Write(HexConverter.ToCharLower(value >> 12)); + writer.Write(HexConverter.ToCharLower(value >> 8)); + writer.Write(HexConverter.ToCharLower(value >> 4)); + writer.Write(HexConverter.ToCharLower(value)); } } } @@ -581,11 +581,9 @@ protected override void WriteEncodedScalar(ref Writer writer, uint value) uint asUtf8 = (uint)UnicodeHelpers.GetUtf8RepresentationForScalarValue(value); do { - char highNibble, lowNibble; - HexUtil.ByteToHexDigits((byte)asUtf8, out highNibble, out lowNibble); writer.Write('%'); - writer.Write(highNibble); - writer.Write(lowNibble); + writer.Write(HexConverter.ToCharLower((int)(asUtf8 >> 4))); + writer.Write(HexConverter.ToCharLower((int)asUtf8)); } while ((asUtf8 >>= 8) != 0); } } diff --git a/src/libraries/System.Text.Json/src/System.Text.Json.csproj b/src/libraries/System.Text.Json/src/System.Text.Json.csproj index 61642e5d5b2f6..88beebf21f637 100644 --- a/src/libraries/System.Text.Json/src/System.Text.Json.csproj +++ b/src/libraries/System.Text.Json/src/System.Text.Json.csproj @@ -178,6 +178,9 @@ Common\System\Runtime\CompilerServices\PreserveDependencyAttribute.cs + + Common\System\HexConverter.cs + diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/JsonWriterHelper.Escaping.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/JsonWriterHelper.Escaping.cs index 143375a9e7c04..f24c7a81fe5e5 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Writer/JsonWriterHelper.Escaping.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Writer/JsonWriterHelper.Escaping.cs @@ -307,22 +307,12 @@ private static void EscapeNextChars(char value, Span destination, ref int #if !BUILDING_INBOX_LIBRARY private static int WriteHex(int value, Span destination, int written) { - destination[written++] = (char)Int32LsbToHexDigit(value >> 12); - destination[written++] = (char)Int32LsbToHexDigit((int)((value >> 8) & 0xFU)); - destination[written++] = (char)Int32LsbToHexDigit((int)((value >> 4) & 0xFU)); - destination[written++] = (char)Int32LsbToHexDigit((int)(value & 0xFU)); + destination[written++] = HexConverter.ToCharUpper(value >> 12); + destination[written++] = HexConverter.ToCharUpper(value >> 8); + destination[written++] = HexConverter.ToCharUpper(value >> 4); + destination[written++] = HexConverter.ToCharUpper(value); return written; } - - /// - /// Converts a number 0 - 15 to its associated hex character '0' - 'F' as byte. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static byte Int32LsbToHexDigit(int value) - { - Debug.Assert(value < 16); - return (byte)((value < 10) ? ('0' + value) : ('A' + (value - 10))); - } #endif } } diff --git a/src/libraries/System.Text.RegularExpressions/src/System.Text.RegularExpressions.csproj b/src/libraries/System.Text.RegularExpressions/src/System.Text.RegularExpressions.csproj index 34207f1b24961..ac34feaec6c0e 100644 --- a/src/libraries/System.Text.RegularExpressions/src/System.Text.RegularExpressions.csproj +++ b/src/libraries/System.Text.RegularExpressions/src/System.Text.RegularExpressions.csproj @@ -51,6 +51,9 @@ Common\System\NotImplemented.cs + + Common\System\HexConverter.cs + Common\System\Collections\Generic\ValueListBuilder.cs diff --git a/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/RegexCharClass.cs b/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/RegexCharClass.cs index f64024fc5c47d..ea9a6c00e584b 100644 --- a/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/RegexCharClass.cs +++ b/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/RegexCharClass.cs @@ -1528,7 +1528,6 @@ private static ReadOnlySpan SetFromProperty(string capname, bool invert, s } #if DEBUG - public static readonly char[] Hex = new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; public static readonly string[] CategoryIdToName = PopulateCategoryIdToName(); private static string[] PopulateCategoryIdToName() @@ -1673,7 +1672,7 @@ public static string CharDescription(char ch) while (shift > 0) { shift -= 4; - sb.Append(Hex[(ch >> shift) & 0xF]); + sb.Append(HexConverter.ToCharLower(ch >> shift)); } return sb.ToString(); diff --git a/src/libraries/System.Web.HttpUtility/src/System.Web.HttpUtility.csproj b/src/libraries/System.Web.HttpUtility/src/System.Web.HttpUtility.csproj index 0bdcd1c988973..8779e2c76d94b 100644 --- a/src/libraries/System.Web.HttpUtility/src/System.Web.HttpUtility.csproj +++ b/src/libraries/System.Web.HttpUtility/src/System.Web.HttpUtility.csproj @@ -10,6 +10,9 @@ + + Common\System\HexConverter.cs + 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 26adef0412d44..92f5cf9fa6b14 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 @@ -501,8 +501,8 @@ internal static string JavaScriptStringEncode(string? value) else { expandedBytes[pos++] = (byte)'%'; - expandedBytes[pos++] = (byte)HttpEncoderUtility.IntToHex((b >> 4) & 0xf); - expandedBytes[pos++] = (byte)HttpEncoderUtility.IntToHex(b & 0x0f); + expandedBytes[pos++] = (byte)HexConverter.ToCharLower(b >> 4); + expandedBytes[pos++] = (byte)HexConverter.ToCharLower(b); } } @@ -549,8 +549,8 @@ private static byte[] UrlEncodeNonAscii(byte[] bytes, int offset, int count) if (IsNonAsciiByte(b)) { expandedBytes[pos++] = (byte)'%'; - expandedBytes[pos++] = (byte)HttpEncoderUtility.IntToHex((b >> 4) & 0xf); - expandedBytes[pos++] = (byte)HttpEncoderUtility.IntToHex(b & 0x0f); + expandedBytes[pos++] = (byte)HexConverter.ToCharLower(b >> 4); + expandedBytes[pos++] = (byte)HexConverter.ToCharLower(b); } else { @@ -590,17 +590,17 @@ private static byte[] UrlEncodeNonAscii(byte[] bytes, int offset, int count) else { sb.Append('%'); - sb.Append(HttpEncoderUtility.IntToHex((ch >> 4) & 0xf)); - sb.Append(HttpEncoderUtility.IntToHex((ch) & 0xf)); + sb.Append(HexConverter.ToCharLower(ch >> 4)); + sb.Append(HexConverter.ToCharLower(ch)); } } else { // arbitrary Unicode? sb.Append("%u"); - sb.Append(HttpEncoderUtility.IntToHex((ch >> 12) & 0xf)); - sb.Append(HttpEncoderUtility.IntToHex((ch >> 8) & 0xf)); - sb.Append(HttpEncoderUtility.IntToHex((ch >> 4) & 0xf)); - sb.Append(HttpEncoderUtility.IntToHex((ch) & 0xf)); + sb.Append(HexConverter.ToCharLower(ch >> 12)); + sb.Append(HexConverter.ToCharLower(ch >> 8)); + sb.Append(HexConverter.ToCharLower(ch >> 4)); + sb.Append(HexConverter.ToCharLower(ch)); } } 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 bb0cb4442eeea..bc07b63f48a23 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 @@ -18,13 +18,6 @@ public static int HexToInt(char h) => ? h - 'A' + 10 : -1; - public static char IntToHex(int n) - { - Debug.Assert(n < 0x10); - - return n <= 9 ? (char)(n + '0') : (char)(n - 10 + 'a'); - } - // Set of safe chars, from RFC 1738.4 minus '+' public static bool IsUrlSafeChar(char ch) {