126 lines
3.7 KiB
Swift
126 lines
3.7 KiB
Swift
import Foundation
|
|
|
|
/// xxHash64 (seed 0) — pure Swift, streaming. Spec: github.com/Cyan4973/xxHash.
|
|
public struct XXHash64 {
|
|
private static let p1: UInt64 = 0x9E3779B185EBCA87
|
|
private static let p2: UInt64 = 0xC2B2AE3D27D4EB4F
|
|
private static let p3: UInt64 = 0x165667B19E3779F9
|
|
private static let p4: UInt64 = 0x85EBCA77C2B2AE63
|
|
private static let p5: UInt64 = 0x27D4EB2F165667C5
|
|
|
|
private var v1: UInt64
|
|
private var v2: UInt64
|
|
private var v3: UInt64
|
|
private var v4: UInt64
|
|
private var buffer = [UInt8]()
|
|
private var totalLength: UInt64 = 0
|
|
private let seed: UInt64
|
|
|
|
public init(seed: UInt64 = 0) {
|
|
self.seed = seed
|
|
v1 = seed &+ Self.p1 &+ Self.p2
|
|
v2 = seed &+ Self.p2
|
|
v3 = seed
|
|
v4 = seed &- Self.p1
|
|
}
|
|
|
|
@inline(__always)
|
|
private static func rotl(_ x: UInt64, _ r: UInt64) -> UInt64 {
|
|
(x << r) | (x >> (64 - r))
|
|
}
|
|
|
|
@inline(__always)
|
|
private static func round(_ acc: UInt64, _ input: UInt64) -> UInt64 {
|
|
rotl(acc &+ input &* p2, 31) &* p1
|
|
}
|
|
|
|
@inline(__always)
|
|
private static func mergeRound(_ acc: UInt64, _ val: UInt64) -> UInt64 {
|
|
(acc ^ round(0, val)) &* p1 &+ p4
|
|
}
|
|
|
|
@inline(__always)
|
|
private static func readLE64(_ bytes: [UInt8], _ offset: Int) -> UInt64 {
|
|
var v: UInt64 = 0
|
|
for i in 0..<8 {
|
|
v |= UInt64(bytes[offset + i]) << (8 * UInt64(i))
|
|
}
|
|
return v
|
|
}
|
|
|
|
@inline(__always)
|
|
private static func readLE32(_ bytes: [UInt8], _ offset: Int) -> UInt64 {
|
|
var v: UInt64 = 0
|
|
for i in 0..<4 {
|
|
v |= UInt64(bytes[offset + i]) << (8 * UInt64(i))
|
|
}
|
|
return v
|
|
}
|
|
|
|
public mutating func update(_ data: Data) {
|
|
totalLength += UInt64(data.count)
|
|
buffer.append(contentsOf: data)
|
|
// Consume full 32-byte stripes, keep remainder buffered.
|
|
var offset = 0
|
|
while buffer.count - offset >= 32 {
|
|
v1 = Self.round(v1, Self.readLE64(buffer, offset))
|
|
v2 = Self.round(v2, Self.readLE64(buffer, offset + 8))
|
|
v3 = Self.round(v3, Self.readLE64(buffer, offset + 16))
|
|
v4 = Self.round(v4, Self.readLE64(buffer, offset + 24))
|
|
offset += 32
|
|
}
|
|
if offset > 0 {
|
|
buffer.removeFirst(offset)
|
|
}
|
|
}
|
|
|
|
public func finalize() -> UInt64 {
|
|
var h: UInt64
|
|
if totalLength >= 32 {
|
|
h = Self.rotl(v1, 1) &+ Self.rotl(v2, 7) &+ Self.rotl(v3, 12) &+ Self.rotl(v4, 18)
|
|
h = Self.mergeRound(h, v1)
|
|
h = Self.mergeRound(h, v2)
|
|
h = Self.mergeRound(h, v3)
|
|
h = Self.mergeRound(h, v4)
|
|
} else {
|
|
h = seed &+ Self.p5
|
|
}
|
|
h &+= totalLength
|
|
|
|
var offset = 0
|
|
let tail = buffer
|
|
while tail.count - offset >= 8 {
|
|
h ^= Self.round(0, Self.readLE64(tail, offset))
|
|
h = Self.rotl(h, 27) &* Self.p1 &+ Self.p4
|
|
offset += 8
|
|
}
|
|
if tail.count - offset >= 4 {
|
|
h ^= Self.readLE32(tail, offset) &* Self.p1
|
|
h = Self.rotl(h, 23) &* Self.p2 &+ Self.p3
|
|
offset += 4
|
|
}
|
|
while offset < tail.count {
|
|
h ^= UInt64(tail[offset]) &* Self.p5
|
|
h = Self.rotl(h, 11) &* Self.p1
|
|
offset += 1
|
|
}
|
|
|
|
h ^= h >> 33
|
|
h &*= Self.p2
|
|
h ^= h >> 29
|
|
h &*= Self.p3
|
|
h ^= h >> 32
|
|
return h
|
|
}
|
|
|
|
/// One-shot.
|
|
public static func hash(_ data: Data, seed: UInt64 = 0) -> UInt64 {
|
|
var h = XXHash64(seed: seed)
|
|
h.update(data)
|
|
return h.finalize()
|
|
}
|
|
|
|
public static func hexString(_ value: UInt64) -> String {
|
|
String(format: "%016lx", value)
|
|
}
|
|
}
|