Skip to main content

xxhash/
xxh3.rs

1use crate::Checksum;
2
3// ---------------------------------------------------------------------------
4// Constants
5// ---------------------------------------------------------------------------
6
7pub(crate) const PRIME32_1: u64 = 0x9E3779B1;
8pub(crate) const PRIME32_3: u64 = 0xC2B2AE3D;
9
10const PRIME64_1: u64 = 0x9E3779B185EBCA87;
11const PRIME64_2: u64 = 0xC2B2AE3D27D4EB4F;
12const PRIME64_5: u64 = 0x27D4EB2F165667C5;
13
14pub(crate) const STRIPE_LEN: usize = 64;
15pub(crate) const SECRET_CONSUME_RATE: usize = 8;
16pub(crate) const ACC_NB: usize = 8;
17const SECRET_MERGEACCS_START: usize = 11;
18const SECRET_LASTACC_START: usize = 7;
19const MID_SIZE_MAX: usize = 240;
20const SECRET_SIZE_MIN: usize = 136;
21const DEFAULT_SECRET_SIZE: usize = 192;
22const STRIPES_PER_BLOCK: usize = (DEFAULT_SECRET_SIZE - STRIPE_LEN) / SECRET_CONSUME_RATE;
23/// Size of the streaming internal buffer (an exact multiple of [`STRIPE_LEN`]).
24const INTERNAL_BUFFER_SIZE: usize = 256;
25/// Number of stripes the internal buffer is flushed in.
26const INTERNAL_BUFFER_STRIPES: usize = INTERNAL_BUFFER_SIZE / STRIPE_LEN;
27/// Secret offset used for the final (overlapping) stripe of a long input.
28const LAST_STRIPE_SECRET_OFFSET: usize = DEFAULT_SECRET_SIZE - STRIPE_LEN - SECRET_LASTACC_START;
29
30/// The default 192-byte secret used by XXH3.
31const DEFAULT_SECRET: [u8; 192] = [
32    0xb8, 0xfe, 0x6c, 0x39, 0x23, 0xa4, 0x4b, 0xbe, 0x7c, 0x01, 0x81, 0x2c, 0xf7, 0x21, 0xad, 0x1c, 0xde, 0xd4, 0x6d,
33    0xe9, 0x83, 0x90, 0x97, 0xdb, 0x72, 0x40, 0xa4, 0xa4, 0xb7, 0xb3, 0x67, 0x1f, 0xcb, 0x79, 0xe6, 0x4e, 0xcc, 0xc0,
34    0xe5, 0x78, 0x82, 0x5a, 0xd0, 0x7d, 0xcc, 0xff, 0x72, 0x21, 0xb8, 0x08, 0x46, 0x74, 0xf7, 0x43, 0x24, 0x8e, 0xe0,
35    0x35, 0x90, 0xe6, 0x81, 0x3a, 0x26, 0x4c, 0x3c, 0x28, 0x52, 0xbb, 0x91, 0xc3, 0x00, 0xcb, 0x88, 0xd0, 0x65, 0x8b,
36    0x1b, 0x53, 0x2e, 0xa3, 0x71, 0x64, 0x48, 0x97, 0xa2, 0x0d, 0xf9, 0x4e, 0x38, 0x19, 0xef, 0x46, 0xa9, 0xde, 0xac,
37    0xd8, 0xa8, 0xfa, 0x76, 0x3f, 0xe3, 0x9c, 0x34, 0x3f, 0xf9, 0xdc, 0xbb, 0xc7, 0xc7, 0x0b, 0x4f, 0x1d, 0x8a, 0x51,
38    0xe0, 0x4b, 0xcd, 0xb4, 0x59, 0x31, 0xc8, 0x9f, 0x7e, 0xc9, 0xd9, 0x78, 0x73, 0x64, 0xea, 0xc5, 0xac, 0x83, 0x34,
39    0xd3, 0xeb, 0xc3, 0xc5, 0x81, 0xa0, 0xff, 0xfa, 0x13, 0x63, 0xeb, 0x17, 0x0d, 0xdd, 0x51, 0xb7, 0xf0, 0xda, 0x49,
40    0xd3, 0x16, 0x55, 0x26, 0x29, 0xd4, 0x68, 0x9e, 0x2b, 0x16, 0xbe, 0x58, 0x7d, 0x47, 0xa1, 0xfc, 0x8f, 0xf8, 0xb8,
41    0xd1, 0x7a, 0xd0, 0x31, 0xce, 0x45, 0xcb, 0x3a, 0x8f, 0x95, 0x16, 0x04, 0x28, 0xaf, 0xd7, 0xfb, 0xca, 0xbb, 0x4b,
42    0x40, 0x7e,
43];
44
45const INITIAL_ACC: [u64; ACC_NB] = [
46    PRIME32_3,
47    PRIME64_1,
48    PRIME64_2,
49    0x165667B19E3779F9, // PRIME64_3
50    0x85EBCA77C2B2AE63, // PRIME64_4
51    0x85EBCA77,         // PRIME32_2
52    PRIME64_5,
53    PRIME32_1,
54];
55
56// ---------------------------------------------------------------------------
57// Low-level helpers
58// ---------------------------------------------------------------------------
59#[inline]
60const fn read_64le(data: &[u8], offset: usize) -> u64 {
61    let (_, rest) = data.split_at(offset);
62    let arr = match rest.split_first_chunk::<8>() {
63        Some((arr, _)) => arr,
64        None => panic!("read_64le: out of bounds"),
65    };
66    u64::from_le_bytes(*arr)
67}
68
69#[inline]
70const fn read_32le(data: &[u8], offset: usize) -> u32 {
71    let (_, rest) = data.split_at(offset);
72    let arr = match rest.split_first_chunk::<4>() {
73        Some((arr, _)) => arr,
74        None => panic!("read_32le: out of bounds"),
75    };
76    u32::from_le_bytes(*arr)
77}
78#[inline]
79const fn xorshift64(value: u64, shift: u64) -> u64 {
80    value ^ (value >> shift)
81}
82
83#[inline]
84const fn avalanche(mut h: u64) -> u64 {
85    h = xorshift64(h, 37);
86    h = h.wrapping_mul(0x165667919E3779F9);
87    xorshift64(h, 32)
88}
89
90/// XXH64 avalanche, used in the 1to3 and empty 0to16 paths.
91const fn xxh64_avalanche(mut h: u64) -> u64 {
92    h ^= h >> 33;
93    h = h.wrapping_mul(PRIME64_2);
94    h ^= h >> 29;
95    h = h.wrapping_mul(0x165667B19E3779F9);
96    h ^= h >> 32;
97    h
98}
99
100#[inline]
101const fn strong_avalanche(mut value: u64, len: u64) -> u64 {
102    value ^= value.rotate_left(49) ^ value.rotate_left(24);
103    value = value.wrapping_mul(0x9FB21C651E98DF25);
104    value ^= (value >> 35).wrapping_add(len);
105    value = value.wrapping_mul(0x9FB21C651E98DF25);
106    xorshift64(value, 28)
107}
108
109#[inline]
110const fn mul128_fold64(l: u64, r: u64) -> u64 {
111    let p = (l as u128).wrapping_mul(r as u128);
112    (p as u64) ^ ((p >> 64) as u64)
113}
114
115#[inline]
116const fn mult32_to64(left: u32, right: u32) -> u64 {
117    (left as u64).wrapping_mul(right as u64)
118}
119
120#[inline]
121const fn mix16b(input: &[u8], input_offset: usize, secret: &[u8], secret_offset: usize, seed: u64) -> u64 {
122    let mut input_lo = read_64le(input, input_offset);
123    let mut input_hi = read_64le(input, input_offset + 8);
124
125    input_lo ^= read_64le(secret, secret_offset).wrapping_add(seed);
126    input_hi ^= read_64le(secret, secret_offset + 8).wrapping_sub(seed);
127
128    mul128_fold64(input_lo, input_hi)
129}
130
131#[inline]
132const fn mix_two_accs(acc: &[u64; ACC_NB], offset: usize, secret: &[u8], sec_off: usize) -> u64 {
133    mul128_fold64(
134        acc[offset] ^ read_64le(secret, sec_off),
135        acc[offset + 1] ^ read_64le(secret, sec_off + 8),
136    )
137}
138
139#[inline]
140const fn merge_accs(acc: &[u64; ACC_NB], secret: &[u8], start_offset: usize, mut result: u64) -> u64 {
141    let mut i = 0;
142    while i < 4 {
143        result = result.wrapping_add(mix_two_accs(acc, i * 2, secret, start_offset + i * 16));
144        i += 1;
145    }
146    avalanche(result)
147}
148
149// ---------------------------------------------------------------------------
150// Core XXH3 accumulate / scramble
151// ---------------------------------------------------------------------------
152
153#[inline]
154const fn accumulate_512_scalar(
155    acc: &mut [u64; ACC_NB],
156    input: &[u8],
157    input_off: usize,
158    secret: &[u8],
159    secret_off: usize,
160) {
161    let mut i = 0;
162    while i < ACC_NB {
163        let data_val = read_64le(input, input_off + i * 8);
164        let data_key = data_val ^ read_64le(secret, secret_off + i * 8);
165        acc[i ^ 1] = acc[i ^ 1].wrapping_add(data_val);
166        acc[i] = acc[i].wrapping_add(mult32_to64((data_key & 0xFFFFFFFF) as u32, (data_key >> 32) as u32));
167        i += 1;
168    }
169}
170
171#[inline]
172const fn accumulate_loop_scalar(
173    acc: &mut [u64; ACC_NB],
174    input: &[u8],
175    input_off: usize,
176    secret: &[u8],
177    secret_off: usize,
178    nb_stripes: usize,
179) {
180    let mut i = 0;
181    while i < nb_stripes {
182        accumulate_512_scalar(
183            acc,
184            input,
185            input_off + i * STRIPE_LEN,
186            secret,
187            secret_off + i * SECRET_CONSUME_RATE,
188        );
189        i += 1;
190    }
191}
192
193#[inline]
194const fn scramble_acc_scalar(acc: &mut [u64; ACC_NB], secret: &[u8], secret_off: usize) {
195    let mut i = 0;
196    while i < ACC_NB {
197        let key = read_64le(secret, secret_off + i * 8);
198        let mut val = xorshift64(acc[i], 47);
199        val ^= key;
200        acc[i] = val.wrapping_mul(PRIME32_1);
201        i += 1;
202    }
203}
204
205#[inline]
206const fn hash_long_internal_loop(acc: &mut [u64; ACC_NB], input: &[u8], secret: &[u8]) {
207    let nb_stripes = STRIPES_PER_BLOCK;
208    let block_len = STRIPE_LEN * nb_stripes;
209    let nb_blocks = (input.len() - 1) / block_len;
210
211    let mut i = 0;
212    while i < nb_blocks {
213        accumulate_loop_scalar(acc, input, i * block_len, secret, 0, nb_stripes);
214        scramble_acc_scalar(acc, secret, secret.len() - STRIPE_LEN);
215        i += 1;
216    }
217
218    // Last partial block
219    let nb_stripes = ((input.len() - 1) - (block_len * nb_blocks)) / STRIPE_LEN;
220    accumulate_loop_scalar(acc, input, nb_blocks * block_len, secret, 0, nb_stripes);
221
222    // Last stripe
223    let last_stripe_start = input.len() - STRIPE_LEN;
224    let last_secret_off = secret.len() - STRIPE_LEN - SECRET_LASTACC_START;
225    accumulate_512_scalar(acc, input, last_stripe_start, secret, last_secret_off);
226}
227
228/// Same as [`hash_long_internal_loop`], but selects the SIMD kernels at runtime
229/// (or at compile time in `no_std` builds).
230#[inline]
231fn hash_long_internal_loop_dispatch(acc: &mut [u64; ACC_NB], input: &[u8], secret: &[u8]) {
232    let nb_stripes = STRIPES_PER_BLOCK;
233    let block_len = STRIPE_LEN * nb_stripes;
234    let nb_blocks = (input.len() - 1) / block_len;
235
236    let mut i = 0;
237    while i < nb_blocks {
238        accumulate_loop(acc, input, i * block_len, secret, 0, nb_stripes);
239        scramble_acc(acc, secret, secret.len() - STRIPE_LEN);
240        i += 1;
241    }
242
243    // Last partial block
244    let nb_stripes = ((input.len() - 1) - (block_len * nb_blocks)) / STRIPE_LEN;
245    accumulate_loop(acc, input, nb_blocks * block_len, secret, 0, nb_stripes);
246
247    // Last stripe
248    let last_stripe_start = input.len() - STRIPE_LEN;
249    accumulate_512(acc, input, last_stripe_start, secret, LAST_STRIPE_SECRET_OFFSET);
250}
251
252/// Feeds `nb_stripes` stripes ending at a block boundary, scrambling in between.
253/// Mirrors the reference `XXH3_consumeStripes()`.
254#[inline]
255fn consume_stripes(
256    acc: &mut [u64; ACC_NB],
257    nb_stripes_so_far: &mut usize,
258    input: &[u8],
259    mut nb_stripes: usize,
260    secret: &[u8],
261) {
262    let mut pos = 0;
263    let mut secret_off = *nb_stripes_so_far * SECRET_CONSUME_RATE;
264
265    // Consume stripes up to the end of a block, then scramble and repeat.
266    if nb_stripes >= STRIPES_PER_BLOCK - *nb_stripes_so_far {
267        let mut nb_this = STRIPES_PER_BLOCK - *nb_stripes_so_far;
268        loop {
269            accumulate_loop(acc, input, pos, secret, secret_off, nb_this);
270            scramble_acc(acc, secret, secret.len() - STRIPE_LEN);
271            pos += nb_this * STRIPE_LEN;
272            nb_stripes -= nb_this;
273            nb_this = STRIPES_PER_BLOCK;
274            secret_off = 0;
275            if nb_stripes < STRIPES_PER_BLOCK {
276                break;
277            }
278        }
279        *nb_stripes_so_far = 0;
280    }
281
282    // Consume the remaining partial block.
283    if nb_stripes > 0 {
284        accumulate_loop(acc, input, pos, secret, secret_off, nb_stripes);
285        *nb_stripes_so_far += nb_stripes;
286    }
287}
288
289// ---------------------------------------------------------------------------
290// Custom default secret generation for seeded hashing
291// ---------------------------------------------------------------------------
292
293const fn custom_default_secret_scalar(seed: u64) -> [u8; DEFAULT_SECRET_SIZE] {
294    let mut result = [0u8; DEFAULT_SECRET_SIZE];
295    let nb_rounds = DEFAULT_SECRET_SIZE / 16;
296    let mut i = 0;
297    while i < nb_rounds {
298        let lo = read_64le(&DEFAULT_SECRET, i * 16).wrapping_add(seed);
299        let hi = read_64le(&DEFAULT_SECRET, i * 16 + 8).wrapping_sub(seed);
300        let lo_bytes = lo.to_le_bytes();
301        let hi_bytes = hi.to_le_bytes();
302        let mut j = 0;
303        while j < 8 {
304            result[i * 16 + j] = lo_bytes[j];
305            result[i * 16 + 8 + j] = hi_bytes[j];
306            j += 1;
307        }
308        i += 1;
309    }
310    result
311}
312
313// ---------------------------------------------------------------------------
314// Dispatch wrappers — select SIMD or scalar at compile / run time.
315// On aarch64 and wasm32 the selection is always compile-time (NEON is
316// mandatory on aarch64, and wasm has no runtime detection).
317// On x86 the selection is compile-time in `no_std` builds, and runtime
318// (cached) when the `std` feature is enabled.
319// ---------------------------------------------------------------------------
320
321#[inline]
322#[allow(unreachable_code, unused_variables)]
323fn accumulate_512(acc: &mut [u64; ACC_NB], input: &[u8], input_off: usize, secret: &[u8], secret_off: usize) {
324    #[cfg(all(target_arch = "wasm32", target_feature = "simd128"))]
325    {
326        crate::xxh3_wasm_simd128::accumulate_512(acc, input, input_off, secret, secret_off);
327        return;
328    }
329
330    #[cfg(all(target_arch = "aarch64", target_feature = "neon"))]
331    {
332        unsafe { crate::xxh3_neon::accumulate_512(acc, input, input_off, secret, secret_off) };
333        return;
334    }
335
336    #[cfg(all(target_arch = "x86_64", feature = "std"))]
337    {
338        match crate::x86_dispatch::level() {
339            crate::x86_dispatch::Level::Avx512 => {
340                unsafe { crate::xxh3_avx512::accumulate_512(acc, input, input_off, secret, secret_off) };
341                return;
342            }
343            crate::x86_dispatch::Level::Avx2 => {
344                unsafe { crate::xxh3_avx2::accumulate_512(acc, input, input_off, secret, secret_off) };
345                return;
346            }
347            crate::x86_dispatch::Level::Scalar => {}
348        }
349    }
350
351    #[cfg(all(target_arch = "x86_64", not(feature = "std"), target_feature = "avx512f"))]
352    {
353        unsafe { crate::xxh3_avx512::accumulate_512(acc, input, input_off, secret, secret_off) };
354        return;
355    }
356
357    #[cfg(all(target_arch = "x86_64", not(feature = "std"), target_feature = "avx2"))]
358    {
359        unsafe { crate::xxh3_avx2::accumulate_512(acc, input, input_off, secret, secret_off) };
360        return;
361    }
362
363    accumulate_512_scalar(acc, input, input_off, secret, secret_off);
364}
365
366#[inline]
367#[allow(unreachable_code, unused_variables)]
368fn accumulate_loop(
369    acc: &mut [u64; ACC_NB],
370    input: &[u8],
371    input_off: usize,
372    secret: &[u8],
373    secret_off: usize,
374    nb_stripes: usize,
375) {
376    #[cfg(all(target_arch = "wasm32", target_feature = "simd128"))]
377    {
378        crate::xxh3_wasm_simd128::accumulate_loop(acc, input, input_off, secret, secret_off, nb_stripes);
379        return;
380    }
381
382    #[cfg(all(target_arch = "aarch64", target_feature = "neon"))]
383    {
384        unsafe {
385            crate::xxh3_neon::accumulate_loop(acc, input, input_off, secret, secret_off, nb_stripes);
386        }
387        return;
388    }
389
390    #[cfg(all(target_arch = "x86_64", feature = "std"))]
391    {
392        match crate::x86_dispatch::level() {
393            crate::x86_dispatch::Level::Avx512 => {
394                unsafe {
395                    crate::xxh3_avx512::accumulate_loop(acc, input, input_off, secret, secret_off, nb_stripes);
396                }
397                return;
398            }
399            crate::x86_dispatch::Level::Avx2 => {
400                unsafe {
401                    crate::xxh3_avx2::accumulate_loop(acc, input, input_off, secret, secret_off, nb_stripes);
402                }
403                return;
404            }
405            crate::x86_dispatch::Level::Scalar => {}
406        }
407    }
408
409    #[cfg(all(target_arch = "x86_64", not(feature = "std"), target_feature = "avx512f"))]
410    {
411        unsafe {
412            crate::xxh3_avx512::accumulate_loop(acc, input, input_off, secret, secret_off, nb_stripes);
413        }
414        return;
415    }
416
417    #[cfg(all(target_arch = "x86_64", not(feature = "std"), target_feature = "avx2"))]
418    {
419        unsafe {
420            crate::xxh3_avx2::accumulate_loop(acc, input, input_off, secret, secret_off, nb_stripes);
421        }
422        return;
423    }
424
425    accumulate_loop_scalar(acc, input, input_off, secret, secret_off, nb_stripes);
426}
427
428#[inline]
429#[allow(unreachable_code, unused_variables)]
430fn scramble_acc(acc: &mut [u64; ACC_NB], secret: &[u8], secret_off: usize) {
431    #[cfg(all(target_arch = "wasm32", target_feature = "simd128"))]
432    {
433        crate::xxh3_wasm_simd128::scramble_acc(acc, secret, secret_off);
434        return;
435    }
436
437    #[cfg(all(target_arch = "aarch64", target_feature = "neon"))]
438    {
439        unsafe { crate::xxh3_neon::scramble_acc(acc, secret, secret_off) };
440        return;
441    }
442
443    #[cfg(all(target_arch = "x86_64", feature = "std"))]
444    {
445        match crate::x86_dispatch::level() {
446            crate::x86_dispatch::Level::Avx512 => {
447                unsafe { crate::xxh3_avx512::scramble_acc(acc, secret, secret_off) };
448                return;
449            }
450            crate::x86_dispatch::Level::Avx2 => {
451                unsafe { crate::xxh3_avx2::scramble_acc(acc, secret, secret_off) };
452                return;
453            }
454            crate::x86_dispatch::Level::Scalar => {}
455        }
456    }
457
458    #[cfg(all(target_arch = "x86_64", not(feature = "std"), target_feature = "avx512f"))]
459    {
460        unsafe { crate::xxh3_avx512::scramble_acc(acc, secret, secret_off) };
461        return;
462    }
463
464    #[cfg(all(target_arch = "x86_64", not(feature = "std"), target_feature = "avx2"))]
465    {
466        unsafe { crate::xxh3_avx2::scramble_acc(acc, secret, secret_off) };
467        return;
468    }
469
470    scramble_acc_scalar(acc, secret, secret_off);
471}
472
473// ---------------------------------------------------------------------------
474// One-shot: 0..16 bytes
475// ---------------------------------------------------------------------------
476
477const fn xxh3_64_1to3(input: &[u8], seed: u64, secret: &[u8]) -> u64 {
478    let len = input.len();
479    let c1 = input[0] as u32;
480    let c2 = input[len >> 1] as u32;
481    let c3 = input[len - 1] as u32;
482    let combined = (c1 << 16) | (c2 << 24) | (c3) | ((len as u32) << 8);
483    let flip = ((read_32le(secret, 0) ^ read_32le(secret, 4)) as u64).wrapping_add(seed);
484    xxh64_avalanche((combined as u64) ^ flip)
485}
486
487const fn xxh3_64_4to8(input: &[u8], mut seed: u64, secret: &[u8]) -> u64 {
488    seed ^= ((seed as u32).swap_bytes() as u64) << 32;
489    let len = input.len();
490    let input1 = read_32le(input, 0);
491    let input2 = read_32le(input, len - 4);
492    let flip = (read_64le(secret, 8) ^ read_64le(secret, 16)).wrapping_sub(seed);
493    let input64 = (input2 as u64).wrapping_add((input1 as u64) << 32);
494    strong_avalanche(input64 ^ flip, len as u64)
495}
496
497const fn xxh3_64_9to16(input: &[u8], seed: u64, secret: &[u8]) -> u64 {
498    let len = input.len();
499    let flip1 = (read_64le(secret, 24) ^ read_64le(secret, 32)).wrapping_add(seed);
500    let flip2 = (read_64le(secret, 40) ^ read_64le(secret, 48)).wrapping_sub(seed);
501    let input_lo = read_64le(input, 0) ^ flip1;
502    let input_hi = read_64le(input, len - 8) ^ flip2;
503    let acc = (len as u64)
504        .wrapping_add(input_lo.swap_bytes())
505        .wrapping_add(input_hi)
506        .wrapping_add(mul128_fold64(input_lo, input_hi));
507    avalanche(acc)
508}
509
510const fn xxh3_64_0to16(input: &[u8], seed: u64, secret: &[u8]) -> u64 {
511    if input.len() > 8 {
512        xxh3_64_9to16(input, seed, secret)
513    } else if input.len() >= 4 {
514        xxh3_64_4to8(input, seed, secret)
515    } else if !input.is_empty() {
516        xxh3_64_1to3(input, seed, secret)
517    } else {
518        xxh64_avalanche(seed ^ read_64le(secret, 56) ^ read_64le(secret, 64))
519    }
520}
521
522// ---------------------------------------------------------------------------
523// One-shot: 17..128 bytes
524// ---------------------------------------------------------------------------
525
526const fn xxh3_64_7to128(input: &[u8], seed: u64, secret: &[u8]) -> u64 {
527    let len = input.len();
528    let mut acc = (len as u64).wrapping_mul(PRIME64_1);
529
530    if len > 32 {
531        if len > 64 {
532            if len > 96 {
533                acc = acc.wrapping_add(mix16b(input, 48, secret, 96, seed));
534                acc = acc.wrapping_add(mix16b(input, len - 64, secret, 112, seed));
535            }
536            acc = acc.wrapping_add(mix16b(input, 32, secret, 64, seed));
537            acc = acc.wrapping_add(mix16b(input, len - 48, secret, 80, seed));
538        }
539        acc = acc.wrapping_add(mix16b(input, 16, secret, 32, seed));
540        acc = acc.wrapping_add(mix16b(input, len - 32, secret, 48, seed));
541    }
542
543    acc = acc.wrapping_add(mix16b(input, 0, secret, 0, seed));
544    acc = acc.wrapping_add(mix16b(input, len - 16, secret, 16, seed));
545
546    avalanche(acc)
547}
548
549// ---------------------------------------------------------------------------
550// One-shot: 129..240 bytes
551// ---------------------------------------------------------------------------
552
553const fn xxh3_64_129to240(input: &[u8], seed: u64, secret: &[u8]) -> u64 {
554    let len = input.len();
555    let mut acc = (len as u64).wrapping_mul(PRIME64_1);
556    let nb_rounds = len / 16;
557
558    let mut i = 0;
559    while i < 8 {
560        acc = acc.wrapping_add(mix16b(input, i * 16, secret, i * 16, seed));
561        i += 1;
562    }
563    acc = avalanche(acc);
564
565    i = 8;
566    while i < nb_rounds {
567        acc = acc.wrapping_add(mix16b(input, i * 16, secret, (i - 8) * 16 + 3, seed));
568        i += 1;
569    }
570
571    acc = acc.wrapping_add(mix16b(input, len - 16, secret, SECRET_SIZE_MIN - 17, seed));
572
573    avalanche(acc)
574}
575
576// ---------------------------------------------------------------------------
577// Long path: >240 bytes (one-shot)
578// ---------------------------------------------------------------------------
579
580/// Long-path hash using the scalar kernels only. Usable in `const` contexts.
581const fn xxh3_64_long_scalar(input: &[u8], secret: &[u8]) -> u64 {
582    let mut acc = INITIAL_ACC;
583    hash_long_internal_loop(&mut acc, input, secret);
584    merge_accs(
585        &acc,
586        secret,
587        SECRET_MERGEACCS_START,
588        (input.len() as u64).wrapping_mul(PRIME64_1),
589    )
590}
591
592/// Long-path hash using the runtime-dispatched (SIMD when available) kernels.
593fn xxh3_64_long(input: &[u8], secret: &[u8]) -> u64 {
594    let mut acc = INITIAL_ACC;
595    hash_long_internal_loop_dispatch(&mut acc, input, secret);
596    merge_accs(
597        &acc,
598        secret,
599        SECRET_MERGEACCS_START,
600        (input.len() as u64).wrapping_mul(PRIME64_1),
601    )
602}
603
604/// Short-path hash for inputs of at most [`MID_SIZE_MAX`] bytes.
605#[inline]
606const fn xxh3_64_short(input: &[u8], seed: u64, secret: &[u8]) -> u64 {
607    if input.len() <= 16 {
608        xxh3_64_0to16(input, seed, secret)
609    } else if input.len() <= 128 {
610        xxh3_64_7to128(input, seed, secret)
611    } else {
612        xxh3_64_129to240(input, seed, secret)
613    }
614}
615
616// ---------------------------------------------------------------------------
617// 128-bit: helper
618// ---------------------------------------------------------------------------
619
620#[inline(always)]
621#[allow(clippy::too_many_arguments)]
622const fn mix32_b(
623    lo: &mut u64,
624    hi: &mut u64,
625    input: &[u8],
626    input1_off: usize,
627    input2_off: usize,
628    secret: &[u8],
629    secret_off: usize,
630    seed: u64,
631) {
632    *lo = lo.wrapping_add(mix16b(input, input1_off, secret, secret_off, seed));
633    *lo ^= read_64le(input, input2_off).wrapping_add(read_64le(input, input2_off + 8));
634    *hi = hi.wrapping_add(mix16b(input, input2_off, secret, secret_off + 16, seed));
635    *hi ^= read_64le(input, input1_off).wrapping_add(read_64le(input, input1_off + 8));
636}
637
638// ---------------------------------------------------------------------------
639// 128-bit: short paths (return u128)
640// ---------------------------------------------------------------------------
641
642const fn xxh3_128_1to3(input: &[u8], seed: u64, secret: &[u8]) -> u128 {
643    let len = input.len();
644    let c1 = input[0] as u32;
645    let c2 = input[len >> 1] as u32;
646    let c3 = input[len - 1] as u32;
647    let combinedl = ((c1) << 16) | (c2 << 24) | (c3) | ((len as u32) << 8);
648    let combinedh = combinedl.swap_bytes().rotate_left(13);
649    let bitflipl = ((read_32le(secret, 0) ^ read_32le(secret, 4)) as u64).wrapping_add(seed);
650    let bitfliph = ((read_32le(secret, 8) ^ read_32le(secret, 12)) as u64).wrapping_sub(seed);
651    let keyed_lo = (combinedl as u64) ^ bitflipl;
652    let keyed_hi = (combinedh as u64) ^ bitfliph;
653    ((xxh64_avalanche(keyed_hi) as u128) << 64) | (xxh64_avalanche(keyed_lo) as u128)
654}
655
656const fn xxh3_128_9to16(input: &[u8], seed: u64, secret: &[u8]) -> u128 {
657    let len = input.len();
658    let bitflipl = (read_64le(secret, 32) ^ read_64le(secret, 40)).wrapping_sub(seed);
659    let bitfliph = (read_64le(secret, 48) ^ read_64le(secret, 56)).wrapping_add(seed);
660    let input_lo = read_64le(input, 0);
661    let mut input_hi = read_64le(input, len - 8);
662
663    let m128_full = (input_lo ^ input_hi ^ bitflipl) as u128 * PRIME64_1 as u128;
664    let mut m128_lo = m128_full as u64;
665    let mut m128_hi = (m128_full >> 64) as u64;
666
667    m128_lo = m128_lo.wrapping_add(((len - 1) as u64) << 54);
668    input_hi ^= bitfliph;
669    m128_hi = m128_hi
670        .wrapping_add(input_hi)
671        .wrapping_add(mult32_to64(input_hi as u32, 0x85EBCA76u32));
672
673    m128_lo ^= m128_hi.swap_bytes();
674
675    let h128_full = (m128_lo as u128).wrapping_mul(PRIME64_2 as u128);
676    let h128_lo = h128_full as u64;
677    let h128_hi = ((h128_full >> 64) as u64).wrapping_add(m128_hi.wrapping_mul(PRIME64_2));
678
679    ((avalanche(h128_hi) as u128) << 64) | (avalanche(h128_lo) as u128)
680}
681
682const fn xxh3_128_4to8_return(input: &[u8], mut seed: u64, secret: &[u8]) -> u128 {
683    seed ^= ((seed as u32).swap_bytes() as u64) << 32;
684    let len = input.len();
685    let input_lo = read_32le(input, 0);
686    let input_hi = read_32le(input, len - 4);
687    let input_64 = (input_lo as u64).wrapping_add((input_hi as u64) << 32);
688    let bitflip = (read_64le(secret, 16) ^ read_64le(secret, 24)).wrapping_add(seed);
689    let keyed = input_64 ^ bitflip;
690    let m128 = (keyed as u128).wrapping_mul(PRIME64_1.wrapping_add((len as u64) << 2) as u128);
691    let mut m128_lo = m128 as u64;
692    let mut m128_hi = (m128 >> 64) as u64;
693    m128_hi = m128_hi.wrapping_add(m128_lo << 1);
694    m128_lo ^= m128_hi >> 3;
695    m128_lo = xorshift64(m128_lo, 35);
696    m128_lo = m128_lo.wrapping_mul(0x9FB21C651E98DF25);
697    m128_lo = xorshift64(m128_lo, 28);
698    m128_hi = avalanche(m128_hi);
699    ((m128_hi as u128) << 64) | (m128_lo as u128)
700}
701
702const fn xxh3_128_0to16(input: &[u8], seed: u64, secret: &[u8]) -> u128 {
703    if input.len() > 8 {
704        xxh3_128_9to16(input, seed, secret)
705    } else if input.len() >= 4 {
706        xxh3_128_4to8_return(input, seed, secret)
707    } else if !input.is_empty() {
708        xxh3_128_1to3(input, seed, secret)
709    } else {
710        let flip_lo = read_64le(secret, 64) ^ read_64le(secret, 72);
711        let flip_hi = read_64le(secret, 80) ^ read_64le(secret, 88);
712        (xxh64_avalanche(seed ^ flip_lo) as u128) | ((xxh64_avalanche(seed ^ flip_hi) as u128) << 64)
713    }
714}
715
716// ---------------------------------------------------------------------------
717// 128-bit: medium paths (17..128), (129..240) — return u128
718// ---------------------------------------------------------------------------
719
720const fn xxh3_128_7to128(input: &[u8], seed: u64, secret: &[u8]) -> u128 {
721    let len = input.len();
722    let mut lo = (len as u64).wrapping_mul(PRIME64_1);
723    let mut hi: u64 = 0;
724
725    if len > 32 {
726        if len > 64 {
727            if len > 96 {
728                mix32_b(&mut lo, &mut hi, input, 48, len - 64, secret, 96, seed);
729            }
730            mix32_b(&mut lo, &mut hi, input, 32, len - 48, secret, 64, seed);
731        }
732        mix32_b(&mut lo, &mut hi, input, 16, len - 32, secret, 32, seed);
733    }
734
735    mix32_b(&mut lo, &mut hi, input, 0, len - 16, secret, 0, seed);
736
737    (avalanche(lo.wrapping_add(hi)) as u128)
738        | ((0u64.wrapping_sub(avalanche(
739            lo.wrapping_mul(PRIME64_1)
740                .wrapping_add(hi.wrapping_mul(0x85EBCA77C2B2AE63))
741                .wrapping_add((len as u64).wrapping_sub(seed).wrapping_mul(PRIME64_2)),
742        )) as u128)
743            << 64)
744}
745
746const fn xxh3_128_129to240(input: &[u8], seed: u64, secret: &[u8]) -> u128 {
747    let len = input.len();
748    let nb_rounds = len / 32;
749    let mut lo = (len as u64).wrapping_mul(PRIME64_1);
750    let mut hi: u64 = 0;
751
752    let mut i = 0;
753    while i < 4 {
754        let offset = 32 * i;
755        mix32_b(&mut lo, &mut hi, input, offset, offset + 16, secret, offset, seed);
756        i += 1;
757    }
758
759    lo = avalanche(lo);
760    hi = avalanche(hi);
761
762    i = 4;
763    while i < nb_rounds {
764        mix32_b(&mut lo, &mut hi, input, 32 * i, 32 * i + 16, secret, 3 + 32 * (i - 4), seed);
765        i += 1;
766    }
767
768    mix32_b(
769        &mut lo,
770        &mut hi,
771        input,
772        len - 16,
773        len - 32,
774        secret,
775        SECRET_SIZE_MIN - 17 - 16,
776        0u64.wrapping_sub(seed),
777    );
778
779    (avalanche(lo.wrapping_add(hi)) as u128)
780        | ((0u64.wrapping_sub(avalanche(
781            lo.wrapping_mul(PRIME64_1)
782                .wrapping_add(hi.wrapping_mul(0x85EBCA77C2B2AE63))
783                .wrapping_add((len as u64).wrapping_sub(seed).wrapping_mul(PRIME64_2)),
784        )) as u128)
785            << 64)
786}
787
788// ---------------------------------------------------------------------------
789// Long path: 128-bit (>240 bytes)
790// ---------------------------------------------------------------------------
791
792const fn xxh3_128_long_scalar(input: &[u8], secret: &[u8]) -> u128 {
793    let mut acc = INITIAL_ACC;
794    hash_long_internal_loop(&mut acc, input, secret);
795
796    let lo = merge_accs(
797        &acc,
798        secret,
799        SECRET_MERGEACCS_START,
800        (input.len() as u64).wrapping_mul(PRIME64_1),
801    );
802    let hi = merge_accs(
803        &acc,
804        secret,
805        secret.len() - ACC_NB * 8 - SECRET_MERGEACCS_START,
806        !(input.len() as u64).wrapping_mul(PRIME64_2),
807    );
808
809    (lo as u128) | ((hi as u128) << 64)
810}
811
812/// Long-path hash using the runtime-dispatched (SIMD when available) kernels.
813fn xxh3_128_long(input: &[u8], secret: &[u8]) -> u128 {
814    let mut acc = INITIAL_ACC;
815    hash_long_internal_loop_dispatch(&mut acc, input, secret);
816
817    let lo = merge_accs(
818        &acc,
819        secret,
820        SECRET_MERGEACCS_START,
821        (input.len() as u64).wrapping_mul(PRIME64_1),
822    );
823    let hi = merge_accs(
824        &acc,
825        secret,
826        secret.len() - ACC_NB * 8 - SECRET_MERGEACCS_START,
827        !(input.len() as u64).wrapping_mul(PRIME64_2),
828    );
829
830    (lo as u128) | ((hi as u128) << 64)
831}
832
833/// Short-path hash for inputs of at most [`MID_SIZE_MAX`] bytes.
834#[inline]
835const fn xxh3_128_short(input: &[u8], seed: u64, secret: &[u8]) -> u128 {
836    if input.len() <= 16 {
837        xxh3_128_0to16(input, seed, secret)
838    } else if input.len() <= 128 {
839        xxh3_128_7to128(input, seed, secret)
840    } else {
841        xxh3_128_129to240(input, seed, secret)
842    }
843}
844
845// ---------------------------------------------------------------------------
846// Streaming state
847//
848// Mirrors the reference `XXH3_state_t`: a 256-byte internal buffer, the eight
849// accumulators, the (possibly seed-derived) secret, and the number of stripes
850// already consumed within the current block.
851//
852// A single internal buffer serves both the short (<= 240 bytes) and long
853// paths; the digest decides which applies from the total length. This mirrors
854// the reference and guarantees that incremental hashing always agrees with the
855// one-shot functions, regardless of how the input is split across `update()`
856// calls.
857// ---------------------------------------------------------------------------
858
859#[derive(Clone)]
860struct Xxh3State {
861    acc: [u64; ACC_NB],
862    secret: [u8; DEFAULT_SECRET_SIZE],
863    buffer: [u8; INTERNAL_BUFFER_SIZE],
864    buffered_size: usize,
865    nb_stripes_so_far: usize,
866    total_len: u64,
867    seed: u64,
868    /// When set, short inputs are hashed with the *default* secret and `seed`
869    /// (the reference `_withSeed` behavior); otherwise the stored `secret` is
870    /// used with `seed == 0` (the reference `_withSecret` behavior).
871    use_seed: bool,
872}
873
874impl Xxh3State {
875    #[inline]
876    const fn new(secret: [u8; DEFAULT_SECRET_SIZE], seed: u64, use_seed: bool) -> Self {
877        Xxh3State {
878            acc: INITIAL_ACC,
879            secret,
880            buffer: [0u8; INTERNAL_BUFFER_SIZE],
881            buffered_size: 0,
882            nb_stripes_so_far: 0,
883            total_len: 0,
884            seed,
885            use_seed,
886        }
887    }
888
889    /// Feeds `data` into the state.
890    ///
891    /// Never panics, whatever the length of `data` or how the input is split
892    /// across calls.
893    fn update(&mut self, input: &[u8]) {
894        self.total_len += input.len() as u64;
895
896        // Small input: just fill the internal buffer.
897        if input.len() <= INTERNAL_BUFFER_SIZE - self.buffered_size {
898            let end = self.buffered_size + input.len();
899            self.buffer[self.buffered_size..end].copy_from_slice(input);
900            self.buffered_size = end;
901            return;
902        }
903
904        let mut data = input;
905
906        // The internal buffer is partially filled: complete it, then consume it.
907        if self.buffered_size > 0 {
908            let load_size = INTERNAL_BUFFER_SIZE - self.buffered_size;
909            self.buffer[self.buffered_size..INTERNAL_BUFFER_SIZE].copy_from_slice(&data[..load_size]);
910            data = &data[load_size..];
911            consume_stripes(
912                &mut self.acc,
913                &mut self.nb_stripes_so_far,
914                &self.buffer,
915                INTERNAL_BUFFER_STRIPES,
916                &self.secret,
917            );
918            self.buffered_size = 0;
919        }
920
921        // Large input: consume full stripes, keeping the tail buffered.
922        if data.len() > INTERNAL_BUFFER_SIZE {
923            let nb_stripes = (data.len() - 1) / STRIPE_LEN;
924            consume_stripes(&mut self.acc, &mut self.nb_stripes_so_far, data, nb_stripes, &self.secret);
925            let consumed = nb_stripes * STRIPE_LEN;
926            // Keep a copy of the last consumed stripe: it is needed to rebuild
927            // the final (overlapping) stripe when fewer than 64 bytes remain.
928            self.buffer[INTERNAL_BUFFER_SIZE - STRIPE_LEN..].copy_from_slice(&data[consumed - STRIPE_LEN..consumed]);
929            data = &data[consumed..];
930        }
931
932        // Any remaining input is always buffered.
933        self.buffer[..data.len()].copy_from_slice(data);
934        self.buffered_size = data.len();
935    }
936
937    /// Hashes the buffered data through the short path (`total_len <= 240`).
938    fn digest_short_64(&self) -> u64 {
939        let data = &self.buffer[..self.buffered_size];
940        if self.use_seed {
941            xxh3_64_short(data, self.seed, &DEFAULT_SECRET)
942        } else {
943            xxh3_64_short(data, 0, &self.secret)
944        }
945    }
946
947    /// Hashes the buffered data through the short path (`total_len <= 240`).
948    fn digest_short_128(&self) -> u128 {
949        let data = &self.buffer[..self.buffered_size];
950        if self.use_seed {
951            xxh3_128_short(data, self.seed, &DEFAULT_SECRET)
952        } else {
953            xxh3_128_short(data, 0, &self.secret)
954        }
955    }
956
957    /// Consumes the remaining stripes and accumulates the final stripe.
958    ///
959    /// Mirrors the reference `XXH3_digest_long()`.
960    fn digest_long_acc(&mut self) {
961        if self.buffered_size >= STRIPE_LEN {
962            let nb_stripes = (self.buffered_size - 1) / STRIPE_LEN;
963            consume_stripes(
964                &mut self.acc,
965                &mut self.nb_stripes_so_far,
966                &self.buffer,
967                nb_stripes,
968                &self.secret,
969            );
970            let last_off = self.buffered_size - STRIPE_LEN;
971            accumulate_512(&mut self.acc, &self.buffer, last_off, &self.secret, LAST_STRIPE_SECRET_OFFSET);
972        } else {
973            // Rebuild the final overlapping stripe from the buffered bytes and
974            // the copy of the last consumed stripe.
975            let catchup = STRIPE_LEN - self.buffered_size;
976            let mut last_stripe = [0u8; STRIPE_LEN];
977            last_stripe[..catchup].copy_from_slice(&self.buffer[INTERNAL_BUFFER_SIZE - catchup..]);
978            last_stripe[catchup..].copy_from_slice(&self.buffer[..self.buffered_size]);
979            accumulate_512(&mut self.acc, &last_stripe, 0, &self.secret, LAST_STRIPE_SECRET_OFFSET);
980        }
981    }
982
983    fn digest_long_64(mut self) -> u64 {
984        self.digest_long_acc();
985        merge_accs(
986            &self.acc,
987            &self.secret,
988            SECRET_MERGEACCS_START,
989            self.total_len.wrapping_mul(PRIME64_1),
990        )
991    }
992
993    fn digest_long_128(mut self) -> u128 {
994        self.digest_long_acc();
995        let lo = merge_accs(
996            &self.acc,
997            &self.secret,
998            SECRET_MERGEACCS_START,
999            self.total_len.wrapping_mul(PRIME64_1),
1000        );
1001        let hi = merge_accs(
1002            &self.acc,
1003            &self.secret,
1004            DEFAULT_SECRET_SIZE - ACC_NB * 8 - SECRET_MERGEACCS_START,
1005            !self.total_len.wrapping_mul(PRIME64_2),
1006        );
1007        (lo as u128) | ((hi as u128) << 64)
1008    }
1009
1010    fn digest_64(self) -> u64 {
1011        if self.total_len <= MID_SIZE_MAX as u64 {
1012            self.digest_short_64()
1013        } else {
1014            self.digest_long_64()
1015        }
1016    }
1017
1018    fn digest_128(self) -> u128 {
1019        if self.total_len <= MID_SIZE_MAX as u64 {
1020            self.digest_short_128()
1021        } else {
1022            self.digest_long_128()
1023        }
1024    }
1025}
1026
1027// ---------------------------------------------------------------------------
1028// Public struct: Xxh3_64
1029// ---------------------------------------------------------------------------
1030
1031/// XXH3 64-bit hash.
1032///
1033/// A fast non-cryptographic hash using the XXH3 algorithm. Supports an
1034/// optional `u64` seed and an optional custom 192-byte secret.
1035///
1036/// Incremental hashing ([`update`](Checksum::update)) produces exactly the same
1037/// value as one-shot hashing ([`checksum`](Checksum::checksum), or the
1038/// [`xxh3_64`] free function), for any input length and any split of the input
1039/// across `update` calls. No input can make it panic.
1040///
1041/// When the `std` feature is enabled (the default), the long-input path uses
1042/// AVX2 / AVX-512 (x86) or NEON (aarch64) kernels selected at runtime; with
1043/// `no_std`, the kernels are selected at compile time from the enabled target
1044/// features.
1045///
1046/// # Security
1047///
1048/// XXH3 is **not** a cryptographic hash: it must not be used to authenticate
1049/// messages, derive keys, or store passwords, and a seed or a custom secret is
1050/// not a security boundary. It is also not collision-resistant against
1051/// deliberate attacks; do not use it to defend a hash table against
1052/// attacker-chosen keys (hash flooding).
1053///
1054/// # Example
1055///
1056/// ```rust
1057/// use xxhash::{Xxh3_64, Checksum};
1058///
1059/// let hash = Xxh3_64::checksum(b"hello world");
1060/// ```
1061#[derive(Clone)]
1062pub struct Xxh3_64 {
1063    state: Xxh3State,
1064}
1065
1066impl Xxh3_64 {
1067    /// Create a new XXH3-64 hasher with the given seed.
1068    ///
1069    /// A zero seed is equivalent to the default, unseeded hasher.
1070    #[inline]
1071    pub const fn with_seed(seed: u64) -> Self {
1072        if seed == 0 {
1073            Xxh3_64 {
1074                state: Xxh3State::new(DEFAULT_SECRET, 0, false),
1075            }
1076        } else {
1077            Xxh3_64 {
1078                state: Xxh3State::new(custom_default_secret_scalar(seed), seed, true),
1079            }
1080        }
1081    }
1082
1083    /// Create a new XXH3-64 hasher with a custom 192-byte secret (seed = 0).
1084    #[inline]
1085    pub const fn with_secret(secret: [u8; DEFAULT_SECRET_SIZE]) -> Self {
1086        Xxh3_64 {
1087            state: Xxh3State::new(secret, 0, false),
1088        }
1089    }
1090
1091    /// Create a new XXH3-64 hasher with a custom seed and secret.
1092    ///
1093    /// This matches the reference `XXH3_64bits_withSecretandSeed()` contract:
1094    /// the `seed` is used for inputs of up to 240 bytes (with the *default*
1095    /// secret), while the custom `secret` is used — and the seed is ignored —
1096    /// for longer inputs.
1097    #[inline]
1098    pub const fn with_seed_and_secret(seed: u64, secret: [u8; DEFAULT_SECRET_SIZE]) -> Self {
1099        Xxh3_64 {
1100            state: Xxh3State::new(secret, seed, true),
1101        }
1102    }
1103}
1104
1105impl Checksum for Xxh3_64 {
1106    type Output = u64;
1107
1108    fn new() -> Self {
1109        Self::with_seed(0)
1110    }
1111
1112    #[inline]
1113    fn checksum(data: &[u8]) -> u64 {
1114        if data.len() <= MID_SIZE_MAX {
1115            xxh3_64_short(data, 0, &DEFAULT_SECRET)
1116        } else {
1117            xxh3_64_long(data, &DEFAULT_SECRET)
1118        }
1119    }
1120
1121    #[inline]
1122    fn update(&mut self, data: &[u8]) {
1123        self.state.update(data);
1124    }
1125
1126    #[inline]
1127    fn sum(self) -> u64 {
1128        self.state.digest_64()
1129    }
1130}
1131
1132impl core::fmt::Debug for Xxh3_64 {
1133    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1134        f.debug_struct("Xxh3_64").finish()
1135    }
1136}
1137
1138impl Default for Xxh3_64 {
1139    #[inline]
1140    fn default() -> Self {
1141        Self::with_seed(0)
1142    }
1143}
1144
1145// ---------------------------------------------------------------------------
1146// Public struct: Xxh3_128
1147// ---------------------------------------------------------------------------
1148
1149/// XXH3 128-bit hash.
1150///
1151/// A fast non-cryptographic hash using the XXH3-128 algorithm. Supports an
1152/// optional `u64` seed and an optional custom 192-byte secret.
1153///
1154/// Incremental hashing ([`update`](Checksum::update)) produces exactly the same
1155/// value as one-shot hashing ([`checksum`](Checksum::checksum), or the
1156/// [`xxh3_128`] free function), for any input length and any split of the input
1157/// across `update` calls. No input can make it panic.
1158///
1159/// When the `std` feature is enabled (the default), the long-input path uses
1160/// AVX2 / AVX-512 (x86) or NEON (aarch64) kernels selected at runtime; with
1161/// `no_std`, the kernels are selected at compile time from the enabled target
1162/// features.
1163///
1164/// # Security
1165///
1166/// XXH3 is **not** a cryptographic hash: it must not be used to authenticate
1167/// messages, derive keys, or store passwords, and a seed or a custom secret is
1168/// not a security boundary. It is also not collision-resistant against
1169/// deliberate attacks; do not use it to defend a hash table against
1170/// attacker-chosen keys (hash flooding).
1171///
1172/// # Example
1173///
1174/// ```rust
1175/// use xxhash::{Xxh3_128, Checksum};
1176///
1177/// let hash = Xxh3_128::checksum(b"hello world");
1178/// ```
1179#[derive(Clone)]
1180pub struct Xxh3_128 {
1181    state: Xxh3State,
1182}
1183
1184impl Xxh3_128 {
1185    /// Create a new XXH3-128 hasher with the given seed.
1186    ///
1187    /// A zero seed is equivalent to the default, unseeded hasher.
1188    #[inline]
1189    pub const fn with_seed(seed: u64) -> Self {
1190        if seed == 0 {
1191            Xxh3_128 {
1192                state: Xxh3State::new(DEFAULT_SECRET, 0, false),
1193            }
1194        } else {
1195            Xxh3_128 {
1196                state: Xxh3State::new(custom_default_secret_scalar(seed), seed, true),
1197            }
1198        }
1199    }
1200
1201    /// Create a new XXH3-128 hasher with a custom 192-byte secret (seed = 0).
1202    #[inline]
1203    pub const fn with_secret(secret: [u8; DEFAULT_SECRET_SIZE]) -> Self {
1204        Xxh3_128 {
1205            state: Xxh3State::new(secret, 0, false),
1206        }
1207    }
1208
1209    /// Create a new XXH3-128 hasher with a custom seed and secret.
1210    ///
1211    /// This matches the reference `XXH3_128bits_withSecretandSeed()` contract:
1212    /// the `seed` is used for inputs of up to 240 bytes (with the *default*
1213    /// secret), while the custom `secret` is used — and the seed is ignored —
1214    /// for longer inputs.
1215    #[inline]
1216    pub const fn with_seed_and_secret(seed: u64, secret: [u8; DEFAULT_SECRET_SIZE]) -> Self {
1217        Xxh3_128 {
1218            state: Xxh3State::new(secret, seed, true),
1219        }
1220    }
1221}
1222
1223impl Checksum for Xxh3_128 {
1224    type Output = u128;
1225
1226    fn new() -> Self {
1227        Self::with_seed(0)
1228    }
1229
1230    #[inline]
1231    fn checksum(data: &[u8]) -> u128 {
1232        if data.len() <= MID_SIZE_MAX {
1233            xxh3_128_short(data, 0, &DEFAULT_SECRET)
1234        } else {
1235            xxh3_128_long(data, &DEFAULT_SECRET)
1236        }
1237    }
1238
1239    #[inline]
1240    fn update(&mut self, data: &[u8]) {
1241        self.state.update(data);
1242    }
1243
1244    #[inline]
1245    fn sum(self) -> u128 {
1246        self.state.digest_128()
1247    }
1248}
1249
1250impl core::fmt::Debug for Xxh3_128 {
1251    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1252        f.debug_struct("Xxh3_128").finish()
1253    }
1254}
1255
1256impl Default for Xxh3_128 {
1257    #[inline]
1258    fn default() -> Self {
1259        Self::with_seed(0)
1260    }
1261}
1262
1263// ---------------------------------------------------------------------------
1264// Standalone const one-shot functions
1265// ---------------------------------------------------------------------------
1266
1267/// Compute the XXH3 64-bit hash of `data` in a single call.
1268///
1269/// Available as a `const fn` for compile-time hashing, using seed=0 and the
1270/// default XXH3 secret. For inputs longer than 240 bytes the `const` path is
1271/// scalar only; use [`Xxh3_64::checksum`](Checksum::checksum) to benefit from
1272/// SIMD acceleration at runtime. Both produce identical results.
1273///
1274/// # Example
1275///
1276/// ```rust
1277/// use xxhash::xxh3_64;
1278///
1279/// let hash: u64 = xxh3_64(b"hello");
1280/// assert_eq!(hash, 0x9555E8555C62DCFD);
1281/// ```
1282#[inline]
1283pub const fn xxh3_64(data: &[u8]) -> u64 {
1284    if data.len() <= MID_SIZE_MAX {
1285        xxh3_64_short(data, 0, &DEFAULT_SECRET)
1286    } else {
1287        xxh3_64_long_scalar(data, &DEFAULT_SECRET)
1288    }
1289}
1290
1291/// Compute the XXH3 128-bit hash of `data` in a single call.
1292///
1293/// Available as a `const fn` for compile-time hashing, using seed=0 and the
1294/// default XXH3 secret. For inputs longer than 240 bytes the `const` path is
1295/// scalar only; use [`Xxh3_128::checksum`](Checksum::checksum) to benefit from
1296/// SIMD acceleration at runtime. Both produce identical results.
1297///
1298/// # Example
1299///
1300/// ```rust
1301/// use xxhash::xxh3_128;
1302///
1303/// let hash: u128 = xxh3_128(b"hello");
1304/// assert_eq!(hash, 0xB5E9C1AD071B3E7FC779CFAA5E523818);
1305/// ```
1306#[inline]
1307pub const fn xxh3_128(data: &[u8]) -> u128 {
1308    if data.len() <= MID_SIZE_MAX {
1309        xxh3_128_short(data, 0, &DEFAULT_SECRET)
1310    } else {
1311        xxh3_128_long_scalar(data, &DEFAULT_SECRET)
1312    }
1313}
1314
1315// ---------------------------------------------------------------------------
1316// Tests
1317// ---------------------------------------------------------------------------
1318
1319#[cfg(test)]
1320mod tests {
1321    use super::*;
1322    use crate::{Checksum, test_helpers::fill_test_buffer};
1323
1324    extern crate alloc;
1325
1326    /// PRIME64 used in the C reference sanity check as seed (0x9E3779B185EBCA8D).
1327    const TEST_SEED: u64 = 0x9E3779B185EBCA8D;
1328    /// PRIME32 used in the C reference sanity check as seed for XXH128 vectors.
1329    const TEST_SEED32: u64 = 0x9E3779B1;
1330
1331    // --- Xxh3_64 tests ---
1332
1333    #[test]
1334    fn test_xh3_64_empty() {
1335        assert_eq!(Xxh3_64::checksum(b""), 0x2D06800538D394C2);
1336    }
1337
1338    #[test]
1339    fn test_xh3_64_hello() {
1340        assert_eq!(Xxh3_64::checksum(b"hello"), 0x9555E8555C62DCFD);
1341    }
1342
1343    #[test]
1344    fn test_xh3_64_fox() {
1345        assert_eq!(
1346            Xxh3_64::checksum(b"The quick brown fox jumps over the lazy dog"),
1347            0xCE7D19A5418FB365
1348        );
1349    }
1350
1351    /// Official XXH3-64 test vectors from `xsum_sanity_check.c`, covering
1352    /// all one-shot paths (0to16, 7to128, 129to240) with both seed=0 and
1353    /// seed=PRIME64.
1354    #[test]
1355    fn test_xh3_64_official_vectors() {
1356        let cases: &[(usize, u64, u64)] = &[
1357            (0, 0, 0x2D06800538D394C2),
1358            (0, TEST_SEED, 0xA8A6B918B2F0364A),
1359            (1, 0, 0xC44BDFF4074EECDB),
1360            (1, TEST_SEED, 0x032BE332DD766EF8),
1361            (6, 0, 0x27B56A84CD2D7325),
1362            (6, TEST_SEED, 0x84589C116AB59AB9),
1363            (12, 0, 0xA713DAF0DFBB77E7),
1364            (12, TEST_SEED, 0xE7303E1B2336DE0E),
1365            (24, 0, 0xA3FE70BF9D3510EB),
1366            (24, TEST_SEED, 0x850E80FC35BDD690),
1367            (48, 0, 0x397DA259ECBA1F11),
1368            (48, TEST_SEED, 0xADC2CBAA44ACC616),
1369            (80, 0, 0xBCDEFBBB2C47C90A),
1370            (80, TEST_SEED, 0xC6DD0CB699532E73),
1371            (195, 0, 0xCD94217EE362EC3A),
1372            (195, TEST_SEED, 0xBA68003D370CB3D9),
1373        ];
1374
1375        for &(len, seed, expected) in cases {
1376            let buf = fill_test_buffer(len);
1377            let mut h = Xxh3_64::with_seed(seed);
1378            h.update(&buf);
1379            assert_eq!(h.sum(), expected, "XXH3-64 length {len} seed {seed:#x}");
1380        }
1381    }
1382
1383    /// Byte-at-a-time incremental produces the same result as one-shot.
1384    #[test]
1385    fn test_xh3_64_byte_at_a_time() {
1386        let cases: &[(usize, u64, u64)] = &[
1387            (0, 0, 0x2D06800538D394C2),
1388            (1, TEST_SEED, 0x032BE332DD766EF8),
1389            (12, 0, 0xA713DAF0DFBB77E7),
1390            (80, TEST_SEED, 0xC6DD0CB699532E73),
1391        ];
1392
1393        for &(len, seed, expected) in cases {
1394            let buf = fill_test_buffer(len);
1395            let mut h = Xxh3_64::with_seed(seed);
1396            for b in &buf {
1397                h.update(&[*b]);
1398            }
1399            assert_eq!(h.sum(), expected, "XXH3-64 byte-at-a-time length {len} seed {seed:#x}");
1400        }
1401    }
1402
1403    /// Boundary sizes that exercise all one-shot code paths.
1404    #[test]
1405    fn test_xh3_64_boundaries() {
1406        let sizes: &[(usize, u64)] = &[
1407            // 0to16 path
1408            (0, 0x2D06800538D394C2),
1409            (1, 0xC44BDFF4074EECDB),
1410            (3, 0x54247382A8D6B94D),
1411            (4, 0xE5DC74BC51848A51),
1412            (8, 0x24CCC9ACAA9F65E4),
1413            (16, 0x981B17D36C7498C9),
1414            // 7to128 path
1415            (17, 0x796F5ACD3A60F862),
1416            (32, 0x9FEADDBDBF57EED3),
1417            (64, 0x9CB48487720EC49D),
1418            (128, 0xFCFF24126754D861),
1419            // 129to240 path
1420            (129, 0x98F1B0A679A2CA29),
1421            (240, 0x81C3C2B67F568CCF),
1422        ];
1423
1424        for &(len, expected) in sizes {
1425            let buf = fill_test_buffer(len);
1426            let mut h = Xxh3_64::new();
1427            h.update(&buf);
1428            assert_eq!(h.sum(), expected, "XXH3-64 boundary length {len}");
1429        }
1430    }
1431
1432    #[test]
1433    fn test_xh3_64_incremental() {
1434        let mut h = Xxh3_64::new();
1435        h.update(b"The quick brown ");
1436        h.update(b"fox jumps over ");
1437        h.update(b"the lazy dog");
1438        assert_eq!(h.sum(), 0xCE7D19A5418FB365);
1439    }
1440
1441    #[test]
1442    fn test_xh3_64_seeded() {
1443        let mut h = Xxh3_64::with_seed(42);
1444        h.update(b"hello");
1445        assert_eq!(h.sum(), 0xBAFA072F07DB7937);
1446    }
1447
1448    #[test]
1449    fn test_xh3_64_long_incremental() {
1450        let mut h = Xxh3_64::new();
1451        let data = [0x55u8; 512];
1452        h.update(&data[..200]);
1453        h.update(&data[200..400]);
1454        h.update(&data[400..]);
1455        assert_eq!(h.sum(), 0x4C1155EA5825B659);
1456    }
1457
1458    // --- Xxh3_128 tests ---
1459
1460    #[test]
1461    fn test_xh3_128_empty() {
1462        assert_eq!(Xxh3_128::checksum(b""), 0x99AA06D3014798D86001C324468D497F);
1463    }
1464
1465    #[test]
1466    fn test_xh3_128_hello() {
1467        assert_eq!(Xxh3_128::checksum(b"hello"), 0xB5E9C1AD071B3E7FC779CFAA5E523818);
1468    }
1469
1470    #[test]
1471    fn test_xh3_128_fox() {
1472        assert_eq!(
1473            Xxh3_128::checksum(b"The quick brown fox jumps over the lazy dog"),
1474            0xDDD650205CA3E7FA24A1CC2E3A8A7651
1475        );
1476    }
1477
1478    /// Official XXH128 test vectors from `xsum_sanity_check.c`, covering
1479    /// all one-shot paths with seed=0 and seed=PRIME32.
1480    #[test]
1481    fn test_xh3_128_official_vectors() {
1482        let cases: &[(usize, u64, u128)] = &[
1483            (0, 0, 0x99AA06D3014798D86001C324468D497F),
1484            (0, TEST_SEED32, 0x92220AE55E14AB505444F7869C671AB0),
1485            (1, 0, 0xA6CD5E9392000F6AC44BDFF4074EECDB),
1486            (1, TEST_SEED32, 0x89B99554BA22467CB53D5557E7F76F8D),
1487            (6, 0, 0x082AFE0B8162D12A3E7039BDDA43CFC6),
1488            (6, TEST_SEED32, 0x5A865B5389ABD2B1269D8F70BE98856E),
1489            (12, 0, 0x6E3EFD8FC7802B18061A192713F69AD9),
1490            (12, TEST_SEED32, 0xD7E09D518A3405D39BE9F9A67F3C7DFB),
1491            (24, 0, 0x0CE966E4678D37611E7044D28B1B901D),
1492            (24, TEST_SEED32, 0x3162026714A6A243D7304C54EBAD40A9),
1493            (48, 0, 0xA002AC4E5478227EF942219AED80F67B),
1494            (48, TEST_SEED32, 0x163ADDE36C0722957BA3C3E453A1934E),
1495            (81, 0, 0x4952F58181AB00425E8BAFB9F95FB803),
1496            (81, TEST_SEED32, 0x2724EC7ADC750FB6703FBB3D7A5F755C),
1497            (222, 0, 0x337E09641B948717F1AEBD597CEC6B3A),
1498            (222, TEST_SEED32, 0x91820016621E97F1AE995BB8AF917A8D),
1499        ];
1500
1501        for &(len, seed, expected) in cases {
1502            let buf = fill_test_buffer(len);
1503            let mut h = Xxh3_128::with_seed(seed);
1504            h.update(&buf);
1505            assert_eq!(h.sum(), expected, "XXH128 length {len} seed {seed:#x}");
1506        }
1507    }
1508
1509    /// Byte-at-a-time incremental produces the same result as one-shot.
1510    #[test]
1511    fn test_xh3_128_byte_at_a_time() {
1512        let cases: &[(usize, u64, u128)] = &[
1513            (0, 0, 0x99AA06D3014798D86001C324468D497F),
1514            (1, TEST_SEED32, 0x89B99554BA22467CB53D5557E7F76F8D),
1515            (12, 0, 0x6E3EFD8FC7802B18061A192713F69AD9),
1516        ];
1517
1518        for &(len, seed, expected) in cases {
1519            let buf = fill_test_buffer(len);
1520            let mut h = Xxh3_128::with_seed(seed);
1521            for b in &buf {
1522                h.update(&[*b]);
1523            }
1524            assert_eq!(h.sum(), expected, "XXH128 byte-at-a-time length {len} seed {seed:#x}");
1525        }
1526    }
1527
1528    /// Boundary sizes for XXH128 (all within one-shot paths).
1529    #[test]
1530    fn test_xh3_128_boundaries() {
1531        let sizes: &[(usize, u128)] = &[
1532            (0, 0x99AA06D3014798D86001C324468D497F),
1533            (1, 0xA6CD5E9392000F6AC44BDFF4074EECDB),
1534            (3, 0x20EFC49FF02422EA54247382A8D6B94D),
1535            (4, 0x970D585AC632BF8E2E7D8D6876A39FE9),
1536            (8, 0x47A7F080D82BB45664C69CAB4BB21DC5),
1537            (16, 0xC68C368ECF8A9C05562980258A998629),
1538            (17, 0x955FA78643ED3669ABBC12D11973D7DB),
1539            (32, 0x98FC6458710DC2E8278410A17595E3F9),
1540            (240, 0xAA4202DAA2769DC85C9AAE94C8EBE5A0),
1541        ];
1542
1543        for &(len, expected) in sizes {
1544            let buf = fill_test_buffer(len);
1545            let mut h = Xxh3_128::new();
1546            h.update(&buf);
1547            assert_eq!(h.sum(), expected, "XXH128 boundary length {len}");
1548        }
1549    }
1550
1551    #[test]
1552    fn test_xh3_128_incremental() {
1553        let mut h = Xxh3_128::new();
1554        h.update(b"The quick brown ");
1555        h.update(b"fox jumps over ");
1556        h.update(b"the lazy dog");
1557        assert_eq!(h.sum(), 0xDDD650205CA3E7FA24A1CC2E3A8A7651);
1558    }
1559
1560    #[test]
1561    fn test_xh3_128_long_incremental() {
1562        let mut h = Xxh3_128::new();
1563        let data = [0x55u8; 512];
1564        h.update(&data[..200]);
1565        h.update(&data[200..400]);
1566        h.update(&data[400..]);
1567        assert_eq!(h.sum(), 0x0DD2485B0318DEF24C1155EA5825B659);
1568    }
1569
1570    /// The `const fn` one-shot produces the same result as the trait-based
1571    /// [`Checksum::checksum`] and is usable at compile time.
1572    #[test]
1573    fn test_const_fn_xh3_64() {
1574        assert_eq!(xxh3_64(b""), Xxh3_64::checksum(b""));
1575        assert_eq!(xxh3_64(b"hello"), Xxh3_64::checksum(b"hello"));
1576        assert_eq!(
1577            xxh3_64(b"The quick brown fox jumps over the lazy dog"),
1578            Xxh3_64::checksum(b"The quick brown fox jumps over the lazy dog")
1579        );
1580        // Test up to 240 bytes (below the streaming threshold where both
1581        // paths agree).
1582        let buf = &[0x42u8; 200];
1583        assert_eq!(xxh3_64(buf), Xxh3_64::checksum(buf));
1584    }
1585
1586    /// The `const fn` one-shot produces the same result as the trait-based
1587    /// [`Checksum::checksum`] and is usable at compile time.
1588    #[test]
1589    fn test_const_fn_xh3_128() {
1590        assert_eq!(xxh3_128(b""), Xxh3_128::checksum(b""));
1591        assert_eq!(xxh3_128(b"hello"), Xxh3_128::checksum(b"hello"));
1592        assert_eq!(
1593            xxh3_128(b"The quick brown fox jumps over the lazy dog"),
1594            Xxh3_128::checksum(b"The quick brown fox jumps over the lazy dog")
1595        );
1596        let buf = &[0x42u8; 200];
1597        assert_eq!(xxh3_128(buf), Xxh3_128::checksum(buf));
1598    }
1599
1600    // -----------------------------------------------------------------------
1601    // Reference vectors generated by the official C implementation
1602    // -----------------------------------------------------------------------
1603
1604    /// The custom secret used by the reference-vector tables.
1605    fn test_secret() -> [u8; 192] {
1606        let mut secret = [0u8; 192];
1607        secret.copy_from_slice(&fill_test_buffer(192));
1608        secret
1609    }
1610
1611    /// `Xxh3_*::with_seed()` must match the C reference at every boundary
1612    /// length, and the `const` (scalar) one-shot functions must agree with it.
1613    #[test]
1614    fn test_reference_vectors_with_seed() {
1615        for &(len, seed, h64, h128) in crate::test_vectors::XXH3_VECTORS {
1616            let buf = fill_test_buffer(len);
1617
1618            let mut hasher = Xxh3_64::with_seed(seed);
1619            hasher.update(&buf);
1620            assert_eq!(hasher.sum(), h64, "XXH3-64 len={len} seed={seed:#018x}");
1621
1622            let mut hasher = Xxh3_128::with_seed(seed);
1623            hasher.update(&buf);
1624            assert_eq!(hasher.sum(), h128, "XXH3-128 len={len} seed={seed:#018x}");
1625
1626            if seed == 0 {
1627                // The scalar `const` path and the (SIMD-dispatched) `checksum`
1628                // entry points must all agree with the reference.
1629                assert_eq!(xxh3_64(&buf), h64, "xxh3_64 const len={len}");
1630                assert_eq!(xxh3_128(&buf), h128, "xxh3_128 const len={len}");
1631                assert_eq!(Xxh3_64::checksum(&buf), h64, "checksum 64 len={len}");
1632                assert_eq!(Xxh3_128::checksum(&buf), h128, "checksum 128 len={len}");
1633            }
1634        }
1635    }
1636
1637    #[test]
1638    fn test_reference_vectors_with_secret() {
1639        let secret = test_secret();
1640        for &(len, h64, h128) in crate::test_vectors::XXH3_SECRET_VECTORS {
1641            let buf = fill_test_buffer(len);
1642
1643            let mut hasher = Xxh3_64::with_secret(secret);
1644            hasher.update(&buf);
1645            assert_eq!(hasher.sum(), h64, "XXH3-64 secret len={len}");
1646
1647            let mut hasher = Xxh3_128::with_secret(secret);
1648            hasher.update(&buf);
1649            assert_eq!(hasher.sum(), h128, "XXH3-128 secret len={len}");
1650        }
1651    }
1652
1653    #[test]
1654    fn test_reference_vectors_with_secret_and_seed() {
1655        let secret = test_secret();
1656        for &(len, seed, h64, h128) in crate::test_vectors::XXH3_SECRET_SEED_VECTORS {
1657            let buf = fill_test_buffer(len);
1658
1659            let mut hasher = Xxh3_64::with_seed_and_secret(seed, secret);
1660            hasher.update(&buf);
1661            assert_eq!(hasher.sum(), h64, "XXH3-64 secret+seed len={len} seed={seed:#018x}");
1662
1663            let mut hasher = Xxh3_128::with_seed_and_secret(seed, secret);
1664            hasher.update(&buf);
1665            assert_eq!(hasher.sum(), h128, "XXH3-128 secret+seed len={len} seed={seed:#018x}");
1666        }
1667    }
1668
1669    // -----------------------------------------------------------------------
1670    // Randomized / structural tests
1671    // -----------------------------------------------------------------------
1672
1673    /// A tiny deterministic PRNG, so the randomized tests are reproducible and
1674    /// need no dependency.
1675    struct Rng(u64);
1676
1677    impl Rng {
1678        fn next(&mut self) -> u64 {
1679            self.0 ^= self.0 << 13;
1680            self.0 ^= self.0 >> 7;
1681            self.0 ^= self.0 << 17;
1682            self.0
1683        }
1684
1685        fn below(&mut self, n: u64) -> u64 {
1686            self.next() % n
1687        }
1688    }
1689
1690    fn random_data(rng: &mut Rng, len: usize) -> alloc::vec::Vec<u8> {
1691        (0..len).map(|_| rng.next() as u8).collect()
1692    }
1693
1694    /// Incremental hashing must equal one-shot hashing for arbitrary inputs,
1695    /// whatever the split across `update()` calls.
1696    #[test]
1697    fn test_streaming_matches_one_shot_randomized() {
1698        let mut rng = Rng(0x243F6A8885A308D3);
1699        for _ in 0..4000 {
1700            let len = rng.below(3000) as usize;
1701            let data = random_data(&mut rng, len);
1702            let seed = rng.next();
1703
1704            // Build a random chunking of `data`.
1705            let mut chunks = alloc::vec::Vec::new();
1706            let mut left = len;
1707            let max_chunk = [1_u64, 3, 64, 65, 240, 241, 1024][rng.below(7) as usize];
1708            while left > 0 {
1709                let n = ((rng.below(max_chunk) + 1) as usize).min(left);
1710                chunks.push(n);
1711                left -= n;
1712            }
1713
1714            let mut hasher = Xxh3_64::with_seed(seed);
1715            let mut hasher128 = Xxh3_128::with_seed(seed);
1716            let mut offset = 0;
1717            for &n in &chunks {
1718                hasher.update(&data[offset..offset + n]);
1719                hasher128.update(&data[offset..offset + n]);
1720                offset += n;
1721            }
1722            let streamed64 = hasher.sum();
1723            let streamed128 = hasher128.sum();
1724
1725            let mut one_shot = Xxh3_64::with_seed(seed);
1726            one_shot.update(&data);
1727            assert_eq!(streamed64, one_shot.sum(), "XXH3-64 len={len} chunks={chunks:?}");
1728
1729            let mut one_shot = Xxh3_128::with_seed(seed);
1730            one_shot.update(&data);
1731            assert_eq!(streamed128, one_shot.sum(), "XXH3-128 len={len} chunks={chunks:?}");
1732        }
1733    }
1734
1735    /// Byte-at-a-time hashing agrees with the one-shot for every small length.
1736    #[test]
1737    fn test_byte_at_a_time_matches_one_shot() {
1738        for len in 0..=600 {
1739            let buf = fill_test_buffer(len);
1740
1741            let mut hasher = Xxh3_64::new();
1742            for byte in &buf {
1743                hasher.update(core::slice::from_ref(byte));
1744            }
1745            assert_eq!(hasher.sum(), xxh3_64(&buf), "XXH3-64 len={len}");
1746
1747            let mut hasher = Xxh3_128::new();
1748            for byte in &buf {
1749                hasher.update(core::slice::from_ref(byte));
1750            }
1751            assert_eq!(hasher.sum(), xxh3_128(&buf), "XXH3-128 len={len}");
1752        }
1753    }
1754
1755    /// Regression: a buffered 64-byte stripe used to be consumed without the
1756    /// per-block bookkeeping. That produced wrong hashes and could panic
1757    /// (`range end index ... out of range`) or, with the SIMD kernels, read past
1758    /// the secret.
1759    #[test]
1760    fn test_regression_flush_at_block_boundary() {
1761        for total in [241_usize, 256, 257, 320, 512, 1024, 1025, 1088, 1089, 1152, 2048, 2049] {
1762            let buf = fill_test_buffer(total);
1763            for split in [1_usize, 63, 64, 65, 240, 241, 256] {
1764                if split >= total {
1765                    continue;
1766                }
1767
1768                let mut hasher = Xxh3_64::new();
1769                hasher.update(&buf[..split]);
1770                hasher.update(&buf[split..]);
1771                assert_eq!(hasher.sum(), Xxh3_64::checksum(&buf), "total={total} split={split}");
1772
1773                let mut hasher = Xxh3_128::new();
1774                hasher.update(&buf[..split]);
1775                hasher.update(&buf[split..]);
1776                assert_eq!(hasher.sum(), Xxh3_128::checksum(&buf), "total={total} split={split}");
1777            }
1778        }
1779    }
1780
1781    /// Mirrors hashing a long SQL statement followed by many 4-byte parameter
1782    /// OIDs (see `postgresql`'s statement cache). This used to panic — or read
1783    /// past the secret — after enough small updates.
1784    #[test]
1785    fn test_many_small_updates_after_long_prefix() {
1786        let sql = fill_test_buffer(300);
1787        let mut hasher = Xxh3_128::new();
1788        hasher.update(&sql);
1789        let mut scratch = sql.clone();
1790        for i in 0..500u32 {
1791            let oid = (2500 + i).to_be_bytes();
1792            hasher.update(&oid);
1793            scratch.extend_from_slice(&oid);
1794        }
1795        assert_eq!(hasher.sum(), xxh3_128(&scratch));
1796    }
1797
1798    /// Empty `update` calls never change the result.
1799    #[test]
1800    fn test_empty_updates_are_noop() {
1801        let buf = fill_test_buffer(500);
1802        let mut hasher = Xxh3_64::new();
1803        hasher.update(&[]);
1804        hasher.update(&buf);
1805        hasher.update(&[]);
1806        assert_eq!(hasher.sum(), Xxh3_64::checksum(&buf));
1807    }
1808
1809    /// The runtime-dispatched (SIMD when available) long path must agree with
1810    /// the scalar `const` path bit for bit.
1811    #[test]
1812    fn test_dispatched_matches_scalar() {
1813        for len in [
1814            241_usize, 255, 256, 257, 300, 512, 1023, 1024, 1025, 2048, 2049, 4096, 5000, 8192, 10000,
1815        ] {
1816            let buf = fill_test_buffer(len);
1817            assert_eq!(Xxh3_64::checksum(&buf), xxh3_64(&buf), "XXH3-64 len={len}");
1818            assert_eq!(Xxh3_128::checksum(&buf), xxh3_128(&buf), "XXH3-128 len={len}");
1819        }
1820    }
1821
1822    /// Hashing must perform no heap allocation and must not leak.
1823    #[cfg(not(target_arch = "wasm32"))]
1824    #[test]
1825    fn test_hashing_does_not_allocate() {
1826        let small = fill_test_buffer(240);
1827        let large = fill_test_buffer(4096);
1828        let secret = test_secret();
1829
1830        let ((), counters) = alloc_counter::measure(|| {
1831            let _ = core::hint::black_box(xxh3_64(core::hint::black_box(&small)));
1832            let _ = core::hint::black_box(xxh3_128(core::hint::black_box(&large)));
1833
1834            let mut hasher = Xxh3_64::with_seed(42);
1835            hasher.update(core::hint::black_box(&large));
1836            let _ = core::hint::black_box(hasher.sum());
1837
1838            let mut hasher = Xxh3_128::with_secret(secret);
1839            for chunk in large.chunks(7) {
1840                hasher.update(core::hint::black_box(chunk));
1841            }
1842            let _ = core::hint::black_box(hasher.sum());
1843
1844            let mut hasher = Xxh3_64::new();
1845            hasher.update(core::hint::black_box(&large));
1846            let _ = core::hint::black_box(hasher.sum());
1847        });
1848
1849        assert_eq!(counters.calls, 0, "hashing must not allocate");
1850        assert_eq!(counters.live_allocations, 0, "hashing must not leak");
1851    }
1852}