A game about forced loneliness, made by TACStudios
1using System;
2using System.Diagnostics;
3using System.Runtime.InteropServices;
4using Unity.Burst;
5using Unity.Burst.Intrinsics;
6using Unity.Collections.LowLevel.Unsafe;
7using Unity.Mathematics;
8
9namespace Unity.Collections
10{
11 [GenerateTestsForBurstCompatibility]
12 public static partial class xxHash3
13 {
14 /// <summary>
15 /// Type used to compute hash based on multiple data feed
16 /// </summary>
17 /// <remarks>
18 /// 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"/>.
19 /// More info about how to use this class in its constructor.
20 /// </remarks>
21 [GenerateTestsForBurstCompatibility]
22 public struct StreamingState
23 {
24 #region Public API
25
26 /// <summary>
27 /// Create a StreamingState object, ready to be used with the streaming API
28 /// </summary>
29 /// <param name="isHash64">true if we are computing a 64bits hash value, false if we are computing a 128bits one</param>
30 /// <param name="seed">A seed value to be used to compute the hash, default is 0</param>
31 /// <remarks>
32 /// Once the object is constructed, you can call the <see cref="Update"/> method as many times as you want to accumulate data to hash.
33 /// When all the data has been sent, call <see cref="DigestHash64"/> or <see cref="DigestHash128"/> to retrieve the corresponding key, the <see cref="StreamingState"/>
34 /// instance will then be reset, using the same hash key size and same Seed in order to be ready to be used again.
35 /// </remarks>
36 public StreamingState(bool isHash64, ulong seed=0)
37 {
38 State = default;
39 Reset(isHash64, seed);
40 }
41
42 /// <summary>
43 /// Reset the state of the streaming instance using the given seed value.
44 /// </summary>
45 /// <param name="isHash64"></param>
46 /// <param name="seed">The seed value to alter the computed hash value from</param>
47 /// <remarks> Call this method to start a new streaming session based on this instance</remarks>
48 public unsafe void Reset(bool isHash64, ulong seed=0UL)
49 {
50 // Reset the whole buffer to 0
51 var size = UnsafeUtility.SizeOf<StreamingStateData>();
52 UnsafeUtility.MemClear(UnsafeUtility.AddressOf(ref State), size);
53
54 // Set back the saved states
55 State.IsHash64 = isHash64 ? 1 : 0;
56
57 // Init the accumulator with the prime numbers
58 var acc = Acc;
59 acc[0] = PRIME32_3;
60 acc[1] = PRIME64_1;
61 acc[2] = PRIME64_2;
62 acc[3] = PRIME64_3;
63 acc[4] = PRIME64_4;
64 acc[5] = PRIME32_2;
65 acc[6] = PRIME64_5;
66 acc[7] = PRIME32_1;
67
68 State.Seed = seed;
69
70 fixed (byte* secret = xxHashDefaultKey.kSecret)
71 {
72 if (seed != 0)
73 {
74 // Must encode the secret key if we're using a seed, we store it in the state object
75 EncodeSecretKey(SecretKey, secret, seed);
76 }
77 else
78 {
79 // Otherwise just copy it
80 UnsafeUtility.MemCpy(SecretKey, secret, SECRET_KEY_SIZE);
81 }
82 }
83 }
84
85 /// <summary>
86 /// Add some data to be hashed
87 /// </summary>
88 /// <param name="input">The memory buffer, can't be null</param>
89 /// <param name="length">The length of the data to accumulate, can be zero</param>
90 /// <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>
91 public unsafe void Update(void* input, int length)
92 {
93 var bInput = (byte*) input;
94 var bEnd = bInput + length;
95 var isHash64 = State.IsHash64;
96 var secret = SecretKey;
97 State.TotalLength += length;
98
99 if (State.BufferedSize + length <= INTERNAL_BUFFER_SIZE)
100 {
101 UnsafeUtility.MemCpy(Buffer + State.BufferedSize, bInput, length);
102 State.BufferedSize += length;
103 return;
104 }
105
106 if (State.BufferedSize != 0)
107 {
108 var loadSize = INTERNAL_BUFFER_SIZE - State.BufferedSize;
109 UnsafeUtility.MemCpy(Buffer + State.BufferedSize, bInput, loadSize);
110 bInput += loadSize;
111
112 ConsumeStripes(Acc, ref State.NbStripesSoFar, Buffer, INTERNAL_BUFFER_STRIPES, secret, isHash64);
113
114 State.BufferedSize = 0;
115 }
116
117 if (bInput + INTERNAL_BUFFER_SIZE < bEnd)
118 {
119 var limit = bEnd - INTERNAL_BUFFER_SIZE;
120 do
121 {
122 ConsumeStripes(Acc, ref State.NbStripesSoFar, bInput, INTERNAL_BUFFER_STRIPES, secret, isHash64);
123 bInput += INTERNAL_BUFFER_SIZE;
124 } while (bInput < limit);
125 UnsafeUtility.MemCpy(Buffer + INTERNAL_BUFFER_SIZE - STRIPE_LEN, bInput - STRIPE_LEN, STRIPE_LEN);
126 }
127
128 if (bInput < bEnd)
129 {
130 var newBufferedSize = bEnd - bInput;
131 UnsafeUtility.MemCpy(Buffer, bInput, newBufferedSize);
132 State.BufferedSize = (int) newBufferedSize;
133 }
134 }
135
136 /// <summary>
137 /// Add the contents of input struct to the hash.
138 /// </summary>
139 /// <typeparam name="T">The input type.</typeparam>
140 /// <param name="input">The input struct that will be hashed</param>
141 /// <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>
142 [GenerateTestsForBurstCompatibility(GenericTypeArguments = new [] { typeof(int) })]
143 public unsafe void Update<T>(in T input) where T : unmanaged
144 {
145 Update(UnsafeUtilityExtensions.AddressOf(input), UnsafeUtility.SizeOf<T>());
146 }
147
148
149 /// <summary>
150 /// Compute the 128bits value based on all the data that have been accumulated
151 /// </summary>
152 /// <returns>The hash value</returns>
153 public unsafe uint4 DigestHash128()
154 {
155 CheckKeySize(0);
156
157 unchecked
158 {
159 var secret = SecretKey;
160 uint4 hash;
161 if (State.TotalLength > MIDSIZE_MAX)
162 {
163 var acc = stackalloc ulong[ACC_NB];
164 DigestLong(acc, secret, 0);
165
166 var low64 = MergeAcc(acc, secret + SECRET_MERGEACCS_START,
167 (ulong) State.TotalLength * PRIME64_1);
168 var high64 = MergeAcc(acc, secret + SECRET_LIMIT - SECRET_MERGEACCS_START,
169 ~((ulong) State.TotalLength * PRIME64_2));
170 hash = ToUint4(low64, high64);
171 }
172 else
173 {
174 hash = Hash128(Buffer, State.TotalLength, State.Seed);
175 }
176 Reset(State.IsHash64==1, State.Seed);
177 return hash;
178 }
179 }
180
181 /// <summary>
182 /// Compute the 64bits value based on all the data that have been accumulated
183 /// </summary>
184 /// <returns>The hash value</returns>
185 public unsafe uint2 DigestHash64()
186 {
187 CheckKeySize(1);
188
189 unchecked
190 {
191 var secret = SecretKey;
192 uint2 hash;
193 if (State.TotalLength > MIDSIZE_MAX)
194 {
195 var acc = stackalloc ulong[ACC_NB];
196 DigestLong(acc, secret, 1);
197
198 hash = ToUint2(MergeAcc(acc, secret + SECRET_MERGEACCS_START, (ulong) State.TotalLength * PRIME64_1));
199 }
200 else
201 {
202 hash = Hash64(Buffer, State.TotalLength, State.Seed);
203 }
204 Reset(State.IsHash64==1, State.Seed);
205 return hash;
206 }
207 }
208
209 #endregion
210
211 #region Constants
212
213 private static readonly int SECRET_LIMIT = SECRET_KEY_SIZE - STRIPE_LEN;
214 private static readonly int NB_STRIPES_PER_BLOCK = SECRET_LIMIT / SECRET_CONSUME_RATE;
215 private static readonly int INTERNAL_BUFFER_SIZE = 256;
216 private static readonly int INTERNAL_BUFFER_STRIPES = INTERNAL_BUFFER_SIZE / STRIPE_LEN;
217
218 #endregion
219
220 #region Wrapper to internal data storage
221
222 unsafe ulong* Acc
223 {
224 [DebuggerStepThrough]
225 get => (ulong*) UnsafeUtility.AddressOf(ref State.Acc);
226 }
227
228 unsafe byte* Buffer
229 {
230 [DebuggerStepThrough]
231 get => (byte*) UnsafeUtility.AddressOf(ref State.Buffer);
232 }
233
234 unsafe byte* SecretKey
235 {
236 [DebuggerStepThrough]
237 get => (byte*) UnsafeUtility.AddressOf(ref State.SecretKey);
238 }
239
240 #endregion
241
242 #region Data storage
243
244 private StreamingStateData State;
245
246 [StructLayout(LayoutKind.Explicit)]
247 struct StreamingStateData
248 {
249 [FieldOffset(0)] public ulong Acc; // 64 bytes
250 [FieldOffset(64)] public byte Buffer; // 256 bytes
251 [FieldOffset(320)] public int IsHash64; // 4 bytes
252 [FieldOffset(324)] public int BufferedSize; // 4 bytes
253 [FieldOffset(328)] public int NbStripesSoFar; // 4 bytes + 4 padding
254 [FieldOffset(336)] public long TotalLength; // 8 bytes
255 [FieldOffset(344)] public ulong Seed; // 8 bytes
256 [FieldOffset(352)] public byte SecretKey; // 192 bytes
257 [FieldOffset(540)] public byte _PadEnd;
258 }
259
260 #endregion
261
262 #region Internals
263
264 private unsafe void DigestLong(ulong* acc, byte* secret, int isHash64)
265 {
266 UnsafeUtility.MemCpy(acc, Acc, STRIPE_LEN);
267 if (State.BufferedSize >= STRIPE_LEN)
268 {
269 var totalNbStripes = (State.BufferedSize - 1) / STRIPE_LEN;
270 ConsumeStripes(acc, ref State.NbStripesSoFar, Buffer, totalNbStripes, secret, isHash64);
271
272 if (X86.Avx2.IsAvx2Supported)
273 {
274 Avx2Accumulate512(acc, Buffer + State.BufferedSize - STRIPE_LEN, null,
275 secret + SECRET_LIMIT - SECRET_LASTACC_START);
276 }
277 else
278 {
279 DefaultAccumulate512(acc, Buffer + State.BufferedSize - STRIPE_LEN, null,
280 secret + SECRET_LIMIT - SECRET_LASTACC_START, isHash64);
281 }
282 }
283 else
284 {
285 var lastStripe = stackalloc byte[STRIPE_LEN];
286 var catchupSize = STRIPE_LEN - State.BufferedSize;
287 UnsafeUtility.MemCpy(lastStripe, Buffer + INTERNAL_BUFFER_SIZE - catchupSize, catchupSize);
288 UnsafeUtility.MemCpy(lastStripe + catchupSize, Buffer, State.BufferedSize);
289 if (X86.Avx2.IsAvx2Supported)
290 {
291 Avx2Accumulate512(acc, lastStripe, null, secret+SECRET_LIMIT-SECRET_LASTACC_START);
292 }
293 else
294 {
295 DefaultAccumulate512(acc, lastStripe, null, secret+SECRET_LIMIT-SECRET_LASTACC_START, isHash64);
296 }
297 }
298 }
299
300 private unsafe void ConsumeStripes(ulong* acc, ref int nbStripesSoFar, byte* input, long totalStripes,
301 byte* secret, int isHash64)
302 {
303 if (NB_STRIPES_PER_BLOCK - nbStripesSoFar <= totalStripes)
304 {
305 var nbStripes = NB_STRIPES_PER_BLOCK - nbStripesSoFar;
306 if (X86.Avx2.IsAvx2Supported)
307 {
308 Avx2Accumulate(acc, input, null, secret + nbStripesSoFar * SECRET_CONSUME_RATE, nbStripes, isHash64);
309 Avx2ScrambleAcc(acc, secret + SECRET_LIMIT);
310 Avx2Accumulate(acc, input + nbStripes * STRIPE_LEN, null, secret, totalStripes - nbStripes, isHash64);
311 }
312 else
313 {
314 DefaultAccumulate(acc, input, null, secret + nbStripesSoFar * SECRET_CONSUME_RATE, nbStripes, isHash64);
315 DefaultScrambleAcc(acc, secret + SECRET_LIMIT);
316 DefaultAccumulate(acc, input + nbStripes * STRIPE_LEN, null, secret, totalStripes - nbStripes, isHash64);
317 }
318
319 nbStripesSoFar = (int) totalStripes - nbStripes;
320 }
321 else
322 {
323 if (X86.Avx2.IsAvx2Supported)
324 {
325 Avx2Accumulate(acc, input, null, secret + nbStripesSoFar * SECRET_CONSUME_RATE, totalStripes, isHash64);
326 }
327 else
328 {
329 DefaultAccumulate(acc, input, null, secret + nbStripesSoFar * SECRET_CONSUME_RATE, totalStripes, isHash64);
330 }
331
332 nbStripesSoFar += (int) totalStripes;
333 }
334 }
335
336 [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS"), Conditional("UNITY_DOTS_DEBUG")]
337 void CheckKeySize(int isHash64)
338 {
339 if (State.IsHash64 != isHash64)
340 {
341 var s = State.IsHash64 != 0 ? "64" : "128";
342 throw new InvalidOperationException(
343 $"The streaming state was create for {s} bits hash key, the calling method doesn't support this key size, please use the appropriate API");
344 }
345 }
346
347 #endregion
348 }
349 }
350}