123456789_123456789_123456789_123456789_123456789_

Module: Redis::XXH3

Relationships & Source Files
Defined in: lib/redis/xxh3.rb

Overview

Pure-Ruby XXH3-64 (unseeded, default secret), matching the ::Redis server's DIGEST command byte-for-byte. Ported directly, function-by-function, from the reference implementation (https://github.com/Cyan4973/xxHash, v0.8.3 — the same version and secret table the server itself vendors) rather than reimplemented from a description, to avoid subtle per-input-length transcription bugs. Every private method below is named after the upstream C function it mirrors, to keep that mapping checkable.

Ships as plain Ruby, no native extension: the 128-bit multiply-and-fold XXH3 needs throughout is the main source of complexity in a C port (no portable 128-bit integer type), but it's trivial with Ruby's native arbitrary-precision integers — lhs * rhs never overflows, so the "fold" is just splitting the product with a shift and mask.

Examples:

digest = Redis::XXH3.hexdigest("bar")
redis.set("foo", "bar")
redis.digest("foo") == digest # => true

Constant Summary

Class Method Summary

Class Method Details

.accumulate(acc, input, input_off, nb_stripes) (private)

XXH3_accumulate_scalar()

[ GitHub ]

  
# File 'lib/redis/xxh3.rb', line 310

def accumulate(acc, input, input_off, nb_stripes)
  nb_stripes.times do |n|
    accumulate_512(acc, input, input_off + (n * STRIPE_LEN), n * SECRET_CONSUME_RATE)
  end
end

.accumulate_512(acc, input, input_off, secret_off) (private)

XXH3_accumulate_512_scalar() / XXH3_scalarRound(): mutates acc (8 lanes) in place; lane order matters since each round writes both acc and acc[lane ^ 1].

[ GitHub ]

  
# File 'lib/redis/xxh3.rb', line 318

def accumulate_512(acc, input, input_off, secret_off)
  8.times do |lane|
    data_val = read_le64(input, input_off + (lane * 8))
    data_key = data_val ^ read_le64(SECRET, secret_off + (lane * 8))
    acc[lane ^ 1] = (acc[lane ^ 1] + data_val) & MASK64
    acc[lane] = (((data_key & MASK32) * ((data_key >> 32) & MASK32)) + acc[lane]) & MASK64
  end
end

.avalanche(h64) (private)

XXH3_avalanche()

[ GitHub ]

  
# File 'lib/redis/xxh3.rb', line 170

def avalanche(h64)
  h64 = xorshift64(h64, 37)
  h64 = (h64 * PRIME_MX1) & MASK64
  xorshift64(h64, 32)
end

.avalanche64(h) (private)

XXH64_avalanche()

[ GitHub ]

  
# File 'lib/redis/xxh3.rb', line 177

def avalanche64(h)
  h ^= (h >> 33)
  h = (h * PRIME64_2) & MASK64
  h ^= (h >> 29)
  h = (h * PRIME64_3) & MASK64
  h ^= (h >> 32)
  h & MASK64
end

.finalize_long_64b(acc, len) (private)

XXH3_finalizeLong_64b()

[ GitHub ]

  
# File 'lib/redis/xxh3.rb', line 338

def finalize_long_64b(acc, len)
  merge_accs(acc, SECRET_MERGEACCS_START, (len * PRIME64_1) & MASK64)
end

.hash64(input) (private)

XXH3_64bits()

[ GitHub ]

  
# File 'lib/redis/xxh3.rb', line 122

def hash64(input)
  len = input.bytesize
  if len <= 16
    len_0to16_64b(input, len)
  elsif len <= 128
    len_17to128_64b(input, len)
  elsif len <= MIDSIZE_MAX
    len_129to240_64b(input, len)
  else
    hash_long_64b(input, len)
  end
end

.hash_long_64b(input, len) (private)

--- >240 bytes: XXH3_hashLong_64b_default and its accumulator loop ---

[ GitHub ]

  
# File 'lib/redis/xxh3.rb', line 284

def hash_long_64b(input, len)
  acc = INIT_ACC.dup
  hash_long_internal_loop(acc, input, len)
  finalize_long_64b(acc, len)
end

.hash_long_internal_loop(acc, input, len) (private)

XXH3_hashLong_internal_loop()

[ GitHub ]

  
# File 'lib/redis/xxh3.rb', line 291

