def test_pull_short_header(self): buf = Buffer(data=load("short_header.bin")) header = pull_quic_header(buf, host_cid_length=8) self.assertFalse(header.is_long_header) self.assertEqual(header.version, None) self.assertEqual(header.packet_type, 0x50) self.assertEqual(header.destination_cid, binascii.unhexlify("f45aa7b59c0e1ad6")) self.assertEqual(header.source_cid, b"") self.assertEqual(header.original_destination_cid, b"") self.assertEqual(header.token, b"") self.assertEqual(header.rest_length, 12) self.assertEqual(buf.tell(), 9)
def test_params_disable_migration(self): data = binascii.unhexlify("0004000c0000") # parse buf = Buffer(data=data) params = pull_quic_transport_parameters(buf) self.assertEqual(params, QuicTransportParameters(disable_migration=True)) # serialize buf = Buffer(capacity=len(data)) push_quic_transport_parameters(buf, params) self.assertEqual(buf.data, data)
def test_transport_close(self): data = binascii.unhexlify( "000a0212696c6c6567616c2041434b206672616d6500") # parse buf = Buffer(data=data) frame = packet.pull_transport_close_frame(buf) self.assertEqual(frame, (10, 2, "illegal ACK frame\x00")) # serialize buf = Buffer(capacity=len(data)) packet.push_transport_close_frame(buf, *frame) self.assertEqual(buf.data, data)
def test_ack_frame_with_one_range_3(self): data = b"\x05\x02\x01\x00\x01\x02" # parse buf = Buffer(data=data) rangeset, delay = packet.pull_ack_frame(buf) self.assertEqual(list(rangeset), [range(0, 3), range(5, 6)]) self.assertEqual(delay, 2) # serialize buf = Buffer(capacity=len(data)) packet.push_ack_frame(buf, rangeset, delay) self.assertEqual(buf.data, data)
def test_pull_initial_client(self): buf = Buffer(data=load("initial_client.bin")) header = pull_quic_header(buf, host_cid_length=8) self.assertTrue(header.is_long_header) self.assertEqual(header.version, QuicProtocolVersion.VERSION_1) self.assertEqual(header.packet_type, PACKET_TYPE_INITIAL) self.assertEqual(header.destination_cid, binascii.unhexlify("858b39368b8e3c6e")) self.assertEqual(header.source_cid, b"") self.assertEqual(header.token, b"") self.assertEqual(header.integrity_tag, b"") self.assertEqual(header.rest_length, 1262) self.assertEqual(buf.tell(), 18)
def test_pull_initial_client(self): buf = Buffer(data=load("initial_client.bin")) header = pull_quic_header(buf, host_cid_length=8) self.assertTrue(header.is_long_header) self.assertEqual(header.version, QuicProtocolVersion.DRAFT_17) self.assertEqual(header.packet_type, PACKET_TYPE_INITIAL) self.assertEqual(header.destination_cid, binascii.unhexlify("90ed1e1c7b04b5d3")) self.assertEqual(header.source_cid, b"") self.assertEqual(header.original_destination_cid, b"") self.assertEqual(header.token, b"") self.assertEqual(header.rest_length, 1263) self.assertEqual(buf.tell(), 17)
def test_ack_frame_with_two_ranges(self): data = b"\x04\x02\x02\x00\x00\x00\x00\x00" # parse buf = Buffer(data=data) rangeset, delay = packet.pull_ack_frame(buf) self.assertEqual(list(rangeset), [range(0, 1), range(2, 3), range(4, 5)]) self.assertEqual(delay, 2) # serialize buf = Buffer(capacity=len(data)) packet.push_ack_frame(buf, rangeset, delay) self.assertEqual(buf.data, data)
def test_pull_initial_server(self): buf = Buffer(data=load("initial_server.bin")) header = pull_quic_header(buf, host_cid_length=8) self.assertTrue(header.is_long_header) self.assertEqual(header.version, QuicProtocolVersion.DRAFT_25) self.assertEqual(header.packet_type, PACKET_TYPE_INITIAL) self.assertEqual(header.destination_cid, b"") self.assertEqual(header.source_cid, binascii.unhexlify("195c68344e28d479")) self.assertEqual(header.token, b"") self.assertEqual(header.integrity_tag, b"") self.assertEqual(header.rest_length, 184) self.assertEqual(buf.tell(), 18)
def parse_settings(data: bytes) -> Dict[int, int]: buf = Buffer(data=data) settings: Dict[int, int] = {} while not buf.eof(): setting = buf.pull_uint_var() value = buf.pull_uint_var() if setting in RESERVED_SETTINGS: raise SettingsError("Setting identifier 0x%x is reserved" % setting) if setting in settings: raise SettingsError("Setting identifier 0x%x is included twice" % setting) settings[setting] = value return dict(settings)
def test_pull_retry(self): original_destination_cid = binascii.unhexlify("fbbd219b7363b64b") data = load("retry.bin") buf = Buffer(data=data) header = pull_quic_header(buf, host_cid_length=8) self.assertTrue(header.is_long_header) self.assertEqual(header.version, QuicProtocolVersion.VERSION_1) self.assertEqual(header.packet_type, PACKET_TYPE_RETRY) self.assertEqual(header.destination_cid, binascii.unhexlify("e9d146d8d14cb28e")) self.assertEqual( header.source_cid, binascii.unhexlify("0b0a205a648fcf82d85f128b67bbe08053e6"), ) self.assertEqual( header.token, binascii.unhexlify( "44397a35d698393c134b08a932737859f446d3aadd00ed81540c8d8de172" "906d3e7a111b503f9729b8928e7528f9a86a4581f9ebb4cb3b53c283661e" "8530741a99192ee56914c5626998ec0f"), ) self.assertEqual( header.integrity_tag, binascii.unhexlify("4620aafd42f1d630588b27575a12da5c")) self.assertEqual(header.rest_length, 0) self.assertEqual(buf.tell(), 125) # check integrity if False: self.assertEqual( get_retry_integrity_tag( buf.data_slice(0, 109), original_destination_cid, version=header.version, ), header.integrity_tag, ) # serialize encoded = encode_quic_retry( version=header.version, source_cid=header.source_cid, destination_cid=header.destination_cid, original_destination_cid=original_destination_cid, retry_token=header.token, ) with open("bob.bin", "wb") as fp: fp.write(encoded) self.assertEqual(encoded, data)
def append(self, data: bytes) -> None: """ Appends the given bytes to this decoder. """ assert not self._final if len(data) == 0: return if self._buffer: remaining = self._buffer.pull_bytes(self._buffer.capacity - self._buffer.tell()) self._buffer = Buffer(data=(remaining + data)) else: self._buffer = Buffer(data=data)
def test_pull_version_negotiation(self): buf = Buffer(data=load("version_negotiation.bin")) header = pull_quic_header(buf, host_cid_length=8) self.assertTrue(header.is_long_header) self.assertEqual(header.version, QuicProtocolVersion.NEGOTIATION) self.assertEqual(header.packet_type, None) self.assertEqual(header.destination_cid, binascii.unhexlify("9aac5a49ba87a849")) self.assertEqual(header.source_cid, binascii.unhexlify("f92f4336fa951ba1")) self.assertEqual(header.original_destination_cid, b"") self.assertEqual(header.token, b"") self.assertEqual(header.rest_length, 8) self.assertEqual(buf.tell(), 23)
def encode_frame(frame_type: int, frame_data: bytes) -> bytes: frame_length = len(frame_data) buf = Buffer(capacity=frame_length + 16) buf.push_uint_var(frame_type) buf.push_uint_var(frame_length) buf.push_bytes(frame_data) return buf.data
def test_pull_version_negotiation(self): buf = Buffer(data=load("version_negotiation.bin")) header = pull_quic_header(buf, host_cid_length=8) self.assertTrue(header.is_long_header) self.assertEqual(header.version, QuicProtocolVersion.NEGOTIATION) self.assertEqual(header.packet_type, None) self.assertEqual(header.destination_cid, binascii.unhexlify("dae1889b81a91c26")) self.assertEqual(header.source_cid, binascii.unhexlify("f49243784f9bf3be")) self.assertEqual(header.original_destination_cid, b"") self.assertEqual(header.token, b"") self.assertEqual(header.rest_length, 8) self.assertEqual(buf.tell(), 22)
def test_data_slice(self): buf = Buffer(data=b"\x08\x07\x06\x05\x04\x03\x02\x01") self.assertEqual(buf.data_slice(0, 8), b"\x08\x07\x06\x05\x04\x03\x02\x01") self.assertEqual(buf.data_slice(1, 3), b"\x07\x06") with self.assertRaises(BufferReadError): buf.data_slice(-1, 3) with self.assertRaises(BufferReadError): buf.data_slice(0, 9) with self.assertRaises(BufferReadError): buf.data_slice(1, 0)
def test_pull_uint_var_truncated(self): buf = Buffer(capacity=0) with self.assertRaises(BufferReadError): buf.pull_uint_var() buf = Buffer(data=b"\xff") with self.assertRaises(BufferReadError): buf.pull_uint_var()
def test_params_disable_active_migration_legacy(self): data = binascii.unhexlify("0004000c0000") # parse buf = Buffer(data=data) params = pull_quic_transport_parameters( buf, protocol_version=QuicProtocolVersion.DRAFT_25) self.assertEqual( params, QuicTransportParameters(disable_active_migration=True)) # serialize buf = Buffer(capacity=len(data)) push_quic_transport_parameters( buf, params, protocol_version=QuicProtocolVersion.DRAFT_25) self.assertEqual(buf.data, data)
def roundtrip(self, data, value): buf = Buffer(data=data) self.assertEqual(buf.pull_uint_var(), value) self.assertEqual(buf.tell(), len(data)) buf = Buffer(capacity=8) buf.push_uint_var(value) self.assertEqual(buf.data, data)
def test_application_close_not_utf8(self): data = binascii.unhexlify("0008676f6f6462798200") # parse buf = Buffer(data=data) frame = packet.pull_application_close_frame(buf) self.assertEqual(frame, (0, ""))
def test_transport_close(self): data = binascii.unhexlify("0a0212696c6c6567616c2041434b206672616d6500") # parse buf = Buffer(data=data) frame = packet.pull_transport_close_frame(buf) self.assertEqual(frame, (10, 2, "illegal ACK frame\x00"))
def test_params_unknown(self): # fb.mvfst.net sends a proprietary parameter 65280 data = binascii.unhexlify( "006400050004800104000006000480010400000700048001040000040004801" "0000000080008c0000000ffffffff00090008c0000000ffffffff0001000480" "00ea60000a00010300030002500000020010616161616262626263636363646" "46464ff00000100") # parse buf = Buffer(data=data) params = pull_quic_transport_parameters(buf) self.assertEqual( params, QuicTransportParameters( idle_timeout=60000, stateless_reset_token=b"aaaabbbbccccdddd", max_packet_size=4096, initial_max_data=1048576, initial_max_stream_data_bidi_local=66560, initial_max_stream_data_bidi_remote=66560, initial_max_stream_data_uni=66560, initial_max_streams_bidi=4294967295, initial_max_streams_uni=4294967295, ack_delay_exponent=3, ), )
def test_handle_stream_data_blocked_frame_send_only(self): client = QuicConnection(is_client=True) server = QuicConnection( is_client=False, certificate=SERVER_CERTIFICATE, private_key=SERVER_PRIVATE_KEY, ) # perform handshake client_transport, server_transport = create_transport(client, server) # client creates unidirectional stream 2 client.create_stream(is_unidirectional=True) # client receives STREAM_DATA_BLOCKED with self.assertRaises(QuicConnectionError) as cm: client._handle_stream_data_blocked_frame( tls.Epoch.ONE_RTT, QuicFrameType.STREAM_DATA_BLOCKED, Buffer(data=b"\x02\x01"), ) self.assertEqual(cm.exception.error_code, QuicErrorCode.STREAM_STATE_ERROR) self.assertEqual(cm.exception.frame_type, QuicFrameType.STREAM_DATA_BLOCKED) self.assertEqual(cm.exception.reason_phrase, "Stream is send-only")
def test_send_stream_data_over_max_streams_uni(self): with client_and_server() as (client, server): # create streams for i in range(128): stream_id = i * 4 + 2 client.send_stream_data(stream_id, b"") self.assertFalse(client._streams[stream_id].is_blocked) self.assertEqual(len(client._streams_blocked_bidi), 0) self.assertEqual(len(client._streams_blocked_uni), 0) self.assertEqual(roundtrip(client, server), (0, 0)) # create one too many -> STREAMS_BLOCKED stream_id = 128 * 4 + 2 client.send_stream_data(stream_id, b"") self.assertTrue(client._streams[stream_id].is_blocked) self.assertEqual(len(client._streams_blocked_bidi), 0) self.assertEqual(len(client._streams_blocked_uni), 1) self.assertEqual(roundtrip(client, server), (1, 1)) # peer raises max streams client._handle_max_streams_uni_frame( client_receive_context(client), QuicFrameType.MAX_STREAMS_UNI, Buffer(data=encode_uint_var(129)), ) self.assertFalse(client._streams[stream_id].is_blocked)
def test_push_certificate(self): certificate = Certificate(request_context=b"", certificates=[(CERTIFICATE_DATA, b"")]) buf = Buffer(1600) push_certificate(buf, certificate) self.assertEqual(buf.data, load("tls_certificate.bin"))
def test_handle_ack_frame_ecn(self): client = QuicConnection(is_client=True) client._handle_ack_frame( tls.Epoch.ONE_RTT, QuicFrameType.ACK_ECN, Buffer(data=b"\x00\x02\x00\x00\x00\x00\x00"), )
def test_handle_stop_sending_frame_receive_only(self): client = QuicConnection(is_client=True) server = QuicConnection( is_client=False, certificate=SERVER_CERTIFICATE, private_key=SERVER_PRIVATE_KEY, ) # perform handshake client_transport, server_transport = create_transport(client, server) # server creates unidirectional stream 3 server.create_stream(is_unidirectional=True) # client receives STOP_SENDING with self.assertRaises(QuicConnectionError) as cm: client._handle_stop_sending_frame( tls.Epoch.ONE_RTT, QuicFrameType.STOP_SENDING, Buffer(data=b"\x03\x11\x22"), ) self.assertEqual(cm.exception.error_code, QuicErrorCode.STREAM_STATE_ERROR) self.assertEqual(cm.exception.frame_type, QuicFrameType.STOP_SENDING) self.assertEqual(cm.exception.reason_phrase, "Stream is receive-only")
async def handle(self, event: Event) -> None: if isinstance(event, RawData): try: header = pull_quic_header(Buffer(data=event.data), host_cid_length=8) except ValueError: return if ( header.version is not None and header.version not in self.quic_config.supported_versions ): data = encode_quic_version_negotiation( source_cid=header.destination_cid, destination_cid=header.source_cid, supported_versions=self.quic_config.supported_versions, ) await self.send(RawData(data=data, address=event.address)) return connection = self.connections.get(header.destination_cid) if ( connection is None and len(event.data) >= 1200 and header.packet_type == PACKET_TYPE_INITIAL ): connection = QuicConnection( configuration=self.quic_config, original_connection_id=None ) self.connections[header.destination_cid] = connection self.connections[connection.host_cid] = connection if connection is not None: connection.receive_datagram(event.data, event.address, now=self.now()) await self._handle_events(connection, event.address) elif isinstance(event, Closed): pass
def test_params_unknown(self): data = binascii.unhexlify("8000ff000100") # parse buf = Buffer(data=data) params = pull_quic_transport_parameters(buf) self.assertEqual(params, QuicTransportParameters())
def test_handle_stream_frame_over_max_stream_data(self): client = QuicConnection(is_client=True) server = QuicConnection( is_client=False, certificate=SERVER_CERTIFICATE, private_key=SERVER_PRIVATE_KEY, ) # perform handshake client_transport, server_transport = create_transport(client, server) # client receives STREAM frame frame_type = QuicFrameType.STREAM_BASE | QuicStreamFlag.OFF stream_id = 1 with self.assertRaises(QuicConnectionError) as cm: client._handle_stream_frame( tls.Epoch.ONE_RTT, frame_type, Buffer( data=encode_uint_var(stream_id) + encode_uint_var(client._local_max_stream_data_bidi_remote + 1)), ) self.assertEqual(cm.exception.error_code, QuicErrorCode.FLOW_CONTROL_ERROR) self.assertEqual(cm.exception.frame_type, frame_type) self.assertEqual(cm.exception.reason_phrase, "Over stream data limit")
def encode(self) -> bytes: """ Encodes this H3Capsule and return the bytes. """ buffer = Buffer(capacity=len(self.data) + 2 * UINT_VAR_MAX_SIZE) buffer.push_uint_var(self.type) buffer.push_uint_var(len(self.data)) buffer.push_bytes(self.data) return buffer.data