// RUN: %target-run-stdlib-swift // REQUIRES: executable_test import StdlibUnittest let SipHashTests = TestSuite("SipHashTests") extension Hasher { typealias HashValue = Int } struct SipHashTest { let input: [UInt8] let seed: (UInt64, UInt64) let output: UInt64 /// Test vector from the reference C implementation. /// /// SipHash output with /// /// seed = 00 01 02 ... /// /// and /// /// input = (empty string) /// input = 00 (1 byte) /// input = 00 01 (2 bytes) /// input = 00 01 02 (3 bytes) /// ... /// input = 00 01 02 ... 3e (63 bytes) init(referenceVectorIndex i: Int, output: UInt64) { self.input = Array(0..: Sequence, IteratorProtocol { var base: C var iterator: C.Iterator init(_ base: C) { self.base = base self.iterator = base.makeIterator() } mutating func next() -> C.Element? { if let element = iterator.next() { return element } iterator = base.makeIterator() return iterator.next() } } SipHashTests.test("Hasher/combine(UnsafeRawBufferPointer)") .forEach(in: sipHash13Tests) { test in var hasher = Hasher(_rawSeed: test.seed) test.input.withUnsafeBytes { hasher.combine(bytes: $0) } let hash = hasher.finalize() expectEqual(Hasher.HashValue(truncatingIfNeeded: test.output), hash) } SipHashTests.test("Hasher/combine(UnsafeRawBufferPointer)/pattern") .forEach(in: cartesianProduct(sipHash13Tests, incrementalPatterns)) { test_ in let (test, pattern) = test_ var hasher = Hasher(_rawSeed: test.seed) var chunkSizes = Loop(pattern).makeIterator() var startIndex = 0 while startIndex != test.input.endIndex { let chunkSize = min( chunkSizes.next()!, test.input.endIndex - startIndex) let slice = test.input[startIndex..<(startIndex+chunkSize)] slice.withUnsafeBytes { hasher.combine(bytes: $0) } startIndex += chunkSize } let hash = hasher.finalize() expectEqual(Hasher.HashValue(truncatingIfNeeded: test.output), hash) } func testIntegerType( _ type: I.Type, combiner: @escaping (inout Hasher, I) -> Void) { SipHashTests.test("Hasher._combine(\(type.self))") .forEach(in: sipHash13Tests) { test in var hasher = Hasher(_rawSeed: test.seed) // Load little-endian chunks and combine them into the hasher. let bitWidth = I.bitWidth var i = 0 var count = 0 var chunk: I = 0 while i < test.input.count { chunk |= I(test.input[i]) << (8 * count) i += 1 count += 1 if 8 * count == bitWidth { combiner(&hasher, chunk) count = 0 chunk = 0 } } // Combine tail bytes. if count > 0 { hasher._combine(bytes: UInt64(truncatingIfNeeded: chunk), count: count) } let hash = hasher.finalize() expectEqual(Hasher.HashValue(truncatingIfNeeded: test.output), hash) } } testIntegerType(UInt.self) { $0._combine($1) } testIntegerType(UInt64.self) { $0._combine($1) } testIntegerType(UInt32.self) { $0._combine($1) } testIntegerType(UInt16.self) { $0._combine($1) } testIntegerType(UInt8.self) { $0._combine($1) } SipHashTests.test("Hasher/OperationsAfterFinalize") { // Verify that finalize is nonmutating. var hasher1 = Hasher(_rawSeed: (0, 0)) hasher1._combine(1 as UInt8) _ = hasher1.finalize() // Hasher is now consumed. The operations below are illegal, but this isn't // currently enforced. Check that the behavior matches that of a nonmutating // function. hasher1._combine(2 as UInt16) let hash1a = hasher1.finalize() let hash1b = hasher1.finalize() expectEqual(hash1a, hash1b) var hasher2 = Hasher(_rawSeed: (0, 0)) hasher2._combine(1 as UInt8) hasher2._combine(2 as UInt16) let hash2 = hasher2.finalize() expectEqual(hash1a, hash2) } runAllTests()