| | 1 | | using NostrSure.Domain.Validation; |
| | 2 | |
|
| | 3 | | namespace NostrSure.Domain.Services; |
| | 4 | |
|
| | 5 | | /// <summary> |
| | 6 | | /// High-performance hexadecimal converter using lookup tables for optimal speed |
| | 7 | | /// </summary> |
| | 8 | | public sealed class OptimizedHexConverter : IHexConverter |
| | 9 | | { |
| | 10 | | // Precomputed lookup table for hex character to byte conversion |
| 1 | 11 | | private static readonly byte[] HexLookup = new byte[128]; |
| | 12 | |
|
| | 13 | | static OptimizedHexConverter() |
| 1 | 14 | | { |
| | 15 | | // Initialize lookup table |
| 258 | 16 | | for (int i = 0; i < HexLookup.Length; i++) |
| 128 | 17 | | HexLookup[i] = 255; // Invalid marker |
| | 18 | |
|
| | 19 | | // Set valid hex characters |
| 22 | 20 | | for (int i = 0; i <= 9; i++) |
| 10 | 21 | | HexLookup['0' + i] = (byte)i; |
| | 22 | |
|
| 14 | 23 | | for (int i = 0; i <= 5; i++) |
| 6 | 24 | | { |
| 6 | 25 | | HexLookup['A' + i] = (byte)(10 + i); |
| 6 | 26 | | HexLookup['a' + i] = (byte)(10 + i); |
| 6 | 27 | | } |
| 1 | 28 | | } |
| | 29 | |
|
| | 30 | | public bool TryParseHex(ReadOnlySpan<char> hex, Span<byte> bytes, out int bytesWritten) |
| 330 | 31 | | { |
| 330 | 32 | | bytesWritten = 0; |
| | 33 | |
|
| | 34 | | // Handle 0x prefix |
| 330 | 35 | | if (hex.Length >= 2 && hex[0] == '0' && (hex[1] == 'x' || hex[1] == 'X')) |
| 0 | 36 | | hex = hex[2..]; |
| | 37 | |
|
| 330 | 38 | | if (hex.Length % 2 != 0) |
| 1 | 39 | | return false; |
| | 40 | |
|
| 329 | 41 | | if (bytes.Length < hex.Length / 2) |
| 0 | 42 | | return false; |
| | 43 | |
|
| 28138 | 44 | | for (int i = 0; i < hex.Length; i += 2) |
| 13743 | 45 | | { |
| 13743 | 46 | | var c1 = (int)hex[i]; |
| 13743 | 47 | | var c2 = (int)hex[i + 1]; |
| | 48 | |
|
| 13743 | 49 | | if (c1 >= HexLookup.Length || c2 >= HexLookup.Length) |
| 0 | 50 | | return false; |
| | 51 | |
|
| 13743 | 52 | | var b1 = HexLookup[c1]; |
| 13743 | 53 | | var b2 = HexLookup[c2]; |
| | 54 | |
|
| 13743 | 55 | | if (b1 == 255 || b2 == 255) |
| 3 | 56 | | return false; |
| | 57 | |
|
| 13740 | 58 | | bytes[bytesWritten++] = (byte)((b1 << 4) | b2); |
| 13740 | 59 | | } |
| | 60 | |
|
| 326 | 61 | | return true; |
| 330 | 62 | | } |
| | 63 | |
|
| | 64 | | public byte[] ParseHex(ReadOnlySpan<char> hex) |
| 3 | 65 | | { |
| 3 | 66 | | var expectedLength = hex.Length; |
| | 67 | |
|
| | 68 | | // Handle 0x prefix |
| 3 | 69 | | if (hex.Length >= 2 && hex[0] == '0' && (hex[1] == 'x' || hex[1] == 'X')) |
| 1 | 70 | | { |
| 1 | 71 | | hex = hex[2..]; |
| 1 | 72 | | expectedLength -= 2; |
| 1 | 73 | | } |
| | 74 | |
|
| 3 | 75 | | var bytes = new byte[expectedLength / 2]; |
| 3 | 76 | | if (!TryParseHex(hex, bytes, out _)) |
| 0 | 77 | | throw new ArgumentException("Invalid hex string", nameof(hex)); |
| 3 | 78 | | return bytes; |
| 3 | 79 | | } |
| | 80 | |
|
| | 81 | | public byte[] ParseHex(string hex) |
| 3 | 82 | | { |
| 3 | 83 | | if (string.IsNullOrEmpty(hex)) |
| 0 | 84 | | return Array.Empty<byte>(); |
| | 85 | |
|
| 3 | 86 | | return ParseHex(hex.AsSpan()); |
| 3 | 87 | | } |
| | 88 | | } |