1212from aioquic .asyncio import QuicConnectionProtocol , serve # type: ignore
1313from aioquic .asyncio .client import connect # type: ignore
1414from aioquic .h3 .connection import H3_ALPN , FrameType , H3Connection , ProtocolError , Setting # type: ignore
15- from aioquic .h3 .events import H3Event , HeadersReceived , WebTransportStreamDataReceived , DatagramReceived # type: ignore
15+ from aioquic .h3 .events import H3Event , HeadersReceived , WebTransportStreamDataReceived , DatagramReceived , DataReceived # type: ignore
1616from aioquic .quic .configuration import QuicConfiguration # type: ignore
1717from aioquic .quic .connection import stream_is_unidirectional # type: ignore
1818from aioquic .quic .events import QuicEvent , ProtocolNegotiated , ConnectionTerminated , StreamReset # type: ignore
@@ -67,6 +67,7 @@ def supports_h3_datagram_04(self) -> bool:
6767 """
6868 return self ._supports_h3_datagram_04
6969
70+
7071class WebTransportH3Protocol (QuicConnectionProtocol ):
7172 def __init__ (self , * args : Any , ** kwargs : Any ) -> None :
7273 super ().__init__ (* args , ** kwargs )
@@ -77,11 +78,14 @@ def __init__(self, *args: Any, **kwargs: Any) -> None:
7778 self ._capsule_decoder_for_session_stream : H3CapsuleDecoder = \
7879 H3CapsuleDecoder ()
7980 self ._allow_calling_session_closed = True
81+ self ._allow_datagrams = False
8082
8183 def quic_event_received (self , event : QuicEvent ) -> None :
8284 if isinstance (event , ProtocolNegotiated ):
8385 self ._http = H3ConnectionWithDatagram04 (
8486 self ._quic , enable_webtransport = True )
87+ if not self ._http .supports_h3_datagram_04 :
88+ self ._allow_datagrams = True
8589
8690 if self ._http is not None :
8791 for http_event in self ._http .handle_event (event ):
@@ -110,7 +114,7 @@ def _h3_event_received(self, event: H3Event) -> None:
110114 else :
111115 self ._send_error_response (event .stream_id , 400 )
112116
113- if isinstance (event , WebTransportStreamDataReceived ) and \
117+ if isinstance (event , DataReceived ) and \
114118 self ._session_stream_id == event .stream_id :
115119 if self ._http and not self ._http .supports_h3_datagram_04 and \
116120 len (event .data ) > 0 :
@@ -124,47 +128,51 @@ def _h3_event_received(self, event: H3Event) -> None:
124128 data = event .data ,
125129 stream_ended = event .stream_ended )
126130 elif isinstance (event , DatagramReceived ):
127- self ._handler .datagram_received (data = event .data )
131+ if self ._allow_datagrams :
132+ self ._handler .datagram_received (data = event .data )
128133
129134 def _receive_data_on_session_stream (self , data : bytes , fin : bool ) -> None :
130135 self ._capsule_decoder_for_session_stream .append (data )
131136 if fin :
132137 self ._capsule_decoder_for_session_stream .final ()
133138 for capsule in self ._capsule_decoder_for_session_stream :
134- if capsule .type == CapsuleType .DATAGRAM :
135- raise ProtocolError (
136- "Unimplemented capsule type: {}" .format (capsule .type ))
137- if capsule .type == CapsuleType .REGISTER_DATAGRAM_CONTEXT :
139+ if capsule .type in {CapsuleType .DATAGRAM ,
140+ CapsuleType .REGISTER_DATAGRAM_CONTEXT ,
141+ CapsuleType .CLOSE_DATAGRAM_CONTEXT }:
138142 raise ProtocolError (
139143 "Unimplemented capsule type: {}" .format (capsule .type ))
140- elif capsule .type == CapsuleType .REGISTER_DATAGRAM_NO_CONTEXT :
141- # TODO(yutakahirano): Check the Datagram Format Type.
142- # TODO(yutakahirano): Check that this arrives before any
143- # datagrams/streams requests.
144- if self ._close_info is not None :
145- raise ProtocolError (
146- "REGISTER_DATAGRAM_NO_CONTEXT after " +
147- "CLOSE_WEBTRANSPORT_SESSION" )
148- elif capsule .type == CapsuleType .CLOSE_DATAGRAM_CONTEXT :
149- raise ProtocolError (
150- "Unimplemented capsule type: {}" .format (capsule .type ))
151- elif capsule .type == CapsuleType .CLOSE_WEBTRANSPORT_SESSION :
152- if self ._close_info is not None :
153- raise ProtocolError (
154- "CLOSE_WEBTRANSPORT_SESSION arrives twice" )
144+ if capsule .type in {CapsuleType .REGISTER_DATAGRAM_NO_CONTEXT ,
145+ CapsuleType .CLOSE_WEBTRANSPORT_SESSION }:
146+ # We'll handle this case below.
147+ pass
155148 else :
156149 # We should ignore unknown capsules.
157150 continue
158151
152+ if self ._close_info is not None :
153+ raise ProtocolError ((
154+ "Receiving a capsule with type = {} after receiving " +
155+ "CLOSE_WEBTRANSPORT_SESSION" ).format (capsule .type ))
156+
157+ if capsule .type == CapsuleType .REGISTER_DATAGRAM_NO_CONTEXT :
158+ buffer = Buffer (data = capsule .data )
159+ format_type = buffer .pull_uint_var ()
160+ # https://ietf-wg-webtrans.github.io/draft-ietf-webtrans-http3/draft-ietf-webtrans-http3.html#name-datagram-format-type
161+ WEBTRANPORT_FORMAT_TYPE = 0xff7c00
162+ if format_type != WEBTRANPORT_FORMAT_TYPE :
163+ raise ProtocolError (
164+ "Unexpected datagram format type: {}" .format (
165+ format_type ))
166+ self ._allow_datagrams = True
167+ elif capsule .type == CapsuleType .CLOSE_WEBTRANSPORT_SESSION :
159168 buffer = Buffer (data = capsule .data )
160169 code = buffer .pull_uint32 ()
161170 # TODO(yutakahirano): Make sure `reason` is a
162171 # UTF-8 text.
163172 reason = buffer .data
164173 self ._close_info = (code , reason )
165- # TODO(yutakahirano): Make sure this is the last capsule.
166- if fin :
167- self ._call_session_closed (self ._close_info , abruptly = False )
174+ if fin :
175+ self ._call_session_closed (self ._close_info , abruptly = False )
168176
169177 def _send_error_response (self , stream_id : int , status_code : int ) -> None :
170178 assert self ._http is not None
@@ -337,6 +345,10 @@ def send_datagram(self, data: bytes) -> None:
337345
338346 :param data: The data to send.
339347 """
348+ if not self ._protocol ._allow_datagrams :
349+ _logger .warn (
350+ "Sending a datagram while that's now allowed - discarding it" )
351+ return
340352 flow_id = self .session_id
341353 if self ._http .supports_h3_datagram_04 :
342354 # The REGISTER_DATAGRAM_NO_CONTEXT capsule was on the session
0 commit comments