def test_close_with_long_reason(): # Long close reasons get silently truncated proto = fp.FrameProtocol(client=False, extensions=[]) data = proto.close(code=fp.CloseReason.NORMAL_CLOSURE, reason="x" * 200) assert data == unhexlify("887d03e8") + b"x" * 123 # While preserving valid utf-8 proto = fp.FrameProtocol(client=False, extensions=[]) # pound sign is 2 bytes in utf-8, so naive truncation to 123 bytes will # cut it in half. Instead we truncate to 122 bytes. data = proto.close(code=fp.CloseReason.NORMAL_CLOSURE, reason="£" * 100) assert data == unhexlify("887c03e8") + "£".encode("utf-8") * 61
def test_single_short_text_data(self) -> None: proto = fp.FrameProtocol(client=False, extensions=[]) payload = "😃😄🙃😉" data = proto.send_data(payload, fin=True) payload = payload.encode("utf8") # type: ignore assert data == b"\x81" + bytearray([len(payload) ]) + payload # type: ignore
def test_data_we_have_no_idea_what_to_do_with(self) -> None: proto = fp.FrameProtocol(client=False, extensions=[]) payload: Dict[str, str] = dict() with pytest.raises(ValueError): # Intentionally passing illegal type. proto.send_data(payload) # type: ignore
def test_overly_reasoned_close(self): proto = fp.FrameProtocol(client=False, extensions=[]) reason = u"¯\_(ツ)_/¯" * 10 data = proto.close(code=fp.CloseReason.NORMAL_CLOSURE, reason=reason) assert bytes(data[0:1]) == b"\x88" assert len(data) <= 127 assert data[4:].decode("utf8")
def test_outbound_handling_single_frame(self): ext = self.FakeExtension() proto = fp.FrameProtocol(client=False, extensions=[ext]) payload = u"😃😄🙃😉" data = proto.send_data(payload, fin=True) payload = (payload + u"®").encode("utf8") assert data == b"\x91" + bytearray([len(payload)]) + payload
def test_client_inbound_compressed_multiple_data_frames(self, client): payload = b'x' * 23 compressed_payload = b'\xaa\xa8\xc0\n\x00\x00' split = 3 data = b'' ext = wpext.PerMessageDeflate() ext._enabled = True proto = fp.FrameProtocol(client=client, extensions=[ext]) result = ext.frame_inbound_header(proto, fp.Opcode.BINARY, fp.RsvBits(True, False, False), split) assert result.rsv1 result = ext.frame_inbound_payload_data(proto, compressed_payload[:split]) assert not isinstance(result, fp.CloseReason) data += result assert ext.frame_inbound_complete(proto, False) is None result = ext.frame_inbound_header(proto, fp.Opcode.CONTINUATION, fp.RsvBits(False, False, False), len(compressed_payload) - split) assert result.rsv1 result = ext.frame_inbound_payload_data(proto, compressed_payload[split:]) assert not isinstance(result, fp.CloseReason) data += result result = ext.frame_inbound_complete(proto, True) assert not isinstance(result, fp.CloseReason) data += result assert data == payload
def test_client_decompress_after_uncompressible_frame( self, client: bool) -> None: ext = wpext.PerMessageDeflate() ext._enabled = True proto = fp.FrameProtocol(client=client, extensions=[ext]) # A PING frame result = ext.frame_inbound_header(proto, fp.Opcode.PING, fp.RsvBits(False, False, False), 0) result2 = ext.frame_inbound_payload_data(proto, b"") assert not isinstance(result2, fp.CloseReason) assert ext.frame_inbound_complete(proto, True) is None # A compressed TEXT frame payload = b"x" * 23 compressed_payload = b"\xaa\xa8\xc0\n\x00\x00" result3 = ext.frame_inbound_header( proto, fp.Opcode.TEXT, fp.RsvBits(True, False, False), len(compressed_payload), ) assert isinstance(result3, fp.RsvBits) assert result3.rsv1 result4 = ext.frame_inbound_payload_data(proto, compressed_payload) assert result4 == payload result5 = ext.frame_inbound_complete(proto, True) assert not isinstance(result5, fp.CloseReason)
def test_inbound_bad_zlib_decoder_end_state( self, monkeypatch: MonkeyPatch) -> None: compressed_payload = b"x" * 23 ext = wpext.PerMessageDeflate() ext._enabled = True proto = fp.FrameProtocol(client=True, extensions=[ext]) result = ext.frame_inbound_header( proto, fp.Opcode.BINARY, fp.RsvBits(True, False, False), len(compressed_payload), ) assert isinstance(result, fp.RsvBits) assert result.rsv1 class FailDecompressor: def decompress(self, data: bytes) -> bytes: return b"" def flush(self) -> None: raise zlib.error() monkeypatch.setattr(ext, "_decompressor", FailDecompressor()) result2 = ext.frame_inbound_complete(proto, True) assert result2 is fp.CloseReason.INVALID_FRAME_PAYLOAD_DATA
def test_decompressor_reset(self, client: bool, no_context_takeover: bool) -> None: if client: args = {"server_no_context_takeover": no_context_takeover} else: args = {"client_no_context_takeover": no_context_takeover} ext = wpext.PerMessageDeflate(**args) ext._enabled = True proto = fp.FrameProtocol(client=client, extensions=[ext]) result = ext.frame_inbound_header(proto, fp.Opcode.BINARY, fp.RsvBits(True, False, False), 0) assert isinstance(result, fp.RsvBits) assert result.rsv1 assert ext._decompressor is not None result2 = ext.frame_inbound_complete(proto, True) assert not isinstance(result2, fp.CloseReason) if no_context_takeover: assert ext._decompressor is None else: assert ext._decompressor is not None result3 = ext.frame_inbound_header(proto, fp.Opcode.BINARY, fp.RsvBits(True, False, False), 0) assert isinstance(result3, fp.RsvBits) assert result3.rsv1 assert ext._decompressor is not None
def _close_test(self, code: Optional[int], reason: str = None, reason_bytes: bytes = None) -> None: payload = b"" if code: payload += struct.pack("!H", code) if reason: payload += reason.encode("utf8") elif reason_bytes: payload += reason_bytes frame_bytes = b"\x88" + bytearray([len(payload)]) + payload protocol = fp.FrameProtocol(client=True, extensions=[]) protocol.receive_bytes(frame_bytes) frames = list(protocol.received_frames()) assert len(frames) == 1 frame = frames[0] assert frame.opcode == fp.Opcode.CLOSE assert frame.payload[0] == code or fp.CloseReason.NO_STATUS_RCVD if reason: assert frame.payload[1] == reason else: assert not frame.payload[1]
def test_compressor_reset(self, client: bool, no_context_takeover: bool) -> None: if client: args = {"client_no_context_takeover": no_context_takeover} else: args = {"server_no_context_takeover": no_context_takeover} ext = wpext.PerMessageDeflate(**args) ext._enabled = True proto = fp.FrameProtocol(client=client, extensions=[ext]) rsv = fp.RsvBits(False, False, False) rsv, data = ext.frame_outbound(proto, fp.Opcode.BINARY, rsv, b"", False) assert rsv.rsv1 is True assert ext._compressor is not None rsv = fp.RsvBits(False, False, False) rsv, data = ext.frame_outbound(proto, fp.Opcode.CONTINUATION, rsv, b"", True) assert rsv.rsv1 is False if no_context_takeover: assert ext._compressor is None else: assert ext._compressor is not None rsv = fp.RsvBits(False, False, False) rsv, data = ext.frame_outbound(proto, fp.Opcode.BINARY, rsv, b"", False) assert rsv.rsv1 is True assert ext._compressor is not None
def test_reasoned_close(self): proto = fp.FrameProtocol(client=False, extensions=[]) reason = u"¯\_(ツ)_/¯" expected_payload = struct.pack( "!H", fp.CloseReason.NORMAL_CLOSURE) + reason.encode("utf8") data = proto.close(code=fp.CloseReason.NORMAL_CLOSURE, reason=reason) assert data == b"\x88" + bytearray([len(expected_payload) ]) + expected_payload
def test_close_one_byte_code(self): frame_bytes = b"\x88\x01\x0e" protocol = fp.FrameProtocol(client=True, extensions=[]) with pytest.raises(fp.ParseFailed) as exc: protocol.receive_bytes(frame_bytes) list(protocol.received_frames()) assert exc.value.code == fp.CloseReason.PROTOCOL_ERROR
def test_reasoned_close(self): proto = fp.FrameProtocol(client=False, extensions=[]) reason = u'¯\_(ツ)_/¯' expected_payload = struct.pack('!H', fp.CloseReason.NORMAL_CLOSURE) + \ reason.encode('utf8') data = proto.close(code=fp.CloseReason.NORMAL_CLOSURE, reason=reason) assert data == b'\x88' + bytearray([len(expected_payload)]) + \ expected_payload
def test_mismatched_data_messages2(self): proto = fp.FrameProtocol(client=False, extensions=[]) payload = b"it's all just ascii, right?" data = proto.send_data(payload, fin=False) assert data == b"\x02" + bytearray([len(payload)]) + payload payload = u"✔️☑️✅✔︎☑" with pytest.raises(TypeError): proto.send_data(payload)
def test_multiple_short_binary_data(self): proto = fp.FrameProtocol(client=False, extensions=[]) payload = b"it's all just ascii, right?" data = proto.send_data(payload, fin=False) assert data == b"\x02" + bytearray([len(payload)]) + payload payload = b"sure no worries" data = proto.send_data(payload, fin=True) assert data == b"\x80" + bytearray([len(payload)]) + payload
def test_client_side_masking_short_frame(self) -> None: proto = fp.FrameProtocol(client=True, extensions=[]) payload = b"x" * 125 data = proto.send_data(payload, fin=True) assert data[0] == 0x82 assert struct.unpack("!B", data[1:2])[0] == len(payload) | 0x80 masking_key = data[2:6] maskbytes = itertools.cycle(masking_key) assert data[6:] == bytearray(b ^ next(maskbytes) for b in bytearray(payload))
def test_mismatched_data_messages1(self): proto = fp.FrameProtocol(client=False, extensions=[]) payload = u"😃😄🙃😉" data = proto.send_data(payload, fin=False) payload = payload.encode("utf8") assert data == b"\x01" + bytearray([len(payload)]) + payload payload = b"seriously, all ascii" with pytest.raises(TypeError): proto.send_data(payload)
def test_client_side_masking_eight_byte_frame(self) -> None: proto = fp.FrameProtocol(client=True, extensions=[]) payload = b"x" * 65536 data = proto.send_data(payload, fin=True) assert data[0] == 0x82 assert data[1] == 0xFF assert struct.unpack("!Q", data[2:10])[0] == len(payload) masking_key = data[10:14] maskbytes = itertools.cycle(masking_key) assert data[14:] == bytearray(b ^ next(maskbytes) for b in bytearray(payload))
def test_multiple_short_text_data(self): proto = fp.FrameProtocol(client=False, extensions=[]) payload = u"😃😄🙃😉" data = proto.send_data(payload, fin=False) payload = payload.encode("utf8") assert data == b"\x01" + bytearray([len(payload)]) + payload payload = u"🙈🙉🙊" data = proto.send_data(payload, fin=True) payload = payload.encode("utf8") assert data == b"\x80" + bytearray([len(payload)]) + payload
def test_multiple_short_text_data(self): proto = fp.FrameProtocol(client=False, extensions=[]) payload = u'😃😄🙃😉' data = proto.send_data(payload, fin=False) payload = payload.encode('utf8') assert data == b'\x01' + bytearray([len(payload)]) + payload payload = u'🙈🙉🙊' data = proto.send_data(payload, fin=True) payload = payload.encode('utf8') assert data == b'\x80' + bytearray([len(payload)]) + payload
def test_client_side_masking_two_byte_frame(self): proto = fp.FrameProtocol(client=True, extensions=[]) payload = b"x" * 126 data = proto.send_data(payload, fin=True) assert data[0] == 0x82 assert data[1] == 0xFE assert struct.unpack("!H", data[2:4])[0] == len(payload) masking_key = data[4:8] maskbytes = itertools.cycle(masking_key) assert data[8:] == bytearray(b ^ next(maskbytes) for b in bytearray(payload))
def test_inbound_compressed_continuation_frame(self) -> None: payload = b"x" * 23 ext = wpext.PerMessageDeflate() ext._enabled = True proto = fp.FrameProtocol(client=True, extensions=[ext]) result = ext.frame_inbound_header(proto, fp.Opcode.CONTINUATION, fp.RsvBits(True, False, False), len(payload)) assert result == fp.CloseReason.PROTOCOL_ERROR
def test_outbound_uncompressible_opcode(self) -> None: ext = wpext.PerMessageDeflate() ext._enabled = True proto = fp.FrameProtocol(client=True, extensions=[ext]) rsv = fp.RsvBits(False, False, False) payload = b"x" * 23 rsv, data = ext.frame_outbound(proto, fp.Opcode.PING, rsv, payload, True) assert rsv.rsv1 is False assert data == payload
def test_outbound_handling_multiple_frames(self): ext = self.FakeExtension() proto = fp.FrameProtocol(client=False, extensions=[ext]) payload = u'😃😄🙃😉' data = proto.send_data(payload, fin=False) payload = payload.encode('utf8') assert data == b'\x11' + bytearray([len(payload)]) + payload payload = u'¯\_(ツ)_/¯' data = proto.send_data(payload, fin=True) payload = (payload + u'®').encode('utf8') assert data == b'\x80' + bytearray([len(payload)]) + payload
def test_random_control_frame(self): payload = b"give me one ping vasily" frame_bytes = b"\x89" + bytearray([len(payload)]) + payload protocol = fp.FrameProtocol(client=True, extensions=[]) protocol.receive_bytes(frame_bytes) frames = list(protocol.received_frames()) assert len(frames) == 1 frame = frames[0] assert frame.opcode == fp.Opcode.PING assert len(frame.payload) == len(payload) assert frame.payload == payload
def test_outbound_handling_multiple_frames(self) -> None: ext = self.FakeExtension() proto = fp.FrameProtocol(client=False, extensions=[ext]) payload = "😃😄🙃😉" data = proto.send_data(payload, fin=False) payload_bytes = payload.encode("utf8") assert data == b"\x11" + bytearray([len(payload_bytes)]) + payload_bytes payload = r"¯\_(ツ)_/¯" data = proto.send_data(payload, fin=True) payload_bytes = (payload + "®").encode("utf8") assert data == b"\x80" + bytearray([len(payload_bytes)]) + payload_bytes
def test_inbound_bad_zlib_payload(self): compressed_payload = b'x' * 23 ext = wpext.PerMessageDeflate() ext._enabled = True proto = fp.FrameProtocol(client=True, extensions=[ext]) result = ext.frame_inbound_header(proto, fp.Opcode.BINARY, fp.RsvBits(True, False, False), len(compressed_payload)) assert result.rsv1 result = ext.frame_inbound_payload_data(proto, compressed_payload) assert result is fp.CloseReason.INVALID_FRAME_PAYLOAD_DATA
def test_outbound_compress_single_frame(self, client: bool) -> None: ext = wpext.PerMessageDeflate() ext._enabled = True proto = fp.FrameProtocol(client=client, extensions=[ext]) rsv = fp.RsvBits(False, False, False) payload = b"x" * 23 compressed_payload = b"\xaa\xa8\xc0\n\x00\x00" rsv, data = ext.frame_outbound(proto, fp.Opcode.BINARY, rsv, payload, True) assert rsv.rsv1 is True assert data == compressed_payload
def test_long_text_message(self): payload = "x" * 65535 encoded_payload = payload.encode("utf-8") payload_len = struct.pack("!H", len(encoded_payload)) frame_bytes = b"\x81\x7e" + payload_len + encoded_payload protocol = fp.FrameProtocol(client=True, extensions=[]) protocol.receive_bytes(frame_bytes) frames = list(protocol.received_frames()) assert len(frames) == 1 frame = frames[0] assert frame.opcode == fp.Opcode.TEXT assert len(frame.payload) == len(payload) assert frame.payload == payload