def hash_long_internal_loop(acc, input, len)
  secret_size = SECRET.bytesize
  nb_stripes_per_block = (secret_size - STRIPE_LEN) / SECRET_CONSUME_RATE
  block_len = STRIPE_LEN * nb_stripes_per_block
  nb_blocks = (len - 1) / block_len

  nb_blocks.times do |n|
    accumulate(acc, input, n * block_len, nb_stripes_per_block)
    scramble_acc(acc, secret_size - STRIPE_LEN)
  end

  nb_stripes = ((len - 1) - (block_len * nb_blocks)) / STRIPE_LEN
  accumulate(acc, input, nb_blocks * block_len, nb_stripes)

  # last stripe
  accumulate_512(acc, input, len - STRIPE_LEN, secret_size - STRIPE_LEN - SECRET_LASTACC_START)
end

.hexdigest(value) ⇒ String

Parameters:

  • value (String, #to_s) —

    the value to hash

Returns:

  • (String) —

    16 lowercase hex characters

[ GitHub ]

  
# File 'lib/redis/xxh3.rb', line 115

def hexdigest(value)
  format("%016x", hash64(value.to_s.b))
end

.len_0to16_64b(input, len) (private)

--- 0-16 bytes: XXH3_len_0to16_64b and its sub-cases (seed is always 0) ---

[ GitHub ]

  
# File 'lib/redis/xxh3.rb', line 197

def len_0to16_64b(input, len)
  if len > 8
    len_9to16_64b(input, len)
  elsif len >= 4
    len_4to8_64b(input, len)
  elsif len > 0
    len_1to3_64b(input, len)
  else
    avalanche64(read_le64(SECRET, 56) ^ read_le64(SECRET, 64))
  end
end

.len_129to240_64b(input, len) (private)

--- 129-240 bytes: XXH3_len_129to240_64b ---

[ GitHub ]

  
# File 'lib/redis/xxh3.rb', line 270

def len_129to240_64b(input, len)
  acc = (len * PRIME64_1) & MASK64
  nb_rounds = len / 16
  8.times { |i| acc = (acc + mix16b(input, 16 * i, 16 * i)) & MASK64 }
  acc = avalanche(acc)
  acc_end = mix16b(input, len - 16, SECRET_SIZE_MIN - MIDSIZE_LASTOFFSET)
  (8...nb_rounds).each do |i|
    acc_end = (acc_end + mix16b(input, 16 * i, (16 * (i - 8)) + MIDSIZE_STARTOFFSET)) & MASK64
  end
  avalanche((acc + acc_end) & MASK64)
end

.len_17to128_64b(input, len) (private)

[ GitHub ]

  
# File 'lib/redis/xxh3.rb', line 249

def len_17to128_64b(input, len)
  acc = (len * PRIME64_1) & MASK64
  if len > 32
    if len > 64
      if len > 96
        acc = (acc + mix16b(input, 48, 96)) & MASK64
        acc = (acc + mix16b(input, len - 64, 112)) & MASK64
      end
      acc = (acc + mix16b(input, 32, 64)) & MASK64
      acc = (acc + mix16b(input, len - 48, 80)) & MASK64
    end
    acc = (acc + mix16b(input, 16, 32)) & MASK64
    acc = (acc + mix16b(input, len - 32, 48)) & MASK64
  end
  acc = (acc + mix16b(input, 0, 0)) & MASK64
  acc = (acc + mix16b(input, len - 16, 16)) & MASK64
  avalanche(acc)
end

.len_1to3_64b(input, len) (private)

XXH3_len_1to3_64b()

[ GitHub ]

  
# File 'lib/redis/xxh3.rb', line 210

def len_1to3_64b(input, len)
  c1 = input.getbyte(0)
  c2 = input.getbyte(len >> 1)
  c3 = input.getbyte(len - 1)
  combined = (c1 << 16) | (c2 << 24) | c3 | (len << 8)
  bitflip = read_le32(SECRET, 0) ^ read_le32(SECRET, 4)
  avalanche64((combined ^ bitflip) & MASK64)
end

.len_4to8_64b(input, len) (private)

XXH3_len_4to8_64b()

[ GitHub ]

  
# File 'lib/redis/xxh3.rb', line 220

def len_4to8_64b(input, len)
  input1 = read_le32(input, 0)
  input2 = read_le32(input, len - 4)
  bitflip = read_le64(SECRET, 8) ^ read_le64(SECRET, 16)
  input64 = (input2 + (input1 << 32)) & MASK64
  rrmxmx(input64 ^ bitflip, len)
end

.len_9to16_64b(input, len) (private)

XXH3_len_9to16_64b()

[ GitHub ]

  
# File 'lib/redis/xxh3.rb', line 229

def len_9to16_64b(input, len)
  bitflip1 = read_le64(SECRET, 24) ^ read_le64(SECRET, 32)
  bitflip2 = read_le64(SECRET, 40) ^ read_le64(SECRET, 48)
  input_lo = read_le64(input, 0) ^ bitflip1
  input_hi = read_le64(input, len - 8) ^ bitflip2
  acc = (len + swap64(input_lo) + input_hi + mul128_fold64(input_lo, input_hi)) & MASK64
  avalanche(acc)
end

.merge_accs(acc, secret_off, start) (private)

XXH3_mergeAccs() / XXH3_mix2Accs()

[ GitHub ]

  
# File 'lib/redis/xxh3.rb', line 343

def merge_accs(acc, secret_off, start)
  result = start
  4.times do |i|
    lo = acc[2 * i] ^ read_le64(SECRET, secret_off + (16 * i))
    hi = acc[(2 * i) + 1] ^ read_le64(SECRET, secret_off + (16 * i) + 8)
    result = (result + mul128_fold64(lo, hi)) & MASK64
  end
  avalanche(result)
end

.mix16b(input, input_off, secret_off) (private)

XXH3_mix16B()

[ GitHub ]

  
# File 'lib/redis/xxh3.rb', line 241

def mix16b(input, input_off, secret_off)
  input_lo = read_le64(input, input_off)
  input_hi = read_le64(input, input_off + 8)
  keyed_lo = input_lo ^ read_le64(SECRET, secret_off)
  keyed_hi = input_hi ^ read_le64(SECRET, secret_off + 8)
  mul128_fold64(keyed_lo, keyed_hi)
end

.mul128_fold64(lhs, rhs) (private)

XXH3_mul128_fold64(): 64x64->128 multiply, then XOR-fold the two 64-bit halves.

[ GitHub ]

  
# File 'lib/redis/xxh3.rb', line 160

def mul128_fold64(lhs, rhs)
  product = lhs * rhs
  (product & MASK64) ^ (product >> 64)
end

.read_le32(str, offset) (private)

[ GitHub ]

  
# File 'lib/redis/xxh3.rb', line 135

def read_le32(str, offset)
  str.unpack1("V", offset: offset)
end

.read_le64(str, offset) (private)

[ GitHub ]

  
# File 'lib/redis/xxh3.rb', line 139

def read_le64(str, offset)
  str.unpack1("Q<", offset: offset)
end

.rotl64(x, r) (private)

[ GitHub ]

  
# File 'lib/redis/xxh3.rb', line 143

def rotl64(x, r)
  ((x << r) | (x >> (64 - r))) & MASK64
end

.rrmxmx(h64, len) (private)

XXH3_rrmxmx()

[ GitHub ]

  
# File 'lib/redis/xxh3.rb', line 187

def rrmxmx(h64, len)
  h64 ^= (rotl64(h64, 49) ^ rotl64(h64, 24))
  h64 = (h64 * PRIME_MX2) & MASK64
  h64 ^= ((h64 >> 35) + len)
  h64 = (h64 * PRIME_MX2) & MASK64
  xorshift64(h64, 28)
end

.scramble_acc(acc, secret_off) (private)

XXH3_scrambleAcc_scalar() / XXH3_scalarScrambleRound()

[ GitHub ]

  
# File 'lib/redis/xxh3.rb', line 328

def scramble_acc(acc, secret_off)
  8.times do |lane|
    key64 = read_le64(SECRET, secret_off + (lane * 8))
    acc64 = xorshift64(acc[lane], 47)
    acc64 ^= key64
    acc[lane] = (acc64 * PRIME32_1) & MASK64
  end
end

.swap64(x) (private)

XXH_swap64()

[ GitHub ]

  
# File 'lib/redis/xxh3.rb', line 148

def swap64(x)
  ((x << 56) & 0xff00000000000000) |
    ((x << 40) & 0x00ff000000000000) |
    ((x << 24) & 0x0000ff0000000000) |
    ((x << 8)  & 0x000000ff00000000) |
    ((x >> 8)  & 0x00000000ff000000) |
    ((x >> 24) & 0x0000000000ff0000) |
    ((x >> 40) & 0x000000000000ff00) |
    ((x >> 56) & 0x00000000000000ff)
end

.xorshift64(v, shift) (private)

[ GitHub ]

  
# File 'lib/redis/xxh3.rb', line 165

def xorshift64(v, shift)
  (v ^ (v >> shift)) & MASK64
end