| ... | ... | @@ -21,7 +21,7 @@ pub fn SipHash128(comptime c_rounds: usize, comptime d_rounds: usize) type { |
| 21 | 21 | return SipHash(u128, c_rounds, d_rounds); |
| 22 | 22 | } |
| 23 | 23 | |
| 24 | | fn SipHash(comptime T: type, comptime c_rounds: usize, comptime d_rounds: usize) type { |
| 24 | fn SipHashStateless(comptime T: type, comptime c_rounds: usize, comptime d_rounds: usize) type { |
| 25 | 25 | assert(T == u64 or T == u128); |
| 26 | 26 | assert(c_rounds > 0 and d_rounds > 0); |
| 27 | 27 | |
| ... | ... | @@ -34,10 +34,6 @@ fn SipHash(comptime T: type, comptime c_rounds: usize, comptime d_rounds: usize) |
| 34 | 34 | v1: u64, |
| 35 | 35 | v2: u64, |
| 36 | 36 | v3: u64, |
| 37 | | |
| 38 | | // streaming cache |
| 39 | | buf: [8]u8, |
| 40 | | buf_len: usize, |
| 41 | 37 | msg_len: u8, |
| 42 | 38 | |
| 43 | 39 | pub fn init(key: []const u8) Self { |
| ... | ... | @@ -51,9 +47,6 @@ fn SipHash(comptime T: type, comptime c_rounds: usize, comptime d_rounds: usize) |
| 51 | 47 | .v1 = k1 ^ 0x646f72616e646f6d, |
| 52 | 48 | .v2 = k0 ^ 0x6c7967656e657261, |
| 53 | 49 | .v3 = k1 ^ 0x7465646279746573, |
| 54 | | |
| 55 | | .buf = undefined, |
| 56 | | .buf_len = 0, |
| 57 | 50 | .msg_len = 0, |
| 58 | 51 | }; |
| 59 | 52 | |
| ... | ... | @@ -64,73 +57,66 @@ fn SipHash(comptime T: type, comptime c_rounds: usize, comptime d_rounds: usize) |
| 64 | 57 | return d; |
| 65 | 58 | } |
| 66 | 59 | |
| 67 | | pub fn update(d: *Self, b: []const u8) void { |
| 68 | | var off: usize = 0; |
| 69 | | |
| 70 | | // Partial from previous. |
| 71 | | if (d.buf_len != 0 and d.buf_len + b.len > 8) { |
| 72 | | off += 8 - d.buf_len; |
| 73 | | mem.copy(u8, d.buf[d.buf_len..], b[0..off]); |
| 74 | | d.round(d.buf[0..]); |
| 75 | | d.buf_len = 0; |
| 76 | | } |
| 60 | pub fn update(self: *Self, b: []const u8) void { |
| 61 | std.debug.assert(b.len % 8 == 0); |
| 77 | 62 | |
| 78 | | // Full middle blocks. |
| 79 | | while (off + 8 <= b.len) : (off += 8) { |
| 80 | | d.round(b[off .. off + 8]); |
| 63 | var off: usize = 0; |
| 64 | while (off < b.len) : (off += 8) { |
| 65 | @inlineCall(self.round, b[off .. off + 8]); |
| 81 | 66 | } |
| 82 | 67 | |
| 83 | | // Remainder for next pass. |
| 84 | | mem.copy(u8, d.buf[d.buf_len..], b[off..]); |
| 85 | | d.buf_len += @intCast(u8, b[off..].len); |
| 86 | | d.msg_len +%= @truncate(u8, b.len); |
| 68 | self.msg_len +%= @truncate(u8, b.len); |
| 87 | 69 | } |
| 88 | 70 | |
| 89 | | pub fn final(d: *Self) T { |
| 90 | | // Padding |
| 91 | | mem.set(u8, d.buf[d.buf_len..], 0); |
| 92 | | d.buf[7] = d.msg_len; |
| 93 | | d.round(d.buf[0..]); |
| 71 | pub fn final(self: *Self, b: []const u8) T { |
| 72 | std.debug.assert(b.len < 8); |
| 73 | |
| 74 | self.msg_len +%= @truncate(u8, b.len); |
| 75 | |
| 76 | var buf = [_]u8{0} ** 8; |
| 77 | mem.copy(u8, buf[0..], b[0..]); |
| 78 | buf[7] = self.msg_len; |
| 79 | self.round(buf[0..]); |
| 94 | 80 | |
| 95 | 81 | if (T == u128) { |
| 96 | | d.v2 ^= 0xee; |
| 82 | self.v2 ^= 0xee; |
| 97 | 83 | } else { |
| 98 | | d.v2 ^= 0xff; |
| 84 | self.v2 ^= 0xff; |
| 99 | 85 | } |
| 100 | 86 | |
| 101 | 87 | comptime var i: usize = 0; |
| 102 | 88 | inline while (i < d_rounds) : (i += 1) { |
| 103 | | @inlineCall(sipRound, d); |
| 89 | @inlineCall(sipRound, self); |
| 104 | 90 | } |
| 105 | 91 | |
| 106 | | const b1 = d.v0 ^ d.v1 ^ d.v2 ^ d.v3; |
| 92 | const b1 = self.v0 ^ self.v1 ^ self.v2 ^ self.v3; |
| 107 | 93 | if (T == u64) { |
| 108 | 94 | return b1; |
| 109 | 95 | } |
| 110 | 96 | |
| 111 | | d.v1 ^= 0xdd; |
| 97 | self.v1 ^= 0xdd; |
| 112 | 98 | |
| 113 | 99 | comptime var j: usize = 0; |
| 114 | 100 | inline while (j < d_rounds) : (j += 1) { |
| 115 | | @inlineCall(sipRound, d); |
| 101 | @inlineCall(sipRound, self); |
| 116 | 102 | } |
| 117 | 103 | |
| 118 | | const b2 = d.v0 ^ d.v1 ^ d.v2 ^ d.v3; |
| 104 | const b2 = self.v0 ^ self.v1 ^ self.v2 ^ self.v3; |
| 119 | 105 | return (u128(b2) << 64) | b1; |
| 120 | 106 | } |
| 121 | 107 | |
| 122 | | fn round(d: *Self, b: []const u8) void { |
| 108 | fn round(self: *Self, b: []const u8) void { |
| 123 | 109 | assert(b.len == 8); |
| 124 | 110 | |
| 125 | 111 | const m = mem.readIntSliceLittle(u64, b[0..]); |
| 126 | | d.v3 ^= m; |
| 112 | self.v3 ^= m; |
| 127 | 113 | |
| 128 | 114 | comptime var i: usize = 0; |
| 129 | 115 | inline while (i < c_rounds) : (i += 1) { |
| 130 | | @inlineCall(sipRound, d); |
| 116 | @inlineCall(sipRound, self); |
| 131 | 117 | } |
| 132 | 118 | |
| 133 | | d.v0 ^= m; |
| 119 | self.v0 ^= m; |
| 134 | 120 | } |
| 135 | 121 | |
| 136 | 122 | fn sipRound(d: *Self) void { |
| ... | ... | @@ -151,9 +137,61 @@ fn SipHash(comptime T: type, comptime c_rounds: usize, comptime d_rounds: usize) |
| 151 | 137 | } |
| 152 | 138 | |
| 153 | 139 | pub fn hash(key: []const u8, input: []const u8) T { |
| 140 | const aligned_len = input.len - (input.len % 8); |
| 141 | |
| 154 | 142 | var c = Self.init(key); |
| 155 | | @inlineCall(c.update, input); |
| 156 | | return @inlineCall(c.final); |
| 143 | @inlineCall(c.update, input[0..aligned_len]); |
| 144 | return @inlineCall(c.final, input[aligned_len..]); |
| 145 | } |
| 146 | }; |
| 147 | } |
| 148 | |
| 149 | pub fn SipHash(comptime T: type, comptime c_rounds: usize, comptime d_rounds: usize) type { |
| 150 | assert(T == u64 or T == u128); |
| 151 | assert(c_rounds > 0 and d_rounds > 0); |
| 152 | |
| 153 | return struct { |
| 154 | const State = SipHashStateless(T, c_rounds, d_rounds); |
| 155 | const Self = @This(); |
| 156 | const digest_size = 64; |
| 157 | const block_size = 64; |
| 158 | |
| 159 | state: State, |
| 160 | buf: [8]u8, |
| 161 | buf_len: usize, |
| 162 | |
| 163 | pub fn init(key: []const u8) Self { |
| 164 | return Self{ |
| 165 | .state = State.init(key), |
| 166 | .buf = undefined, |
| 167 | .buf_len = 0, |
| 168 | }; |
| 169 | } |
| 170 | |
| 171 | pub fn update(self: *Self, b: []const u8) void { |
| 172 | var off: usize = 0; |
| 173 | |
| 174 | if (self.buf_len != 0 and self.buf_len + b.len >= 8) { |
| 175 | off += 8 - self.buf_len; |
| 176 | mem.copy(u8, self.buf[self.buf_len..], b[0..off]); |
| 177 | self.state.update(self.buf[0..]); |
| 178 | self.buf_len = 0; |
| 179 | } |
| 180 | |
| 181 | const remain_len = b.len - off; |
| 182 | const aligned_len = remain_len - (remain_len % 8); |
| 183 | self.state.update(b[off .. off + aligned_len]); |
| 184 | |
| 185 | mem.copy(u8, self.buf[self.buf_len..], b[off + aligned_len ..]); |
| 186 | self.buf_len += @intCast(u8, b[off + aligned_len ..].len); |
| 187 | } |
| 188 | |
| 189 | pub fn final(self: *Self) T { |
| 190 | return self.state.final(self.buf[0..self.buf_len]); |
| 191 | } |
| 192 | |
| 193 | pub fn hash(key: []const u8, input: []const u8) T { |
| 194 | return State.hash(key, input); |
| 157 | 195 | } |
| 158 | 196 | }; |
| 159 | 197 | } |
| ... | ... | @@ -237,7 +275,7 @@ test "siphash64-2-4 sanity" { |
| 237 | 275 | buffer[i] = @intCast(u8, i); |
| 238 | 276 | |
| 239 | 277 | const expected = mem.readIntLittle(u64, &vector); |
| 240 | | testing.expect(siphash.hash(test_key, buffer[0..i]) == expected); |
| 278 | testing.expectEqual(siphash.hash(test_key, buffer[0..i]), expected); |
| 241 | 279 | } |
| 242 | 280 | } |
| 243 | 281 | |
| ... | ... | @@ -316,6 +354,30 @@ test "siphash128-2-4 sanity" { |
| 316 | 354 | buffer[i] = @intCast(u8, i); |
| 317 | 355 | |
| 318 | 356 | const expected = mem.readIntLittle(u128, &vector); |
| 319 | | testing.expect(siphash.hash(test_key, buffer[0..i]) == expected); |
| 357 | testing.expectEqual(siphash.hash(test_key, buffer[0..i]), expected); |
| 358 | } |
| 359 | } |
| 360 | |
| 361 | test "iterative non-divisible update" { |
| 362 | var buf: [1024]u8 = undefined; |
| 363 | for (buf) |*e, i| { |
| 364 | e.* = @truncate(u8, i); |
| 365 | } |
| 366 | |
| 367 | const key = "0x128dad08f12307"; |
| 368 | const Siphash = SipHash64(2, 4); |
| 369 | |
| 370 | var end: usize = 9; |
| 371 | while (end < buf.len) : (end += 9) { |
| 372 | const non_iterative_hash = Siphash.hash(key, buf[0..end]); |
| 373 | |
| 374 | var wy = Siphash.init(key); |
| 375 | var i: usize = 0; |
| 376 | while (i < end) : (i += 7) { |
| 377 | wy.update(buf[i..std.math.min(i + 7, end)]); |
| 378 | } |
| 379 | const iterative_hash = wy.final(); |
| 380 | |
| 381 | std.testing.expectEqual(iterative_hash, non_iterative_hash); |
| 320 | 382 | } |
| 321 | 383 | } |