source: TFP-WebServer/SpaceWizards.HttpListener/src/System/HexConverter.cs@ 484

Last change on this file since 484 was 377, checked in by alloc, 2 years ago

Made SpaceWizards.HttpListener compilable against .NET 4.8 with preprocessor define UNITY_NETFRAMEWORK

File size: 11.2 KB
Line 
1// Licensed to the .NET Foundation under one or more agreements.
2// The .NET Foundation licenses this file to you under the MIT license.
3
4// ReSharper disable RedundantCast
5
6#nullable disable
7using System.Diagnostics;
8using System.Runtime.CompilerServices;
9
10namespace System
11{
12 internal static class HexConverter
13 {
14 public enum Casing : uint
15 {
16 // Output [ '0' .. '9' ] and [ 'A' .. 'F' ].
17 Upper = 0,
18
19 // Output [ '0' .. '9' ] and [ 'a' .. 'f' ].
20 // This works because values in the range [ 0x30 .. 0x39 ] ([ '0' .. '9' ])
21 // already have the 0x20 bit set, so ORing them with 0x20 is a no-op,
22 // while outputs in the range [ 0x41 .. 0x46 ] ([ 'A' .. 'F' ])
23 // don't have the 0x20 bit set, so ORing them maps to
24 // [ 0x61 .. 0x66 ] ([ 'a' .. 'f' ]), which is what we want.
25 Lower = 0x2020U,
26 }
27
28 // We want to pack the incoming byte into a single integer [ 0000 HHHH 0000 LLLL ],
29 // where HHHH and LLLL are the high and low nibbles of the incoming byte. Then
30 // subtract this integer from a constant minuend as shown below.
31 //
32 // [ 1000 1001 1000 1001 ]
33 // - [ 0000 HHHH 0000 LLLL ]
34 // =========================
35 // [ *YYY **** *ZZZ **** ]
36 //
37 // The end result of this is that YYY is 0b000 if HHHH <= 9, and YYY is 0b111 if HHHH >= 10.
38 // Similarly, ZZZ is 0b000 if LLLL <= 9, and ZZZ is 0b111 if LLLL >= 10.
39 // (We don't care about the value of asterisked bits.)
40 //
41 // To turn a nibble in the range [ 0 .. 9 ] into hex, we calculate hex := nibble + 48 (ascii '0').
42 // To turn a nibble in the range [ 10 .. 15 ] into hex, we calculate hex := nibble - 10 + 65 (ascii 'A').
43 // => hex := nibble + 55.
44 // The difference in the starting ASCII offset is (55 - 48) = 7, depending on whether the nibble is <= 9 or >= 10.
45 // Since 7 is 0b111, this conveniently matches the YYY or ZZZ value computed during the earlier subtraction.
46
47 // The commented out code below is code that directly implements the logic described above.
48
49 // uint packedOriginalValues = (((uint)value & 0xF0U) << 4) + ((uint)value & 0x0FU);
50 // uint difference = 0x8989U - packedOriginalValues;
51 // uint add7Mask = (difference & 0x7070U) >> 4; // line YYY and ZZZ back up with the packed values
52 // uint packedResult = packedOriginalValues + add7Mask + 0x3030U /* ascii '0' */;
53
54 // The code below is equivalent to the commented out code above but has been tweaked
55 // to allow codegen to make some extra optimizations.
56
57 // The low byte of the packed result contains the hex representation of the incoming byte's low nibble.
58 // The adjacent byte of the packed result contains the hex representation of the incoming byte's high nibble.
59
60 // Finally, write to the output buffer starting with the *highest* index so that codegen can
61 // elide all but the first bounds check. (This only works if 'startingIndex' is a compile-time constant.)
62
63 // The JIT can elide bounds checks if 'startingIndex' is constant and if the caller is
64 // writing to a span of known length (or the caller has already checked the bounds of the
65 // furthest access).
66 [MethodImpl(MethodImplOptions.AggressiveInlining)]
67 public static void ToBytesBuffer(byte value, Span<byte> buffer, int startingIndex = 0, Casing casing = Casing.Upper)
68 {
69 uint difference = (((uint)value & 0xF0U) << 4) + ((uint)value & 0x0FU) - 0x8989U;
70 uint packedResult = ((((uint)(-(int)difference) & 0x7070U) >> 4) + difference + 0xB9B9U) | (uint)casing;
71
72 buffer[startingIndex + 1] = (byte)packedResult;
73 buffer[startingIndex] = (byte)(packedResult >> 8);
74 }
75
76#if ALLOW_PARTIALLY_TRUSTED_CALLERS
77 [System.Security.SecuritySafeCriticalAttribute]
78#endif
79 [MethodImpl(MethodImplOptions.AggressiveInlining)]
80 public static void ToCharsBuffer(byte value, Span<char> buffer, int startingIndex = 0, Casing casing = Casing.Upper)
81 {
82 uint difference = (((uint)value & 0xF0U) << 4) + ((uint)value & 0x0FU) - 0x8989U;
83 uint packedResult = ((((uint)(-(int)difference) & 0x7070U) >> 4) + difference + 0xB9B9U) | (uint)casing;
84
85 buffer[startingIndex + 1] = (char)(packedResult & 0xFF);
86 buffer[startingIndex] = (char)(packedResult >> 8);
87 }
88
89 public static void EncodeToUtf16(ReadOnlySpan<byte> bytes, Span<char> chars, Casing casing = Casing.Upper)
90 {
91 Debug.Assert(chars.Length >= bytes.Length * 2);
92
93 for (int pos = 0; pos < bytes.Length; ++pos)
94 {
95 ToCharsBuffer(bytes[pos], chars, pos * 2, casing);
96 }
97 }
98
99#if !UNITY_NETFRAMEWORK
100#if ALLOW_PARTIALLY_TRUSTED_CALLERS
101 [System.Security.SecuritySafeCriticalAttribute]
102#endif
103 public static unsafe string ToString(ReadOnlySpan<byte> bytes, Casing casing = Casing.Upper)
104 {
105#if NETFRAMEWORK || NETSTANDARD1_0 || NETSTANDARD1_3 || NETSTANDARD2_0
106 Span<char> result = stackalloc char[0];
107 if (bytes.Length > 16)
108 {
109 var array = new char[bytes.Length * 2];
110 result = array.AsSpan();
111 }
112 else
113 {
114 result = stackalloc char[bytes.Length * 2];
115 }
116
117 int pos = 0;
118 foreach (byte b in bytes)
119 {
120 ToCharsBuffer(b, result, pos, casing);
121 pos += 2;
122 }
123 return result.ToString();
124#else
125 fixed (byte* bytesPtr = bytes)
126 {
127 return string.Create(bytes.Length * 2, (Ptr: (IntPtr)bytesPtr, bytes.Length, casing), static (chars, args) =>
128 {
129 var ros = new ReadOnlySpan<byte>((byte*)args.Ptr, args.Length);
130 EncodeToUtf16(ros, chars, args.casing);
131 });
132 }
133#endif
134 }
135#endif
136
137 [MethodImpl(MethodImplOptions.AggressiveInlining)]
138 public static char ToCharUpper(int value)
139 {
140 value &= 0xF;
141 value += '0';
142
143 if (value > '9')
144 {
145 value += ('A' - ('9' + 1));
146 }
147
148 return (char)value;
149 }
150
151 [MethodImpl(MethodImplOptions.AggressiveInlining)]
152 public static char ToCharLower(int value)
153 {
154 value &= 0xF;
155 value += '0';
156
157 if (value > '9')
158 {
159 value += ('a' - ('9' + 1));
160 }
161
162 return (char)value;
163 }
164
165 public static bool TryDecodeFromUtf16(ReadOnlySpan<char> chars, Span<byte> bytes)
166 {
167 return TryDecodeFromUtf16(chars, bytes, out _);
168 }
169
170 public static bool TryDecodeFromUtf16(ReadOnlySpan<char> chars, Span<byte> bytes, out int charsProcessed)
171 {
172 Debug.Assert(chars.Length % 2 == 0, "Un-even number of characters provided");
173 Debug.Assert(chars.Length / 2 == bytes.Length, "Target buffer not right-sized for provided characters");
174
175 int i = 0;
176 int j = 0;
177 int byteLo = 0;
178 int byteHi = 0;
179 while (j < bytes.Length)
180 {
181 byteLo = FromChar(chars[i + 1]);
182 byteHi = FromChar(chars[i]);
183
184 // byteHi hasn't been shifted to the high half yet, so the only way the bitwise or produces this pattern
185 // is if either byteHi or byteLo was not a hex character.
186 if ((byteLo | byteHi) == 0xFF)
187 break;
188
189 bytes[j++] = (byte)((byteHi << 4) | byteLo);
190 i += 2;
191 }
192
193 if (byteLo == 0xFF)
194 i++;
195
196 charsProcessed = i;
197 return (byteLo | byteHi) != 0xFF;
198 }
199
200 [MethodImpl(MethodImplOptions.AggressiveInlining)]
201 public static int FromChar(int c)
202 {
203 return c >= CharToHexLookup.Length ? 0xFF : CharToHexLookup[c];
204 }
205
206 [MethodImpl(MethodImplOptions.AggressiveInlining)]
207 public static int FromUpperChar(int c)
208 {
209 return c > 71 ? 0xFF : CharToHexLookup[c];
210 }
211
212 [MethodImpl(MethodImplOptions.AggressiveInlining)]
213 public static int FromLowerChar(int c)
214 {
215 if ((uint)(c - '0') <= '9' - '0')
216 return c - '0';
217
218 if ((uint)(c - 'a') <= 'f' - 'a')
219 return c - 'a' + 10;
220
221 return 0xFF;
222 }
223
224 [MethodImpl(MethodImplOptions.AggressiveInlining)]
225 public static bool IsHexChar(int c)
226 {
227 return FromChar(c) != 0xFF;
228 }
229
230 [MethodImpl(MethodImplOptions.AggressiveInlining)]
231 public static bool IsHexUpperChar(int c)
232 {
233 return (uint)(c - '0') <= 9 || (uint)(c - 'A') <= ('F' - 'A');
234 }
235
236 [MethodImpl(MethodImplOptions.AggressiveInlining)]
237 public static bool IsHexLowerChar(int c)
238 {
239 return (uint)(c - '0') <= 9 || (uint)(c - 'a') <= ('f' - 'a');
240 }
241
242 /// <summary>Map from an ASCII char to its hex value, e.g. arr['b'] == 11. 0xFF means it's not a hex digit.</summary>
243 public static ReadOnlySpan<byte> CharToHexLookup => new byte[]
244 {
245 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 15
246 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 31
247 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 47
248 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 63
249 0xFF, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 79
250 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 95
251 0xFF, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 111
252 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 127
253 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 143
254 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 159
255 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 175
256 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 191
257 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 207
258 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 223
259 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 239
260 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF // 255
261 };
262 }
263}
Note: See TracBrowser for help on using the repository browser.