diff options
| -rw-r--r-- | MediaBrowser.Common/Hex.cs | 53 | ||||
| -rw-r--r-- | benches/Jellyfin.Common.Benches/HexDecodeBenches.cs | 9 |
2 files changed, 51 insertions, 11 deletions
diff --git a/MediaBrowser.Common/Hex.cs b/MediaBrowser.Common/Hex.cs index e19a9a1f4..6c86e4e6d 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 + + /// <summary> + /// Map from an ASCII char to its hex value shifted, + /// e.g. <c>b</c> -> 11. 0xFF means it's not a hex symbol. + /// </summary> + /// <value></value> + internal static ReadOnlySpan<byte> 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 + }; + /// <summary> /// Encodes <c>bytes</c> as a hex string. /// </summary> @@ -41,17 +58,37 @@ namespace MediaBrowser.Common /// <returns>The decoded bytes.</returns> public static byte[] Decode(ReadOnlySpan<char> 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<byte>(); + } + + 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 281275597..2efe5273f 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); |
