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);