Skip to content

Commit 89f442b

Browse files
committed
Switch to aiohttp masking method
This utilises the masking method used in aiohttp as it has better performance, in addition the aiohttp test is also used. With thanks to @willmcgugan for the original implementation. This also removes the option of using wsaccel, it was previously usable as an undocumented optional extra. This is considered acceptable as the aiohttp masking method has similar performance to wsaccel.
1 parent f15e015 commit 89f442b

File tree

4 files changed

+38
-19
lines changed

4 files changed

+38
-19
lines changed

.travis.yml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,21 +24,21 @@ matrix:
2424
dist: xenial
2525
sudo: true # required workaround for https://github.com/travis-ci/travis-ci/issues/9815
2626
- python: 3.6
27-
env: TOXENV=py36 EXTRA_DEPS=wsaccel
27+
env: TOXENV=py36
2828
- python: 3.7
29-
env: TOXENV=py37 EXTRA_DEPS=wsaccel
29+
env: TOXENV=py37
3030
dist: xenial
3131
sudo: true # required workaround for https://github.com/travis-ci/travis-ci/issues/9815
3232
- python: 3.6
33-
env: TOXENV=autobahn EXTRA_DEPS=wsaccel SIDE=client
33+
env: TOXENV=autobahn SIDE=client
3434
- python: 3.7
35-
env: TOXENV=autobahn EXTRA_DEPS=wsaccel SIDE=client
35+
env: TOXENV=autobahn SIDE=client
3636
dist: xenial
3737
sudo: true # required workaround for https://github.com/travis-ci/travis-ci/issues/9815
3838
- python: 3.6
39-
env: TOXENV=autobahn EXTRA_DEPS=wsaccel SIDE=server
39+
env: TOXENV=autobahn SIDE=server
4040
- python: 3.7
41-
env: TOXENV=autobahn EXTRA_DEPS=wsaccel SIDE=server
41+
env: TOXENV=autobahn SIDE=server
4242
dist: xenial
4343
sudo: true # required workaround for https://github.com/travis-ci/travis-ci/issues/9815
4444

README.rst

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -92,9 +92,6 @@ Testing
9292
It passes the autobahn test suite completely and strictly in both client and
9393
server modes and using permessage-deflate.
9494

95-
If `wsaccel <https://pypi.python.org/pypi/wsaccel>`_ is installed
96-
(optional), then it will be used to speed things up.
97-
9895
If you want to run the compliance tests, go into the compliance directory and
9996
then to test client mode, in one shell run the Autobahn test server:
10097

test/test_frame_protocol.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1192,3 +1192,10 @@ def test_data_we_have_no_idea_what_to_do_with(self) -> None:
11921192
with pytest.raises(ValueError):
11931193
# Intentionally passing illegal type.
11941194
proto.send_data(payload) # type: ignore
1195+
1196+
1197+
def test_xor_mask_simple() -> None:
1198+
masker = fp.XorMaskerSimple(b"1234")
1199+
assert masker.process(b"some very long data for masking by websocket") == (
1200+
b"B]^Q\x11DVFH\x12_[_U\x13PPFR\x14W]A\x14\\S@_X\\T\x14SK\x13CTP@[RYV@"
1201+
)

wsproto/frame_protocol.py

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
WebSocket frame protocol implementation.
77
"""
88

9-
import itertools
109
import os
1110
import struct
1211
from codecs import getincrementaldecoder, IncrementalDecoder
@@ -16,17 +15,33 @@
1615
if TYPE_CHECKING:
1716
from .extensions import Extension # noqa
1817

19-
try:
20-
from wsaccel.xormask import XorMaskerSimple # type: ignore
21-
except ImportError:
2218

23-
class XorMaskerSimple: # type: ignore
24-
def __init__(self, masking_key: bytes) -> None:
25-
self._maskbytes = itertools.cycle(bytearray(masking_key))
19+
_XOR_TABLE = [bytes(a ^ b for a in range(256)) for b in range(256)]
2620

27-
def process(self, data: bytes) -> bytes:
28-
maskbytes = self._maskbytes
29-
return bytearray(b ^ next(maskbytes) for b in bytearray(data))
21+
22+
class XorMaskerSimple:
23+
def __init__(self, masking_key: bytes) -> None:
24+
self._masking_key = masking_key
25+
26+
def process(self, data: bytes) -> bytes:
27+
if data: # pylint:disable=no-else-return
28+
data_array = bytearray(data)
29+
a, b, c, d = (_XOR_TABLE[n] for n in self._masking_key)
30+
data_array[::4] = data_array[::4].translate(a)
31+
data_array[1::4] = data_array[1::4].translate(b)
32+
data_array[2::4] = data_array[2::4].translate(c)
33+
data_array[3::4] = data_array[3::4].translate(d)
34+
35+
# Rotate the maksing key so that the next usage continues
36+
# with the next key element, rather than restarting.
37+
key_rotation = len(data) % 4
38+
self._masking_key = (
39+
self._masking_key[key_rotation:] + self._masking_key[:key_rotation]
40+
)
41+
42+
return bytes(data_array)
43+
else:
44+
return data
3045

3146

3247
class XorMaskerNull:

0 commit comments

Comments
 (0)