Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add methods to convert between hexadecimal strings and bytes #37546

Merged
merged 13 commits into from
Jul 18, 2020
Merged
106 changes: 102 additions & 4 deletions src/libraries/Common/src/System/HexConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System.Diagnostics;
using System.Runtime.CompilerServices;

namespace System
Expand Down Expand Up @@ -83,6 +84,16 @@ public static void ToCharsBuffer(byte value, Span<char> buffer, int startingInde
buffer[startingIndex] = (char)(packedResult >> 8);
}

public static void EncodeToUtf16(ReadOnlySpan<byte> bytes, Span<char> chars, Casing casing = Casing.Upper)
{
Debug.Assert(chars.Length >= bytes.Length * 2);

for (int pos = 0; pos < bytes.Length; ++pos)
{
ToCharsBuffer(bytes[pos], chars, pos * 2, casing);
}
}

#if ALLOW_PARTIALLY_TRUSTED_CALLERS
[System.Security.SecuritySafeCriticalAttribute]
#endif
Expand Down Expand Up @@ -113,10 +124,7 @@ public static unsafe string ToString(ReadOnlySpan<byte> bytes, Casing casing = C
return string.Create(bytes.Length * 2, (Ptr: (IntPtr)bytesPtr, bytes.Length, casing), (chars, args) =>
{
var ros = new ReadOnlySpan<byte>((byte*)args.Ptr, args.Length);
for (int pos = 0; pos < ros.Length; ++pos)
{
ToCharsBuffer(ros[pos], chars, pos * 2, args.casing);
}
EncodeToUtf16(ros, chars, args.casing);
});
}
#endif
Expand Down Expand Up @@ -149,5 +157,95 @@ public static char ToCharLower(int value)

return (char)value;
}

public static bool TryDecodeFromUtf16(ReadOnlySpan<char> chars, Span<byte> bytes)
{
tkp1n marked this conversation as resolved.
Show resolved Hide resolved
return TryDecodeFromUtf16(chars, bytes, out _);
}

