Skip to content
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -1782,7 +1782,11 @@ public static ActivityTraceId CreateFromBytes(ReadOnlySpan<byte> 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<byte> idData) => new ActivityTraceId(idData);

Expand Down Expand Up @@ -1861,7 +1865,11 @@ private ActivityTraceId(ReadOnlySpan<byte> 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
}

/// <summary>
Expand Down Expand Up @@ -1956,14 +1964,22 @@ public static unsafe ActivitySpanId CreateRandom()
{
ulong id;
ActivityTraceId.SetToRandomBytes(new Span<byte>(&id, sizeof(ulong)));
#if NET9_0_OR_GREATER
return new ActivitySpanId(Convert.ToHexStringLower(new ReadOnlySpan<byte>(&id, sizeof(ulong))));
#else
return new ActivitySpanId(HexConverter.ToString(new ReadOnlySpan<byte>(&id, sizeof(ulong)), HexConverter.Casing.Lower));
#endif
}
public static ActivitySpanId CreateFromBytes(ReadOnlySpan<byte> 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<byte> idData) => new ActivitySpanId(idData);

Expand Down Expand Up @@ -2031,7 +2047,11 @@ private unsafe ActivitySpanId(ReadOnlySpan<byte> idData)
id = BinaryPrimitives.ReverseEndianness(id);
}

#if NET9_0_OR_GREATER
_hexString = Convert.ToHexStringLower(new ReadOnlySpan<byte>(&id, sizeof(ulong)));
#else
_hexString = HexConverter.ToString(new ReadOnlySpan<byte>(&id, sizeof(ulong)), HexConverter.Casing.Lower);
#endif
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,27 +63,27 @@ public unsafe void WriteSecret()
string clientRandom = string.Empty;
if (_tlsSecrets->IsSet.ClientRandom != 0)
{
clientRandom = HexConverter.ToString(new ReadOnlySpan<byte>(_tlsSecrets->ClientRandom, 32));
clientRandom = Convert.ToHexString(new ReadOnlySpan<byte>(_tlsSecrets->ClientRandom, 32));
}
if (_tlsSecrets->IsSet.ClientHandshakeTrafficSecret != 0)
{
s_fileStream.Write(Encoding.ASCII.GetBytes($"CLIENT_HANDSHAKE_TRAFFIC_SECRET {clientRandom} {HexConverter.ToString(new ReadOnlySpan<byte>(_tlsSecrets->ClientHandshakeTrafficSecret, _tlsSecrets->SecretLength))}\n"));
s_fileStream.Write(Encoding.ASCII.GetBytes($"CLIENT_HANDSHAKE_TRAFFIC_SECRET {clientRandom} {Convert.ToHexString(new ReadOnlySpan<byte>(_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<byte>(_tlsSecrets->ServerHandshakeTrafficSecret, _tlsSecrets->SecretLength))}\n"));
s_fileStream.Write(Encoding.ASCII.GetBytes($"SERVER_HANDSHAKE_TRAFFIC_SECRET {clientRandom} {Convert.ToHexString(new ReadOnlySpan<byte>(_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<byte>(_tlsSecrets->ClientTrafficSecret0, _tlsSecrets->SecretLength))}\n"));
s_fileStream.Write(Encoding.ASCII.GetBytes($"CLIENT_TRAFFIC_SECRET_0 {clientRandom} {Convert.ToHexString(new ReadOnlySpan<byte>(_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<byte>(_tlsSecrets->ServerTrafficSecret0, _tlsSecrets->SecretLength))}\n"));
s_fileStream.Write(Encoding.ASCII.GetBytes($"SERVER_TRAFFIC_SECRET_0 {clientRandom} {Convert.ToHexString(new ReadOnlySpan<byte>(_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<byte>(_tlsSecrets->ClientEarlyTrafficSecret, _tlsSecrets->SecretLength))}\n"));
s_fileStream.Write(Encoding.ASCII.GetBytes($"CLIENT_EARLY_TRAFFIC_SECRET {clientRandom} {Convert.ToHexString(new ReadOnlySpan<byte>(_tlsSecrets->ClientEarlyTrafficSecret, _tlsSecrets->SecretLength))}\n"));
}
s_fileStream.Flush();
}
Expand Down
82 changes: 79 additions & 3 deletions src/libraries/System.Private.CoreLib/src/System/Convert.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3077,7 +3077,6 @@ public static string ToHexString(ReadOnlySpan<byte> bytes)
return HexConverter.ToString(bytes, HexConverter.Casing.Upper);
}


