public static void FormatHex(byte[] hex, StringBuilder output, HexCasing casing) { if (hex == null) { throw new ArgumentNullException("hex"); } char[] digits; if (casing == HexCasing.Upper) { digits = hexDigitsU; } else if (casing == HexCasing.Lower) { digits = hexDigitsL; } else { throw new ArgumentException("Invalid value for casing.", "casing"); } for (int i = 0; i < hex.Length; i++) { int hexVal = hex[i]; int highNib = hexVal >> 4; int lowNib = hexVal & 0xF; output.Append(digits[highNib]); output.Append(digits[lowNib]); } }
/// <summary> /// Returns a string representation of the <see cref="Sha1"/> or <see cref="Sha256"/> instance using the 'n' or 'N' format. /// </summary> /// <param name="sha"></param> /// <param name="casing">The ASCII casing to use.</param> /// <returns></returns> internal static string ToString(ReadOnlySpan <byte> sha, HexCasing casing) { int byteLength = sha.Length; // 20|32 Debug.Assert(byteLength == Sha1.ByteLength || byteLength == Sha256.ByteLength); // Text is treated as 5|8 groups of 8 chars (5|8 x 4 bytes) int hexLength = byteLength * 2; // 40|64 #if !NETSTANDARD2_0 Span <char> span = stackalloc char[hexLength]; #else var span = new char[hexLength]; #endif int pos = 0; for (int i = 0; i < byteLength; i++) // 20|32 { // Each byte is two hexits // Output *highest* index first so codegen can elide all but first bounds check byte byt = sha[i]; span[pos + 1] = (char)(HexChars[byt & 15] | (uint)casing); // == b % 16 span[pos] = (char)(HexChars[byt >> 4] | (uint)casing); // == b / 16 pos += 2; } string str = new string(span); return(str); }
public static string FormatHex(byte[] hex, HexCasing casing) { if (hex == null) { throw new ArgumentNullException("hex"); } StringBuilder result = new StringBuilder(hex.Length * 2); FormatHex(hex, result, casing); return(result.ToString()); }
/// <summary> /// Returns a string representation of the <see cref="Sha1"/> or <see cref="Sha256"/> instance. /// n: a9993e364706816aba3e25717850c26c9cd0d89d, cdc76e5c9914fb9281a1c7e284d73e67f1809a48a497200e046d39ccc7112cd0 /// d: a9993e36-4706816a-ba3e2571-7850c26c-9cd0d89d, cdc76e5c-9914fb92-81a1c7e2-84d73e67-f1809a48-a497200e-046d39cc-c7112cd0 /// s: a9993e36 4706816a ba3e2571 7850c26c 9cd0d89d, cdc76e5c 9914fb92 81a1c7e2 84d73e67 f1809a48 a497200e 046d39cc c7112cd0 /// </summary> /// <param name="sha"></param> /// <param name="separator"></param> /// <param name="casing">The ASCII casing to use.</param> /// <returns></returns> internal static string ToString(ReadOnlySpan <byte> sha, char separator, HexCasing casing) { Debug.Assert(separator == '-' || separator == ' '); int byteLength = sha.Length; // 20|32 Debug.Assert(byteLength == Sha1.ByteLength || byteLength == Sha256.ByteLength); // Text is treated as 5|8 groups of 8 chars (5|8 x 4 bytes) with 4|7 separators int hexLength = byteLength * 2; // 40|64 int n = hexLength / 8; // 5|8 #if !NETSTANDARD2_0 Span <char> span = stackalloc char[hexLength + n - 1]; // + 4|7 #else var span = new char[hexLength + n - 1]; // + 4|7 #endif int pos = 0; int sep = 8; for (int i = 0; i < byteLength; i++) // 20|32 { // Each byte is two hexits (convention is lowercase) // Output *highest* index first so codegen can elide all but first bounds check byte byt = sha[i]; span[pos + 1] = (char)(HexChars[byt & 15] | (uint)casing); // == b % 16 span[pos] = (char)(HexChars[byt >> 4] | (uint)casing); // == b / 16 pos += 2; // Append a separator if required if (pos == sep) // pos >= 2, sep = 0|N { span[pos++] = separator; sep = pos + 8; if (sep >= span.Length) { sep = 0; // Prevent IndexOutOfRangeException } } } string str = new string(span); return(str); }
/// <summary> /// Converts the <see cref="Sha1"/> or <see cref="Sha256"/> instance to a string using the 'n' or 'N' format, /// and returns the value split into two tokens. /// </summary> /// <param name="sha">The sha value.</param> /// <param name="prefixLength">The length of the first token.</param> /// <param name="casing">The ASCII casing to use.</param> /// <returns></returns> internal static KeyValuePair <string, string> Split(ReadOnlySpan <byte> sha, int prefixLength, HexCasing casing) { int byteLength = sha.Length; // 20|32 Debug.Assert(byteLength == Sha1.ByteLength || byteLength == Sha256.ByteLength); // Text is treated as 5|8 groups of 8 chars (5|8 x 4 bytes) int hexLength = byteLength * 2; #if !NETSTANDARD2_0 Span <char> span = stackalloc char[hexLength]; #else var span = new char[hexLength]; #endif int pos = 0; for (int i = 0; i < byteLength; i++) // 20|32 { // Each byte is two hexits // Output *highest* index first so codegen can elide all but first bounds check byte byt = sha[i]; span[pos + 1] = (char)(HexChars[byt & 15] | (uint)casing); // == b % 16 span[pos] = (char)(HexChars[byt >> 4] | (uint)casing); // == b / 16 pos += 2; } if (prefixLength >= hexLength) { string pfx = new string(span); return(new KeyValuePair <string, string>(pfx, string.Empty)); } if (prefixLength <= 0) { string ext = new string(span); return(new KeyValuePair <string, string>(string.Empty, ext)); } #if !NETSTANDARD2_0 Span <char> p = span.Slice(0, prefixLength); Span <char> e = span.Slice(prefixLength, hexLength - prefixLength); string prefix = new string(p); string extra = new string(e); #else string prefix = new string(span, 0, prefixLength); string extra = new string(span, prefixLength, hexLength - prefixLength); #endif var kvp = new KeyValuePair <string, string>(prefix, extra); return(kvp); }
public static void WriteHexByte(byte value, Span <byte> buffer, int startingIndex = 0, HexCasing casing = HexCasing.Uppercase) { // We want to pack the incoming byte into a single integer [ 0000 HHHH 0000 LLLL ], // where HHHH and LLLL are the high and low nibbles of the incoming byte. Then // subtract this integer from a constant minuend as shown below. // // [ 1000 1001 1000 1001 ] // - [ 0000 HHHH 0000 LLLL ] // ========================= // [ *YYY **** *ZZZ **** ] // // The end result of this is that YYY is 0b000 if HHHH <= 9, and YYY is 0b111 if HHHH >= 10. // Similarly, ZZZ is 0b000 if LLLL <= 9, and ZZZ is 0b111 if LLLL >= 10. // (We don't care about the value of asterisked bits.) // // To turn a nibble in the range [ 0 .. 9 ] into hex, we calculate hex := nibble + 48 (ascii '0'). // To turn a nibble in the range [ 10 .. 15 ] into hex, we calculate hex := nibble - 10 + 65 (ascii 'A'). // => hex := nibble + 55. // The difference in the starting ASCII offset is (55 - 48) = 7, depending on whether the nibble is <= 9 or >= 10. // Since 7 is 0b111, this conveniently matches the YYY or ZZZ value computed during the earlier subtraction. // The commented out code below is code that directly implements the logic described above. //uint packedOriginalValues = (((uint)value & 0xF0U) << 4) + ((uint)value & 0x0FU); //uint difference = 0x8989U - packedOriginalValues; //uint add7Mask = (difference & 0x7070U) >> 4; // line YYY and ZZZ back up with the packed values //uint packedResult = packedOriginalValues + add7Mask + 0x3030U /* ascii '0' */; // The code below is equivalent to the commented out code above but has been tweaked // to allow codegen to make some extra optimizations. uint difference = (((uint)value & 0xF0U) << 4) + ((uint)value & 0x0FU) - 0x8989U; uint packedResult = ((((uint)(-(int)difference) & 0x7070U) >> 4) + difference + 0xB9B9U) | (uint)casing; // The low byte of the packed result contains the hex representation of the incoming byte's low nibble. // The adjacent byte of the packed result contains the hex representation of the incoming byte's high nibble. // Finally, write to the output buffer starting with the *highest* index so that codegen can // elide all but the first bounds check. (This only works if 'startingIndex' is a compile-time constant.) buffer[startingIndex + 1] = (byte)(packedResult); buffer[startingIndex] = (byte)(packedResult >> 8); }