850 lines
30 KiB
Python
850 lines
30 KiB
Python
|
import unittest
|
||
|
import zlib
|
||
|
|
||
|
from ..exceptions import (
|
||
|
DuplicateParameter, InvalidParameterName, InvalidParameterValue,
|
||
|
NegotiationError, PayloadTooBig
|
||
|
)
|
||
|
from ..framing import (
|
||
|
OP_BINARY, OP_CLOSE, OP_CONT, OP_PING, OP_PONG, OP_TEXT, Frame,
|
||
|
serialize_close
|
||
|
)
|
||
|
from .permessage_deflate import *
|
||
|
|
||
|
|
||
|
class ExtensionTestsMixin:
|
||
|
|
||
|
def assertExtensionEqual(self, extension1, extension2):
|
||
|
self.assertEqual(extension1.remote_no_context_takeover,
|
||
|
extension2.remote_no_context_takeover)
|
||
|
self.assertEqual(extension1.local_no_context_takeover,
|
||
|
extension2.local_no_context_takeover)
|
||
|
self.assertEqual(extension1.remote_max_window_bits,
|
||
|
extension2.remote_max_window_bits)
|
||
|
self.assertEqual(extension1.local_max_window_bits,
|
||
|
extension2.local_max_window_bits)
|
||
|
|
||
|
|
||
|
class ClientPerMessageDeflateFactoryTests(unittest.TestCase,
|
||
|
ExtensionTestsMixin):
|
||
|
|
||
|
def test_name(self):
|
||
|
assert ClientPerMessageDeflateFactory.name == 'permessage-deflate'
|
||
|
|
||
|
def test_init(self):
|
||
|
for config in [
|
||
|
(False, False, 8, None), # server_max_window_bits ≥ 8
|
||
|
(False, True, 15, None), # server_max_window_bits ≤ 15
|
||
|
(True, False, None, 8), # client_max_window_bits ≥ 8
|
||
|
(True, True, None, 15), # client_max_window_bits ≤ 15
|
||
|
(False, False, None, True), # client_max_window_bits
|
||
|
(False, False, None, None, {'memLevel': 4}),
|
||
|
]:
|
||
|
with self.subTest(config=config):
|
||
|
# This does not raise an exception.
|
||
|
ClientPerMessageDeflateFactory(*config)
|
||
|
|
||
|
def test_init_error(self):
|
||
|
for config in [
|
||
|
(False, False, 7, 8), # server_max_window_bits < 8
|
||
|
(False, True, 8, 7), # client_max_window_bits < 8
|
||
|
(True, False, 16, 15), # server_max_window_bits > 15
|
||
|
(True, True, 15, 16), # client_max_window_bits > 15
|
||
|
(False, False, True, None), # server_max_window_bits
|
||
|
(False, False, None, None, {'wbits': 11}),
|
||
|
]:
|
||
|
with self.subTest(config=config):
|
||
|
with self.assertRaises(ValueError):
|
||
|
ClientPerMessageDeflateFactory(*config)
|
||
|
|
||
|
def test_get_request_params(self):
|
||
|
for config, result in [
|
||
|
# Test without any parameter
|
||
|
(
|
||
|
(False, False, None, None),
|
||
|
[],
|
||
|
),
|
||
|
# Test server_no_context_takeover
|
||
|
(
|
||
|
(True, False, None, None),
|
||
|
[('server_no_context_takeover', None)],
|
||
|
),
|
||
|
# Test client_no_context_takeover
|
||
|
(
|
||
|
(False, True, None, None),
|
||
|
[('client_no_context_takeover', None)],
|
||
|
),
|
||
|
# Test server_max_window_bits
|
||
|
(
|
||
|
(False, False, 10, None),
|
||
|
[('server_max_window_bits', '10')],
|
||
|
),
|
||
|
# Test client_max_window_bits
|
||
|
(
|
||
|
(False, False, None, 10),
|
||
|
[('client_max_window_bits', '10')],
|
||
|
),
|
||
|
(
|
||
|
(False, False, None, True),
|
||
|
[('client_max_window_bits', None)],
|
||
|
),
|
||
|
# Test all parameters together
|
||
|
(
|
||
|
(True, True, 12, 12),
|
||
|
[
|
||
|
('server_no_context_takeover', None),
|
||
|
('client_no_context_takeover', None),
|
||
|
('server_max_window_bits', '12'),
|
||
|
('client_max_window_bits', '12'),
|
||
|
],
|
||
|
),
|
||
|
]:
|
||
|
with self.subTest(config=config):
|
||
|
factory = ClientPerMessageDeflateFactory(*config)
|
||
|
self.assertEqual(factory.get_request_params(), result)
|
||
|
|
||
|
def test_process_response_params(self):
|
||
|
for config, response_params, result in [
|
||
|
# Test without any parameter
|
||
|
(
|
||
|
(False, False, None, None),
|
||
|
[],
|
||
|
(False, False, 15, 15),
|
||
|
),
|
||
|
(
|
||
|
(False, False, None, None),
|
||
|
[('unknown', None)],
|
||
|
InvalidParameterName,
|
||
|
),
|
||
|
# Test server_no_context_takeover
|
||
|
(
|
||
|
(False, False, None, None),
|
||
|
[('server_no_context_takeover', None)],
|
||
|
(True, False, 15, 15),
|
||
|
),
|
||
|
(
|
||
|
(True, False, None, None),
|
||
|
[],
|
||
|
NegotiationError,
|
||
|
),
|
||
|
(
|
||
|
(True, False, None, None),
|
||
|
[('server_no_context_takeover', None)],
|
||
|
(True, False, 15, 15),
|
||
|
),
|
||
|
(
|
||
|
(True, False, None, None),
|
||
|
[('server_no_context_takeover', None)] * 2,
|
||
|
DuplicateParameter,
|
||
|
),
|
||
|
(
|
||
|
(True, False, None, None),
|
||
|
[('server_no_context_takeover', '42')],
|
||
|
InvalidParameterValue,
|
||
|
),
|
||
|
# Test client_no_context_takeover
|
||
|
(
|
||
|
(False, False, None, None),
|
||
|
[('client_no_context_takeover', None)],
|
||
|
(False, True, 15, 15),
|
||
|
),
|
||
|
(
|
||
|
(False, True, None, None),
|
||
|
[],
|
||
|
(False, True, 15, 15),
|
||
|
),
|
||
|
(
|
||
|
(False, True, None, None),
|
||
|
[('client_no_context_takeover', None)],
|
||
|
(False, True, 15, 15),
|
||
|
),
|
||
|
(
|
||
|
(False, True, None, None),
|
||
|
[('client_no_context_takeover', None)] * 2,
|
||
|
DuplicateParameter,
|
||
|
),
|
||
|
(
|
||
|
(False, True, None, None),
|
||
|
[('client_no_context_takeover', '42')],
|
||
|
InvalidParameterValue,
|
||
|
),
|
||
|
# Test server_max_window_bits
|
||
|
(
|
||
|
(False, False, None, None),
|
||
|
[('server_max_window_bits', '7')],
|
||
|
NegotiationError,
|
||
|
),
|
||
|
(
|
||
|
(False, False, None, None),
|
||
|
[('server_max_window_bits', '10')],
|
||
|
(False, False, 10, 15),
|
||
|
),
|
||
|
(
|
||
|
(False, False, None, None),
|
||
|
[('server_max_window_bits', '16')],
|
||
|
NegotiationError,
|
||
|
),
|
||
|
(
|
||
|
(False, False, 12, None),
|
||
|
[],
|
||
|
NegotiationError,
|
||
|
),
|
||
|
(
|
||
|
(False, False, 12, None),
|
||
|
[('server_max_window_bits', '10')],
|
||
|
(False, False, 10, 15),
|
||
|
),
|
||
|
(
|
||
|
(False, False, 12, None),
|
||
|
[('server_max_window_bits', '12')],
|
||
|
(False, False, 12, 15),
|
||
|
),
|
||
|
(
|
||
|
(False, False, 12, None),
|
||
|
[('server_max_window_bits', '13')],
|
||
|
NegotiationError,
|
||
|
),
|
||
|
(
|
||
|
(False, False, 12, None),
|
||
|
[('server_max_window_bits', '12')] * 2,
|
||
|
DuplicateParameter,
|
||
|
),
|
||
|
(
|
||
|
(False, False, 12, None),
|
||
|
[('server_max_window_bits', '42')],
|
||
|
InvalidParameterValue,
|
||
|
),
|
||
|
# Test client_max_window_bits
|
||
|
(
|
||
|
(False, False, None, None),
|
||
|
[('client_max_window_bits', '10')],
|
||
|
NegotiationError,
|
||
|
),
|
||
|
(
|
||
|
(False, False, None, True),
|
||
|
[],
|
||
|
(False, False, 15, 15),
|
||
|
),
|
||
|
(
|
||
|
(False, False, None, True),
|
||
|
[('client_max_window_bits', '7')],
|
||
|
NegotiationError,
|
||
|
),
|
||
|
(
|
||
|
(False, False, None, True),
|
||
|
[('client_max_window_bits', '10')],
|
||
|
(False, False, 15, 10),
|
||
|
),
|
||
|
(
|
||
|
(False, False, None, True),
|
||
|
[('client_max_window_bits', '16')],
|
||
|
NegotiationError,
|
||
|
),
|
||
|
(
|
||
|
(False, False, None, 12),
|
||
|
[],
|
||
|
(False, False, 15, 12),
|
||
|
),
|
||
|
(
|
||
|
(False, False, None, 12),
|
||
|
[('client_max_window_bits', '10')],
|
||
|
(False, False, 15, 10),
|
||
|
),
|
||
|
(
|
||
|
(False, False, None, 12),
|
||
|
[('client_max_window_bits', '12')],
|
||
|
(False, False, 15, 12),
|
||
|
),
|
||
|
(
|
||
|
(False, False, None, 12),
|
||
|
[('client_max_window_bits', '13')],
|
||
|
NegotiationError,
|
||
|
),
|
||
|
(
|
||
|
(False, False, None, 12),
|
||
|
[('client_max_window_bits', '12')] * 2,
|
||
|
DuplicateParameter,
|
||
|
),
|
||
|
(
|
||
|
(False, False, None, 12),
|
||
|
[('client_max_window_bits', '42')],
|
||
|
InvalidParameterValue,
|
||
|
),
|
||
|
# Test all parameters together
|
||
|
(
|
||
|
(True, True, 12, 12),
|
||
|
[
|
||
|
('server_no_context_takeover', None),
|
||
|
('client_no_context_takeover', None),
|
||
|
('server_max_window_bits', '10'),
|
||
|
('client_max_window_bits', '10'),
|
||
|
],
|
||
|
(True, True, 10, 10),
|
||
|
),
|
||
|
(
|
||
|
(False, False, None, True),
|
||
|
[
|
||
|
('server_no_context_takeover', None),
|
||
|
('client_no_context_takeover', None),
|
||
|
('server_max_window_bits', '10'),
|
||
|
('client_max_window_bits', '10'),
|
||
|
],
|
||
|
(True, True, 10, 10),
|
||
|
),
|
||
|
(
|
||
|
(True, True, 12, 12),
|
||
|
[
|
||
|
('server_no_context_takeover', None),
|
||
|
('server_max_window_bits', '12'),
|
||
|
],
|
||
|
(True, True, 12, 12),
|
||
|
),
|
||
|
]:
|
||
|
with self.subTest(
|
||
|
config=config,
|
||
|
response_params=response_params,
|
||
|
):
|
||
|
factory = ClientPerMessageDeflateFactory(*config)
|
||
|
if isinstance(result, type) and issubclass(result, Exception):
|
||
|
with self.assertRaises(result):
|
||
|
factory.process_response_params(response_params, [])
|
||
|
else:
|
||
|
extension = factory.process_response_params(
|
||
|
response_params, [])
|
||
|
expected = PerMessageDeflate(*result)
|
||
|
self.assertExtensionEqual(extension, expected)
|
||
|
|
||
|
def test_process_response_params_deduplication(self):
|
||
|
factory = ClientPerMessageDeflateFactory(False, False, None, None)
|
||
|
with self.assertRaises(NegotiationError):
|
||
|
factory.process_response_params(
|
||
|
[], [PerMessageDeflate(False, False, 15, 15)])
|
||
|
|
||
|
|
||
|
class ServerPerMessageDeflateFactoryTests(unittest.TestCase,
|
||
|
ExtensionTestsMixin):
|
||
|
|
||
|
def test_name(self):
|
||
|
assert ServerPerMessageDeflateFactory.name == 'permessage-deflate'
|
||
|
|
||
|
def test_init(self):
|
||
|
for config in [
|
||
|
(False, False, 8, None), # server_max_window_bits ≥ 8
|
||
|
(False, True, 15, None), # server_max_window_bits ≤ 15
|
||
|
(True, False, None, 8), # client_max_window_bits ≥ 8
|
||
|
(True, True, None, 15), # client_max_window_bits ≤ 15
|
||
|
(False, False, None, None, {'memLevel': 4}),
|
||
|
]:
|
||
|
with self.subTest(config=config):
|
||
|
# This does not raise an exception.
|
||
|
ServerPerMessageDeflateFactory(*config)
|
||
|
|
||
|
def test_init_error(self):
|
||
|
for config in [
|
||
|
(False, False, 7, 8), # server_max_window_bits < 8
|
||
|
(False, True, 8, 7), # client_max_window_bits < 8
|
||
|
(True, False, 16, 15), # server_max_window_bits > 15
|
||
|
(True, True, 15, 16), # client_max_window_bits > 15
|
||
|
(False, False, None, True), # client_max_window_bits
|
||
|
(False, False, True, None), # server_max_window_bits
|
||
|
(False, False, None, None, {'wbits': 11}),
|
||
|
]:
|
||
|
with self.subTest(config=config):
|
||
|
with self.assertRaises(ValueError):
|
||
|
ServerPerMessageDeflateFactory(*config)
|
||
|
|
||
|
def test_process_request_params(self):
|
||
|
# Parameters in result appear swapped vs. config because the order is
|
||
|
# (remote, local) vs. (server, client).
|
||
|
for config, request_params, response_params, result in [
|
||
|
# Test without any parameter
|
||
|
(
|
||
|
(False, False, None, None),
|
||
|
[],
|
||
|
[],
|
||
|
(False, False, 15, 15),
|
||
|
),
|
||
|
(
|
||
|
(False, False, None, None),
|
||
|
[('unknown', None)],
|
||
|
None,
|
||
|
InvalidParameterName,
|
||
|
),
|
||
|
# Test server_no_context_takeover
|
||
|
(
|
||
|
(False, False, None, None),
|
||
|
[('server_no_context_takeover', None)],
|
||
|
[('server_no_context_takeover', None)],
|
||
|
(False, True, 15, 15),
|
||
|
),
|
||
|
(
|
||
|
(True, False, None, None),
|
||
|
[],
|
||
|
[('server_no_context_takeover', None)],
|
||
|
(False, True, 15, 15),
|
||
|
),
|
||
|
(
|
||
|
(True, False, None, None),
|
||
|
[('server_no_context_takeover', None)],
|
||
|
[('server_no_context_takeover', None)],
|
||
|
(False, True, 15, 15),
|
||
|
),
|
||
|
(
|
||
|
(True, False, None, None),
|
||
|
[('server_no_context_takeover', None)] * 2,
|
||
|
None,
|
||
|
DuplicateParameter,
|
||
|
),
|
||
|
(
|
||
|
(True, False, None, None),
|
||
|
[('server_no_context_takeover', '42')],
|
||
|
None,
|
||
|
InvalidParameterValue,
|
||
|
),
|
||
|
# Test client_no_context_takeover
|
||
|
(
|
||
|
(False, False, None, None),
|
||
|
[('client_no_context_takeover', None)],
|
||
|
[('client_no_context_takeover', None)], # doesn't matter
|
||
|
(True, False, 15, 15),
|
||
|
),
|
||
|
(
|
||
|
(False, True, None, None),
|
||
|
[],
|
||
|
[('client_no_context_takeover', None)],
|
||
|
(True, False, 15, 15),
|
||
|
),
|
||
|
(
|
||
|
(False, True, None, None),
|
||
|
[('client_no_context_takeover', None)],
|
||
|
[('client_no_context_takeover', None)], # doesn't matter
|
||
|
(True, False, 15, 15),
|
||
|
),
|
||
|
(
|
||
|
(False, True, None, None),
|
||
|
[('client_no_context_takeover', None)] * 2,
|
||
|
None,
|
||
|
DuplicateParameter,
|
||
|
),
|
||
|
(
|
||
|
(False, True, None, None),
|
||
|
[('client_no_context_takeover', '42')],
|
||
|
None,
|
||
|
InvalidParameterValue,
|
||
|
),
|
||
|
# Test server_max_window_bits
|
||
|
(
|
||
|
(False, False, None, None),
|
||
|
[('server_max_window_bits', '7')],
|
||
|
None,
|
||
|
NegotiationError,
|
||
|
),
|
||
|
(
|
||
|
(False, False, None, None),
|
||
|
[('server_max_window_bits', '10')],
|
||
|
[('server_max_window_bits', '10')],
|
||
|
(False, False, 15, 10),
|
||
|
),
|
||
|
(
|
||
|
(False, False, None, None),
|
||
|
[('server_max_window_bits', '16')],
|
||
|
None,
|
||
|
NegotiationError,
|
||
|
),
|
||
|
(
|
||
|
(False, False, 12, None),
|
||
|
[],
|
||
|
[('server_max_window_bits', '12')],
|
||
|
(False, False, 15, 12),
|
||
|
),
|
||
|
(
|
||
|
(False, False, 12, None),
|
||
|
[('server_max_window_bits', '10')],
|
||
|
[('server_max_window_bits', '10')],
|
||
|
(False, False, 15, 10),
|
||
|
),
|
||
|
(
|
||
|
(False, False, 12, None),
|
||
|
[('server_max_window_bits', '12')],
|
||
|
[('server_max_window_bits', '12')],
|
||
|
(False, False, 15, 12),
|
||
|
),
|
||
|
(
|
||
|
(False, False, 12, None),
|
||
|
[('server_max_window_bits', '13')],
|
||
|
[('server_max_window_bits', '12')],
|
||
|
(False, False, 15, 12),
|
||
|
),
|
||
|
(
|
||
|
(False, False, 12, None),
|
||
|
[('server_max_window_bits', '12')] * 2,
|
||
|
None,
|
||
|
DuplicateParameter,
|
||
|
),
|
||
|
(
|
||
|
(False, False, 12, None),
|
||
|
[('server_max_window_bits', '42')],
|
||
|
None,
|
||
|
InvalidParameterValue,
|
||
|
),
|
||
|
# Test client_max_window_bits
|
||
|
(
|
||
|
(False, False, None, None),
|
||
|
[('client_max_window_bits', None)],
|
||
|
[],
|
||
|
(False, False, 15, 15),
|
||
|
),
|
||
|
(
|
||
|
(False, False, None, None),
|
||
|
[('client_max_window_bits', '7')],
|
||
|
None,
|
||
|
InvalidParameterValue,
|
||
|
),
|
||
|
(
|
||
|
(False, False, None, None),
|
||
|
[('client_max_window_bits', '10')],
|
||
|
[('client_max_window_bits', '10')], # doesn't matter
|
||
|
(False, False, 10, 15),
|
||
|
),
|
||
|
(
|
||
|
(False, False, None, None),
|
||
|
[('client_max_window_bits', '16')],
|
||
|
None,
|
||
|
InvalidParameterValue,
|
||
|
),
|
||
|
(
|
||
|
(False, False, None, 12),
|
||
|
[],
|
||
|
None,
|
||
|
NegotiationError,
|
||
|
),
|
||
|
(
|
||
|
(False, False, None, 12),
|
||
|
[('client_max_window_bits', None)],
|
||
|
[('client_max_window_bits', '12')],
|
||
|
(False, False, 12, 15),
|
||
|
),
|
||
|
(
|
||
|
(False, False, None, 12),
|
||
|
[('client_max_window_bits', '10')],
|
||
|
[('client_max_window_bits', '10')],
|
||
|
(False, False, 10, 15),
|
||
|
),
|
||
|
(
|
||
|
(False, False, None, 12),
|
||
|
[('client_max_window_bits', '12')],
|
||
|
[('client_max_window_bits', '12')], # doesn't matter
|
||
|
(False, False, 12, 15),
|
||
|
),
|
||
|
(
|
||
|
(False, False, None, 12),
|
||
|
[('client_max_window_bits', '13')],
|
||
|
[('client_max_window_bits', '12')], # doesn't matter
|
||
|
(False, False, 12, 15),
|
||
|
),
|
||
|
(
|
||
|
(False, False, None, 12),
|
||
|
[('client_max_window_bits', '12')] * 2,
|
||
|
None,
|
||
|
DuplicateParameter,
|
||
|
),
|
||
|
(
|
||
|
(False, False, None, 12),
|
||
|
[('client_max_window_bits', '42')],
|
||
|
None,
|
||
|
InvalidParameterValue,
|
||
|
),
|
||
|
# # Test all parameters together
|
||
|
(
|
||
|
(True, True, 12, 12),
|
||
|
[
|
||
|
('server_no_context_takeover', None),
|
||
|
('client_no_context_takeover', None),
|
||
|
('server_max_window_bits', '10'),
|
||
|
('client_max_window_bits', '10'),
|
||
|
],
|
||
|
[
|
||
|
('server_no_context_takeover', None),
|
||
|
('client_no_context_takeover', None),
|
||
|
('server_max_window_bits', '10'),
|
||
|
('client_max_window_bits', '10'),
|
||
|
],
|
||
|
(True, True, 10, 10),
|
||
|
),
|
||
|
(
|
||
|
(False, False, None, None),
|
||
|
[
|
||
|
('server_no_context_takeover', None),
|
||
|
('client_no_context_takeover', None),
|
||
|
('server_max_window_bits', '10'),
|
||
|
('client_max_window_bits', '10'),
|
||
|
],
|
||
|
[
|
||
|
('server_no_context_takeover', None),
|
||
|
('client_no_context_takeover', None),
|
||
|
('server_max_window_bits', '10'),
|
||
|
('client_max_window_bits', '10'),
|
||
|
],
|
||
|
(True, True, 10, 10),
|
||
|
),
|
||
|
(
|
||
|
(True, True, 12, 12),
|
||
|
[
|
||
|
('client_max_window_bits', None),
|
||
|
],
|
||
|
[
|
||
|
('server_no_context_takeover', None),
|
||
|
('client_no_context_takeover', None),
|
||
|
('server_max_window_bits', '12'),
|
||
|
('client_max_window_bits', '12'),
|
||
|
],
|
||
|
(True, True, 12, 12),
|
||
|
),
|
||
|
]:
|
||
|
with self.subTest(
|
||
|
config=config,
|
||
|
request_params=request_params,
|
||
|
response_params=response_params,
|
||
|
):
|
||
|
factory = ServerPerMessageDeflateFactory(*config)
|
||
|
if isinstance(result, type) and issubclass(result, Exception):
|
||
|
with self.assertRaises(result):
|
||
|
factory.process_request_params(request_params, [])
|
||
|
else:
|
||
|
params, extension = factory.process_request_params(
|
||
|
request_params, [])
|
||
|
self.assertEqual(params, response_params)
|
||
|
expected = PerMessageDeflate(*result)
|
||
|
self.assertExtensionEqual(extension, expected)
|
||
|
|
||
|
def test_process_response_params_deduplication(self):
|
||
|
factory = ServerPerMessageDeflateFactory(False, False, None, None)
|
||
|
with self.assertRaises(NegotiationError):
|
||
|
factory.process_request_params(
|
||
|
[], [PerMessageDeflate(False, False, 15, 15)])
|
||
|
|
||
|
|
||
|
class PerMessageDeflateTests(unittest.TestCase):
|
||
|
|
||
|
def setUp(self):
|
||
|
# Set up an instance of the permessage-deflate extension with the most
|
||
|
# common settings. Since the extension is symmetrical, this instance
|
||
|
# may be used for testing both encoding and decoding.
|
||
|
self.extension = PerMessageDeflate(False, False, 15, 15)
|
||
|
|
||
|
def test_name(self):
|
||
|
assert self.extension.name == 'permessage-deflate'
|
||
|
|
||
|
# Control frames aren't encoded or decoded.
|
||
|
|
||
|
def test_no_encode_decode_ping_frame(self):
|
||
|
frame = Frame(True, OP_PING, b'')
|
||
|
|
||
|
self.assertEqual(self.extension.encode(frame), frame)
|
||
|
|
||
|
self.assertEqual(self.extension.decode(frame), frame)
|
||
|
|
||
|
def test_no_encode_decode_pong_frame(self):
|
||
|
frame = Frame(True, OP_PONG, b'')
|
||
|
|
||
|
self.assertEqual(self.extension.encode(frame), frame)
|
||
|
|
||
|
self.assertEqual(self.extension.decode(frame), frame)
|
||
|
|
||
|
def test_no_encode_decode_close_frame(self):
|
||
|
frame = Frame(True, OP_CLOSE, serialize_close(1000, ''))
|
||
|
|
||
|
self.assertEqual(self.extension.encode(frame), frame)
|
||
|
|
||
|
self.assertEqual(self.extension.decode(frame), frame)
|
||
|
|
||
|
# Data frames are encoded and decoded.
|
||
|
|
||
|
def test_encode_decode_text_frame(self):
|
||
|
frame = Frame(True, OP_TEXT, 'café'.encode('utf-8'))
|
||
|
|
||
|
enc_frame = self.extension.encode(frame)
|
||
|
|
||
|
self.assertEqual(enc_frame, frame._replace(
|
||
|
rsv1=True,
|
||
|
data=b'JNL;\xbc\x12\x00',
|
||
|
))
|
||
|
|
||
|
dec_frame = self.extension.decode(enc_frame)
|
||
|
|
||
|
self.assertEqual(dec_frame, frame)
|
||
|
|
||
|
def test_encode_decode_binary_frame(self):
|
||
|
frame = Frame(True, OP_BINARY, b'tea')
|
||
|
|
||
|
enc_frame = self.extension.encode(frame)
|
||
|
|
||
|
self.assertEqual(enc_frame, frame._replace(
|
||
|
rsv1=True,
|
||
|
data=b'*IM\x04\x00',
|
||
|
))
|
||
|
|
||
|
dec_frame = self.extension.decode(enc_frame)
|
||
|
|
||
|
self.assertEqual(dec_frame, frame)
|
||
|
|
||
|
def test_encode_decode_fragmented_text_frame(self):
|
||
|
frame1 = Frame(False, OP_TEXT, 'café'.encode('utf-8'))
|
||
|
frame2 = Frame(False, OP_CONT, ' & '.encode('utf-8'))
|
||
|
frame3 = Frame(True, OP_CONT, 'croissants'.encode('utf-8'))
|
||
|
|
||
|
enc_frame1 = self.extension.encode(frame1)
|
||
|
enc_frame2 = self.extension.encode(frame2)
|
||
|
enc_frame3 = self.extension.encode(frame3)
|
||
|
|
||
|
self.assertEqual(enc_frame1, frame1._replace(
|
||
|
rsv1=True,
|
||
|
data=b'JNL;\xbc\x12\x00\x00\x00\xff\xff',
|
||
|
))
|
||
|
self.assertEqual(enc_frame2, frame2._replace(
|
||
|
rsv1=True,
|
||
|
data=b'RPS\x00\x00\x00\x00\xff\xff',
|
||
|
))
|
||
|
self.assertEqual(enc_frame3, frame3._replace(
|
||
|
rsv1=True,
|
||
|
data=b'J.\xca\xcf,.N\xcc+)\x06\x00',
|
||
|
))
|
||
|
|
||
|
dec_frame1 = self.extension.decode(enc_frame1)
|
||
|
dec_frame2 = self.extension.decode(enc_frame2)
|
||
|
dec_frame3 = self.extension.decode(enc_frame3)
|
||
|
|
||
|
self.assertEqual(dec_frame1, frame1)
|
||
|
self.assertEqual(dec_frame2, frame2)
|
||
|
self.assertEqual(dec_frame3, frame3)
|
||
|
|
||
|
def test_encode_decode_fragmented_binary_frame(self):
|
||
|
frame1 = Frame(False, OP_TEXT, b'tea ')
|
||
|
frame2 = Frame(True, OP_CONT, b'time')
|
||
|
|
||
|
enc_frame1 = self.extension.encode(frame1)
|
||
|
enc_frame2 = self.extension.encode(frame2)
|
||
|
|
||
|
self.assertEqual(enc_frame1, frame1._replace(
|
||
|
rsv1=True,
|
||
|
data=b'*IMT\x00\x00\x00\x00\xff\xff',
|
||
|
))
|
||
|
self.assertEqual(enc_frame2, frame2._replace(
|
||
|
rsv1=True,
|
||
|
data=b'*\xc9\xccM\x05\x00',
|
||
|
))
|
||
|
|
||
|
dec_frame1 = self.extension.decode(enc_frame1)
|
||
|
dec_frame2 = self.extension.decode(enc_frame2)
|
||
|
|
||
|
self.assertEqual(dec_frame1, frame1)
|
||
|
self.assertEqual(dec_frame2, frame2)
|
||
|
|
||
|
def test_no_decode_text_frame(self):
|
||
|
frame = Frame(True, OP_TEXT, 'café'.encode('utf-8'))
|
||
|
|
||
|
# Try decoding a frame that wasn't encoded.
|
||
|
self.assertEqual(self.extension.decode(frame), frame)
|
||
|
|
||
|
def test_no_decode_binary_frame(self):
|
||
|
frame = Frame(True, OP_TEXT, b'tea')
|
||
|
|
||
|
# Try decoding a frame that wasn't encoded.
|
||
|
self.assertEqual(self.extension.decode(frame), frame)
|
||
|
|
||
|
def test_no_decode_fragmented_text_frame(self):
|
||
|
frame1 = Frame(False, OP_TEXT, 'café'.encode('utf-8'))
|
||
|
frame2 = Frame(False, OP_CONT, ' & '.encode('utf-8'))
|
||
|
frame3 = Frame(True, OP_CONT, 'croissants'.encode('utf-8'))
|
||
|
|
||
|
dec_frame1 = self.extension.decode(frame1)
|
||
|
dec_frame2 = self.extension.decode(frame2)
|
||
|
dec_frame3 = self.extension.decode(frame3)
|
||
|
|
||
|
self.assertEqual(dec_frame1, frame1)
|
||
|
self.assertEqual(dec_frame2, frame2)
|
||
|
self.assertEqual(dec_frame3, frame3)
|
||
|
|
||
|
def test_no_decode_fragmented_binary_frame(self):
|
||
|
frame1 = Frame(False, OP_TEXT, b'tea ')
|
||
|
frame2 = Frame(True, OP_CONT, b'time')
|
||
|
|
||
|
dec_frame1 = self.extension.decode(frame1)
|
||
|
dec_frame2 = self.extension.decode(frame2)
|
||
|
|
||
|
self.assertEqual(dec_frame1, frame1)
|
||
|
self.assertEqual(dec_frame2, frame2)
|
||
|
|
||
|
def test_context_takeover(self):
|
||
|
frame = Frame(True, OP_TEXT, 'café'.encode('utf-8'))
|
||
|
|
||
|
enc_frame1 = self.extension.encode(frame)
|
||
|
enc_frame2 = self.extension.encode(frame)
|
||
|
|
||
|
self.assertEqual(enc_frame1.data, b'JNL;\xbc\x12\x00')
|
||
|
self.assertEqual(enc_frame2.data, b'J\x06\x11\x00\x00')
|
||
|
|
||
|
def test_remote_no_context_takeover(self):
|
||
|
# No context takeover when decoding messages.
|
||
|
self.extension = PerMessageDeflate(True, False, 15, 15)
|
||
|
|
||
|
frame = Frame(True, OP_TEXT, 'café'.encode('utf-8'))
|
||
|
|
||
|
enc_frame1 = self.extension.encode(frame)
|
||
|
enc_frame2 = self.extension.encode(frame)
|
||
|
|
||
|
self.assertEqual(enc_frame1.data, b'JNL;\xbc\x12\x00')
|
||
|
self.assertEqual(enc_frame2.data, b'J\x06\x11\x00\x00')
|
||
|
|
||
|
dec_frame1 = self.extension.decode(enc_frame1)
|
||
|
self.assertEqual(dec_frame1, frame)
|
||
|
|
||
|
with self.assertRaises(zlib.error) as exc:
|
||
|
self.extension.decode(enc_frame2)
|
||
|
self.assertIn("invalid distance too far back", str(exc.exception))
|
||
|
|
||
|
def test_local_no_context_takeover(self):
|
||
|
# No context takeover when encoding and decoding messages.
|
||
|
self.extension = PerMessageDeflate(True, True, 15, 15)
|
||
|
|
||
|
frame = Frame(True, OP_TEXT, 'café'.encode('utf-8'))
|
||
|
|
||
|
enc_frame1 = self.extension.encode(frame)
|
||
|
enc_frame2 = self.extension.encode(frame)
|
||
|
|
||
|
self.assertEqual(enc_frame1.data, b'JNL;\xbc\x12\x00')
|
||
|
self.assertEqual(enc_frame2.data, b'JNL;\xbc\x12\x00')
|
||
|
|
||
|
dec_frame1 = self.extension.decode(enc_frame1)
|
||
|
dec_frame2 = self.extension.decode(enc_frame2)
|
||
|
|
||
|
self.assertEqual(dec_frame1, frame)
|
||
|
self.assertEqual(dec_frame2, frame)
|
||
|
|
||
|
# Compression settings can be customized.
|
||
|
|
||
|
def test_compress_settings(self):
|
||
|
# Configure an extension so that no compression actually occurs.
|
||
|
extension = PerMessageDeflate(False, False, 15, 15, {'level': 0})
|
||
|
|
||
|
frame = Frame(True, OP_TEXT, 'café'.encode('utf-8'))
|
||
|
|
||
|
enc_frame = extension.encode(frame)
|
||
|
|
||
|
self.assertEqual(enc_frame, frame._replace(
|
||
|
rsv1=True,
|
||
|
data=b'\x00\x05\x00\xfa\xffcaf\xc3\xa9\x00', # not compressed
|
||
|
))
|
||
|
|
||
|
# Frames aren't decoded beyond max_length.
|
||
|
|
||
|
def test_decompress_max_size(self):
|
||
|
frame = Frame(True, OP_TEXT, ('a' * 20).encode('utf-8'))
|
||
|
|
||
|
enc_frame = self.extension.encode(frame)
|
||
|
|
||
|
self.assertEqual(enc_frame.data, b'JL\xc4\x04\x00\x00')
|
||
|
|
||
|
with self.assertRaises(PayloadTooBig):
|
||
|
self.extension.decode(enc_frame, max_size=10)
|