Skip to content

Instantly share code, notes, and snippets.

@khellang
Last active February 28, 2019 15:12
Show Gist options
  • Save khellang/f5bc75a79250e7df95b97ffa0c31d554 to your computer and use it in GitHub Desktop.
Save khellang/f5bc75a79250e7df95b97ffa0c31d554 to your computer and use it in GitHub Desktop.
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