Skip to content

Commit d909d0b

Browse files
committed
Implement fast XOR
Closes #64
1 parent 1f07294 commit d909d0b

File tree

4 files changed

+46
-21
lines changed

4 files changed

+46
-21
lines changed

mask.go

-17
This file was deleted.

websocket.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,7 @@ func (c *Conn) handleControl(h header) {
231231
}
232232

233233
if h.masked {
234-
mask(h.maskKey, 0, b)
234+
xor(h.maskKey, 0, b)
235235
}
236236

237237
switch h.opcode {
@@ -325,7 +325,7 @@ func (c *Conn) dataReadLoop(h header) (err error) {
325325
left -= int64(len(b))
326326

327327
if h.masked {
328-
maskPos = mask(h.maskKey, maskPos, b)
328+
maskPos = xor(h.maskKey, maskPos, b)
329329
}
330330

331331
// Must set this before we signal the read is done.

xor.go

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package websocket
2+
3+
import (
4+
"encoding/binary"
5+
)
6+
7+
// xor applies the WebSocket masking algorithm to p
8+
// with the given key where the first 3 bits of pos
9+
// are the starting position in the key.
10+
// See https://tools.ietf.org/html/rfc6455#section-5.3
11+
//
12+
// The returned value is the position of the next byte
13+
// to be used for masking in the key. This is so that
14+
// unmasking can be performed without the entire frame.
15+
func xor(key [4]byte, keyPos int, b []byte) int {
16+
// If the payload is greater than 16 bytes, then it's worth
17+
// masking 8 bytes at a time.
18+
// Optimization from https://github.com/golang/go/issues/31586#issuecomment-485530859
19+
if len(b) > 16 {
20+
// We first create a key that is 8 bytes long
21+
// and is aligned on the position correctly.
22+
var alignedKey [8]byte
23+
for i := range alignedKey {
24+
alignedKey[i] = key[(i+keyPos)&3]
25+
}
26+
k := binary.LittleEndian.Uint64(alignedKey[:])
27+
28+
// Then we xor until b is less than 8 bytes.
29+
for len(b) >= 8 {
30+
v := binary.LittleEndian.Uint64(b)
31+
binary.LittleEndian.PutUint64(b, v^k)
32+
b = b[8:]
33+
}
34+
}
35+
36+
// xor remaining bytes.
37+
for i := range b {
38+
b[i] ^= key[keyPos&3]
39+
keyPos++
40+
}
41+
return keyPos & 3
42+
}

mask_test.go renamed to xor_test.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,13 @@ import (
66
"github.com/google/go-cmp/cmp"
77
)
88

9-
func Test_mask(t *testing.T) {
9+
func Test_xor(t *testing.T) {
1010
t.Parallel()
1111

1212
key := [4]byte{0xa, 0xb, 0xc, 0xff}
1313
p := []byte{0xa, 0xb, 0xc, 0xf2, 0xc}
1414
pos := 0
15-
pos = mask(key, pos, p)
15+
pos = xor(key, pos, p)
1616

1717
if exp := []byte{0, 0, 0, 0x0d, 0x6}; !cmp.Equal(exp, p) {
1818
t.Fatalf("unexpected mask: %v", cmp.Diff(exp, p))

0 commit comments

Comments
 (0)