Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions src/aioquic/h3/connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,10 @@ class DatagramError(ProtocolError):
error_code = ErrorCode.H3_DATAGRAM_ERROR


class FrameError(ProtocolError):
error_code = ErrorCode.H3_FRAME_ERROR


class FrameUnexpected(ProtocolError):
error_code = ErrorCode.H3_FRAME_UNEXPECTED

Expand Down Expand Up @@ -354,6 +358,7 @@ def __init__(self, stream_id: int) -> None:
self.headers_recv_state: HeadersState = HeadersState.INITIAL
self.headers_send_state: HeadersState = HeadersState.INITIAL
self.push_id: Optional[int] = None
self.received_frame = False
self.session_id: Optional[int] = None
self.stream_id = stream_id
self.stream_type: Optional[int] = None
Expand Down Expand Up @@ -994,6 +999,19 @@ def _receive_request_or_push_data(

# WEBTRANSPORT_STREAM frames last until the end of the stream
if stream.frame_type == FrameType.WEBTRANSPORT_STREAM:
# Per draft-ietf-webtrans-http3, a WEBTRANSPORT_STREAM
# frame is only allowed as the very first frame of a
# request stream, in any other position it is an
# H3_FRAME_ERROR.
if (
stream.headers_send_state != HeadersState.INITIAL
or stream.push_id is not None
or stream.received_frame
):
raise FrameError(
"WEBTRANSPORT_STREAM frame must be the first frame"
)

stream.session_id = stream.frame_size
stream.frame_size = None

Expand All @@ -1015,6 +1033,8 @@ def _receive_request_or_push_data(
)
return http_events

stream.received_frame = True

# log frame
if (
self._quic_logger is not None
Expand Down
78 changes: 77 additions & 1 deletion tests/test_webtransport.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
from unittest import TestCase

from aioquic.h3.connection import H3_ALPN, ErrorCode, H3Connection
from aioquic.buffer import encode_uint_var
from aioquic.h3.connection import (
H3_ALPN,
ErrorCode,
FrameType,
H3Connection,
encode_frame,
)
from aioquic.h3.events import (
DatagramReceived,
HeadersReceived,
Expand Down Expand Up @@ -191,6 +198,75 @@ def test_bidirectional_stream_server_initiated(self):
],
)

def test_bidirectional_stream_on_connect_stream(self):
"""
A WEBTRANSPORT_STREAM frame on the CONNECT stream is a protocol
violation and must close the connection with H3_FRAME_ERROR.
"""
with h3_fake_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)

# receive a WEBTRANSPORT_STREAM frame on the CONNECT stream
quic_client.send_stream_data(
session_id,
encode_uint_var(FrameType.WEBTRANSPORT_STREAM)
+ encode_uint_var(session_id)
+ b"AAAA",
)

events = h3_transfer(quic_client, h3_server)
self.assertEqual(events, [])
self.assertEqual(
quic_server.closed,
(
ErrorCode.H3_FRAME_ERROR,
"WEBTRANSPORT_STREAM frame must be the first frame",
),
)

def test_bidirectional_stream_not_first_frame(self):
"""
A WEBTRANSPORT_STREAM frame is only allowed as the very first frame
of a stream, any prior frame must close the connection with
H3_FRAME_ERROR.
"""
with h3_fake_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)

# receive an unknown frame followed by a WEBTRANSPORT_STREAM
# frame on a fresh bidirectional stream
stream_id = quic_client.get_next_available_stream_id()
quic_client.send_stream_data(
stream_id,
encode_frame(0x21, b"")
+ encode_uint_var(FrameType.WEBTRANSPORT_STREAM)
+ encode_uint_var(session_id),
)

events = h3_transfer(quic_client, h3_server)
self.assertEqual(events, [])
self.assertEqual(
quic_server.closed,
(
ErrorCode.H3_FRAME_ERROR,
"WEBTRANSPORT_STREAM frame must be the first frame",
),
)

def test_unidirectional_stream(self):
with h3_client_and_server(QUIC_CONFIGURATION_OPTIONS) as (
quic_client,
Expand Down