diff --git a/MediaBrowser.Common/Hex.cs b/MediaBrowser.Common/Hex.cs
index e19a9a1f40..6c86e4e6d0 100644
--- a/MediaBrowser.Common/Hex.cs
+++ b/MediaBrowser.Common/Hex.cs
@@ -1,5 +1,5 @@
using System;
-using System.Globalization;
+using System.Diagnostics.CodeAnalysis;
namespace MediaBrowser.Common
{
@@ -11,6 +11,23 @@ namespace MediaBrowser.Common
internal const string HexCharsLower = "0123456789abcdef";
internal const string HexCharsUpper = "0123456789ABCDEF";
+ internal const int LastHexSymbol = 0x66; // 102: f
+
+ ///
+ /// Map from an ASCII char to its hex value shifted,
+ /// e.g. b -> 11. 0xFF means it's not a hex symbol.
+ ///
+ ///
+ internal static ReadOnlySpan HexLookup => new byte[] {
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f
+ };
+
///
/// Encodes bytes as a hex string.
///
@@ -41,17 +58,37 @@ namespace MediaBrowser.Common
/// The decoded bytes.
public static byte[] Decode(ReadOnlySpan str)
{
- byte[] bytes = new byte[str.Length / 2];
- int j = 0;
- for (int i = 0; i < str.Length; i += 2)
+ if (str.Length == 0)
{
- bytes[j++] = byte.Parse(
- str.Slice(i, 2),
- NumberStyles.HexNumber,
- CultureInfo.InvariantCulture);
+ return Array.Empty();
+ }
+
+ var unHex = HexLookup;
+
+ int byteLen = str.Length / 2;
+ byte[] bytes = new byte[byteLen];
+ int i = 0;
+ for (int j = 0; j < byteLen; j++)
+ {
+ byte a;
+ byte b;
+ if (str[i] > LastHexSymbol
+ || (a = unHex[str[i++]]) == 0xFF
+ || str[i] > LastHexSymbol
+ || (b = unHex[str[i++]]) == 0xFF)
+ {
+ ThrowArgumentException(nameof(str));
+ break; // Unreachable
+ }
+
+ bytes[j] = (byte)((a << 4) | b);
}
return bytes;
}
+
+ [DoesNotReturn]
+ private static void ThrowArgumentException(string paramName)
+ => throw new ArgumentException("Character is not a hex symbol.", paramName);
}
}
diff --git a/benches/Jellyfin.Common.Benches/HexDecodeBenches.cs b/benches/Jellyfin.Common.Benches/HexDecodeBenches.cs
index 2812755978..2efe5273f7 100644
--- a/benches/Jellyfin.Common.Benches/HexDecodeBenches.cs
+++ b/benches/Jellyfin.Common.Benches/HexDecodeBenches.cs
@@ -9,10 +9,13 @@ namespace Jellyfin.Common.Benches
[MemoryDiagnoser]
public class HexDecodeBenches
{
- private const int N = 1000000;
- private readonly string data;
+ [Params(0, 10, 100, 1000, 10000, 1000000)]
+ public int N { get; set; }
- public HexDecodeBenches()
+ private string data;
+
+ [GlobalSetup]
+ public void GlobalSetup()
{
var tmp = new byte[N];
new Random(42).NextBytes(tmp);