169 lines
5.2 KiB
Dart
169 lines
5.2 KiB
Dart
|
|
/// XXH64, the hash zstd uses for `Content_Checksum`.
|
||
|
|
///
|
||
|
|
/// RFC 8878 section 3.1.1 stores the low 32 bits of the XXH64 of the whole
|
||
|
|
/// decompressed frame content, seeded with zero, little-endian, after the last
|
||
|
|
/// block. [Xxh64.digest32] is that value.
|
||
|
|
///
|
||
|
|
/// The algorithm is defined on unsigned 64-bit words that wrap. Dart's `int`
|
||
|
|
/// on the VM and on AOT is a 64-bit two's complement integer whose `+`, `-`,
|
||
|
|
/// `*` and `<<` wrap on overflow, and `>>>` shifts the raw bits in, so every
|
||
|
|
/// operation here is exact with no masking. None of that holds under dart2js,
|
||
|
|
/// where `int` is a double: the multiplies, the rotates and the wrapping adds
|
||
|
|
/// would all be wrong. Like the rest of this package, it targets native only.
|
||
|
|
library;
|
||
|
|
|
||
|
|
import 'dart:typed_data';
|
||
|
|
|
||
|
|
// The five primes, written as they appear in the reference implementation.
|
||
|
|
// Dart reads a hex literal above 2^63 as its two's complement, so _prime1,
|
||
|
|
// _prime2 and _prime4 are negative here. The bits are what matter.
|
||
|
|
const int _prime1 = 0x9E3779B185EBCA87;
|
||
|
|
const int _prime2 = 0xC2B2AE3D27D4EB4F;
|
||
|
|
const int _prime3 = 0x165667B19E3779F9;
|
||
|
|
const int _prime4 = 0x85EBCA77C2B2AE63;
|
||
|
|
const int _prime5 = 0x27D4EB2F165667C5;
|
||
|
|
|
||
|
|
/// Hashes [data] in one pass. Equivalent to `(Xxh64()..update(data)).digest`.
|
||
|
|
int xxh64(Uint8List data, {int seed = 0}) =>
|
||
|
|
(Xxh64(seed: seed)..update(data)).digest;
|
||
|
|
|
||
|
|
/// XXH64 over as many chunks as the caller has.
|
||
|
|
///
|
||
|
|
/// A multi-block frame can feed each block to [update] as it is written,
|
||
|
|
/// rather than hashing the output again once it is whole. Reading [digest]
|
||
|
|
/// does not end the hash; more may be fed in afterwards.
|
||
|
|
class Xxh64 {
|
||
|
|
Xxh64({int seed = 0}) : _seed = seed {
|
||
|
|
reset();
|
||
|
|
}
|
||
|
|
|
||
|
|
final int _seed;
|
||
|
|
|
||
|
|
/// Whole 32-byte stripes are consumed straight from the caller's bytes.
|
||
|
|
/// This holds only the remainder between calls.
|
||
|
|
final Uint8List _tail = Uint8List(32);
|
||
|
|
late final ByteData _tailView = ByteData.sublistView(_tail);
|
||
|
|
|
||
|
|
int _tailLength = 0;
|
||
|
|
int _length = 0;
|
||
|
|
|
||
|
|
int _v1 = 0;
|
||
|
|
int _v2 = 0;
|
||
|
|
int _v3 = 0;
|
||
|
|
int _v4 = 0;
|
||
|
|
|
||
|
|
/// Discards everything fed in so far, back to the seed given to the
|
||
|
|
/// constructor.
|
||
|
|
void reset() {
|
||
|
|
_v1 = _seed + _prime1 + _prime2;
|
||
|
|
_v2 = _seed + _prime2;
|
||
|
|
_v3 = _seed;
|
||
|
|
_v4 = _seed - _prime1;
|
||
|
|
_tailLength = 0;
|
||
|
|
_length = 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Feeds `data[start..end)`, defaulting to all of [data].
|
||
|
|
///
|
||
|
|
/// The range form is so a decoder can hand over the part of an output buffer
|
||
|
|
/// it just filled without taking a view of it.
|
||
|
|
void update(Uint8List data, [int start = 0, int? end]) {
|
||
|
|
final stop = RangeError.checkValidRange(start, end, data.length);
|
||
|
|
var at = start;
|
||
|
|
_length += stop - at;
|
||
|
|
|
||
|
|
if (_tailLength > 0) {
|
||
|
|
final take = stop - at < 32 - _tailLength ? stop - at : 32 - _tailLength;
|
||
|
|
_tail.setRange(_tailLength, _tailLength + take, data, at);
|
||
|
|
_tailLength += take;
|
||
|
|
at += take;
|
||
|
|
if (_tailLength < 32) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
_stripe(_tailView, 0);
|
||
|
|
_tailLength = 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
final view = ByteData.sublistView(data);
|
||
|
|
while (at + 32 <= stop) {
|
||
|
|
_stripe(view, at);
|
||
|
|
at += 32;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (at < stop) {
|
||
|
|
_tail.setRange(0, stop - at, data, at);
|
||
|
|
_tailLength = stop - at;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/// The 64-bit hash of everything fed in so far.
|
||
|
|
int get digest {
|
||
|
|
// Under 32 bytes the accumulators were never touched, so the hash starts
|
||
|
|
// from the seed instead of from their merge. _v3 still holds the seed.
|
||
|
|
var h = _length >= 32
|
||
|
|
? _merge(
|
||
|
|
_merge(
|
||
|
|
_merge(
|
||
|
|
_merge(
|
||
|
|
_rotl(_v1, 1) +
|
||
|
|
_rotl(_v2, 7) +
|
||
|
|
_rotl(_v3, 12) +
|
||
|
|
_rotl(_v4, 18),
|
||
|
|
_v1,
|
||
|
|
),
|
||
|
|
_v2,
|
||
|
|
),
|
||
|
|
_v3,
|
||
|
|
),
|
||
|
|
_v4,
|
||
|
|
)
|
||
|
|
: _v3 + _prime5;
|
||
|
|
h += _length;
|
||
|
|
|
||
|
|
// The remainder is consumed eight bytes at a time, then four, then one.
|
||
|
|
var at = 0;
|
||
|
|
while (at + 8 <= _tailLength) {
|
||
|
|
h ^= _round(0, _tailView.getInt64(at, Endian.little));
|
||
|
|
h = _rotl(h, 27) * _prime1 + _prime4;
|
||
|
|
at += 8;
|
||
|
|
}
|
||
|
|
if (at + 4 <= _tailLength) {
|
||
|
|
h ^= _tailView.getUint32(at, Endian.little) * _prime1;
|
||
|
|
h = _rotl(h, 23) * _prime2 + _prime3;
|
||
|
|
at += 4;
|
||
|
|
}
|
||
|
|
while (at < _tailLength) {
|
||
|
|
h ^= _tail[at] * _prime5;
|
||
|
|
h = _rotl(h, 11) * _prime1;
|
||
|
|
at++;
|
||
|
|
}
|
||
|
|
|
||
|
|
h ^= h >>> 33;
|
||
|
|
h *= _prime2;
|
||
|
|
h ^= h >>> 29;
|
||
|
|
h *= _prime3;
|
||
|
|
h ^= h >>> 32;
|
||
|
|
return h;
|
||
|
|
}
|
||
|
|
|
||
|
|
/// The low 32 bits of [digest]: the value zstd stores as
|
||
|
|
/// `Content_Checksum`.
|
||
|
|
int get digest32 => digest & 0xFFFFFFFF;
|
||
|
|
|
||
|
|
void _stripe(ByteData view, int at) {
|
||
|
|
_v1 = _round(_v1, view.getInt64(at, Endian.little));
|
||
|
|
_v2 = _round(_v2, view.getInt64(at + 8, Endian.little));
|
||
|
|
_v3 = _round(_v3, view.getInt64(at + 16, Endian.little));
|
||
|
|
_v4 = _round(_v4, view.getInt64(at + 24, Endian.little));
|
||
|
|
}
|
||
|
|
|
||
|
|
static int _round(int accumulator, int input) {
|
||
|
|
final v = accumulator + input * _prime2;
|
||
|
|
return _rotl(v, 31) * _prime1;
|
||
|
|
}
|
||
|
|
|
||
|
|
static int _merge(int h, int accumulator) =>
|
||
|
|
(h ^ _round(0, accumulator)) * _prime1 + _prime4;
|
||
|
|
|
||
|
|
static int _rotl(int v, int bits) => (v << bits) | (v >>> (64 - bits));
|
||
|
|
}
|