1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package io.netty.handler.codec.quic;
17
18 import io.netty.util.internal.ObjectUtil;
19
20 import java.nio.ByteBuffer;
21 import java.nio.ByteOrder;
22
23
24
25
26 final class SipHash {
27
28 static final int SEED_LENGTH = 16;
29
30
31 private final int compressionRounds;
32 private final int finalizationRounds;
33
34
35 private static final long INITIAL_STATE_V0 = 0x736f6d6570736575L;
36 private static final long INITIAL_STATE_V1 = 0x646f72616e646f6dL;
37 private static final long INITIAL_STATE_V2 = 0x6c7967656e657261L;
38 private static final long INITIAL_STATE_V3 = 0x7465646279746573L;
39
40 private final long initialStateV0;
41 private final long initialStateV1;
42 private final long initialStateV2;
43 private final long initialStateV3;
44
45 private long v0;
46 private long v1;
47 private long v2;
48 private long v3;
49
50 SipHash(int compressionRounds, int finalizationRounds, byte[] seed) {
51 if (seed.length != SEED_LENGTH) {
52 throw new IllegalArgumentException("seed must be of length " + SEED_LENGTH);
53 }
54 this.compressionRounds = ObjectUtil.checkPositive(compressionRounds, "compressionRounds");
55 this.finalizationRounds = ObjectUtil.checkPositive(finalizationRounds, "finalizationRounds");
56
57
58
59 ByteBuffer keyBuffer = ByteBuffer.wrap(seed).order(ByteOrder.LITTLE_ENDIAN);
60 final long k0 = keyBuffer.getLong();
61 final long k1 = keyBuffer.getLong();
62
63 initialStateV0 = INITIAL_STATE_V0 ^ k0;
64 initialStateV1 = INITIAL_STATE_V1 ^ k1;
65 initialStateV2 = INITIAL_STATE_V2 ^ k0;
66 initialStateV3 = INITIAL_STATE_V3 ^ k1;
67 }
68
69 long macHash(ByteBuffer input) {
70 v0 = initialStateV0;
71 v1 = initialStateV1;
72 v2 = initialStateV2;
73 v3 = initialStateV3;
74 int remaining = input.remaining();
75 int position = input.position();
76 int len = remaining - (remaining % Long.BYTES);
77 boolean needsReverse = input.order() == ByteOrder.BIG_ENDIAN;
78 for (int offset = position; offset < len; offset += Long.BYTES) {
79 long m = input.getLong(offset);
80 if (needsReverse) {
81
82 m = Long.reverseBytes(m);
83 }
84 v3 ^= m;
85 for (int i = 0; i < compressionRounds; i++) {
86 sipround();
87 }
88 v0 ^= m;
89 }
90
91
92 final int left = remaining & (Long.BYTES - 1);
93 long b = (long) remaining << 56;
94 assert left < Long.BYTES;
95 switch (left) {
96 case 7:
97 b |= (long) input.get(position + len + 6) << 48;
98 case 6:
99 b |= (long) input.get(position + len + 5) << 40;
100 case 5:
101 b |= (long) input.get(position + len + 4) << 32;
102 case 4:
103 b |= (long) input.get(position + len + 3) << 24;
104 case 3:
105 b |= (long) input.get(position + len + 2) << 16;
106 case 2:
107 b |= (long) input.get(position + len + 1) << 8;
108 case 1:
109 b |= input.get(position + len);
110 break;
111 case 0:
112 break;
113 default:
114 throw new IllegalStateException("Unexpected value: " + left);
115 }
116
117 v3 ^= b;
118 for (int i = 0; i < compressionRounds; i++) {
119 sipround();
120 }
121
122 v0 ^= b;
123 v2 ^= 0xFF;
124 for (int i = 0; i < finalizationRounds; i++) {
125 sipround();
126 }
127
128 return v0 ^ v1 ^ v2 ^ v3;
129 }
130
131 private void sipround() {
132 v0 += v1;
133 v2 += v3;
134 v1 = Long.rotateLeft(v1, 13);
135 v3 = Long.rotateLeft(v3, 16);
136 v1 ^= v0;
137 v3 ^= v2;
138
139 v0 = Long.rotateLeft(v0, 32);
140
141 v2 += v1;
142 v0 += v3;
143 v1 = Long.rotateLeft(v1, 17);
144 v3 = Long.rotateLeft(v3, 21);
145 v1 ^= v2;
146 v3 ^= v0;
147
148 v2 = Long.rotateLeft(v2, 32);
149 }
150 }