def test_bidirectional_stream(self): with h3_client_and_server(QUIC_CONFIGURATION_OPTIONS) as ( quic_client, quic_server, ): h3_client = H3Connection(quic_client, enable_webtransport=True) h3_server = H3Connection(quic_server, enable_webtransport=True) # create session session_id = self._make_session(h3_client, h3_server) # send data on bidirectional stream stream_id = h3_client.create_webtransport_stream(session_id) quic_client.send_stream_data(stream_id, b"foo", end_stream=True) # receive data events = h3_transfer(quic_client, h3_server) self.assertEqual( events, [ WebTransportStreamDataReceived( data=b"foo", session_id=session_id, stream_ended=True, stream_id=stream_id, ) ], )
def test_request_with_server_push_max_push_id(self): with h3_client_and_server() as (quic_client, quic_server): h3_client = H3Connection(quic_client) h3_server = H3Connection(quic_server) # send request stream_id = quic_client.get_next_available_stream_id() h3_client.send_headers( stream_id=stream_id, headers=[ (b":method", b"GET"), (b":scheme", b"https"), (b":authority", b"localhost"), (b":path", b"/"), ], end_stream=True, ) # receive request events = h3_transfer(quic_client, h3_server) self.assertEqual( events, [ HeadersReceived( headers=[ (b":method", b"GET"), (b":scheme", b"https"), (b":authority", b"localhost"), (b":path", b"/"), ], stream_id=stream_id, stream_ended=True, ) ], ) # send push promises for i in range(0, 8): h3_server.send_push_promise( stream_id=stream_id, headers=[ (b":method", b"GET"), (b":scheme", b"https"), (b":authority", b"localhost"), (b":path", "/{}.css".format(i).encode("ascii")), ], ) # send one too many with self.assertRaises(NoAvailablePushIDError): h3_server.send_push_promise( stream_id=stream_id, headers=[ (b":method", b"GET"), (b":scheme", b"https"), (b":authority", b"localhost"), (b":path", b"/8.css"), ], )
def test_request(self): with h3_client_and_server() as (quic_client, quic_server): h3_client = H3Connection(quic_client) h3_server = H3Connection(quic_server) # make first request self._make_request(h3_client, h3_server) # make second request self._make_request(h3_client, h3_server) # make third request -> dynamic table self._make_request(h3_client, h3_server)
def _consume_events(self, connection: QuicConnection) -> None: # process events event = connection.next_event() while event is not None: if isinstance(event, aioquic.events.HandshakeCompleted): if event.alpn_protocol == "h3-20": self._http[connection] = H3Connection(connection) elif event.alpn_protocol == "hq-20": self._http[connection] = H0Connection(connection) elif isinstance(event, aioquic.events.ConnectionIdIssued): self._connections[event.connection_id] = connection elif isinstance(event, aioquic.events.ConnectionIdRetired): assert self._connections[event.connection_id] == connection del self._connections[event.connection_id] # pass event to the HTTP layer http = self._http.get(connection) if http is not None: for http_event in http.handle_event(event): handle_http_event(http, http_event) event = connection.next_event() # send datagrams for data, addr in connection.datagrams_to_send(now=self._loop.time()): self._transport.sendto(data, addr) # re-arm timer """
def quic_event_received(self, event: QuicEvent) -> None: global totalQuicEvents totalQuicEvents += 1 # logger.info('quic event recieved:{}'.format(totalQuicEvents)) if isinstance(event, ProtocolNegotiated): if event.alpn_protocol.startswith("h3-"): self._http = H3Connection(self._quic) elif event.alpn_protocol.startswith("hq-"): self._http = H0Connection(self._quic) elif event.alpn_protocol.startswith("quic"): self.quic_client = True if isinstance(event, DatagramFrameReceived): if event.data == b'quic': self._quic.send_datagram_frame(b'quic-ack') if isinstance(event, StreamDataReceived): # logger.info('aakash kuch to aaya') if self.quic_client is True: print(f"print event {event.data}") data = b'quic stream-data recv' end_stream = False self._quic.send_stream_data(event.stream_id, data, end_stream) else: #TODO: Yet to handle a http_client streamDataReceived event pass # pass event to the HTTP layer if self._http is not None: for http_event in self._http.handle_event(event): logger.debug('http event recieved') self.http_event_received(http_event)
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 quic_event_received(self, event: QuicEvent) -> None: if isinstance(event, ProtocolNegotiated): self._http = H3Connection(self._quic, enable_webtransport=True) if self._http is not None: for http_event in self._http.handle_event(event): self._h3_event_received(http_event)
def test_uni_stream_type(self): with h3_client_and_server() as (quic_client, quic_server): h3_server = H3Connection(quic_server) # unknown stream type 9 stream_id = quic_client.get_next_available_stream_id( is_unidirectional=True) self.assertEqual(stream_id, 2) quic_client.send_stream_data(stream_id, b"\x09") self.assertEqual(h3_transfer(quic_client, h3_server), []) self.assertEqual(list(h3_server._stream.keys()), [2]) self.assertEqual(h3_server._stream[2].buffer, b"") self.assertEqual(h3_server._stream[2].stream_type, 9) # unknown stream type 64, one byte at a time stream_id = quic_client.get_next_available_stream_id( is_unidirectional=True) self.assertEqual(stream_id, 6) quic_client.send_stream_data(stream_id, b"\x40") self.assertEqual(h3_transfer(quic_client, h3_server), []) self.assertEqual(list(h3_server._stream.keys()), [2, 6]) self.assertEqual(h3_server._stream[2].buffer, b"") self.assertEqual(h3_server._stream[2].stream_type, 9) self.assertEqual(h3_server._stream[6].buffer, b"\x40") self.assertEqual(h3_server._stream[6].stream_type, None) quic_client.send_stream_data(stream_id, b"\x40") self.assertEqual(h3_transfer(quic_client, h3_server), []) self.assertEqual(list(h3_server._stream.keys()), [2, 6]) self.assertEqual(h3_server._stream[2].buffer, b"") self.assertEqual(h3_server._stream[2].stream_type, 9) self.assertEqual(h3_server._stream[6].buffer, b"") self.assertEqual(h3_server._stream[6].stream_type, 64)
def test_uni_stream_grease(self): with h3_client_and_server() as (quic_client, quic_server): h3_server = H3Connection(quic_server) quic_client.send_stream_data( 14, b"\xff\xff\xff\xff\xff\xff\xff\xfeGREASE is the word") self.assertEqual(h3_transfer(quic_client, h3_server), [])
def test_send_headers_after_trailers(self): """ We should not send HEADERS after trailers. """ quic_client = FakeQuicConnection(configuration=QuicConfiguration( is_client=True)) h3_client = H3Connection(quic_client) stream_id = quic_client.get_next_available_stream_id() h3_client.send_headers( stream_id=stream_id, headers=[ (b":method", b"GET"), (b":scheme", b"https"), (b":authority", b"localhost"), (b":path", b"/"), ], ) h3_client.send_headers(stream_id=stream_id, headers=[(b"x-some-trailer", b"foo")], end_stream=False) with self.assertRaises(FrameUnexpected): h3_client.send_headers( stream_id=stream_id, headers=[(b"x-other-trailer", b"foo")], end_stream=False, )
def test_request(self): with client_and_server( client_options={"alpn_protocols": H3_ALPN}, server_options={"alpn_protocols": H3_ALPN}, ) as (quic_client, quic_server): h3_client = H3Connection(quic_client) h3_server = H3Connection(quic_server) # make first request self._make_request(h3_client, h3_server) # make second request self._make_request(h3_client, h3_server) # make third request -> dynamic table self._make_request(h3_client, h3_server)
def test_uni_stream_type(self): with client_and_server( client_options={"alpn_protocols": ["h3-20"]}, server_options={"alpn_protocols": ["h3-20"]}, ) as (quic_client, quic_server): h3_server = H3Connection(quic_server) # unknown stream type 9 stream_id = quic_client.get_next_available_stream_id( is_unidirectional=True) self.assertEqual(stream_id, 2) quic_client.send_stream_data(stream_id, b"\x09") self.assertEqual(h3_transfer(quic_client, h3_server), []) self.assertEqual(h3_server._stream_buffers, {2: b""}) self.assertEqual(h3_server._stream_types, {2: 9}) # unknown stream type 64, one byte at a time stream_id = quic_client.get_next_available_stream_id( is_unidirectional=True) self.assertEqual(stream_id, 6) quic_client.send_stream_data(stream_id, b"\x40") self.assertEqual(h3_transfer(quic_client, h3_server), []) self.assertEqual(h3_server._stream_buffers, {2: b"", 6: b"\x40"}) self.assertEqual(h3_server._stream_types, {2: 9}) quic_client.send_stream_data(stream_id, b"\x40") self.assertEqual(h3_transfer(quic_client, h3_server), []) self.assertEqual(h3_server._stream_buffers, {2: b"", 6: b""}) self.assertEqual(h3_server._stream_types, {2: 9, 6: 64})
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 test_blocked_stream(self): quic_client = FakeQuicConnection(configuration=QuicConfiguration( is_client=True)) h3_client = H3Connection(quic_client) h3_client.handle_event( StreamDataReceived( stream_id=3, data=binascii.unhexlify( "0004170150000680020000074064091040bcc0000000faceb00c"), end_stream=False, )) h3_client.handle_event( StreamDataReceived(stream_id=7, data=b"\x02", end_stream=False)) h3_client.handle_event( StreamDataReceived(stream_id=11, data=b"\x03", end_stream=False)) h3_client.handle_event( StreamDataReceived(stream_id=0, data=binascii.unhexlify("01040280d910"), end_stream=False)) h3_client.handle_event( StreamDataReceived( stream_id=0, data=binascii.unhexlify( "00408d796f752072656163686564206d766673742e6e65742c20726561636820" "746865202f6563686f20656e64706f696e7420666f7220616e206563686f2072" "6573706f6e7365207175657279202f3c6e756d6265723e20656e64706f696e74" "7320666f722061207661726961626c652073697a6520726573706f6e73652077" "6974682072616e646f6d206279746573"), end_stream=True, )) self.assertEqual( h3_client.handle_event( StreamDataReceived( stream_id=7, data=binascii.unhexlify( "3fe101c696d07abe941094cb6d0a08017d403971966e32ca98b46f" ), end_stream=False, )), [ HeadersReceived( headers=[ (b":status", b"200"), (b"date", b"Mon, 22 Jul 2019 06:33:33 GMT"), ], stream_id=0, stream_ended=False, ), DataReceived( data= (b"you reached mvfst.net, reach the /echo endpoint for an " b"echo response query /<number> endpoints for a variable " b"size response with random bytes"), stream_id=0, stream_ended=True, ), ], )
def __init__(self, *args, **kwargs) -> None: super().__init__(*args, **kwargs) self._http = H3Connection(self._quic) self._request_events = {} self._request_waiter = {} self.terminated = False
def test_uni_stream_grease(self): with client_and_server( client_options={"alpn_protocols": H3_ALPN}, server_options={"alpn_protocols": H3_ALPN}, ) as (quic_client, quic_server): h3_server = H3Connection(quic_server) quic_client.send_stream_data( 14, b"\xff\xff\xff\xff\xff\xff\xff\xfeGREASE is the word") self.assertEqual(h3_transfer(quic_client, h3_server), [])
def quic_event_received(self, event: QuicEvent): if isinstance(event, ProtocolNegotiated): if event.alpn_protocol == "h3-22": self._http = H3Connection(self._quic) elif event.alpn_protocol == "hq-22": self._http = H0Connection(self._quic) # pass event to the HTTP layer if self._http is not None: for http_event in self._http.handle_event(event): self.http_event_received(http_event)
def test_datagram(self): with h3_client_and_server(QUIC_CONFIGURATION_OPTIONS) as ( quic_client, quic_server, ): h3_client = H3Connection(quic_client, enable_webtransport=True) h3_server = H3Connection(quic_server, enable_webtransport=True) # create session session_id = self._make_session(h3_client, h3_server) # send datagram h3_client.send_datagram(data=b"foo", flow_id=session_id) # receive datagram events = h3_transfer(quic_client, h3_server) self.assertEqual( events, [DatagramReceived(data=b"foo", flow_id=session_id)], )
def quic_event_received(self, event: QuicEvent) -> None: if isinstance(event, ProtocolNegotiated): self._http = H3Connection(self._quic, enable_webtransport=True) elif isinstance(event, StreamReset) and self._handler is not None: # Streams in QUIC can be closed in two ways: normal (FIN) and # abnormal (resets). FIN is handled by the handler; the code # below handles the resets. self._handler.stream_closed(event.stream_id) if self._http is not None: for h3_event in self._http.handle_event(event): self._h3_event_received(h3_event)
def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._http: Optional[HttpConnection] = None self._request_events: Dict[int, Deque[H3Event]] = {} self._request_waiter: Dict[int, asyncio.Future[Deque[H3Event]]] = {} self._websockets: Dict[int, WebSocket] = {} if self._quic.configuration.alpn_protocols[0].startswith("hq-"): self._http = H0Connection(self._quic) else: self._http = H3Connection(self._quic)
def run(url: str) -> None: # parse URL parsed = urlparse(url) assert parsed.scheme == "https", "Only HTTPS URLs are supported." if ":" in parsed.netloc: server_name, port_str = parsed.netloc.split(":") port = int(port_str) else: server_name = parsed.netloc port = 443 # prepare socket server_addr = (socket.gethostbyname(server_name), port) sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # prepare QUIC connection quic = QuicConnection(configuration=QuicConfiguration( alpn_protocols=["h3-20"], is_client=True, secrets_log_file=open("/tmp/ssl.log", "w"), server_name=server_name, )) quic.connect(server_addr, now=time.time()) # send request http = H3Connection(quic) stream_id = quic.get_next_available_stream_id() http.send_headers( stream_id=stream_id, headers=[ (b":method", b"GET"), (b":scheme", parsed.scheme.encode("utf8")), (b":authority", parsed.netloc.encode("utf8")), (b":path", parsed.path.encode("utf8")), ], ) http.send_data(stream_id=stream_id, data=b"", end_stream=True) for data, addr in quic.datagrams_to_send(now=time.time()): sock.sendto(data, addr) # handle events stream_ended = False while not stream_ended: data, addr = sock.recvfrom(2048) quic.receive_datagram(data, addr, now=time.time()) for event in http.handle_events(): print(event) if isinstance(event, (DataReceived, ResponseReceived)): stream_ended = event.stream_ended for data, addr in quic.datagrams_to_send(now=time.time()): sock.sendto(data, addr)
def test_send_data_before_headers(self): """ We should not send DATA before headers. """ quic_client = FakeQuicConnection(configuration=QuicConfiguration( is_client=True)) h3_client = H3Connection(quic_client) stream_id = quic_client.get_next_available_stream_id() with self.assertRaises(FrameUnexpected): h3_client.send_data(stream_id=stream_id, data=b"hello", end_stream=False)
def test_handle_request_frame_headers_after_trailers(self): """ We should not receive HEADERS after receiving trailers. """ quic_client = FakeQuicConnection(configuration=QuicConfiguration( is_client=True)) quic_server = FakeQuicConnection(configuration=QuicConfiguration( is_client=False)) h3_client = H3Connection(quic_client) h3_server = H3Connection(quic_server) stream_id = quic_client.get_next_available_stream_id() h3_client.send_headers( stream_id=stream_id, headers=[ (b":method", b"GET"), (b":scheme", b"https"), (b":authority", b"localhost"), (b":path", b"/"), ], ) h3_client.send_headers(stream_id=stream_id, headers=[(b"x-some-trailer", b"foo")], end_stream=True) h3_transfer(quic_client, h3_server) h3_server.handle_event( StreamDataReceived(stream_id=0, data=encode_frame(FrameType.HEADERS, b""), end_stream=False)) self.assertEqual( quic_server.closed, ( ErrorCode.HTTP_FRAME_UNEXPECTED, "HEADERS frame is not allowed in this state", ), )
def quic_event_received(self, event: QuicEvent) -> None: if isinstance(event, ProtocolNegotiated): if event.alpn_protocol in H3_ALPN: self._http = H3Connection(self._quic, enable_webtransport=True) elif event.alpn_protocol in H0_ALPN: self._http = H0Connection(self._quic) elif isinstance(event, DatagramFrameReceived): if event.data == b"quack": self._quic.send_datagram_frame(b"quack-ack") # pass event to the HTTP layer if self._http is not None: for http_event in self._http.handle_event(event): self.http_event_received(http_event)
def quic_event_received(self, event: QuicEvent) -> None: if isinstance(event, ProtocolNegotiated): if event.alpn_protocol.startswith("h3-"): self._http = H3Connection(self._quic) elif event.alpn_protocol.startswith("hq-"): self._http = H0Connection(self._quic) elif isinstance(event, DatagramFrameReceived): if event.data == b"quack": self._quic.send_datagram_frame(b"quack-ack") # pass event to the HTTP layer if self._http is not None: for http_event in self._http.handle_event(event): self.http_event_received(http_event)
def test_handle_request_frame_bad_headers(self): """ We should not receive HEADERS which cannot be decoded. """ quic_server = FakeQuicConnection(configuration=QuicConfiguration( is_client=False)) h3_server = H3Connection(quic_server) h3_server.handle_event( StreamDataReceived(stream_id=0, data=encode_frame(FrameType.HEADERS, b""), end_stream=False)) self.assertEqual(quic_server.closed, (ErrorCode.HTTP_QPACK_DECOMPRESSION_FAILED, ""))
def test_handle_datagram_truncated(self): quic_server = FakeQuicConnection( configuration=QuicConfiguration(is_client=False) ) h3_server = H3Connection(quic_server) # receive a datagram with a truncated session ID h3_server.handle_event(DatagramFrameReceived(data=b"\xff")) self.assertEqual( quic_server.closed, ( ErrorCode.H3_GENERAL_PROTOCOL_ERROR, "Could not parse flow ID", ), )
def __init__( self, config: Config, client: Optional[Tuple[str, int]], server: Optional[Tuple[str, int]], spawn_app: Callable[[dict, Callable], Awaitable[Callable]], quic: QuicConnection, send: Callable[[], Awaitable[None]], ) -> None: self.client = client self.config = config self.connection = H3Connection(quic) self.send = send self.server = server self.spawn_app = spawn_app self.streams: Dict[int, Union[HTTPStream, WSStream]] = {}
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_request_frame_wrong_frame_type(self): quic_server = FakeQuicConnection(configuration=QuicConfiguration( is_client=False)) h3_server = H3Connection(quic_server) h3_server.handle_event( StreamDataReceived( stream_id=0, data=encode_frame(FrameType.SETTINGS, b""), end_stream=False, )) self.assertEqual( quic_server.closed, (ErrorCode.HTTP_FRAME_UNEXPECTED, "Invalid frame type on request stream"), )