/// <summary>
/// Converts a span of 8-bit unsigned integers to its equivalent span representation that is encoded with uppercase hex characters.
/// </summary>
Expand All @@ -3092,14 +3091,91 @@ public static bool TryToHexString(ReadOnlySpan<byte> source, Span<char> 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;
}

/// <summary>
/// Converts an array of 8-bit unsigned integers to its equivalent string representation that is encoded with lowercase hex characters.
/// </summary>
/// <param name="inArray">An array of 8-bit unsigned integers.</param>
/// <returns>The string representation in hex of the elements in <paramref name="inArray"/>.</returns>
/// <exception cref="ArgumentNullException"><paramref name="inArray"/> is <code>null</code>.</exception>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="inArray"/> is too large to be encoded.</exception>
public static string ToHexStringLower(byte[] inArray)
{
ArgumentNullException.ThrowIfNull(inArray);

return ToHexStringLower(new ReadOnlySpan<byte>(inArray));
}

/// <summary>
/// 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.
/// </summary>
/// <param name="inArray">An array of 8-bit unsigned integers.</param>
/// <param name="offset">An offset in <paramref name="inArray"/>.</param>
/// <param name="length">The number of elements of <paramref name="inArray"/> to convert.</param>
/// <returns>The string representation in hex of <paramref name="length"/> elements of <paramref name="inArray"/>, starting at position <paramref name="offset"/>.</returns>
/// <exception cref="ArgumentNullException"><paramref name="inArray"/> is <code>null</code>.</exception>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="offset"/> or <paramref name="length"/> is negative.</exception>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="offset"/> plus <paramref name="length"/> is greater than the length of <paramref name="inArray"/>.</exception>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="inArray"/> is too large to be encoded.</exception>
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<byte>(inArray, offset, length));
}

/// <summary>
/// Converts a span of 8-bit unsigned integers to its equivalent string representation that is encoded with lowercase hex characters.
/// </summary>
/// <param name="bytes">A span of 8-bit unsigned integers.</param>
/// <returns>The string representation in hex of the elements in <paramref name="bytes"/>.</returns>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="bytes"/> is too large to be encoded.</exception>
public static string ToHexStringLower(ReadOnlySpan<byte> bytes)
{
if (bytes.Length == 0)
return string.Empty;
ArgumentOutOfRangeException.ThrowIfGreaterThan(bytes.Length, int.MaxValue / 2, nameof(bytes));

return HexConverter.ToString(bytes, HexConverter.Casing.Lower);
}

