123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350 |
- using System;
- using System.Diagnostics;
- using System.Runtime.InteropServices;
- using Unity.Burst;
- using Unity.Burst.Intrinsics;
- using Unity.Collections.LowLevel.Unsafe;
- using Unity.Mathematics;
-
- namespace Unity.Collections
- {
- [GenerateTestsForBurstCompatibility]
- public static partial class xxHash3
- {
- /// <summary>
- /// Type used to compute hash based on multiple data feed
- /// </summary>
- /// <remarks>
- /// Allow to feed the internal hashing accumulators with data through multiple calls to <see cref="Update"/>, then retrieving the final hash value using <see cref="DigestHash64"/> or <see cref="DigestHash128"/>.
- /// More info about how to use this class in its constructor.
- /// </remarks>
- [GenerateTestsForBurstCompatibility]
- public struct StreamingState
- {
- #region Public API
-
- /// <summary>
- /// Create a StreamingState object, ready to be used with the streaming API
- /// </summary>
- /// <param name="isHash64">true if we are computing a 64bits hash value, false if we are computing a 128bits one</param>
- /// <param name="seed">A seed value to be used to compute the hash, default is 0</param>
- /// <remarks>
- /// Once the object is constructed, you can call the <see cref="Update"/> method as many times as you want to accumulate data to hash.
- /// When all the data has been sent, call <see cref="DigestHash64"/> or <see cref="DigestHash128"/> to retrieve the corresponding key, the <see cref="StreamingState"/>
- /// instance will then be reset, using the same hash key size and same Seed in order to be ready to be used again.
- /// </remarks>
- public StreamingState(bool isHash64, ulong seed=0)
- {
- State = default;
- Reset(isHash64, seed);
- }
-
- /// <summary>
- /// Reset the state of the streaming instance using the given seed value.
- /// </summary>
- /// <param name="isHash64"></param>
- /// <param name="seed">The seed value to alter the computed hash value from</param>
- /// <remarks> Call this method to start a new streaming session based on this instance</remarks>
- public unsafe void Reset(bool isHash64, ulong seed=0UL)
- {
- // Reset the whole buffer to 0
- var size = UnsafeUtility.SizeOf<StreamingStateData>();
- UnsafeUtility.MemClear(UnsafeUtility.AddressOf(ref State), size);
-
- // Set back the saved states
- State.IsHash64 = isHash64 ? 1 : 0;
-
- // Init the accumulator with the prime numbers
- var acc = Acc;
- acc[0] = PRIME32_3;
- acc[1] = PRIME64_1;
- acc[2] = PRIME64_2;
- acc[3] = PRIME64_3;
- acc[4] = PRIME64_4;
- acc[5] = PRIME32_2;
- acc[6] = PRIME64_5;
- acc[7] = PRIME32_1;
-
- State.Seed = seed;
-
- fixed (byte* secret = xxHashDefaultKey.kSecret)
- {
- if (seed != 0)
- {
- // Must encode the secret key if we're using a seed, we store it in the state object
- EncodeSecretKey(SecretKey, secret, seed);
- }
- else
- {
- // Otherwise just copy it
- UnsafeUtility.MemCpy(SecretKey, secret, SECRET_KEY_SIZE);
- }
- }
- }
-
- /// <summary>
- /// Add some data to be hashed
- /// </summary>
- /// <param name="input">The memory buffer, can't be null</param>
- /// <param name="length">The length of the data to accumulate, can be zero</param>
- /// <remarks>This API allows you to feed very small data to be hashed, avoiding you to accumulate them in a big buffer, then computing the hash value from.</remarks>
- public unsafe void Update(void* input, int length)
- {
- var bInput = (byte*) input;
- var bEnd = bInput + length;
- var isHash64 = State.IsHash64;
- var secret = SecretKey;
- State.TotalLength += length;
-
- if (State.BufferedSize + length <= INTERNAL_BUFFER_SIZE)
- {
- UnsafeUtility.MemCpy(Buffer + State.BufferedSize, bInput, length);
- State.BufferedSize += length;
- return;
- }
-
- if (State.BufferedSize != 0)
- {
- var loadSize = INTERNAL_BUFFER_SIZE - State.BufferedSize;
- UnsafeUtility.MemCpy(Buffer + State.BufferedSize, bInput, loadSize);
- bInput += loadSize;
-
- ConsumeStripes(Acc, ref State.NbStripesSoFar, Buffer, INTERNAL_BUFFER_STRIPES, secret, isHash64);
-
- State.BufferedSize = 0;
- }
-
- if (bInput + INTERNAL_BUFFER_SIZE < bEnd)
- {
- var limit = bEnd - INTERNAL_BUFFER_SIZE;
- do
- {
- ConsumeStripes(Acc, ref State.NbStripesSoFar, bInput, INTERNAL_BUFFER_STRIPES, secret, isHash64);
- bInput += INTERNAL_BUFFER_SIZE;
- } while (bInput < limit);
- UnsafeUtility.MemCpy(Buffer + INTERNAL_BUFFER_SIZE - STRIPE_LEN, bInput - STRIPE_LEN, STRIPE_LEN);
- }
-
- if (bInput < bEnd)
- {
- var newBufferedSize = bEnd - bInput;
- UnsafeUtility.MemCpy(Buffer, bInput, newBufferedSize);
- State.BufferedSize = (int) newBufferedSize;
- }
- }
-
- /// <summary>
- /// Add the contents of input struct to the hash.
- /// </summary>
- /// <typeparam name="T">The input type.</typeparam>
- /// <param name="input">The input struct that will be hashed</param>
- /// <remarks>This API allows you to feed very small data to be hashed, avoiding you to accumulate them in a big buffer, then computing the hash value from.</remarks>
- [GenerateTestsForBurstCompatibility(GenericTypeArguments = new [] { typeof(int) })]
- public unsafe void Update<T>(in T input) where T : unmanaged
- {
- Update(UnsafeUtilityExtensions.AddressOf(input), UnsafeUtility.SizeOf<T>());
- }
-
-
- /// <summary>
- /// Compute the 128bits value based on all the data that have been accumulated
- /// </summary>
- /// <returns>The hash value</returns>
- public unsafe uint4 DigestHash128()
- {
- CheckKeySize(0);
-
- unchecked
- {
- var secret = SecretKey;
- uint4 hash;
- if (State.TotalLength > MIDSIZE_MAX)
- {
- var acc = stackalloc ulong[ACC_NB];
- DigestLong(acc, secret, 0);
-
- var low64 = MergeAcc(acc, secret + SECRET_MERGEACCS_START,
- (ulong) State.TotalLength * PRIME64_1);
- var high64 = MergeAcc(acc, secret + SECRET_LIMIT - SECRET_MERGEACCS_START,
- ~((ulong) State.TotalLength * PRIME64_2));
- hash = ToUint4(low64, high64);
- }
- else
- {
- hash = Hash128(Buffer, State.TotalLength, State.Seed);
- }
- Reset(State.IsHash64==1, State.Seed);
- return hash;
- }
- }
-
- /// <summary>
- /// Compute the 64bits value based on all the data that have been accumulated
- /// </summary>
- /// <returns>The hash value</returns>
- public unsafe uint2 DigestHash64()
- {
- CheckKeySize(1);
-
- unchecked
- {
- var secret = SecretKey;
- uint2 hash;
- if (State.TotalLength > MIDSIZE_MAX)
- {
- var acc = stackalloc ulong[ACC_NB];
- DigestLong(acc, secret, 1);
-
- hash = ToUint2(MergeAcc(acc, secret + SECRET_MERGEACCS_START, (ulong) State.TotalLength * PRIME64_1));
- }
- else
- {
- hash = Hash64(Buffer, State.TotalLength, State.Seed);
- }
- Reset(State.IsHash64==1, State.Seed);
- return hash;
- }
- }
-
- #endregion
-
- #region Constants
-
- private static readonly int SECRET_LIMIT = SECRET_KEY_SIZE - STRIPE_LEN;
- private static readonly int NB_STRIPES_PER_BLOCK = SECRET_LIMIT / SECRET_CONSUME_RATE;
- private static readonly int INTERNAL_BUFFER_SIZE = 256;
- private static readonly int INTERNAL_BUFFER_STRIPES = INTERNAL_BUFFER_SIZE / STRIPE_LEN;
-
- #endregion
-
- #region Wrapper to internal data storage
-
- unsafe ulong* Acc
- {
- [DebuggerStepThrough]
- get => (ulong*) UnsafeUtility.AddressOf(ref State.Acc);
- }
-
- unsafe byte* Buffer
- {
- [DebuggerStepThrough]
- get => (byte*) UnsafeUtility.AddressOf(ref State.Buffer);
- }
-
- unsafe byte* SecretKey
- {
- [DebuggerStepThrough]
- get => (byte*) UnsafeUtility.AddressOf(ref State.SecretKey);
- }
-
- #endregion
-
- #region Data storage
-
- private StreamingStateData State;
-
- [StructLayout(LayoutKind.Explicit)]
- struct StreamingStateData
- {
- [FieldOffset(0)] public ulong Acc; // 64 bytes
- [FieldOffset(64)] public byte Buffer; // 256 bytes
- [FieldOffset(320)] public int IsHash64; // 4 bytes
- [FieldOffset(324)] public int BufferedSize; // 4 bytes
- [FieldOffset(328)] public int NbStripesSoFar; // 4 bytes + 4 padding
- [FieldOffset(336)] public long TotalLength; // 8 bytes
- [FieldOffset(344)] public ulong Seed; // 8 bytes
- [FieldOffset(352)] public byte SecretKey; // 192 bytes
- [FieldOffset(540)] public byte _PadEnd;
- }
-
- #endregion
-
- #region Internals
-
- private unsafe void DigestLong(ulong* acc, byte* secret, int isHash64)
- {
- UnsafeUtility.MemCpy(acc, Acc, STRIPE_LEN);
- if (State.BufferedSize >= STRIPE_LEN)
- {
- var totalNbStripes = (State.BufferedSize - 1) / STRIPE_LEN;
- ConsumeStripes(acc, ref State.NbStripesSoFar, Buffer, totalNbStripes, secret, isHash64);
-
- if (X86.Avx2.IsAvx2Supported)
- {
- Avx2Accumulate512(acc, Buffer + State.BufferedSize - STRIPE_LEN, null,
- secret + SECRET_LIMIT - SECRET_LASTACC_START);
- }
- else
- {
- DefaultAccumulate512(acc, Buffer + State.BufferedSize - STRIPE_LEN, null,
- secret + SECRET_LIMIT - SECRET_LASTACC_START, isHash64);
- }
- }
- else
- {
- var lastStripe = stackalloc byte[STRIPE_LEN];
- var catchupSize = STRIPE_LEN - State.BufferedSize;
- UnsafeUtility.MemCpy(lastStripe, Buffer + INTERNAL_BUFFER_SIZE - catchupSize, catchupSize);
- UnsafeUtility.MemCpy(lastStripe + catchupSize, Buffer, State.BufferedSize);
- if (X86.Avx2.IsAvx2Supported)
- {
- Avx2Accumulate512(acc, lastStripe, null, secret+SECRET_LIMIT-SECRET_LASTACC_START);
- }
- else
- {
- DefaultAccumulate512(acc, lastStripe, null, secret+SECRET_LIMIT-SECRET_LASTACC_START, isHash64);
- }
- }
- }
-
- private unsafe void ConsumeStripes(ulong* acc, ref int nbStripesSoFar, byte* input, long totalStripes,
- byte* secret, int isHash64)
- {
- if (NB_STRIPES_PER_BLOCK - nbStripesSoFar <= totalStripes)
- {
- var nbStripes = NB_STRIPES_PER_BLOCK - nbStripesSoFar;
- if (X86.Avx2.IsAvx2Supported)
- {
- Avx2Accumulate(acc, input, null, secret + nbStripesSoFar * SECRET_CONSUME_RATE, nbStripes, isHash64);
- Avx2ScrambleAcc(acc, secret + SECRET_LIMIT);
- Avx2Accumulate(acc, input + nbStripes * STRIPE_LEN, null, secret, totalStripes - nbStripes, isHash64);
- }
- else
- {
- DefaultAccumulate(acc, input, null, secret + nbStripesSoFar * SECRET_CONSUME_RATE, nbStripes, isHash64);
- DefaultScrambleAcc(acc, secret + SECRET_LIMIT);
- DefaultAccumulate(acc, input + nbStripes * STRIPE_LEN, null, secret, totalStripes - nbStripes, isHash64);
- }
-
- nbStripesSoFar = (int) totalStripes - nbStripes;
- }
- else
- {
- if (X86.Avx2.IsAvx2Supported)
- {
- Avx2Accumulate(acc, input, null, secret + nbStripesSoFar * SECRET_CONSUME_RATE, totalStripes, isHash64);
- }
- else
- {
- DefaultAccumulate(acc, input, null, secret + nbStripesSoFar * SECRET_CONSUME_RATE, totalStripes, isHash64);
- }
-
- nbStripesSoFar += (int) totalStripes;
- }
- }
-
- [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS"), Conditional("UNITY_DOTS_DEBUG")]
- void CheckKeySize(int isHash64)
- {
- if (State.IsHash64 != isHash64)
- {
- var s = State.IsHash64 != 0 ? "64" : "128";
- throw new InvalidOperationException(
- $"The streaming state was create for {s} bits hash key, the calling method doesn't support this key size, please use the appropriate API");
- }
- }
-
- #endregion
- }
- }
- }
|