public static bool TryDecodeFromUtf16(ReadOnlySpan<char> chars, Span<byte> bytes, out int charsProcessed)
{
Debug.Assert(chars.Length % 2 == 0, "Un-even number of characters provided");
Debug.Assert(chars.Length / 2 == bytes.Length, "Target buffer not right-sized for provided characters");

int i = 0;
int j = 0;
int byteLo = 0;
int byteHi = 0;
while (j < bytes.Length)
{
byteLo = FromChar(chars[i + 1]);
byteHi = FromChar(chars[i]);

// byteHi hasn't been shifted to the high half yet, so the only way the bitwise or produces this pattern
// is if either byteHi or byteLo was not a hex character.
if ((byteLo | byteHi) == 0xFF)
break;

bytes[j++] = (byte)((byteHi << 4) | byteLo);
i+=2;
tkp1n marked this conversation as resolved.
Show resolved Hide resolved
}

if (byteLo == 0xFF)
i++;

charsProcessed = i;
return (byteLo | byteHi) != 0xFF;
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int FromChar(int c)
{
ReadOnlySpan<byte> charToHexLookup = CharToHexLookup;
return c >= charToHexLookup.Length ? 0xFF : charToHexLookup[c];
tkp1n marked this conversation as resolved.
Show resolved Hide resolved
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int FromUpperChar(int c)
{
ReadOnlySpan<byte> charToHexLookup = CharToHexLookup;
return c > 71 ? 0xFF : charToHexLookup[c];
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int FromLowerChar(int c)
{
if ('0' <= c && c <= '9')
return (byte)(c - '0');
tkp1n marked this conversation as resolved.
Show resolved Hide resolved
if ('a' <= c && c <= 'f')
return (byte)(c - ('a' - 10));

return 0xFF;
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsHexChar(int c)
{
return FromChar(c) != 0xFF;
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsHexUpperChar(int c)
{
return (uint)(c - '0') <= 9 || (uint)(c - 'A') <= ('F' - 'A');
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsHexLowerChar(int c)
{
return (uint)(c - '0') <= 9 || (uint)(c - 'a') <= ('f' - 'a');
}

/// <summary>Map from an ASCII char to its hex value, e.g. arr['b'] == 11. 0xFF means it's not a hex digit.</summary>
private static ReadOnlySpan<byte> CharToHexLookup => new byte[]
{
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 15
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 31
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 47
0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 63
0xFF, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 79
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 95
0xFF, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf // 102
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -804,7 +804,7 @@ internal static bool TryConvertIdToContext(string id, out ActivityContext contex
ReadOnlySpan<char> spanIdSpan = id.AsSpan(36, 16);

if (!ActivityTraceId.IsLowerCaseHexAndNotAllZeros(traceIdSpan) || !ActivityTraceId.IsLowerCaseHexAndNotAllZeros(spanIdSpan) ||
!ActivityTraceId.IsHexadecimalLowercaseChar(id[53]) || !ActivityTraceId.IsHexadecimalLowercaseChar(id[54]))
!HexConverter.IsHexLowerChar(id[53]) || !HexConverter.IsHexLowerChar(id[54]))
{
return false;
}
Expand Down Expand Up @@ -1136,7 +1136,7 @@ private void TrySetTraceFlagsFromParent()
}
else if (_parentId != null && IsW3CId(_parentId))
{
if (ActivityTraceId.IsHexadecimalLowercaseChar(_parentId[53]) && ActivityTraceId.IsHexadecimalLowercaseChar(_parentId[54]))
if (HexConverter.IsHexLowerChar(_parentId[53]) && HexConverter.IsHexLowerChar(_parentId[54]))
{
_w3CIdFlags = (byte)(ActivityTraceId.HexByteFromChars(_parentId[53], _parentId[54]) | ActivityTraceFlagsIsSet);
}
Expand Down Expand Up @@ -1408,15 +1408,14 @@ internal static void SetSpanFromHexChars(ReadOnlySpan<char> charData, Span<byte>
}
internal static byte HexByteFromChars(char char1, char char2)
{
return (byte)(HexDigitToBinary(char1) * 16 + HexDigitToBinary(char2));
}
private static byte HexDigitToBinary(char c)
{
if ('0' <= c && c <= '9')
return (byte)(c - '0');
if ('a' <= c && c <= 'f')
return (byte)(c - ('a' - 10));
throw new ArgumentOutOfRangeException("idData");
int hi = HexConverter.FromLowerChar(char1);
int lo = HexConverter.FromLowerChar(char2);
if ((hi | lo) == 0xFF)
{
throw new ArgumentOutOfRangeException("idData");
}

return (byte)((hi << 4) | lo);
}

internal static bool IsLowerCaseHexAndNotAllZeros(ReadOnlySpan<char> idData)
Expand All @@ -1427,7 +1426,7 @@ internal static bool IsLowerCaseHexAndNotAllZeros(ReadOnlySpan<char> idData)
for (; i < idData.Length; i++)
{
char c = idData[i];
if (!IsHexadecimalLowercaseChar(c))
if (!HexConverter.IsHexLowerChar(c))
{
return false;
}
Expand All @@ -1440,13 +1439,6 @@ internal static bool IsLowerCaseHexAndNotAllZeros(ReadOnlySpan<char> idData)

return isNonZero;
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static bool IsHexadecimalLowercaseChar(char c)
{
// Between 0 - 9 or lowercased between a - f
return (uint)(c - '0') <= 9 || (uint)(c - 'a') <= ('f' - 'a');
}
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -307,20 +307,15 @@ private static bool TryReadUnknownPercentEncodedAlpnProtocolName(ReadOnlySpan<ch
/// </summary>
private static bool TryReadAlpnHexDigit(char ch, out int nibble)
{
if ((uint)(ch - '0') <= '9' - '0') // ch >= '0' && ch <= '9'
int result = HexConverter.FromUpperChar(ch);
if (result == 0xFF)
{
nibble = ch - '0';
return true;
}

if ((uint)(ch - 'A') <= 'F' - 'A') // ch >= 'A' && ch <= 'F'
{
nibble = ch - 'A' + 10;
return true;
nibble = 0;
return false;
}

nibble = 0;
return false;
nibble = result;
return true;
}

private static bool TryReadQuotedAltAuthority(string value, int startIndex, out string? host, out int port, out int readLength)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -237,17 +237,7 @@ private static string ComputeHash(string data, string algorithm)
bool hashComputed = hash.TryComputeHash(Encoding.UTF8.GetBytes(data), result, out int bytesWritten);
Debug.Assert(hashComputed && bytesWritten == result.Length);

StringBuilder sb = StringBuilderCache.Acquire(result.Length * 2);

Span<char> byteX2 = stackalloc char[2];
for (int i = 0; i < result.Length; i++)
{
bool formatted = result[i].TryFormat(byteX2, out int charsWritten, "x2");
Debug.Assert(formatted && charsWritten == 2);
sb.Append(byteX2);
}

return StringBuilderCache.GetStringAndRelease(sb);
return HexConverter.ToString(result, HexConverter.Casing.Lower);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@
<Compile Include="$(CommonPath)System\Net\WebSockets\WebSocketValidate.cs"
Link="Common\System\Net\WebSockets\WebSocketValidate.cs" />
<Compile Include="System\Net\Windows\CookieExtensions.cs" />
<Compile Include="$(CommonPath)System\HexConverter.cs"
Link="Common\System\HexConverter.cs" />
</ItemGroup>
<ItemGroup Condition="'$(TargetsWindows)' == 'true' and '$(ForceManagedImplementation)' != 'true'">
<Compile Include="System\Net\Windows\HttpListener.Windows.cs" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -438,12 +438,12 @@ private static string UrlDecodeStringFromStringInternal(string s, Encoding e)
{
if (s[pos + 1] == 'u' && pos < count - 5)
{
int h1 = HexToInt(s[pos + 2]);
int h2 = HexToInt(s[pos + 3]);
int h3 = HexToInt(s[pos + 4]);
int h4 = HexToInt(s[pos + 5]);
int h1 = HexConverter.FromChar(s[pos + 2]);
int h2 = HexConverter.FromChar(s[pos + 3]);
int h3 = HexConverter.FromChar(s[pos + 4]);
int h4 = HexConverter.FromChar(s[pos + 5]);

if (h1 >= 0 && h2 >= 0 && h3 >= 0 && h4 >= 0)
if ((h1 | h2 | h3 | h4) != 0xFF)
{ // valid 4 hex chars
ch = (char)((h1 << 12) | (h2 << 8) | (h3 << 4) | h4);
pos += 5;
Expand All @@ -455,10 +455,10 @@ private static string UrlDecodeStringFromStringInternal(string s, Encoding e)
}
else
{
int h1 = HexToInt(s[pos + 1]);
int h2 = HexToInt(s[pos + 2]);
int h1 = HexConverter.FromChar(s[pos + 1]);
int h2 = HexConverter.FromChar(s[pos + 2]);

if (h1 >= 0 && h2 >= 0)
if ((h1 | h2) != 0xFF)
{ // valid 2 hex chars
byte b = (byte)((h1 << 4) | h2);
pos += 2;
Expand All @@ -479,14 +479,6 @@ private static string UrlDecodeStringFromStringInternal(string s, Encoding e)
return helper.GetString();
}

private static int HexToInt(char h)
{
return (h >= '0' && h <= '9') ? h - '0' :
(h >= 'a' && h <= 'f') ? h - 'a' + 10 :
(h >= 'A' && h <= 'F') ? h - 'A' + 10 :
-1;
}

private class UrlDecoder
{
private readonly int _bufferSize;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
// See the LICENSE file in the project root for more information.

using System.Diagnostics.CodeAnalysis;
using System.Text;

namespace System.Net.NetworkInformation
{
Expand Down Expand Up @@ -92,7 +91,7 @@ public override bool Equals(object? comparand)

public override string ToString()
{
return HexConverter.ToString(_address.AsSpan(), HexConverter.Casing.Upper);
return Convert.ToHexString(_address ?? Array.Empty<byte>());
tkp1n marked this conversation as resolved.
Show resolved Hide resolved
}

public byte[] GetAddressBytes()
Expand Down Expand Up @@ -185,20 +184,8 @@ public static bool TryParse(ReadOnlySpan<char> address, [NotNullWhen(true)] out
for (int i = 0; i < address.Length; i++)
{
int character = address[i];

if (character >= '0' && character <= '9')
{
character -= '0';
}
else if (character >= 'A' && character <= 'F')
{
character -= ('A' - 10);
}
else if (character >= 'a' && character <= 'f')
{
character -= ('a' - 10);
}
else
int tmp;
if ((tmp = HexConverter.FromChar(character)) == 0xFF)
{
if (delimiter == character && validCount == validSegmentLength)
{
Expand All @@ -209,6 +196,8 @@ public static bool TryParse(ReadOnlySpan<char> address, [NotNullWhen(true)] out
return false;
}

character = tmp;

// we had too many characters after the last delimiter
if (validCount >= validSegmentLength)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -326,22 +326,13 @@ private static IPAddress ParseIPv6HexString(string hexAddress, bool isNetworkOrd

private static byte HexToByte(char val)
{
if (val <= '9' && val >= '0')
{
return (byte)(val - '0');
}
else if (val >= 'a' && val <= 'f')
{
return (byte)((val - 'a') + 10);
}
else if (val >= 'A' && val <= 'F')
{
return (byte)((val - 'A') + 10);
}
else
byte result = (byte)HexConverter.FromChar(val);
if (result == 0xFF)
{
throw ExceptionHelper.CreateForParseFailure();
}

return result;
tkp1n marked this conversation as resolved.
Show resolved Hide resolved
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@
Link="Common\System\IO\StringParser.cs" />
<Compile Include="$(CommonPath)System\IO\RowConfigReader.cs"
Link="Common\System\IO\RowConfigReader.cs" />
<Compile Include="$(CommonPath)System\HexConverter.cs"
Link="Common\System\HexConverter.cs" />
</ItemGroup>
<ItemGroup>
<Compile Include="LoggingTest.cs" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2224,6 +2224,12 @@
<data name="Format_NoFormatSpecifier" xml:space="preserve">
<value>No format specifiers were provided.</value>
</data>
<data name="Format_BadHexChar" xml:space="preserve">
<value>The input is not a valid hex string as it contains a non-hex character.</value>
</data>
<data name="Format_BadHexLength" xml:space="preserve">
<value>The input is not a valid hex string as its length is not a multiple of 2.</value>
</data>
<data name="Format_BadQuote" xml:space="preserve">
<value>Cannot find a matching quote character for the character '{0}'.</value>
</data>
Expand Down
Loading