def send_push_promise(self, stream_id: int, headers: Headers) -> int: """ Send a push promise related to the specified stream. Returns the stream ID on which headers and data can be sent. :param stream_id: The stream ID on which to send the data. :param headers: The HTTP request headers for this push. """ assert not self._is_client, "Only servers may send a push promise." if self._max_push_id is None or self._next_push_id >= self._max_push_id: raise NoAvailablePushIDError # send push promise push_id = self._next_push_id self._next_push_id += 1 self._quic.send_stream_data( stream_id, encode_frame( FrameType.PUSH_PROMISE, encode_uint_var(push_id) + self._encode_headers(stream_id, headers), ), ) # create push stream push_stream_id = self._create_uni_stream(StreamType.PUSH) self._quic.send_stream_data(push_stream_id, encode_uint_var(push_id)) return push_stream_id
def test_handle_control_stream_duplicate(self): """ We must only receive a single control stream. """ quic_server = FakeQuicConnection(configuration=QuicConfiguration( is_client=False)) h3_server = H3Connection(quic_server) # receive a first control stream h3_server.handle_event( StreamDataReceived(stream_id=2, data=encode_uint_var(StreamType.CONTROL), end_stream=False)) # receive a second control stream h3_server.handle_event( StreamDataReceived(stream_id=6, data=encode_uint_var(StreamType.CONTROL), end_stream=False)) self.assertEqual( quic_server.closed, ( ErrorCode.HTTP_STREAM_CREATION_ERROR, "Only one control stream is allowed", ), )
def test_handle_qpack_encoder_duplicate(self): """ We must only receive a single QPACK encoder stream. """ quic_client = FakeQuicConnection(configuration=QuicConfiguration( is_client=True)) h3_client = H3Connection(quic_client) # receive a first encoder stream h3_client.handle_event( StreamDataReceived( stream_id=11, data=encode_uint_var(StreamType.QPACK_ENCODER), end_stream=False, )) # receive a second encoder stream h3_client.handle_event( StreamDataReceived( stream_id=15, data=encode_uint_var(StreamType.QPACK_ENCODER), end_stream=False, )) self.assertEqual( quic_client.closed, ( ErrorCode.HTTP_STREAM_CREATION_ERROR, "Only one QPACK encoder stream is allowed", ), )
def _create_uni_stream(self, stream_type: int) -> int: """ Create an unidirectional stream of the given type. """ stream_id = self._quic.get_next_available_stream_id(is_unidirectional=True) self._quic.send_stream_data(stream_id, encode_uint_var(stream_type)) return stream_id
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 _init_connection(self) -> None: # send our settings self._local_control_stream_id = self._create_uni_stream( StreamType.CONTROL) self._quic.send_stream_data( self._local_control_stream_id, encode_frame( FrameType.SETTINGS, encode_settings({ Setting.QPACK_MAX_TABLE_CAPACITY: self._max_table_capacity, Setting.QPACK_BLOCKED_STREAMS: self._blocked_streams, }), ), ) if self._is_client and self._max_push_id is not None: self._quic.send_stream_data( self._local_control_stream_id, encode_frame(FrameType.MAX_PUSH_ID, encode_uint_var(self._max_push_id)), ) # create encoder and decoder streams self._local_encoder_stream_id = self._create_uni_stream( StreamType.QPACK_ENCODER) self._local_decoder_stream_id = self._create_uni_stream( StreamType.QPACK_DECODER)
def send_datagram(self, flow_id: int, data: bytes) -> None: """ Send a datagram for the specified flow. :param flow_id: The flow ID. :param data: The HTTP/3 datagram payload. """ self._quic.send_datagram_frame(encode_uint_var(flow_id) + data)
def send_goaway(self): """ Send GOAWAY control frame. """ self._quic.send_stream_data( self._local_control_stream_id, encode_frame(FrameType.GOAWAY, encode_uint_var(0)), )
def test_handle_stream_frame_over_max_stream_data(self): with client_and_server() as (client, server): # client receives STREAM frame frame_type = QuicFrameType.STREAM_BASE | 4 stream_id = 1 with self.assertRaises(QuicConnectionError) as cm: client._handle_stream_frame( client_receive_context(client), 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 test_handle_data_blocked_frame(self): with client_and_server() as (client, server): # client receives DATA_BLOCKED: 12345 client._handle_data_blocked_frame( client_receive_context(client), QuicFrameType.DATA_BLOCKED, Buffer(data=encode_uint_var(12345)), )
def test_handle_max_streams_uni_frame(self): with client_and_server() as (client, server): self.assertEqual(client._remote_max_streams_uni, 128) # client receives MAX_STREAMS_UNI raising limit client._handle_max_streams_uni_frame( client_receive_context(client), QuicFrameType.MAX_STREAMS_UNI, Buffer(data=encode_uint_var(129)), ) self.assertEqual(client._remote_max_streams_uni, 129) # client receives MAX_STREAMS_UNI raising limit client._handle_max_streams_uni_frame( client_receive_context(client), QuicFrameType.MAX_STREAMS_UNI, Buffer(data=encode_uint_var(127)), ) self.assertEqual(client._remote_max_streams_uni, 129)
def send_cancel_push(self, push_id): """ Send CANCEL_PUSH control frame for cancellation of a server push. :param push_id: server push id to cancel """ self._quic.send_stream_data( self._local_control_stream_id, encode_frame(FrameType.CANCEL_PUSH, encode_uint_var(push_id)), )
def test_handle_max_data_frame(self): with client_and_server() as (client, server): self.assertEqual(client._remote_max_data, 1048576) # client receives MAX_DATA raising limit client._handle_max_data_frame( client_receive_context(client), QuicFrameType.MAX_DATA, Buffer(data=encode_uint_var(1048577)), ) self.assertEqual(client._remote_max_data, 1048577)
def create_webtransport_stream(self, session_id: int, is_unidirectional: bool = False) -> int: """ Create a WebTransport stream and return the stream ID. :param session_id: The WebTransport session identifier. :param is_unidirectional: Whether to create a unidirectional stream. """ if is_unidirectional: stream_id = self._create_uni_stream(StreamType.WEBTRANSPORT) self._quic.send_stream_data(stream_id, encode_uint_var(session_id)) else: stream_id = self._quic.get_next_available_stream_id() self._quic.send_stream_data( stream_id, encode_uint_var(FrameType.WEBTRANSPORT_STREAM) + encode_uint_var(session_id), ) return stream_id
def test_handle_max_stream_data_frame(self): with client_and_server() as (client, server): # client creates bidirectional stream 0 stream = client._create_stream(stream_id=0) self.assertEqual(stream.max_stream_data_remote, 1048576) # client receives MAX_STREAM_DATA raising limit client._handle_max_stream_data_frame( client_receive_context(client), QuicFrameType.MAX_STREAM_DATA, Buffer(data=b"\x00" + encode_uint_var(1048577)), ) self.assertEqual(stream.max_stream_data_remote, 1048577) # client receives MAX_STREAM_DATA lowering limit client._handle_max_stream_data_frame( client_receive_context(client), QuicFrameType.MAX_STREAM_DATA, Buffer(data=b"\x00" + encode_uint_var(1048575)), ) self.assertEqual(stream.max_stream_data_remote, 1048577)
def test_handle_push_frame_wrong_frame_type(self): """ We should not received SETTINGS on a push stream. """ quic_client = FakeQuicConnection(configuration=QuicConfiguration( is_client=True)) h3_client = H3Connection(quic_client) h3_client.handle_event( StreamDataReceived( stream_id=15, data=encode_uint_var(StreamType.PUSH) + encode_uint_var(0) # push ID + encode_frame(FrameType.SETTINGS, b""), end_stream=False, )) self.assertEqual( quic_client.closed, (ErrorCode.HTTP_FRAME_UNEXPECTED, "Invalid frame type on push stream"), )
def test_handle_stream_frame_over_max_streams(self): with client_and_server() as (client, server): # client receives STREAM frame with self.assertRaises(QuicConnectionError) as cm: client._handle_stream_frame( client_receive_context(client), QuicFrameType.STREAM_BASE, Buffer(data=encode_uint_var( client._local_max_stream_data_uni * 4 + 3)), ) self.assertEqual(cm.exception.error_code, QuicErrorCode.STREAM_LIMIT_ERROR) self.assertEqual(cm.exception.frame_type, QuicFrameType.STREAM_BASE) self.assertEqual(cm.exception.reason_phrase, "Too many streams open")
def test_handle_qpack_encoder_stream_error(self): """ Receiving garbage on the QPACK encoder stream triggers an exception. """ quic_client = FakeQuicConnection(configuration=QuicConfiguration( is_client=True)) h3_client = H3Connection(quic_client) h3_client.handle_event( StreamDataReceived( stream_id=7, data=encode_uint_var(StreamType.QPACK_ENCODER) + b"\x00", end_stream=False, )) self.assertEqual(quic_client.closed, (ErrorCode.HTTP_QPACK_ENCODER_STREAM_ERROR, ""))
def test_handle_control_frame_headers(self): """ We should not receive HEADERS on the control stream. """ quic_server = FakeQuicConnection(configuration=QuicConfiguration( is_client=False)) h3_server = H3Connection(quic_server) h3_server.handle_event( StreamDataReceived( stream_id=2, data=encode_uint_var(StreamType.CONTROL) + encode_frame(FrameType.HEADERS, b""), end_stream=False, )) self.assertEqual( quic_server.closed, (ErrorCode.HTTP_FRAME_UNEXPECTED, "Invalid frame type on control stream"), )
def test_handle_control_frame_max_push_id_from_server(self): """ A client should not receive MAX_PUSH_ID on the control stream. """ quic_client = FakeQuicConnection(configuration=QuicConfiguration( is_client=True)) h3_client = H3Connection(quic_client) h3_client.handle_event( StreamDataReceived( stream_id=3, data=encode_uint_var(StreamType.CONTROL) + encode_frame(FrameType.MAX_PUSH_ID, b""), end_stream=False, )) self.assertEqual( quic_client.closed, (ErrorCode.HTTP_FRAME_UNEXPECTED, "Servers must not send MAX_PUSH_ID"), )
def _init_connection(self) -> None: # send our settings self._local_control_stream_id = self._create_uni_stream( StreamType.CONTROL) self._quic.send_stream_data( self._local_control_stream_id, encode_frame(FrameType.SETTINGS, encode_settings(self._get_local_settings())), ) if self._is_client and self._max_push_id is not None: self._quic.send_stream_data( self._local_control_stream_id, encode_frame(FrameType.MAX_PUSH_ID, encode_uint_var(self._max_push_id)), ) # create encoder and decoder streams self._local_encoder_stream_id = self._create_uni_stream( StreamType.QPACK_ENCODER) self._local_decoder_stream_id = self._create_uni_stream( StreamType.QPACK_DECODER)