Last active
February 28, 2019 15:12
-
-
Save khellang/f5bc75a79250e7df95b97ffa0c31d554 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Buffers; | |
using System.Buffers.Binary; | |
using System.Runtime.InteropServices; | |
using System.Security.Cryptography; | |
public class DeterministicGuidGenerator | |
{ | |
private const int StackallocThreshold = 128; | |
private const int BitsPerByte = 8; | |
public DeterministicGuidGenerator(Guid @namespace) | |
{ | |
Namespace = GetBytes(@namespace); | |
} | |
public DeterministicGuidGenerator(ReadOnlyMemory<byte> @namespace) | |
{ | |
Namespace = @namespace; | |
} | |
public ReadOnlyMemory<byte> Namespace { get; } | |
public Guid Create(ReadOnlySpan<byte> value) | |
{ | |
using (var sha1 = SHA1.Create()) | |
{ | |
var @namespace = Namespace.Span; | |
var length = @namespace.Length + value.Length; | |
if (length < StackallocThreshold) | |
{ | |
Span<byte> span = stackalloc byte[length]; | |
@namespace.CopyTo(span); | |
value.CopyTo(span.Slice(@namespace.Length)); | |
return Create(span, sha1); | |
} | |
var buffer = ArrayPool<byte>.Shared.Rent(length); | |
try | |
{ | |
Span<byte> span = buffer; | |
@namespace.CopyTo(span); | |
value.CopyTo(span.Slice(@namespace.Length)); | |
return Create(span, sha1); | |
} | |
finally | |
{ | |
ArrayPool<byte>.Shared.Return(buffer); | |
} | |
} | |
} | |
private static Guid Create(Span<byte> buffer, HashAlgorithm algorithm) | |
{ | |
Span<byte> result = stackalloc byte[algorithm.HashSize / BitsPerByte]; | |
if (!algorithm.TryComputeHash(buffer, result, out var bytesWritten)) | |
{ | |
throw new InvalidOperationException("Invalid implementation."); | |
} | |
result[6] = (byte)((result[6] & 0x0F) | (5 << 4)); | |
result[8] = (byte)((result[8] & 0x3F) | 0x80); | |
var decomposed = MemoryMarshal.Read<DecomposedGuid>(result.Slice(0, bytesWritten)); | |
if (BitConverter.IsLittleEndian) | |
{ | |
decomposed.Data1 = BinaryPrimitives.ReverseEndianness(decomposed.Data1); | |
decomposed.Data2 = BinaryPrimitives.ReverseEndianness(decomposed.Data2); | |
decomposed.Data3 = BinaryPrimitives.ReverseEndianness(decomposed.Data3); | |
} | |
return decomposed.Value; | |
} | |
private static ReadOnlyMemory<byte> GetBytes(Guid value) | |
{ | |
var decomposed = new DecomposedGuid(value); | |
Memory<byte> bytes = new byte[16]; | |
var span = bytes.Span; | |
BinaryPrimitives.WriteInt32BigEndian(span, decomposed.Data1); | |
BinaryPrimitives.WriteInt16BigEndian(span = span.Slice(sizeof(int)), decomposed.Data2); | |
BinaryPrimitives.WriteInt16BigEndian(span = span.Slice(sizeof(short)), decomposed.Data3); | |
MemoryMarshal.Write(span.Slice(sizeof(short)), ref decomposed.Data4); | |
return bytes; | |
} | |
[StructLayout(LayoutKind.Explicit)] | |
private struct DecomposedGuid | |
{ | |
[FieldOffset(0)] public readonly Guid Value; | |
[FieldOffset(0)] public int Data1; | |
[FieldOffset(4)] public short Data2; | |
[FieldOffset(6)] public short Data3; | |
[FieldOffset(8)] public long Data4; | |
public DecomposedGuid(Guid value) : this() => Value = value; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment