12
12
from aioquic .asyncio import QuicConnectionProtocol , serve # type: ignore
13
13
from aioquic .asyncio .client import connect # type: ignore
14
14
from 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
16
16
from aioquic .quic .configuration import QuicConfiguration # type: ignore
17
17
from aioquic .quic .connection import stream_is_unidirectional # type: ignore
18
18
from aioquic .quic .events import QuicEvent , ProtocolNegotiated , ConnectionTerminated , StreamReset # type: ignore
@@ -67,6 +67,7 @@ def supports_h3_datagram_04(self) -> bool:
67
67
"""
68
68
return self ._supports_h3_datagram_04
69
69
70
+
70
71
class WebTransportH3Protocol (QuicConnectionProtocol ):
71
72
def __init__ (self , * args : Any , ** kwargs : Any ) -> None :
72
73
super ().__init__ (* args , ** kwargs )
@@ -77,11 +78,14 @@ def __init__(self, *args: Any, **kwargs: Any) -> None:
77
78
self ._capsule_decoder_for_session_stream : H3CapsuleDecoder = \
78
79
H3CapsuleDecoder ()
79
80
self ._allow_calling_session_closed = True
81
+ self ._allow_datagrams = False
80
82
81
83
def quic_event_received (self , event : QuicEvent ) -> None :
82
84
if isinstance (event , ProtocolNegotiated ):
83
85
self ._http = H3ConnectionWithDatagram04 (
84
86
self ._quic , enable_webtransport = True )
87
+ if not self ._http .supports_h3_datagram_04 :
88
+ self ._allow_datagrams = True
85
89
86
90
if self ._http is not None :
87
91
for http_event in self ._http .handle_event (event ):
@@ -110,7 +114,7 @@ def _h3_event_received(self, event: H3Event) -> None:
110
114
else :
111
115
self ._send_error_response (event .stream_id , 400 )
112
116
113
- if isinstance (event , WebTransportStreamDataReceived ) and \
117
+ if isinstance (event , DataReceived ) and \
114
118
self ._session_stream_id == event .stream_id :
115
119
if self ._http and not self ._http .supports_h3_datagram_04 and \
116
120
len (event .data ) > 0 :
@@ -124,47 +128,51 @@ def _h3_event_received(self, event: H3Event) -> None:
124
128
data = event .data ,
125
129
stream_ended = event .stream_ended )
126
130
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 )
128
133
129
134
def _receive_data_on_session_stream (self , data : bytes , fin : bool ) -> None :
130
135
self ._capsule_decoder_for_session_stream .append (data )
131
136
if fin :
132
137
self ._capsule_decoder_for_session_stream .final ()
133
138
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 }:
138
142
raise ProtocolError (
139
143
"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
155
148
else :
156
149
# We should ignore unknown capsules.
157
150
continue
158
151
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 :
159
168
buffer = Buffer (data = capsule .data )
160
169
code = buffer .pull_uint32 ()
161
170
# TODO(yutakahirano): Make sure `reason` is a
162
171
# UTF-8 text.
163
172
reason = buffer .data
164
173
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 )
168
176
169
177
def _send_error_response (self , stream_id : int , status_code : int ) -> None :
170
178
assert self ._http is not None
@@ -337,6 +345,10 @@ def send_datagram(self, data: bytes) -> None:
337
345
338
346
:param data: The data to send.
339
347
"""
348
+ if not self ._protocol ._allow_datagrams :
349
+ _logger .warn (
350
+ "Sending a datagram while that's now allowed - discarding it" )
351
+ return
340
352
flow_id = self .session_id
341
353
if self ._http .supports_h3_datagram_04 :
342
354
# The REGISTER_DATAGRAM_NO_CONTEXT capsule was on the session
0 commit comments