/// <summary>
/// Converts a span of 8-bit unsigned integers to its equivalent span representation that is encoded with lowercase hex characters.
/// </summary>
/// <param name="source">A span of 8-bit unsigned integers.</param>
/// <param name="destination">The span representation in hex of the elements in <paramref name="source"/>.</param>
/// <param name="charsWritten">When this method returns, contains the number of chars that were written in <paramref name="destination"/>.</param>
/// <returns>true if the conversion was successful; otherwise, false.</returns>
public static bool TryToHexStringLower(ReadOnlySpan<byte> source, Span<char> 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
Expand Down
6 changes: 5 additions & 1 deletion src/libraries/System.Runtime/ref/System.Runtime.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1284,7 +1284,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<byte> bytes) { throw null; }
public static bool TryToHexString(System.ReadOnlySpan<byte> source, System.Span<char> 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<byte> 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; }
Expand Down Expand Up @@ -1577,6 +1579,8 @@ public static partial class Convert
public static bool TryFromBase64Chars(System.ReadOnlySpan<char> chars, System.Span<byte> bytes, out int bytesWritten) { throw null; }
public static bool TryFromBase64String(string s, System.Span<byte> bytes, out int bytesWritten) { throw null; }
public static bool TryToBase64Chars(System.ReadOnlySpan<byte> bytes, System.Span<char> chars, out int charsWritten, System.Base64FormattingOptions options = System.Base64FormattingOptions.None) { throw null; }
public static bool TryToHexString(System.ReadOnlySpan<byte> source, System.Span<char> destination, out int charsWritten) { throw null; }
public static bool TryToHexStringLower(System.ReadOnlySpan<byte> source, System.Span<char> destination, out int charsWritten) { throw null; }
}
public delegate TOutput Converter<in TInput, out TOutput>(TInput input);
public readonly partial struct DateOnly : System.IComparable, System.IComparable<System.DateOnly>, System.IEquatable<System.DateOnly>, System.IFormattable, System.IParsable<System.DateOnly>, System.ISpanFormattable, System.ISpanParsable<System.DateOnly>, System.IUtf8SpanFormattable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ public static void ToHexFromHexRoundtrip()
{
const int loopCount = 50;
Span<char> buffer = stackalloc char[loopCount * 2];
Span<char> bufferLower = stackalloc char[loopCount * 2];
for (int i = 1; i < loopCount; i++)
{
byte[] data = Security.Cryptography.RandomNumberGenerator.GetBytes(i);
Expand All @@ -97,6 +98,12 @@ public static void ToHexFromHexRoundtrip()
AssertExtensions.SequenceEqual(hex.AsSpan(), currentBuffer);
Assert.Equal(hex.Length, written);

Span<char> 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());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
using System.Collections.Generic;

namespace System.Tests
{
{
public class ConvertToHexStringTests
{
[Fact]
Expand All @@ -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()
{
Expand All @@ -30,18 +37,35 @@ 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]
public static void InvalidInputBuffer()
{
AssertExtensions.Throws<ArgumentNullException>("inArray", () => Convert.ToHexString(null));
AssertExtensions.Throws<ArgumentNullException>("inArray", () => Convert.ToHexString(null, 0, 0));
AssertExtensions.Throws<ArgumentNullException>("inArray", () => Convert.ToHexStringLower(null));
AssertExtensions.Throws<ArgumentNullException>("inArray", () => Convert.ToHexStringLower(null, 0, 0));
}

[Fact]
Expand All @@ -50,6 +74,8 @@ public static void InvalidOffset()
byte[] inputBytes = Convert.FromHexString("000102FDFEFF");
AssertExtensions.Throws<ArgumentOutOfRangeException>("offset", () => Convert.ToHexString(inputBytes, -1, inputBytes.Length));
AssertExtensions.Throws<ArgumentOutOfRangeException>("offset", () => Convert.ToHexString(inputBytes, inputBytes.Length, inputBytes.Length));
AssertExtensions.Throws<ArgumentOutOfRangeException>("offset", () => Convert.ToHexStringLower(inputBytes, -1, inputBytes.Length));
AssertExtensions.Throws<ArgumentOutOfRangeException>("offset", () => Convert.ToHexStringLower(inputBytes, inputBytes.Length, inputBytes.Length));
}

[Fact]
Expand All @@ -59,12 +85,16 @@ public static void InvalidLength()
AssertExtensions.Throws<ArgumentOutOfRangeException>("length", () => Convert.ToHexString(inputBytes, 0, -1));
AssertExtensions.Throws<ArgumentOutOfRangeException>("offset", () => Convert.ToHexString(inputBytes, 0, inputBytes.Length + 1));
AssertExtensions.Throws<ArgumentOutOfRangeException>("offset", () => Convert.ToHexString(inputBytes, 1, inputBytes.Length));
AssertExtensions.Throws<ArgumentOutOfRangeException>("length", () => Convert.ToHexStringLower(inputBytes, 0, -1));
AssertExtensions.Throws<ArgumentOutOfRangeException>("offset", () => Convert.ToHexStringLower(inputBytes, 0, inputBytes.Length + 1));
AssertExtensions.Throws<ArgumentOutOfRangeException>("offset", () => Convert.ToHexStringLower(inputBytes, 1, inputBytes.Length));
}

[Fact]
public static unsafe void InputTooLarge()
{
AssertExtensions.Throws<ArgumentOutOfRangeException>("bytes", () => Convert.ToHexString(new ReadOnlySpan<byte>((void*)0, Int32.MaxValue)));
AssertExtensions.Throws<ArgumentOutOfRangeException>("bytes", () => Convert.ToHexStringLower(new ReadOnlySpan<byte>((void*)0, Int32.MaxValue)));
}

public static IEnumerable<object[]> ToHexStringTestData()
Expand Down Expand Up @@ -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);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<byte> ba)
{
return Convert.ToHexString(ba);
}
#elif NETCOREAPP || NETSTANDARD2_1
private static string ToUpperHexString(ReadOnlySpan<byte> ba)
{
return HexConverter.ToString(ba, HexConverter.Casing.Upper);
Expand Down
Loading