Skip to content

Commit

Permalink
Add Convert.ToHexString/FromHexString
Browse files Browse the repository at this point in the history
Add methods to convert between hexadecimal strings and bytes, and converts internal usage to use those APIs when possible, or reuse the shared code as helpers otherwise.
  • Loading branch information
tkp1n authored Jul 18, 2020
1 parent 264cb76 commit f6dc87f
Show file tree
Hide file tree
Showing 42 changed files with 535 additions and 504 deletions.
114 changes: 110 additions & 4 deletions src/libraries/Common/src/System/HexConverter.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

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

namespace System
Expand Down Expand Up @@ -82,6 +83,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 @@ -112,10 +123,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 @@ -148,5 +156,103 @@ public static char ToCharLower(int value)

return (char)value;
}

public static bool TryDecodeFromUtf16(ReadOnlySpan<char> chars, Span<byte> bytes)
{
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;
}

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

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

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int FromChar(int c)
{
return c >= CharToHexLookup.Length ? 0xFF : CharToHexLookup[c];
}

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

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int FromLowerChar(int c)
{
if ((uint)(c - '0') <= '9' - '0')
return c - '0';

if ((uint)(c - 'a') <= 'f' - 'a')
return c - 'a' + 10;

return 0xFF;
}

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

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

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

/// <summary>Map from an ASCII char to its hex value, e.g. arr['b'] == 11. 0xFF means it's not a hex digit.</summary>
public 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, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 111
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 127
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 143
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 159
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 175
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 191
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 207
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 223
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 239
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF // 255
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -851,7 +851,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 @@ -1183,7 +1183,7 @@ private void TrySetTraceFlagsFromParent()
}
else if (_parentId != null && IsW3CId(_parentId))
{
if (ActivityTraceId.IsHexadecimalLowercaseChar(_parentId[53]) && ActivityTraceId.IsHexadecimalLowercaseChar(_parentId[54]))
if (HexConverter.IsHexLowerChar(_parentId[53]) && HexConverter.IsHexLowerChar(_parentId[54]))
{
_w3CIdFlags = (byte)(ActivityTraceId.HexByteFromChars(_parentId[53], _parentId[54]) | ActivityTraceFlagsIsSet);
}
Expand Down Expand Up @@ -1584,15 +1584,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 @@ -1603,7 +1602,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 @@ -1616,13 +1615,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 @@ -306,20 +306,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 @@ -236,17 +236,7 @@ private static string ComputeHash(string data, string algorithm)
bool hashComputed = hash.TryComputeHash(Encoding.UTF8.GetBytes(data), result, out int bytesWritten);
Debug.Assert(hashComputed && bytesWritten == result.Length);

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

Span<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 @@ -430,12 +430,12 @@ private static string UrlDecodeStringFromStringInternal(string s, Encoding e)
{
if (s[pos + 1] == 'u' && pos < count - 5)
{
int h1 = HexToInt(s[pos + 2]);
int h2 = HexToInt(s[pos + 3]);
int h3 = HexToInt(s[pos + 4]);
int h4 = HexToInt(s[pos + 5]);
int h1 = HexConverter.FromChar(s[pos + 2]);
int h2 = HexConverter.FromChar(s[pos + 3]);
int h3 = HexConverter.FromChar(s[pos + 4]);
int h4 = HexConverter.FromChar(s[pos + 5]);

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

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

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

private class UrlDecoder
{
private readonly int _bufferSize;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
// The .NET Foundation licenses this file to you under the MIT license.

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

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

public override string ToString()
{
return HexConverter.ToString(_address.AsSpan(), HexConverter.Casing.Upper);
return Convert.ToHexString(_address.AsSpan());
}

public byte[] GetAddressBytes()
Expand Down Expand Up @@ -184,20 +183,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 @@ -208,6 +195,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 @@ -325,22 +325,13 @@ private static IPAddress ParseIPv6HexString(string hexAddress, bool isNetworkOrd

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

return (byte)result;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,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
Loading

0 comments on commit f6dc87f

Please sign in to comment.