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
14const STRIPE_LEN: usize = 64;
15const 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
24/// The default 192-byte secret used by XXH3.
25const DEFAULT_SECRET: [u8; 192] = [
26    0xb8, 0xfe, 0x6c, 0x39, 0x23, 0xa4, 0x4b, 0xbe, 0x7c, 0x01, 0x81, 0x2c, 0xf7, 0x21, 0xad, 0x1c, 0xde, 0xd4, 0x6d,
27    0xe9, 0x83, 0x90, 0x97, 0xdb, 0x72, 0x40, 0xa4, 0xa4, 0xb7, 0xb3, 0x67, 0x1f, 0xcb, 0x79, 0xe6, 0x4e, 0xcc, 0xc0,
28    0xe5, 0x78, 0x82, 0x5a, 0xd0, 0x7d, 0xcc, 0xff, 0x72, 0x21, 0xb8, 0x08, 0x46, 0x74, 0xf7, 0x43, 0x24, 0x8e, 0xe0,
29    0x35, 0x90, 0xe6, 0x81, 0x3a, 0x26, 0x4c, 0x3c, 0x28, 0x52, 0xbb, 0x91, 0xc3, 0x00, 0xcb, 0x88, 0xd0, 0x65, 0x8b,
30    0x1b, 0x53, 0x2e, 0xa3, 0x71, 0x64, 0x48, 0x97, 0xa2, 0x0d, 0xf9, 0x4e, 0x38, 0x19, 0xef, 0x46, 0xa9, 0xde, 0xac,
31    0xd8, 0xa8, 0xfa, 0x76, 0x3f, 0xe3, 0x9c, 0x34, 0x3f, 0xf9, 0xdc, 0xbb, 0xc7, 0xc7, 0x0b, 0x4f, 0x1d, 0x8a, 0x51,
32    0xe0, 0x4b, 0xcd, 0xb4, 0x59, 0x31, 0xc8, 0x9f, 0x7e, 0xc9, 0xd9, 0x78, 0x73, 0x64, 0xea, 0xc5, 0xac, 0x83, 0x34,
33    0xd3, 0xeb, 0xc3, 0xc5, 0x81, 0xa0, 0xff, 0xfa, 0x13, 0x63, 0xeb, 0x17, 0x0d, 0xdd, 0x51, 0xb7, 0xf0, 0xda, 0x49,
34    0xd3, 0x16, 0x55, 0x26, 0x29, 0xd4, 0x68, 0x9e, 0x2b, 0x16, 0xbe, 0x58, 0x7d, 0x47, 0xa1, 0xfc, 0x8f, 0xf8, 0xb8,
35    0xd1, 0x7a, 0xd0, 0x31, 0xce, 0x45, 0xcb, 0x3a, 0x8f, 0x95, 0x16, 0x04, 0x28, 0xaf, 0xd7, 0xfb, 0xca, 0xbb, 0x4b,
36    0x40, 0x7e,
37];
38
39const INITIAL_ACC: [u64; ACC_NB] = [
40    PRIME32_3,
41    PRIME64_1,
42    PRIME64_2,
43    0x165667B19E3779F9, // PRIME64_3
44    0x85EBCA77C2B2AE63, // PRIME64_4
45    0x85EBCA77,         // PRIME32_2
46    PRIME64_5,
47    PRIME32_1,
48];
49
50// ---------------------------------------------------------------------------
51// Low-level helpers
52// ---------------------------------------------------------------------------
53#[inline]
54const fn read_64le(data: &[u8], offset: usize) -> u64 {
55    let (_, rest) = data.split_at(offset);
56    let arr = match rest.split_first_chunk::<8>() {
57        Some((arr, _)) => arr,
58        None => panic!("read_64le: out of bounds"),
59    };
60    u64::from_le_bytes(*arr)
61}
62
63#[inline]
64const fn read_32le(data: &[u8], offset: usize) -> u32 {
65    let (_, rest) = data.split_at(offset);
66    let arr = match rest.split_first_chunk::<4>() {
67        Some((arr, _)) => arr,
68        None => panic!("read_32le: out of bounds"),
69    };
70    u32::from_le_bytes(*arr)
71}
72#[inline]
73const fn xorshift64(value: u64, shift: u64) -> u64 {
74    value ^ (value >> shift)
75}
76
77#[inline]
78const fn avalanche(mut h: u64) -> u64 {
79    h = xorshift64(h, 37);
80    h = h.wrapping_mul(0x165667919E3779F9);
81    xorshift64(h, 32)
82}
83
84/// XXH64 avalanche, used in the 1to3 and empty 0to16 paths.
85const fn xxh64_avalanche(mut h: u64) -> u64 {
86    h ^= h >> 33;
87    h = h.wrapping_mul(PRIME64_2);
88    h ^= h >> 29;
89    h = h.wrapping_mul(0x165667B19E3779F9);
90    h ^= h >> 32;
91    h
92}
93
94#[inline]
95const fn strong_avalanche(mut value: u64, len: u64) -> u64 {
96    value ^= value.rotate_left(49) ^ value.rotate_left(24);
97    value = value.wrapping_mul(0x9FB21C651E98DF25);
98    value ^= (value >> 35).wrapping_add(len);
99    value = value.wrapping_mul(0x9FB21C651E98DF25);
100    xorshift64(value, 28)
101}
102
103#[inline]
104const fn mul128_fold64(l: u64, r: u64) -> u64 {
105    let p = (l as u128).wrapping_mul(r as u128);
106    (p as u64) ^ ((p >> 64) as u64)
107}
108
109#[inline]
110const fn mult32_to64(left: u32, right: u32) -> u64 {
111    (left as u64).wrapping_mul(right as u64)
112}
113
114#[inline]
115const fn mix16b(input: &[u8], input_offset: usize, secret: &[u8], secret_offset: usize, seed: u64) -> u64 {
116    let mut input_lo = read_64le(input, input_offset);
117    let mut input_hi = read_64le(input, input_offset + 8);
118
119    input_lo ^= read_64le(secret, secret_offset).wrapping_add(seed);
120    input_hi ^= read_64le(secret, secret_offset + 8).wrapping_sub(seed);
121
122    mul128_fold64(input_lo, input_hi)
123}
124
125#[inline]
126const fn mix_two_accs(acc: &[u64; ACC_NB], offset: usize, secret: &[u8], sec_off: usize) -> u64 {
127    mul128_fold64(
128        acc[offset] ^ read_64le(secret, sec_off),
129        acc[offset + 1] ^ read_64le(secret, sec_off + 8),
130    )
131}
132
133#[inline]
134const fn merge_accs(acc: &[u64; ACC_NB], secret: &[u8], start_offset: usize, mut result: u64) -> u64 {
135    let mut i = 0;
136    while i < 4 {
137        result = result.wrapping_add(mix_two_accs(acc, i * 2, secret, start_offset + i * 16));
138        i += 1;
139    }
140    avalanche(result)
141}
142
143// ---------------------------------------------------------------------------
144// Core XXH3 accumulate / scramble
145// ---------------------------------------------------------------------------
146
147#[inline]
148const fn accumulate_512_scalar(
149    acc: &mut [u64; ACC_NB],
150    input: &[u8],
151    input_off: usize,
152    secret: &[u8],
153    secret_off: usize,
154) {
155    let mut i = 0;
156    while i < ACC_NB {
157        let data_val = read_64le(input, input_off + i * 8);
158        let data_key = data_val ^ read_64le(secret, secret_off + i * 8);
159        acc[i ^ 1] = acc[i ^ 1].wrapping_add(data_val);
160        acc[i] = acc[i].wrapping_add(mult32_to64((data_key & 0xFFFFFFFF) as u32, (data_key >> 32) as u32));
161        i += 1;
162    }
163}
164
165#[inline]
166const fn accumulate_loop_scalar(
167    acc: &mut [u64; ACC_NB],
168    input: &[u8],
169    input_off: usize,
170    secret: &[u8],
171    secret_off: usize,
172    nb_stripes: usize,
173) {
174    let mut i = 0;
175    while i < nb_stripes {
176        accumulate_512_scalar(
177            acc,
178            input,
179            input_off + i * STRIPE_LEN,
180            secret,
181            secret_off + i * SECRET_CONSUME_RATE,
182        );
183        i += 1;
184    }
185}
186
187#[inline]
188const fn scramble_acc_scalar(acc: &mut [u64; ACC_NB], secret: &[u8], secret_off: usize) {
189    let mut i = 0;
190    while i < ACC_NB {
191        let key = read_64le(secret, secret_off + i * 8);
192        let mut val = xorshift64(acc[i], 47);
193        val ^= key;
194        acc[i] = val.wrapping_mul(PRIME32_1);
195        i += 1;
196    }
197}
198
199#[inline]
200const fn hash_long_internal_loop(acc: &mut [u64; ACC_NB], input: &[u8], secret: &[u8]) {
201    let nb_stripes = STRIPES_PER_BLOCK;
202    let block_len = STRIPE_LEN * nb_stripes;
203    let nb_blocks = (input.len() - 1) / block_len;
204
205    let mut i = 0;
206    while i < nb_blocks {
207        accumulate_loop_scalar(acc, input, i * block_len, secret, 0, nb_stripes);
208        scramble_acc_scalar(acc, secret, secret.len() - STRIPE_LEN);
209        i += 1;
210    }
211
212    // Last partial block
213    let nb_stripes = ((input.len() - 1) - (block_len * nb_blocks)) / STRIPE_LEN;
214    accumulate_loop_scalar(acc, input, nb_blocks * block_len, secret, 0, nb_stripes);
215
216    // Last stripe
217    let last_stripe_start = input.len() - STRIPE_LEN;
218    let last_secret_off = secret.len() - STRIPE_LEN - SECRET_LASTACC_START;
219    accumulate_512_scalar(acc, input, last_stripe_start, secret, last_secret_off);
220}
221
222// ---------------------------------------------------------------------------
223// Custom default secret generation for seeded hashing
224// ---------------------------------------------------------------------------
225
226const fn custom_default_secret_scalar(seed: u64) -> [u8; DEFAULT_SECRET_SIZE] {
227    let mut result = [0u8; DEFAULT_SECRET_SIZE];
228    let nb_rounds = DEFAULT_SECRET_SIZE / 16;
229    let mut i = 0;
230    while i < nb_rounds {
231        let lo = read_64le(&DEFAULT_SECRET, i * 16).wrapping_add(seed);
232        let hi = read_64le(&DEFAULT_SECRET, i * 16 + 8).wrapping_sub(seed);
233        let lo_bytes = lo.to_le_bytes();
234        let hi_bytes = hi.to_le_bytes();
235        let mut j = 0;
236        while j < 8 {
237            result[i * 16 + j] = lo_bytes[j];
238            result[i * 16 + 8 + j] = hi_bytes[j];
239            j += 1;
240        }
241        i += 1;
242    }
243    result
244}
245
246// ---------------------------------------------------------------------------
247// Dispatch wrappers — select SIMD or scalar at compile / run time.
248// On aarch64  NEON is always available (compile-time dispatch).
249// On x86_64   Uses AVX512 when available, otherwise AVX2 (compile-time dispatch).
250// ---------------------------------------------------------------------------
251
252#[inline]
253#[allow(unreachable_code)]
254fn accumulate_512(acc: &mut [u64; ACC_NB], input: &[u8], input_off: usize, secret: &[u8], secret_off: usize) {
255    #[cfg(all(target_arch = "wasm32", target_feature = "simd128"))]
256    {
257        crate::xxh3_wasm_simd128::accumulate_512(acc, input, input_off, secret, secret_off);
258        return;
259    }
260
261    #[cfg(all(target_arch = "aarch64", target_feature = "neon"))]
262    {
263        unsafe { crate::xxh3_neon::accumulate_512(acc, input, input_off, secret, secret_off) };
264        return;
265    }
266
267    #[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), target_feature = "avx512f"))]
268    {
269        unsafe { crate::xxh3_avx512::accumulate_512(acc, input, input_off, secret, secret_off) };
270        return;
271    }
272
273    #[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), target_feature = "avx2"))]
274    {
275        unsafe { crate::xxh3_avx2::accumulate_512(acc, input, input_off, secret, secret_off) };
276        return;
277    }
278
279    accumulate_512_scalar(acc, input, input_off, secret, secret_off);
280}
281
282#[inline]
283#[allow(unreachable_code)]
284fn accumulate_loop(
285    acc: &mut [u64; ACC_NB],
286    input: &[u8],
287    input_off: usize,
288    secret: &[u8],
289    secret_off: usize,
290    nb_stripes: usize,
291) {
292    let mut idx = 0;
293    while idx < nb_stripes {
294        accumulate_512(
295            acc,
296            input,
297            input_off + idx * STRIPE_LEN,
298            secret,
299            secret_off + idx * SECRET_CONSUME_RATE,
300        );
301        idx += 1;
302    }
303}
304
305#[inline]
306#[allow(unreachable_code)]
307fn scramble_acc(acc: &mut [u64; ACC_NB], secret: &[u8], secret_off: usize) {
308    #[cfg(all(target_arch = "wasm32", target_feature = "simd128"))]
309    {
310        crate::xxh3_wasm_simd128::scramble_acc(acc, secret, secret_off);
311        return;
312    }
313
314    #[cfg(all(target_arch = "aarch64", target_feature = "neon"))]
315    {
316        unsafe { crate::xxh3_neon::scramble_acc(acc, secret, secret_off) };
317        return;
318    }
319
320    #[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), target_feature = "avx512f"))]
321    {
322        unsafe { crate::xxh3_avx512::scramble_acc(acc, secret, secret_off) };
323        return;
324    }
325
326    #[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), target_feature = "avx2"))]
327    {
328        unsafe { crate::xxh3_avx2::scramble_acc(acc, secret, secret_off) };
329        return;
330    }
331
332    scramble_acc_scalar(acc, secret, secret_off);
333}
334
335#[inline]
336fn custom_default_secret(seed: u64) -> [u8; DEFAULT_SECRET_SIZE] {
337    custom_default_secret_scalar(seed)
338}
339
340// ---------------------------------------------------------------------------
341// One-shot: 0..16 bytes
342// ---------------------------------------------------------------------------
343
344const fn xxh3_64_1to3(input: &[u8], seed: u64, secret: &[u8]) -> u64 {
345    let len = input.len();
346    let c1 = input[0] as u32;
347    let c2 = input[len >> 1] as u32;
348    let c3 = input[len - 1] as u32;
349    let combined = (c1 << 16) | (c2 << 24) | (c3) | ((len as u32) << 8);
350    let flip = ((read_32le(secret, 0) ^ read_32le(secret, 4)) as u64).wrapping_add(seed);
351    xxh64_avalanche((combined as u64) ^ flip)
352}
353
354const fn xxh3_64_4to8(input: &[u8], mut seed: u64, secret: &[u8]) -> u64 {
355    seed ^= ((seed as u32).swap_bytes() as u64) << 32;
356    let len = input.len();
357    let input1 = read_32le(input, 0);
358    let input2 = read_32le(input, len - 4);
359    let flip = (read_64le(secret, 8) ^ read_64le(secret, 16)).wrapping_sub(seed);
360    let input64 = (input2 as u64).wrapping_add((input1 as u64) << 32);
361    strong_avalanche(input64 ^ flip, len as u64)
362}
363
364const fn xxh3_64_9to16(input: &[u8], seed: u64, secret: &[u8]) -> u64 {
365    let len = input.len();
366    let flip1 = (read_64le(secret, 24) ^ read_64le(secret, 32)).wrapping_add(seed);
367    let flip2 = (read_64le(secret, 40) ^ read_64le(secret, 48)).wrapping_sub(seed);
368    let input_lo = read_64le(input, 0) ^ flip1;
369    let input_hi = read_64le(input, len - 8) ^ flip2;
370    let acc = (len as u64)
371        .wrapping_add(input_lo.swap_bytes())
372        .wrapping_add(input_hi)
373        .wrapping_add(mul128_fold64(input_lo, input_hi));
374    avalanche(acc)
375}
376
377const fn xxh3_64_0to16(input: &[u8], seed: u64, secret: &[u8]) -> u64 {
378    if input.len() > 8 {
379        xxh3_64_9to16(input, seed, secret)
380    } else if input.len() >= 4 {
381        xxh3_64_4to8(input, seed, secret)
382    } else if !input.is_empty() {
383        xxh3_64_1to3(input, seed, secret)
384    } else {
385        xxh64_avalanche(seed ^ read_64le(secret, 56) ^ read_64le(secret, 64))
386    }
387}
388
389// ---------------------------------------------------------------------------
390// One-shot: 17..128 bytes
391// ---------------------------------------------------------------------------
392
393const fn xxh3_64_7to128(input: &[u8], seed: u64, secret: &[u8]) -> u64 {
394    let len = input.len();
395    let mut acc = (len as u64).wrapping_mul(PRIME64_1);
396
397    if len > 32 {
398        if len > 64 {
399            if len > 96 {
400                acc = acc.wrapping_add(mix16b(input, 48, secret, 96, seed));
401                acc = acc.wrapping_add(mix16b(input, len - 64, secret, 112, seed));
402            }
403            acc = acc.wrapping_add(mix16b(input, 32, secret, 64, seed));
404            acc = acc.wrapping_add(mix16b(input, len - 48, secret, 80, seed));
405        }
406        acc = acc.wrapping_add(mix16b(input, 16, secret, 32, seed));
407        acc = acc.wrapping_add(mix16b(input, len - 32, secret, 48, seed));
408    }
409
410    acc = acc.wrapping_add(mix16b(input, 0, secret, 0, seed));
411    acc = acc.wrapping_add(mix16b(input, len - 16, secret, 16, seed));
412
413    avalanche(acc)
414}
415
416// ---------------------------------------------------------------------------
417// One-shot: 129..240 bytes
418// ---------------------------------------------------------------------------
419
420const fn xxh3_64_129to240(input: &[u8], seed: u64, secret: &[u8]) -> u64 {
421    let len = input.len();
422    let mut acc = (len as u64).wrapping_mul(PRIME64_1);
423    let nb_rounds = len / 16;
424
425    let mut i = 0;
426    while i < 8 {
427        acc = acc.wrapping_add(mix16b(input, i * 16, secret, i * 16, seed));
428        i += 1;
429    }
430    acc = avalanche(acc);
431
432    i = 8;
433    while i < nb_rounds {
434        acc = acc.wrapping_add(mix16b(input, i * 16, secret, (i - 8) * 16 + 3, seed));
435        i += 1;
436    }
437
438    acc = acc.wrapping_add(mix16b(input, len - 16, secret, SECRET_SIZE_MIN - 17, seed));
439
440    avalanche(acc)
441}
442
443// ---------------------------------------------------------------------------
444// Long path: >240 bytes (one-shot)
445// ---------------------------------------------------------------------------
446
447const fn xxh3_64_long_impl(input: &[u8], secret: &[u8]) -> u64 {
448    let mut acc = INITIAL_ACC;
449    hash_long_internal_loop(&mut acc, input, secret);
450    merge_accs(
451        &acc,
452        secret,
453        SECRET_MERGEACCS_START,
454        (input.len() as u64).wrapping_mul(PRIME64_1),
455    )
456}
457
458const fn xxh3_64_long_with_seed(input: &[u8], seed: u64, secret: &[u8]) -> u64 {
459    if seed == 0 {
460        xxh3_64_long_impl(input, secret)
461    } else {
462        xxh3_64_long_impl(input, &custom_default_secret_scalar(seed))
463    }
464}
465
466const fn xxh3_64_one_shot(input: &[u8], seed: u64, secret: &[u8]) -> u64 {
467    if input.len() <= 16 {
468        xxh3_64_0to16(input, seed, secret)
469    } else if input.len() <= 128 {
470        xxh3_64_7to128(input, seed, secret)
471    } else if input.len() <= MID_SIZE_MAX {
472        xxh3_64_129to240(input, seed, secret)
473    } else {
474        xxh3_64_long_with_seed(input, seed, secret)
475    }
476}
477
478// ---------------------------------------------------------------------------
479// 128-bit: helper
480// ---------------------------------------------------------------------------
481
482#[inline(always)]
483#[allow(clippy::too_many_arguments)]
484const fn mix32_b(
485    lo: &mut u64,
486    hi: &mut u64,
487    input: &[u8],
488    input1_off: usize,
489    input2_off: usize,
490    secret: &[u8],
491    secret_off: usize,
492    seed: u64,
493) {
494    *lo = lo.wrapping_add(mix16b(input, input1_off, secret, secret_off, seed));
495    *lo ^= read_64le(input, input2_off).wrapping_add(read_64le(input, input2_off + 8));
496    *hi = hi.wrapping_add(mix16b(input, input2_off, secret, secret_off + 16, seed));
497    *hi ^= read_64le(input, input1_off).wrapping_add(read_64le(input, input1_off + 8));
498}
499
500// ---------------------------------------------------------------------------
501// 128-bit: short paths (return u128)
502// ---------------------------------------------------------------------------
503
504const fn xxh3_128_1to3(input: &[u8], seed: u64, secret: &[u8]) -> u128 {
505    let len = input.len();
506    let c1 = input[0] as u32;
507    let c2 = input[len >> 1] as u32;
508    let c3 = input[len - 1] as u32;
509    let combinedl = ((c1) << 16) | (c2 << 24) | (c3) | ((len as u32) << 8);
510    let combinedh = combinedl.swap_bytes().rotate_left(13);
511    let bitflipl = ((read_32le(secret, 0) ^ read_32le(secret, 4)) as u64).wrapping_add(seed);
512    let bitfliph = ((read_32le(secret, 8) ^ read_32le(secret, 12)) as u64).wrapping_sub(seed);
513    let keyed_lo = (combinedl as u64) ^ bitflipl;
514    let keyed_hi = (combinedh as u64) ^ bitfliph;
515    ((xxh64_avalanche(keyed_hi) as u128) << 64) | (xxh64_avalanche(keyed_lo) as u128)
516}
517
518const fn xxh3_128_9to16(input: &[u8], seed: u64, secret: &[u8]) -> u128 {
519    let len = input.len();
520    let bitflipl = (read_64le(secret, 32) ^ read_64le(secret, 40)).wrapping_sub(seed);
521    let bitfliph = (read_64le(secret, 48) ^ read_64le(secret, 56)).wrapping_add(seed);
522    let input_lo = read_64le(input, 0);
523    let mut input_hi = read_64le(input, len - 8);
524
525    let m128_full = (input_lo ^ input_hi ^ bitflipl) as u128 * PRIME64_1 as u128;
526    let mut m128_lo = m128_full as u64;
527    let mut m128_hi = (m128_full >> 64) as u64;
528
529    m128_lo = m128_lo.wrapping_add(((len - 1) as u64) << 54);
530    input_hi ^= bitfliph;
531    m128_hi = m128_hi
532        .wrapping_add(input_hi)
533        .wrapping_add(mult32_to64(input_hi as u32, 0x85EBCA76u32));
534
535    m128_lo ^= m128_hi.swap_bytes();
536
537    let h128_full = (m128_lo as u128).wrapping_mul(PRIME64_2 as u128);
538    let h128_lo = h128_full as u64;
539    let h128_hi = ((h128_full >> 64) as u64).wrapping_add(m128_hi.wrapping_mul(PRIME64_2));
540
541    ((avalanche(h128_hi) as u128) << 64) | (avalanche(h128_lo) as u128)
542}
543
544const fn xxh3_128_4to8_return(input: &[u8], mut seed: u64, secret: &[u8]) -> u128 {
545    seed ^= ((seed as u32).swap_bytes() as u64) << 32;
546    let len = input.len();
547    let input_lo = read_32le(input, 0);
548    let input_hi = read_32le(input, len - 4);
549    let input_64 = (input_lo as u64).wrapping_add((input_hi as u64) << 32);
550    let bitflip = (read_64le(secret, 16) ^ read_64le(secret, 24)).wrapping_add(seed);
551    let keyed = input_64 ^ bitflip;
552    let m128 = (keyed as u128).wrapping_mul(PRIME64_1.wrapping_add((len as u64) << 2) as u128);
553    let mut m128_lo = m128 as u64;
554    let mut m128_hi = (m128 >> 64) as u64;
555    m128_hi = m128_hi.wrapping_add(m128_lo << 1);
556    m128_lo ^= m128_hi >> 3;
557    m128_lo = xorshift64(m128_lo, 35);
558    m128_lo = m128_lo.wrapping_mul(0x9FB21C651E98DF25);
559    m128_lo = xorshift64(m128_lo, 28);
560    m128_hi = avalanche(m128_hi);
561    ((m128_hi as u128) << 64) | (m128_lo as u128)
562}
563
564const fn xxh3_128_0to16(input: &[u8], seed: u64, secret: &[u8]) -> u128 {
565    if input.len() > 8 {
566        xxh3_128_9to16(input, seed, secret)
567    } else if input.len() >= 4 {
568        xxh3_128_4to8_return(input, seed, secret)
569    } else if !input.is_empty() {
570        xxh3_128_1to3(input, seed, secret)
571    } else {
572        let flip_lo = read_64le(secret, 64) ^ read_64le(secret, 72);
573        let flip_hi = read_64le(secret, 80) ^ read_64le(secret, 88);
574        (xxh64_avalanche(seed ^ flip_lo) as u128) | ((xxh64_avalanche(seed ^ flip_hi) as u128) << 64)
575    }
576}
577
578// ---------------------------------------------------------------------------
579// 128-bit: medium paths (17..128), (129..240) — return u128
580// ---------------------------------------------------------------------------
581
582const fn xxh3_128_7to128(input: &[u8], seed: u64, secret: &[u8]) -> u128 {
583    let len = input.len();
584    let mut lo = (len as u64).wrapping_mul(PRIME64_1);
585    let mut hi: u64 = 0;
586
587    if len > 32 {
588        if len > 64 {
589            if len > 96 {
590                mix32_b(&mut lo, &mut hi, input, 48, len - 64, secret, 96, seed);
591            }
592            mix32_b(&mut lo, &mut hi, input, 32, len - 48, secret, 64, seed);
593        }
594        mix32_b(&mut lo, &mut hi, input, 16, len - 32, secret, 32, seed);
595    }
596
597    mix32_b(&mut lo, &mut hi, input, 0, len - 16, secret, 0, seed);
598
599    (avalanche(lo.wrapping_add(hi)) as u128)
600        | ((0u64.wrapping_sub(avalanche(
601            lo.wrapping_mul(PRIME64_1)
602                .wrapping_add(hi.wrapping_mul(0x85EBCA77C2B2AE63))
603                .wrapping_add((len as u64).wrapping_sub(seed).wrapping_mul(PRIME64_2)),
604        )) as u128)
605            << 64)
606}
607
608const fn xxh3_128_129to240(input: &[u8], seed: u64, secret: &[u8]) -> u128 {
609    let len = input.len();
610    let nb_rounds = len / 32;
611    let mut lo = (len as u64).wrapping_mul(PRIME64_1);
612    let mut hi: u64 = 0;
613
614    let mut i = 0;
615    while i < 4 {
616        let offset = 32 * i;
617        mix32_b(&mut lo, &mut hi, input, offset, offset + 16, secret, offset, seed);
618        i += 1;
619    }
620
621    lo = avalanche(lo);
622    hi = avalanche(hi);
623
624    i = 4;
625    while i < nb_rounds {
626        mix32_b(&mut lo, &mut hi, input, 32 * i, 32 * i + 16, secret, 3 + 32 * (i - 4), seed);
627        i += 1;
628    }
629
630    mix32_b(
631        &mut lo,
632        &mut hi,
633        input,
634        len - 16,
635        len - 32,
636        secret,
637        SECRET_SIZE_MIN - 17 - 16,
638        0u64.wrapping_sub(seed),
639    );
640
641    (avalanche(lo.wrapping_add(hi)) as u128)
642        | ((0u64.wrapping_sub(avalanche(
643            lo.wrapping_mul(PRIME64_1)
644                .wrapping_add(hi.wrapping_mul(0x85EBCA77C2B2AE63))
645                .wrapping_add((len as u64).wrapping_sub(seed).wrapping_mul(PRIME64_2)),
646        )) as u128)
647            << 64)
648}
649
650// ---------------------------------------------------------------------------
651// Long path: 128-bit (>240 bytes)
652// ---------------------------------------------------------------------------
653
654const fn xxh3_128_long_impl(input: &[u8], secret: &[u8]) -> u128 {
655    let mut acc = INITIAL_ACC;
656    hash_long_internal_loop(&mut acc, input, secret);
657
658    let lo = merge_accs(
659        &acc,
660        secret,
661        SECRET_MERGEACCS_START,
662        (input.len() as u64).wrapping_mul(PRIME64_1),
663    );
664    let hi = merge_accs(
665        &acc,
666        secret,
667        secret.len() - ACC_NB * 8 - SECRET_MERGEACCS_START,
668        !(input.len() as u64).wrapping_mul(PRIME64_2),
669    );
670
671    (lo as u128) | ((hi as u128) << 64)
672}
673
674const fn xxh3_128_long_with_seed(input: &[u8], seed: u64, secret: &[u8]) -> u128 {
675    if seed == 0 {
676        xxh3_128_long_impl(input, secret)
677    } else {
678        xxh3_128_long_impl(input, &custom_default_secret_scalar(seed))
679    }
680}
681
682const fn xxh3_128_one_shot(input: &[u8], seed: u64, secret: &[u8]) -> u128 {
683    if input.len() <= 16 {
684        xxh3_128_0to16(input, seed, secret)
685    } else if input.len() <= 128 {
686        xxh3_128_7to128(input, seed, secret)
687    } else if input.len() <= MID_SIZE_MAX {
688        xxh3_128_129to240(input, seed, secret)
689    } else {
690        xxh3_128_long_with_seed(input, seed, secret)
691    }
692}
693
694// ---------------------------------------------------------------------------
695// Streaming state for long inputs (>240 bytes)
696// ---------------------------------------------------------------------------
697
698#[derive(Clone)]
699struct LongState {
700    acc: [u64; ACC_NB],
701    secret: [u8; DEFAULT_SECRET_SIZE],
702    buf: [u8; STRIPE_LEN],
703    buf_len: u8,
704    nb_stripes_acc: usize,
705    total_len: u64,
706}
707
708impl LongState {
709    fn new(secret: &[u8; DEFAULT_SECRET_SIZE]) -> Self {
710        LongState {
711            acc: INITIAL_ACC,
712            secret: *secret,
713            buf: [0u8; STRIPE_LEN],
714            buf_len: 0,
715            nb_stripes_acc: 0,
716            total_len: 0,
717        }
718    }
719
720    fn update(&mut self, mut data: &[u8]) {
721        self.total_len += data.len() as u64;
722
723        if self.buf_len > 0 {
724            let take = (STRIPE_LEN - self.buf_len as usize).min(data.len());
725            self.buf[self.buf_len as usize..self.buf_len as usize + take].copy_from_slice(&data[..take]);
726            self.buf_len += take as u8;
727            data = &data[take..];
728            if self.buf_len as usize == STRIPE_LEN {
729                accumulate_512(
730                    &mut self.acc,
731                    &self.buf,
732                    0,
733                    &self.secret,
734                    self.nb_stripes_acc * SECRET_CONSUME_RATE,
735                );
736                self.nb_stripes_acc += 1;
737                self.buf_len = 0;
738            }
739        }
740
741        // Process full stripes, leaving enough room for the last stripe
742        // in the buffer (matches C reference's (len - 1) / STRIPE_LEN logic)
743        while data.len() > STRIPE_LEN {
744            let nb_stripes = (data.len() - 1) / STRIPE_LEN;
745            let nb = nb_stripes.min(STRIPES_PER_BLOCK - self.nb_stripes_acc);
746            if nb == 0 {
747                break;
748            }
749            accumulate_loop(
750                &mut self.acc,
751                data,
752                0,
753                &self.secret,
754                self.nb_stripes_acc * SECRET_CONSUME_RATE,
755                nb,
756            );
757            self.nb_stripes_acc += nb;
758            if self.nb_stripes_acc >= STRIPES_PER_BLOCK {
759                scramble_acc(&mut self.acc, &self.secret, DEFAULT_SECRET_SIZE - STRIPE_LEN);
760                self.nb_stripes_acc = 0;
761            }
762            data = &data[nb * STRIPE_LEN..];
763        }
764
765        if !data.is_empty() {
766            self.buf[..data.len()].copy_from_slice(data);
767            self.buf_len = data.len() as u8;
768        }
769    }
770
771    fn sum_64b(self) -> u64 {
772        let mut acc = self.acc;
773
774        if self.buf_len > 0 {
775            let mut last_stripe = [0u8; STRIPE_LEN];
776            last_stripe[..self.buf_len as usize].copy_from_slice(&self.buf[..self.buf_len as usize]);
777            let copy_len = (SECRET_LASTACC_START).min(STRIPE_LEN - self.buf_len as usize);
778            let secret_tail_start = DEFAULT_SECRET_SIZE - SECRET_LASTACC_START;
779            last_stripe[STRIPE_LEN - SECRET_LASTACC_START..STRIPE_LEN - SECRET_LASTACC_START + copy_len]
780                .copy_from_slice(&self.secret[secret_tail_start..secret_tail_start + copy_len]);
781            let sec_off = DEFAULT_SECRET_SIZE - STRIPE_LEN - SECRET_LASTACC_START;
782            accumulate_512(&mut acc, &last_stripe, 0, &self.secret, sec_off);
783        }
784
785        merge_accs(
786            &acc,
787            &self.secret,
788            SECRET_MERGEACCS_START,
789            (self.total_len).wrapping_mul(PRIME64_1),
790        )
791    }
792
793    fn sum_128b(self) -> u128 {
794        let mut acc = self.acc;
795
796        if self.buf_len > 0 {
797            let mut last_stripe = [0u8; STRIPE_LEN];
798            last_stripe[..self.buf_len as usize].copy_from_slice(&self.buf[..self.buf_len as usize]);
799            let copy_len = (SECRET_LASTACC_START).min(STRIPE_LEN - self.buf_len as usize);
800            let secret_tail_start = DEFAULT_SECRET_SIZE - SECRET_LASTACC_START;
801            last_stripe[STRIPE_LEN - SECRET_LASTACC_START..STRIPE_LEN - SECRET_LASTACC_START + copy_len]
802                .copy_from_slice(&self.secret[secret_tail_start..secret_tail_start + copy_len]);
803            let sec_off = DEFAULT_SECRET_SIZE - STRIPE_LEN - SECRET_LASTACC_START;
804            accumulate_512(&mut acc, &last_stripe, 0, &self.secret, sec_off);
805        }
806
807        let lo = merge_accs(
808            &acc,
809            &self.secret,
810            SECRET_MERGEACCS_START,
811            (self.total_len).wrapping_mul(PRIME64_1),
812        );
813        let hi = merge_accs(
814            &acc,
815            &self.secret,
816            DEFAULT_SECRET_SIZE - ACC_NB * 8 - SECRET_MERGEACCS_START,
817            !(self.total_len).wrapping_mul(PRIME64_2),
818        );
819
820        (lo as u128) | ((hi as u128) << 64)
821    }
822}
823
824// ---------------------------------------------------------------------------
825// Public struct: Xxh3_64
826// ---------------------------------------------------------------------------
827
828/// XXH3 64-bit hash.
829///
830/// A fast non-cryptographic hash using the XXH3 algorithm. Supports an
831/// optional `u64` seed and an optional custom 192-byte secret.
832///
833/// # Example
834///
835/// ```rust
836/// use xxhash::{Xxh3_64, Checksum};
837///
838/// let hash = Xxh3_64::checksum(b"hello world");
839/// ```
840#[derive(Clone)]
841pub struct Xxh3_64 {
842    seed: u64,
843    secret: [u8; DEFAULT_SECRET_SIZE],
844    buf: [u8; MID_SIZE_MAX],
845    buf_len: usize,
846    long: Option<LongState>,
847}
848
849impl Xxh3_64 {
850    /// Create a new XXH3-64 hasher with the given seed.
851    #[inline]
852    pub const fn with_seed(seed: u64) -> Self {
853        Xxh3_64 {
854            seed,
855            secret: DEFAULT_SECRET,
856            buf: [0u8; MID_SIZE_MAX],
857            buf_len: 0,
858            long: None,
859        }
860    }
861
862    /// Create a new XXH3-64 hasher with a custom 192-byte secret (seed = 0).
863    #[inline]
864    pub const fn with_secret(secret: [u8; DEFAULT_SECRET_SIZE]) -> Self {
865        Xxh3_64 {
866            seed: 0,
867            secret,
868            buf: [0u8; MID_SIZE_MAX],
869            buf_len: 0,
870            long: None,
871        }
872    }
873
874    /// Create a new XXH3-64 hasher with a custom seed and secret.
875    #[inline]
876    pub const fn with_seed_and_secret(seed: u64, secret: [u8; DEFAULT_SECRET_SIZE]) -> Self {
877        Xxh3_64 {
878            seed,
879            secret,
880            buf: [0u8; MID_SIZE_MAX],
881            buf_len: 0,
882            long: None,
883        }
884    }
885}
886
887impl Checksum for Xxh3_64 {
888    type Output = u64;
889
890    fn new() -> Self {
891        Self::with_seed(0)
892    }
893
894    fn checksum(data: &[u8]) -> Self::Output {
895        if data.len() <= MID_SIZE_MAX {
896            xxh3_64(data)
897        } else {
898            let mut hasher = Self::new();
899            hasher.update(data);
900            hasher.sum()
901        }
902    }
903
904    fn update(&mut self, data: &[u8]) {
905        if let Some(long) = &mut self.long {
906            long.update(data);
907            return;
908        }
909
910        let mut remaining = data;
911
912        if self.buf_len < MID_SIZE_MAX {
913            let take = (MID_SIZE_MAX - self.buf_len).min(remaining.len());
914            self.buf[self.buf_len..self.buf_len + take].copy_from_slice(&remaining[..take]);
915            self.buf_len += take;
916            remaining = &remaining[take..];
917        }
918
919        if self.buf_len >= MID_SIZE_MAX && !remaining.is_empty() {
920            let long_secret = if self.seed == 0 {
921                self.secret
922            } else {
923                custom_default_secret(self.seed)
924            };
925            let mut long = LongState::new(&long_secret);
926            long.update(&self.buf[..self.buf_len]);
927            self.long = Some(long);
928            self.buf_len = 0;
929
930            if !remaining.is_empty() {
931                self.long.as_mut().unwrap().update(remaining);
932            }
933        }
934    }
935
936    fn sum(self) -> Self::Output {
937        if let Some(long) = self.long {
938            return long.sum_64b();
939        }
940        xxh3_64_one_shot(&self.buf[..self.buf_len], self.seed, &self.secret)
941    }
942}
943
944impl core::fmt::Debug for Xxh3_64 {
945    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
946        f.debug_struct("Xxh3_64").finish()
947    }
948}
949
950impl Default for Xxh3_64 {
951    #[inline]
952    fn default() -> Self {
953        Self::with_seed(0)
954    }
955}
956
957// ---------------------------------------------------------------------------
958// Public struct: Xxh3_128
959// ---------------------------------------------------------------------------
960
961/// XXH3 128-bit hash.
962///
963/// A fast non-cryptographic hash using the XXH3-128 algorithm. Supports an
964/// optional `u64` seed and an optional custom 192-byte secret.
965///
966/// # Example
967///
968/// ```rust
969/// use xxhash::{Xxh3_128, Checksum};
970///
971/// let hash = Xxh3_128::checksum(b"hello world");
972/// ```
973#[derive(Clone)]
974pub struct Xxh3_128 {
975    seed: u64,
976    secret: [u8; DEFAULT_SECRET_SIZE],
977    buf: [u8; MID_SIZE_MAX],
978    buf_len: usize,
979    long: Option<LongState>,
980}
981
982impl Xxh3_128 {
983    /// Create a new XXH3-128 hasher with the given seed.
984    #[inline]
985    pub const fn with_seed(seed: u64) -> Self {
986        Xxh3_128 {
987            seed,
988            secret: DEFAULT_SECRET,
989            buf: [0u8; MID_SIZE_MAX],
990            buf_len: 0,
991            long: None,
992        }
993    }
994
995    /// Create a new XXH3-128 hasher with a custom 192-byte secret (seed = 0).
996    #[inline]
997    pub const fn with_secret(secret: [u8; DEFAULT_SECRET_SIZE]) -> Self {
998        Xxh3_128 {
999            seed: 0,
1000            secret,
1001            buf: [0u8; MID_SIZE_MAX],
1002            buf_len: 0,
1003            long: None,
1004        }
1005    }
1006
1007    /// Create a new XXH3-128 hasher with a custom seed and secret.
1008    #[inline]
1009    pub const fn with_seed_and_secret(seed: u64, secret: [u8; DEFAULT_SECRET_SIZE]) -> Self {
1010        Xxh3_128 {
1011            seed,
1012            secret,
1013            buf: [0u8; MID_SIZE_MAX],
1014            buf_len: 0,
1015            long: None,
1016        }
1017    }
1018}
1019
1020impl Checksum for Xxh3_128 {
1021    type Output = u128;
1022
1023    fn new() -> Self {
1024        Self::with_seed(0)
1025    }
1026
1027    fn checksum(data: &[u8]) -> Self::Output {
1028        if data.len() <= MID_SIZE_MAX {
1029            xxh3_128(data)
1030        } else {
1031            let mut hasher = Self::new();
1032            hasher.update(data);
1033            hasher.sum()
1034        }
1035    }
1036
1037    fn update(&mut self, data: &[u8]) {
1038        if let Some(long) = &mut self.long {
1039            long.update(data);
1040            return;
1041        }
1042
1043        let mut remaining = data;
1044
1045        if self.buf_len < MID_SIZE_MAX {
1046            let take = (MID_SIZE_MAX - self.buf_len).min(remaining.len());
1047            self.buf[self.buf_len..self.buf_len + take].copy_from_slice(&remaining[..take]);
1048            self.buf_len += take;
1049            remaining = &remaining[take..];
1050        }
1051
1052        if self.buf_len >= MID_SIZE_MAX && !remaining.is_empty() {
1053            let long_secret = if self.seed == 0 {
1054                self.secret
1055            } else {
1056                custom_default_secret(self.seed)
1057            };
1058            let mut long = LongState::new(&long_secret);
1059            long.update(&self.buf[..self.buf_len]);
1060            self.long = Some(long);
1061            self.buf_len = 0;
1062
1063            if !remaining.is_empty() {
1064                self.long.as_mut().unwrap().update(remaining);
1065            }
1066        }
1067    }
1068
1069    fn sum(self) -> Self::Output {
1070        if let Some(long) = self.long {
1071            return long.sum_128b();
1072        }
1073        xxh3_128_one_shot(&self.buf[..self.buf_len], self.seed, &self.secret)
1074    }
1075}
1076
1077impl core::fmt::Debug for Xxh3_128 {
1078    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1079        f.debug_struct("Xxh3_128").finish()
1080    }
1081}
1082
1083impl Default for Xxh3_128 {
1084    #[inline]
1085    fn default() -> Self {
1086        Self::with_seed(0)
1087    }
1088}
1089
1090// ---------------------------------------------------------------------------
1091// Standalone const one-shot functions
1092// ---------------------------------------------------------------------------
1093
1094/// Compute the XXH3 64-bit hash of `data` in a single call.
1095///
1096/// Available as a `const fn` for compile-time hashing.
1097/// Uses seed=0 and the default XXH3 secret.
1098///
1099/// # Example
1100///
1101/// ```rust
1102/// use xxhash::xxh3_64;
1103///
1104/// let hash: u64 = xxh3_64(b"hello");
1105/// assert_eq!(hash, 0x9555E8555C62DCFD);
1106/// ```
1107#[inline]
1108pub const fn xxh3_64(data: &[u8]) -> u64 {
1109    xxh3_64_one_shot(data, 0, &DEFAULT_SECRET)
1110}
1111
1112/// Compute the XXH3 128-bit hash of `data` in a single call.
1113///
1114/// Available as a `const fn` for compile-time hashing.
1115/// Uses seed=0 and the default XXH3 secret.
1116///
1117/// # Example
1118///
1119/// ```rust
1120/// use xxhash::xxh3_128;
1121///
1122/// let hash: u128 = xxh3_128(b"hello");
1123/// assert_eq!(hash, 0xB5E9C1AD071B3E7FC779CFAA5E523818);
1124/// ```
1125#[inline]
1126pub const fn xxh3_128(data: &[u8]) -> u128 {
1127    xxh3_128_one_shot(data, 0, &DEFAULT_SECRET)
1128}
1129
1130// ---------------------------------------------------------------------------
1131// Tests
1132// ---------------------------------------------------------------------------
1133
1134#[cfg(test)]
1135mod tests {
1136    use super::*;
1137    use crate::{Checksum, test_helpers::fill_test_buffer};
1138
1139    /// PRIME64 used in the C reference sanity check as seed (0x9E3779B185EBCA8D).
1140    const TEST_SEED: u64 = 0x9E3779B185EBCA8D;
1141    /// PRIME32 used in the C reference sanity check as seed for XXH128 vectors.
1142    const TEST_SEED32: u64 = 0x9E3779B1;
1143
1144    // --- Xxh3_64 tests ---
1145
1146    #[test]
1147    fn test_xh3_64_empty() {
1148        assert_eq!(Xxh3_64::checksum(b""), 0x2D06800538D394C2);
1149    }
1150
1151    #[test]
1152    fn test_xh3_64_hello() {
1153        assert_eq!(Xxh3_64::checksum(b"hello"), 0x9555E8555C62DCFD);
1154    }
1155
1156    #[test]
1157    fn test_xh3_64_fox() {
1158        assert_eq!(
1159            Xxh3_64::checksum(b"The quick brown fox jumps over the lazy dog"),
1160            0xCE7D19A5418FB365
1161        );
1162    }
1163
1164    /// Official XXH3-64 test vectors from `xsum_sanity_check.c`, covering
1165    /// all one-shot paths (0to16, 7to128, 129to240) with both seed=0 and
1166    /// seed=PRIME64.
1167    #[test]
1168    fn test_xh3_64_official_vectors() {
1169        let cases: &[(usize, u64, u64)] = &[
1170            (0, 0, 0x2D06800538D394C2),
1171            (0, TEST_SEED, 0xA8A6B918B2F0364A),
1172            (1, 0, 0xC44BDFF4074EECDB),
1173            (1, TEST_SEED, 0x032BE332DD766EF8),
1174            (6, 0, 0x27B56A84CD2D7325),
1175            (6, TEST_SEED, 0x84589C116AB59AB9),
1176            (12, 0, 0xA713DAF0DFBB77E7),
1177            (12, TEST_SEED, 0xE7303E1B2336DE0E),
1178            (24, 0, 0xA3FE70BF9D3510EB),
1179            (24, TEST_SEED, 0x850E80FC35BDD690),
1180            (48, 0, 0x397DA259ECBA1F11),
1181            (48, TEST_SEED, 0xADC2CBAA44ACC616),
1182            (80, 0, 0xBCDEFBBB2C47C90A),
1183            (80, TEST_SEED, 0xC6DD0CB699532E73),
1184            (195, 0, 0xCD94217EE362EC3A),
1185            (195, TEST_SEED, 0xBA68003D370CB3D9),
1186        ];
1187
1188        for &(len, seed, expected) in cases {
1189            let buf = fill_test_buffer(len);
1190            let mut h = Xxh3_64::with_seed(seed);
1191            h.update(&buf);
1192            assert_eq!(h.sum(), expected, "XXH3-64 length {len} seed {seed:#x}");
1193        }
1194    }
1195
1196    /// Byte-at-a-time incremental produces the same result as one-shot.
1197    #[test]
1198    fn test_xh3_64_byte_at_a_time() {
1199        let cases: &[(usize, u64, u64)] = &[
1200            (0, 0, 0x2D06800538D394C2),
1201            (1, TEST_SEED, 0x032BE332DD766EF8),
1202            (12, 0, 0xA713DAF0DFBB77E7),
1203            (80, TEST_SEED, 0xC6DD0CB699532E73),
1204        ];
1205
1206        for &(len, seed, expected) in cases {
1207            let buf = fill_test_buffer(len);
1208            let mut h = Xxh3_64::with_seed(seed);
1209            for b in &buf {
1210                h.update(&[*b]);
1211            }
1212            assert_eq!(h.sum(), expected, "XXH3-64 byte-at-a-time length {len} seed {seed:#x}");
1213        }
1214    }
1215
1216    /// Boundary sizes that exercise all one-shot code paths.
1217    #[test]
1218    fn test_xh3_64_boundaries() {
1219        let sizes: &[(usize, u64)] = &[
1220            // 0to16 path
1221            (0, 0x2D06800538D394C2),
1222            (1, 0xC44BDFF4074EECDB),
1223            (3, 0x54247382A8D6B94D),
1224            (4, 0xE5DC74BC51848A51),
1225            (8, 0x24CCC9ACAA9F65E4),
1226            (16, 0x981B17D36C7498C9),
1227            // 7to128 path
1228            (17, 0x796F5ACD3A60F862),
1229            (32, 0x9FEADDBDBF57EED3),
1230            (64, 0x9CB48487720EC49D),
1231            (128, 0xFCFF24126754D861),
1232            // 129to240 path
1233            (129, 0x98F1B0A679A2CA29),
1234            (240, 0x81C3C2B67F568CCF),
1235        ];
1236
1237        for &(len, expected) in sizes {
1238            let buf = fill_test_buffer(len);
1239            let mut h = Xxh3_64::new();
1240            h.update(&buf);
1241            assert_eq!(h.sum(), expected, "XXH3-64 boundary length {len}");
1242        }
1243    }
1244
1245    #[test]
1246    fn test_xh3_64_incremental() {
1247        let mut h = Xxh3_64::new();
1248        h.update(b"The quick brown ");
1249        h.update(b"fox jumps over ");
1250        h.update(b"the lazy dog");
1251        assert_eq!(h.sum(), 0xCE7D19A5418FB365);
1252    }
1253
1254    #[test]
1255    fn test_xh3_64_seeded() {
1256        let mut h = Xxh3_64::with_seed(42);
1257        h.update(b"hello");
1258        assert_eq!(h.sum(), 0xBAFA072F07DB7937);
1259    }
1260
1261    #[test]
1262    fn test_xh3_64_long_incremental() {
1263        let mut h = Xxh3_64::new();
1264        let data = [0x55u8; 512];
1265        h.update(&data[..200]);
1266        h.update(&data[200..400]);
1267        h.update(&data[400..]);
1268        assert_eq!(h.sum(), 0x4C1155EA5825B659);
1269    }
1270
1271    // --- Xxh3_128 tests ---
1272
1273    #[test]
1274    fn test_xh3_128_empty() {
1275        assert_eq!(Xxh3_128::checksum(b""), 0x99AA06D3014798D86001C324468D497F);
1276    }
1277
1278    #[test]
1279    fn test_xh3_128_hello() {
1280        assert_eq!(Xxh3_128::checksum(b"hello"), 0xB5E9C1AD071B3E7FC779CFAA5E523818);
1281    }
1282
1283    #[test]
1284    fn test_xh3_128_fox() {
1285        assert_eq!(
1286            Xxh3_128::checksum(b"The quick brown fox jumps over the lazy dog"),
1287            0xDDD650205CA3E7FA24A1CC2E3A8A7651
1288        );
1289    }
1290
1291    /// Official XXH128 test vectors from `xsum_sanity_check.c`, covering
1292    /// all one-shot paths with seed=0 and seed=PRIME32.
1293    #[test]
1294    fn test_xh3_128_official_vectors() {
1295        let cases: &[(usize, u64, u128)] = &[
1296            (0, 0, 0x99AA06D3014798D86001C324468D497F),
1297            (0, TEST_SEED32, 0x92220AE55E14AB505444F7869C671AB0),
1298            (1, 0, 0xA6CD5E9392000F6AC44BDFF4074EECDB),
1299            (1, TEST_SEED32, 0x89B99554BA22467CB53D5557E7F76F8D),
1300            (6, 0, 0x082AFE0B8162D12A3E7039BDDA43CFC6),
1301            (6, TEST_SEED32, 0x5A865B5389ABD2B1269D8F70BE98856E),
1302            (12, 0, 0x6E3EFD8FC7802B18061A192713F69AD9),
1303            (12, TEST_SEED32, 0xD7E09D518A3405D39BE9F9A67F3C7DFB),
1304            (24, 0, 0x0CE966E4678D37611E7044D28B1B901D),
1305            (24, TEST_SEED32, 0x3162026714A6A243D7304C54EBAD40A9),
1306            (48, 0, 0xA002AC4E5478227EF942219AED80F67B),
1307            (48, TEST_SEED32, 0x163ADDE36C0722957BA3C3E453A1934E),
1308            (81, 0, 0x4952F58181AB00425E8BAFB9F95FB803),
1309            (81, TEST_SEED32, 0x2724EC7ADC750FB6703FBB3D7A5F755C),
1310            (222, 0, 0x337E09641B948717F1AEBD597CEC6B3A),
1311            (222, TEST_SEED32, 0x91820016621E97F1AE995BB8AF917A8D),
1312        ];
1313
1314        for &(len, seed, expected) in cases {
1315            let buf = fill_test_buffer(len);
1316            let mut h = Xxh3_128::with_seed(seed);
1317            h.update(&buf);
1318            assert_eq!(h.sum(), expected, "XXH128 length {len} seed {seed:#x}");
1319        }
1320    }
1321
1322    /// Byte-at-a-time incremental produces the same result as one-shot.
1323    #[test]
1324    fn test_xh3_128_byte_at_a_time() {
1325        let cases: &[(usize, u64, u128)] = &[
1326            (0, 0, 0x99AA06D3014798D86001C324468D497F),
1327            (1, TEST_SEED32, 0x89B99554BA22467CB53D5557E7F76F8D),
1328            (12, 0, 0x6E3EFD8FC7802B18061A192713F69AD9),
1329        ];
1330
1331        for &(len, seed, expected) in cases {
1332            let buf = fill_test_buffer(len);
1333            let mut h = Xxh3_128::with_seed(seed);
1334            for b in &buf {
1335                h.update(&[*b]);
1336            }
1337            assert_eq!(h.sum(), expected, "XXH128 byte-at-a-time length {len} seed {seed:#x}");
1338        }
1339    }
1340
1341    /// Boundary sizes for XXH128 (all within one-shot paths).
1342    #[test]
1343    fn test_xh3_128_boundaries() {
1344        let sizes: &[(usize, u128)] = &[
1345            (0, 0x99AA06D3014798D86001C324468D497F),
1346            (1, 0xA6CD5E9392000F6AC44BDFF4074EECDB),
1347            (3, 0x20EFC49FF02422EA54247382A8D6B94D),
1348            (4, 0x970D585AC632BF8E2E7D8D6876A39FE9),
1349            (8, 0x47A7F080D82BB45664C69CAB4BB21DC5),
1350            (16, 0xC68C368ECF8A9C05562980258A998629),
1351            (17, 0x955FA78643ED3669ABBC12D11973D7DB),
1352            (32, 0x98FC6458710DC2E8278410A17595E3F9),
1353            (240, 0xAA4202DAA2769DC85C9AAE94C8EBE5A0),
1354        ];
1355
1356        for &(len, expected) in sizes {
1357            let buf = fill_test_buffer(len);
1358            let mut h = Xxh3_128::new();
1359            h.update(&buf);
1360            assert_eq!(h.sum(), expected, "XXH128 boundary length {len}");
1361        }
1362    }
1363
1364    #[test]
1365    fn test_xh3_128_incremental() {
1366        let mut h = Xxh3_128::new();
1367        h.update(b"The quick brown ");
1368        h.update(b"fox jumps over ");
1369        h.update(b"the lazy dog");
1370        assert_eq!(h.sum(), 0xDDD650205CA3E7FA24A1CC2E3A8A7651);
1371    }
1372
1373    #[test]
1374    fn test_xh3_128_long_incremental() {
1375        let mut h = Xxh3_128::new();
1376        let data = [0x55u8; 512];
1377        h.update(&data[..200]);
1378        h.update(&data[200..400]);
1379        h.update(&data[400..]);
1380        assert_eq!(h.sum(), 0x0DD2485B0318DEF24C1155EA5825B659);
1381    }
1382
1383    /// The `const fn` one-shot produces the same result as the trait-based
1384    /// [`Checksum::checksum`] and is usable at compile time.
1385    #[test]
1386    fn test_const_fn_xh3_64() {
1387        assert_eq!(xxh3_64(b""), Xxh3_64::checksum(b""));
1388        assert_eq!(xxh3_64(b"hello"), Xxh3_64::checksum(b"hello"));
1389        assert_eq!(
1390            xxh3_64(b"The quick brown fox jumps over the lazy dog"),
1391            Xxh3_64::checksum(b"The quick brown fox jumps over the lazy dog")
1392        );
1393        // Test up to 240 bytes (below the streaming threshold where both
1394        // paths agree).
1395        let buf = &[0x42u8; 200];
1396        assert_eq!(xxh3_64(buf), Xxh3_64::checksum(buf));
1397    }
1398
1399    /// The `const fn` one-shot produces the same result as the trait-based
1400    /// [`Checksum::checksum`] and is usable at compile time.
1401    #[test]
1402    fn test_const_fn_xh3_128() {
1403        assert_eq!(xxh3_128(b""), Xxh3_128::checksum(b""));
1404        assert_eq!(xxh3_128(b"hello"), Xxh3_128::checksum(b"hello"));
1405        assert_eq!(
1406            xxh3_128(b"The quick brown fox jumps over the lazy dog"),
1407            Xxh3_128::checksum(b"The quick brown fox jumps over the lazy dog")
1408        );
1409        let buf = &[0x42u8; 200];
1410        assert_eq!(xxh3_128(buf), Xxh3_128::checksum(buf));
1411    }
1412}