From b80a4fdb128dbce21c7f0bd192d390a997614066 Mon Sep 17 00:00:00 2001 From: Azuk 443 Date: Mon, 20 Nov 2023 10:54:45 +0800 Subject: [PATCH] `Convert.ToHexStringLower`: lower variant for `Convert.ToHexString` (#92483) * Add System.Convert.ToHexStringLower with tests * Use new public API `Convert.ToHexStringLower` * Add `Convert.TryToHexStringLower` * format code * sort methods * use old `HexConverter` API on `System.Diagnostics.DiagnosticSource` * Add unit test for `Convert.TryToHexStringLower` * add if-defs for libs * removed whitespace --- .../src/System/Diagnostics/Activity.cs | 20 +++++ .../AuthenticationHelper.Digest.cs | 2 +- .../Net/Quic/Internal/MsQuicTlsSecret.cs | 12 +-- .../src/System/Convert.cs | 82 ++++++++++++++++++- .../System.Runtime/ref/System.Runtime.cs | 6 +- .../System/Convert.FromHexString.cs | 7 ++ .../System/Convert.ToHexString.cs | 40 ++++++++- .../src/Internal/Cryptography/PkcsHelpers.cs | 7 +- .../Cryptography/Xml/SignedXmlDebugLog.cs | 4 + .../System/Security/Cryptography/Xml/Utils.cs | 7 ++ 10 files changed, 174 insertions(+), 13 deletions(-) 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 1a5365594438e..b5f5385a190d0 100644 --- a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Activity.cs +++ b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Activity.cs @@ -1782,7 +1782,11 @@ public static ActivityTraceId CreateFromBytes(ReadOnlySpan idData) if (idData.Length != 16) throw new ArgumentOutOfRangeException(nameof(idData)); +#if NET9_0_OR_GREATER + return new ActivityTraceId(Convert.ToHexStringLower(idData)); +#else return new ActivityTraceId(HexConverter.ToString(idData, HexConverter.Casing.Lower)); +#endif } public static ActivityTraceId CreateFromUtf8String(ReadOnlySpan idData) => new ActivityTraceId(idData); @@ -1861,7 +1865,11 @@ private ActivityTraceId(ReadOnlySpan idData) span[1] = BinaryPrimitives.ReverseEndianness(span[1]); } +#if NET9_0_OR_GREATER + _hexString = Convert.ToHexStringLower(MemoryMarshal.AsBytes(span)); +#else _hexString = HexConverter.ToString(MemoryMarshal.AsBytes(span), HexConverter.Casing.Lower); +#endif } /// @@ -1956,14 +1964,22 @@ public static unsafe ActivitySpanId CreateRandom() { ulong id; ActivityTraceId.SetToRandomBytes(new Span(&id, sizeof(ulong))); +#if NET9_0_OR_GREATER + return new ActivitySpanId(Convert.ToHexStringLower(new ReadOnlySpan(&id, sizeof(ulong)))); +#else return new ActivitySpanId(HexConverter.ToString(new ReadOnlySpan(&id, sizeof(ulong)), HexConverter.Casing.Lower)); +#endif } public static ActivitySpanId CreateFromBytes(ReadOnlySpan idData) { if (idData.Length != 8) throw new ArgumentOutOfRangeException(nameof(idData)); +#if NET9_0_OR_GREATER + return new ActivitySpanId(Convert.ToHexStringLower(idData)); +#else return new ActivitySpanId(HexConverter.ToString(idData, HexConverter.Casing.Lower)); +#endif } public static ActivitySpanId CreateFromUtf8String(ReadOnlySpan idData) => new ActivitySpanId(idData); @@ -2031,7 +2047,11 @@ private unsafe ActivitySpanId(ReadOnlySpan idData) id = BinaryPrimitives.ReverseEndianness(id); } +#if NET9_0_OR_GREATER + _hexString = Convert.ToHexStringLower(new ReadOnlySpan(&id, sizeof(ulong))); +#else _hexString = HexConverter.ToString(new ReadOnlySpan(&id, sizeof(ulong)), HexConverter.Casing.Lower); +#endif } /// 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 58cf311b01ee8..ba07a0d702cd2 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 @@ -230,7 +230,7 @@ private static string ComputeHash(string data, string algorithm) #pragma warning restore CA5351 } - return HexConverter.ToString(hashBuffer.Slice(0, written), HexConverter.Casing.Lower); + return Convert.ToHexStringLower(hashBuffer.Slice(0, written)); } internal sealed class DigestResponse diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/Internal/MsQuicTlsSecret.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/Internal/MsQuicTlsSecret.cs index f151f12312909..26f69c1653aa8 100644 --- a/src/libraries/System.Net.Quic/src/System/Net/Quic/Internal/MsQuicTlsSecret.cs +++ b/src/libraries/System.Net.Quic/src/System/Net/Quic/Internal/MsQuicTlsSecret.cs @@ -63,27 +63,27 @@ public unsafe void WriteSecret() string clientRandom = string.Empty; if (_tlsSecrets->IsSet.ClientRandom != 0) { - clientRandom = HexConverter.ToString(new ReadOnlySpan(_tlsSecrets->ClientRandom, 32)); + clientRandom = Convert.ToHexString(new ReadOnlySpan(_tlsSecrets->ClientRandom, 32)); } if (_tlsSecrets->IsSet.ClientHandshakeTrafficSecret != 0) { - s_fileStream.Write(Encoding.ASCII.GetBytes($"CLIENT_HANDSHAKE_TRAFFIC_SECRET {clientRandom} {HexConverter.ToString(new ReadOnlySpan(_tlsSecrets->ClientHandshakeTrafficSecret, _tlsSecrets->SecretLength))}\n")); + s_fileStream.Write(Encoding.ASCII.GetBytes($"CLIENT_HANDSHAKE_TRAFFIC_SECRET {clientRandom} {Convert.ToHexString(new ReadOnlySpan(_tlsSecrets->ClientHandshakeTrafficSecret, _tlsSecrets->SecretLength))}\n")); } if (_tlsSecrets->IsSet.ServerHandshakeTrafficSecret != 0) { - s_fileStream.Write(Encoding.ASCII.GetBytes($"SERVER_HANDSHAKE_TRAFFIC_SECRET {clientRandom} {HexConverter.ToString(new ReadOnlySpan(_tlsSecrets->ServerHandshakeTrafficSecret, _tlsSecrets->SecretLength))}\n")); + s_fileStream.Write(Encoding.ASCII.GetBytes($"SERVER_HANDSHAKE_TRAFFIC_SECRET {clientRandom} {Convert.ToHexString(new ReadOnlySpan(_tlsSecrets->ServerHandshakeTrafficSecret, _tlsSecrets->SecretLength))}\n")); } if (_tlsSecrets->IsSet.ClientTrafficSecret0 != 0) { - s_fileStream.Write(Encoding.ASCII.GetBytes($"CLIENT_TRAFFIC_SECRET_0 {clientRandom} {HexConverter.ToString(new ReadOnlySpan(_tlsSecrets->ClientTrafficSecret0, _tlsSecrets->SecretLength))}\n")); + s_fileStream.Write(Encoding.ASCII.GetBytes($"CLIENT_TRAFFIC_SECRET_0 {clientRandom} {Convert.ToHexString(new ReadOnlySpan(_tlsSecrets->ClientTrafficSecret0, _tlsSecrets->SecretLength))}\n")); } if (_tlsSecrets->IsSet.ServerTrafficSecret0 != 0) { - s_fileStream.Write(Encoding.ASCII.GetBytes($"SERVER_TRAFFIC_SECRET_0 {clientRandom} {HexConverter.ToString(new ReadOnlySpan(_tlsSecrets->ServerTrafficSecret0, _tlsSecrets->SecretLength))}\n")); + s_fileStream.Write(Encoding.ASCII.GetBytes($"SERVER_TRAFFIC_SECRET_0 {clientRandom} {Convert.ToHexString(new ReadOnlySpan(_tlsSecrets->ServerTrafficSecret0, _tlsSecrets->SecretLength))}\n")); } if (_tlsSecrets->IsSet.ClientEarlyTrafficSecret != 0) { - s_fileStream.Write(Encoding.ASCII.GetBytes($"CLIENT_EARLY_TRAFFIC_SECRET {clientRandom} {HexConverter.ToString(new ReadOnlySpan(_tlsSecrets->ClientEarlyTrafficSecret, _tlsSecrets->SecretLength))}\n")); + s_fileStream.Write(Encoding.ASCII.GetBytes($"CLIENT_EARLY_TRAFFIC_SECRET {clientRandom} {Convert.ToHexString(new ReadOnlySpan(_tlsSecrets->ClientEarlyTrafficSecret, _tlsSecrets->SecretLength))}\n")); } s_fileStream.Flush(); } diff --git a/src/libraries/System.Private.CoreLib/src/System/Convert.cs b/src/libraries/System.Private.CoreLib/src/System/Convert.cs index ebf4ee8caaebf..ca8979c231f8b 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Convert.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Convert.cs @@ -3077,7 +3077,6 @@ public static string ToHexString(ReadOnlySpan bytes) return HexConverter.ToString(bytes, HexConverter.Casing.Upper); } - /// /// Converts a span of 8-bit unsigned integers to its equivalent span representation that is encoded with uppercase hex characters. /// @@ -3092,14 +3091,91 @@ public static bool TryToHexString(ReadOnlySpan source, Span destinat charsWritten = 0; return true; } - else if (source.Length > int.MaxValue / 2 || destination.Length > source.Length * 2) + else if (source.Length > int.MaxValue / 2 || destination.Length > source.Length * 2) { charsWritten = 0; return false; } HexConverter.EncodeToUtf16(source, destination); - charsWritten = source.Length * 2; + charsWritten = source.Length * 2; + return true; + } + + /// + /// Converts an array of 8-bit unsigned integers to its equivalent string representation that is encoded with lowercase 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 ToHexStringLower(byte[] inArray) + { + ArgumentNullException.ThrowIfNull(inArray); + + return ToHexStringLower(new ReadOnlySpan(inArray)); + } + + /// + /// Converts a subset of an array of 8-bit unsigned integers to its equivalent string representation that is encoded with lowercase 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 ToHexStringLower(byte[] inArray, int offset, int length) + { + ArgumentNullException.ThrowIfNull(inArray); + + ArgumentOutOfRangeException.ThrowIfNegative(length); + ArgumentOutOfRangeException.ThrowIfNegative(offset); + ArgumentOutOfRangeException.ThrowIfGreaterThan(offset, inArray.Length - length); + + return ToHexStringLower(new ReadOnlySpan(inArray, offset, length)); + } + + /// + /// Converts a span of 8-bit unsigned integers to its equivalent string representation that is encoded with lowercase 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 ToHexStringLower(ReadOnlySpan bytes) + { + if (bytes.Length == 0) + return string.Empty; + ArgumentOutOfRangeException.ThrowIfGreaterThan(bytes.Length, int.MaxValue / 2, nameof(bytes)); + + return HexConverter.ToString(bytes, HexConverter.Casing.Lower); + } + + /// + /// Converts a span of 8-bit unsigned integers to its equivalent span representation that is encoded with lowercase hex characters. + /// + /// A span of 8-bit unsigned integers. + /// The span representation in hex of the elements in . + /// When this method returns, contains the number of chars that were written in . + /// true if the conversion was successful; otherwise, false. + public static bool TryToHexStringLower(ReadOnlySpan source, Span destination, out int charsWritten) + { + if (source.Length == 0) + { + charsWritten = 0; + return true; + } + else if (source.Length > int.MaxValue / 2 || destination.Length > source.Length * 2) + { + charsWritten = 0; + return false; + } + + HexConverter.EncodeToUtf16(source, destination, HexConverter.Casing.Lower); + charsWritten = source.Length * 2; return true; } } // class Convert diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs index f253ada5f48a4..0993c790b4738 100644 --- a/src/libraries/System.Runtime/ref/System.Runtime.cs +++ b/src/libraries/System.Runtime/ref/System.Runtime.cs @@ -1287,7 +1287,9 @@ public static partial class Convert 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 bool TryToHexString(System.ReadOnlySpan source, System.Span destination, out int charsWritten) { throw null; } + public static string ToHexStringLower(byte[] inArray) { throw null; } + public static string ToHexStringLower(byte[] inArray, int offset, int length) { throw null; } + public static string ToHexStringLower(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; } @@ -1580,6 +1582,8 @@ public static partial class Convert public static bool TryFromBase64Chars(System.ReadOnlySpan chars, System.Span bytes, out int bytesWritten) { throw null; } public static bool TryFromBase64String(string s, System.Span bytes, out int bytesWritten) { throw null; } public static bool TryToBase64Chars(System.ReadOnlySpan bytes, System.Span chars, out int charsWritten, System.Base64FormattingOptions options = System.Base64FormattingOptions.None) { throw null; } + public static bool TryToHexString(System.ReadOnlySpan source, System.Span destination, out int charsWritten) { throw null; } + public static bool TryToHexStringLower(System.ReadOnlySpan source, System.Span destination, out int charsWritten) { throw null; } } public delegate TOutput Converter(TInput input); public readonly partial struct DateOnly : System.IComparable, System.IComparable, System.IEquatable, System.IFormattable, System.IParsable, System.ISpanFormattable, System.ISpanParsable, System.IUtf8SpanFormattable diff --git a/src/libraries/System.Runtime/tests/System.Runtime.Extensions.Tests/System/Convert.FromHexString.cs b/src/libraries/System.Runtime/tests/System.Runtime.Extensions.Tests/System/Convert.FromHexString.cs index ad032d931f2bb..dc80b4c0e9b78 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Extensions.Tests/System/Convert.FromHexString.cs +++ b/src/libraries/System.Runtime/tests/System.Runtime.Extensions.Tests/System/Convert.FromHexString.cs @@ -86,6 +86,7 @@ public static void ToHexFromHexRoundtrip() { const int loopCount = 50; Span buffer = stackalloc char[loopCount * 2]; + Span bufferLower = stackalloc char[loopCount * 2]; for (int i = 1; i < loopCount; i++) { byte[] data = Security.Cryptography.RandomNumberGenerator.GetBytes(i); @@ -97,6 +98,12 @@ public static void ToHexFromHexRoundtrip() AssertExtensions.SequenceEqual(hex.AsSpan(), currentBuffer); Assert.Equal(hex.Length, written); + Span currentBufferLower = bufferLower.Slice(0, i * 2); + tryHex = Convert.TryToHexStringLower(data, currentBufferLower, out written); + Assert.True(tryHex); + AssertExtensions.SequenceEqual(hex.ToLowerInvariant().AsSpan(), currentBufferLower); + Assert.Equal(hex.Length, written); + TestSequence(data, hex); TestSequence(data, hex.ToLowerInvariant()); TestSequence(data, hex.ToUpperInvariant()); diff --git a/src/libraries/System.Runtime/tests/System.Runtime.Extensions.Tests/System/Convert.ToHexString.cs b/src/libraries/System.Runtime/tests/System.Runtime.Extensions.Tests/System/Convert.ToHexString.cs index b92685667dd50..fdcc022b03f90 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Extensions.Tests/System/Convert.ToHexString.cs +++ b/src/libraries/System.Runtime/tests/System.Runtime.Extensions.Tests/System/Convert.ToHexString.cs @@ -6,7 +6,7 @@ using System.Collections.Generic; namespace System.Tests -{ +{ public class ConvertToHexStringTests { [Fact] @@ -16,6 +16,13 @@ public static void KnownByteSequence() Assert.Equal("000102FDFEFF", Convert.ToHexString(inputBytes)); } + [Fact] + public static void KnownByteSequenceLower() + { + byte[] inputBytes = new byte[] { 0x00, 0x01, 0x02, 0xFD, 0xFE, 0xFF }; + Assert.Equal("000102fdfeff", Convert.ToHexStringLower(inputBytes)); + } + [Fact] public static void CompleteValueRange() { @@ -30,11 +37,26 @@ public static void CompleteValueRange() Assert.Equal(sb.ToString(), Convert.ToHexString(values)); } + [Fact] + public static void CompleteValueRangeLower() + { + 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:x2}"); + } + + Assert.Equal(sb.ToString(), Convert.ToHexStringLower(values)); + } + [Fact] public static void ZeroLength() { byte[] inputBytes = Convert.FromHexString("000102FDFEFF"); Assert.Same(string.Empty, Convert.ToHexString(inputBytes, 0, 0)); + Assert.Same(string.Empty, Convert.ToHexStringLower(inputBytes, 0, 0)); } [Fact] @@ -42,6 +64,8 @@ public static void InvalidInputBuffer() { AssertExtensions.Throws("inArray", () => Convert.ToHexString(null)); AssertExtensions.Throws("inArray", () => Convert.ToHexString(null, 0, 0)); + AssertExtensions.Throws("inArray", () => Convert.ToHexStringLower(null)); + AssertExtensions.Throws("inArray", () => Convert.ToHexStringLower(null, 0, 0)); } [Fact] @@ -50,6 +74,8 @@ 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)); + AssertExtensions.Throws("offset", () => Convert.ToHexStringLower(inputBytes, -1, inputBytes.Length)); + AssertExtensions.Throws("offset", () => Convert.ToHexStringLower(inputBytes, inputBytes.Length, inputBytes.Length)); } [Fact] @@ -59,12 +85,16 @@ public static void InvalidLength() 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)); + AssertExtensions.Throws("length", () => Convert.ToHexStringLower(inputBytes, 0, -1)); + AssertExtensions.Throws("offset", () => Convert.ToHexStringLower(inputBytes, 0, inputBytes.Length + 1)); + AssertExtensions.Throws("offset", () => Convert.ToHexStringLower(inputBytes, 1, inputBytes.Length)); } [Fact] public static unsafe void InputTooLarge() { AssertExtensions.Throws("bytes", () => Convert.ToHexString(new ReadOnlySpan((void*)0, Int32.MaxValue))); + AssertExtensions.Throws("bytes", () => Convert.ToHexStringLower(new ReadOnlySpan((void*)0, Int32.MaxValue))); } public static IEnumerable ToHexStringTestData() @@ -106,5 +136,13 @@ public static unsafe void ToHexString(byte[] input, string expected) string actual = Convert.ToHexString(input); Assert.Equal(expected, actual); } + + [Theory] + [MemberData(nameof(ToHexStringTestData))] + public static unsafe void ToHexStringLower(byte[] input, string expected) + { + string actual = Convert.ToHexStringLower(input); + Assert.Equal(expected.ToLower(), actual); + } } } 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 758be866d1caf..6351a376c0bc7 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 @@ -364,7 +364,12 @@ public static string ToSerialString(this byte[] serialBytes) return ToUpperHexString(serialBytes); } -#if NETCOREAPP || NETSTANDARD2_1 +#if NET5_0_OR_GREATER + private static string ToUpperHexString(ReadOnlySpan ba) + { + return Convert.ToHexString(ba); + } +#elif NETCOREAPP || NETSTANDARD2_1 private static string ToUpperHexString(ReadOnlySpan ba) { return HexConverter.ToString(ba, HexConverter.Casing.Upper); 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 64268c03726a8..476de02a3fa4c 100644 --- a/src/libraries/System.Security.Cryptography.Xml/src/System/Security/Cryptography/Xml/SignedXmlDebugLog.cs +++ b/src/libraries/System.Security.Cryptography.Xml/src/System/Security/Cryptography/Xml/SignedXmlDebugLog.cs @@ -197,7 +197,11 @@ private static string FormatBytes(byte[]? bytes) if (bytes == null) return NullString; +#if NET9_0_OR_GREATER + return Convert.ToHexStringLower(bytes); +#else return HexConverter.ToString(bytes, HexConverter.Casing.Lower); +#endif } /// 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 0483550c4b781..ffffeefa5bac3 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 @@ -720,10 +720,17 @@ internal static X509Certificate2Collection BuildBagOfCerts(KeyInfoX509Data keyIn return collection; } +#if NET5_0_OR_GREATER + internal static string EncodeHexString(byte[] sArray) + { + return Convert.ToHexString(sArray); + } +#else internal static string EncodeHexString(byte[] sArray) { return HexConverter.ToString(sArray); } +#endif internal static byte[] DecodeHexString(string s) {