boring-http 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- boring_http/__init__.py +58 -0
- boring_http/client.py +692 -0
- boring_http/codings.py +39 -0
- boring_http/cookies.py +127 -0
- boring_http/errors.py +49 -0
- boring_http/hpack.py +247 -0
- boring_http/hpack_tables.py +326 -0
- boring_http/http1.py +259 -0
- boring_http/http2.py +685 -0
- boring_http/http3.py +551 -0
- boring_http/message.py +598 -0
- boring_http/pool.py +96 -0
- boring_http/py.typed +0 -0
- boring_http/qpack.py +245 -0
- boring_http/qpack_tables.py +104 -0
- boring_http/quic/__init__.py +0 -0
- boring_http/quic/connection.py +1261 -0
- boring_http/quic/packet.py +655 -0
- boring_http/quic/protocol.py +474 -0
- boring_http/quic/recovery.py +303 -0
- boring_http/quic/streams.py +154 -0
- boring_http/timing.py +63 -0
- boring_http/websocket.py +216 -0
- boring_http-1.0.0.dist-info/METADATA +303 -0
- boring_http-1.0.0.dist-info/RECORD +26 -0
- boring_http-1.0.0.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,1261 @@
|
|
|
1
|
+
import secrets
|
|
2
|
+
from collections import deque
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from enum import IntEnum
|
|
5
|
+
from typing import Final, final
|
|
6
|
+
|
|
7
|
+
from boring_ssl2 import ClientContext, EncryptionLevel, PacketProtection, QuicHandshake
|
|
8
|
+
from ssl2 import NextProtocol, TLSError
|
|
9
|
+
|
|
10
|
+
from boring_http.errors import ConnectError, ProtocolError
|
|
11
|
+
from boring_http.quic.packet import (
|
|
12
|
+
ACK_ELICITING_EXCLUSIONS,
|
|
13
|
+
QUIC_V1,
|
|
14
|
+
QUIC_V2,
|
|
15
|
+
Ack,
|
|
16
|
+
ConnectionClose,
|
|
17
|
+
Crypto,
|
|
18
|
+
DataBlocked,
|
|
19
|
+
Datagram,
|
|
20
|
+
Frame,
|
|
21
|
+
HandshakeDone,
|
|
22
|
+
Header,
|
|
23
|
+
MaxData,
|
|
24
|
+
MaxStreamData,
|
|
25
|
+
MaxStreams,
|
|
26
|
+
NewConnectionId,
|
|
27
|
+
NewToken,
|
|
28
|
+
PacketType,
|
|
29
|
+
Padding,
|
|
30
|
+
PathChallenge,
|
|
31
|
+
PathResponse,
|
|
32
|
+
Ping,
|
|
33
|
+
Reader,
|
|
34
|
+
ResetStream,
|
|
35
|
+
RetireConnectionId,
|
|
36
|
+
StopSending,
|
|
37
|
+
Stream,
|
|
38
|
+
StreamDataBlocked,
|
|
39
|
+
StreamsBlocked,
|
|
40
|
+
TransportError,
|
|
41
|
+
Writer,
|
|
42
|
+
)
|
|
43
|
+
from boring_http.quic.recovery import GRANULARITY, Recovery, SentPacket
|
|
44
|
+
from boring_http.quic.streams import ReceiveStream, SendStream
|
|
45
|
+
|
|
46
|
+
DATAGRAM_SIZE: Final = 1200
|
|
47
|
+
TAG_LENGTH: Final = 16
|
|
48
|
+
SAMPLE_LENGTH: Final = 16
|
|
49
|
+
CRYPTO_STREAM: Final = -1
|
|
50
|
+
MAX_BURST: Final = 10
|
|
51
|
+
ACK_THRESHOLD: Final = 2
|
|
52
|
+
PACING_GAIN: Final = 1.25
|
|
53
|
+
INTERNAL_ERROR_ALERT: Final = 80
|
|
54
|
+
VERSIONS: Final = (QUIC_V1, QUIC_V2)
|
|
55
|
+
LEVEL_OF: Final = {
|
|
56
|
+
PacketType.INITIAL: EncryptionLevel.INITIAL,
|
|
57
|
+
PacketType.ZERO_RTT: EncryptionLevel.EARLY_DATA,
|
|
58
|
+
PacketType.HANDSHAKE: EncryptionLevel.HANDSHAKE,
|
|
59
|
+
PacketType.ONE_RTT: EncryptionLevel.APPLICATION,
|
|
60
|
+
}
|
|
61
|
+
PACKET_TYPE_OF: Final = {
|
|
62
|
+
EncryptionLevel.INITIAL: PacketType.INITIAL,
|
|
63
|
+
EncryptionLevel.EARLY_DATA: PacketType.ZERO_RTT,
|
|
64
|
+
EncryptionLevel.HANDSHAKE: PacketType.HANDSHAKE,
|
|
65
|
+
EncryptionLevel.APPLICATION: PacketType.ONE_RTT,
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class Parameter(IntEnum):
|
|
70
|
+
ORIGINAL_DESTINATION_CONNECTION_ID = 0x00
|
|
71
|
+
MAX_IDLE_TIMEOUT = 0x01
|
|
72
|
+
STATELESS_RESET_TOKEN = 0x02
|
|
73
|
+
MAX_UDP_PAYLOAD_SIZE = 0x03
|
|
74
|
+
INITIAL_MAX_DATA = 0x04
|
|
75
|
+
INITIAL_MAX_STREAM_DATA_BIDI_LOCAL = 0x05
|
|
76
|
+
INITIAL_MAX_STREAM_DATA_BIDI_REMOTE = 0x06
|
|
77
|
+
INITIAL_MAX_STREAM_DATA_UNI = 0x07
|
|
78
|
+
INITIAL_MAX_STREAMS_BIDI = 0x08
|
|
79
|
+
INITIAL_MAX_STREAMS_UNI = 0x09
|
|
80
|
+
ACK_DELAY_EXPONENT = 0x0A
|
|
81
|
+
MAX_ACK_DELAY = 0x0B
|
|
82
|
+
DISABLE_ACTIVE_MIGRATION = 0x0C
|
|
83
|
+
PREFERRED_ADDRESS = 0x0D
|
|
84
|
+
ACTIVE_CONNECTION_ID_LIMIT = 0x0E
|
|
85
|
+
INITIAL_SOURCE_CONNECTION_ID = 0x0F
|
|
86
|
+
RETRY_SOURCE_CONNECTION_ID = 0x10
|
|
87
|
+
VERSION_INFORMATION = 0x11
|
|
88
|
+
MAX_DATAGRAM_FRAME_SIZE = 0x20
|
|
89
|
+
GREASE_QUIC_BIT = 0x2AB2
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
EARLY_DATA_LIMITS: Final = (
|
|
93
|
+
Parameter.INITIAL_MAX_DATA,
|
|
94
|
+
Parameter.INITIAL_MAX_STREAM_DATA_BIDI_LOCAL,
|
|
95
|
+
Parameter.INITIAL_MAX_STREAM_DATA_BIDI_REMOTE,
|
|
96
|
+
Parameter.INITIAL_MAX_STREAM_DATA_UNI,
|
|
97
|
+
Parameter.INITIAL_MAX_STREAMS_BIDI,
|
|
98
|
+
Parameter.INITIAL_MAX_STREAMS_UNI,
|
|
99
|
+
)
|
|
100
|
+
# RFC 9000 section 7.4.1: values a client must take from the new handshake, never from the previous one
|
|
101
|
+
NEVER_REMEMBERED: Final = (
|
|
102
|
+
Parameter.ACK_DELAY_EXPONENT,
|
|
103
|
+
Parameter.MAX_ACK_DELAY,
|
|
104
|
+
Parameter.ACTIVE_CONNECTION_ID_LIMIT,
|
|
105
|
+
Parameter.INITIAL_SOURCE_CONNECTION_ID,
|
|
106
|
+
Parameter.ORIGINAL_DESTINATION_CONNECTION_ID,
|
|
107
|
+
Parameter.RETRY_SOURCE_CONNECTION_ID,
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
@final
|
|
112
|
+
@dataclass(frozen=True, slots=True)
|
|
113
|
+
class TransportParameters:
|
|
114
|
+
max_idle_timeout: int = 30000
|
|
115
|
+
max_udp_payload_size: int = 1472
|
|
116
|
+
initial_max_data: int = 15728640
|
|
117
|
+
initial_max_stream_data_bidi_local: int = 6291456
|
|
118
|
+
initial_max_stream_data_bidi_remote: int = 6291456
|
|
119
|
+
initial_max_stream_data_uni: int = 6291456
|
|
120
|
+
initial_max_streams_bidi: int = 100
|
|
121
|
+
initial_max_streams_uni: int = 103
|
|
122
|
+
ack_delay_exponent: int = 3
|
|
123
|
+
max_ack_delay: int = 25
|
|
124
|
+
active_connection_id_limit: int = 8
|
|
125
|
+
max_datagram_frame_size: int = 65536
|
|
126
|
+
grease: bool = True
|
|
127
|
+
shuffle: bool = True
|
|
128
|
+
order: tuple[Parameter, ...] = (
|
|
129
|
+
Parameter.MAX_IDLE_TIMEOUT,
|
|
130
|
+
Parameter.MAX_UDP_PAYLOAD_SIZE,
|
|
131
|
+
Parameter.INITIAL_MAX_DATA,
|
|
132
|
+
Parameter.INITIAL_MAX_STREAM_DATA_BIDI_LOCAL,
|
|
133
|
+
Parameter.INITIAL_MAX_STREAM_DATA_BIDI_REMOTE,
|
|
134
|
+
Parameter.INITIAL_MAX_STREAM_DATA_UNI,
|
|
135
|
+
Parameter.INITIAL_MAX_STREAMS_BIDI,
|
|
136
|
+
Parameter.INITIAL_MAX_STREAMS_UNI,
|
|
137
|
+
Parameter.ACK_DELAY_EXPONENT,
|
|
138
|
+
Parameter.MAX_ACK_DELAY,
|
|
139
|
+
Parameter.ACTIVE_CONNECTION_ID_LIMIT,
|
|
140
|
+
Parameter.INITIAL_SOURCE_CONNECTION_ID,
|
|
141
|
+
Parameter.VERSION_INFORMATION,
|
|
142
|
+
Parameter.MAX_DATAGRAM_FRAME_SIZE,
|
|
143
|
+
Parameter.GREASE_QUIC_BIT,
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
def encode(
|
|
147
|
+
self, source_connection_id: bytes, version: int, versions: tuple[int, ...]
|
|
148
|
+
) -> bytes:
|
|
149
|
+
writer = Writer()
|
|
150
|
+
integers = {
|
|
151
|
+
Parameter.MAX_IDLE_TIMEOUT: self.max_idle_timeout,
|
|
152
|
+
Parameter.MAX_UDP_PAYLOAD_SIZE: self.max_udp_payload_size,
|
|
153
|
+
Parameter.INITIAL_MAX_DATA: self.initial_max_data,
|
|
154
|
+
Parameter.INITIAL_MAX_STREAM_DATA_BIDI_LOCAL: self.initial_max_stream_data_bidi_local,
|
|
155
|
+
Parameter.INITIAL_MAX_STREAM_DATA_BIDI_REMOTE: self.initial_max_stream_data_bidi_remote,
|
|
156
|
+
Parameter.INITIAL_MAX_STREAM_DATA_UNI: self.initial_max_stream_data_uni,
|
|
157
|
+
Parameter.INITIAL_MAX_STREAMS_BIDI: self.initial_max_streams_bidi,
|
|
158
|
+
Parameter.INITIAL_MAX_STREAMS_UNI: self.initial_max_streams_uni,
|
|
159
|
+
Parameter.ACK_DELAY_EXPONENT: self.ack_delay_exponent,
|
|
160
|
+
Parameter.MAX_ACK_DELAY: self.max_ack_delay,
|
|
161
|
+
Parameter.ACTIVE_CONNECTION_ID_LIMIT: self.active_connection_id_limit,
|
|
162
|
+
Parameter.MAX_DATAGRAM_FRAME_SIZE: self.max_datagram_frame_size,
|
|
163
|
+
}
|
|
164
|
+
pairs: list[tuple[int, bytes]] = []
|
|
165
|
+
for parameter in self.order:
|
|
166
|
+
if parameter in integers:
|
|
167
|
+
value = Writer.encode_varint(integers[parameter])
|
|
168
|
+
elif parameter is Parameter.INITIAL_SOURCE_CONNECTION_ID:
|
|
169
|
+
value = source_connection_id
|
|
170
|
+
elif parameter is Parameter.VERSION_INFORMATION:
|
|
171
|
+
value = b"".join(
|
|
172
|
+
candidate.to_bytes(4, "big") for candidate in (version, *versions)
|
|
173
|
+
)
|
|
174
|
+
elif parameter is Parameter.GREASE_QUIC_BIT:
|
|
175
|
+
value = b""
|
|
176
|
+
else:
|
|
177
|
+
raise ValueError(f"{parameter.name} is not a parameter a client sends")
|
|
178
|
+
pairs.append((parameter, value))
|
|
179
|
+
if self.grease:
|
|
180
|
+
# RFC 9000 section 18.1: a reserved identifier the peer must ignore, drawn the way Chrome draws it
|
|
181
|
+
pairs.insert(
|
|
182
|
+
secrets.randbelow(len(pairs) + 1),
|
|
183
|
+
(
|
|
184
|
+
31 * secrets.randbelow((1 << 62) // 31) + 27,
|
|
185
|
+
secrets.token_bytes(secrets.randbelow(16)),
|
|
186
|
+
),
|
|
187
|
+
)
|
|
188
|
+
if self.shuffle:
|
|
189
|
+
# Chrome sends its transport parameters in a fresh random order on every connection
|
|
190
|
+
secrets.SystemRandom().shuffle(pairs)
|
|
191
|
+
for identifier, value in pairs:
|
|
192
|
+
writer.varint(identifier)
|
|
193
|
+
writer.varint(len(value))
|
|
194
|
+
writer.put(value)
|
|
195
|
+
return writer.finish()
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
@final
|
|
199
|
+
class PeerParameters:
|
|
200
|
+
def __init__(self, data: bytes) -> None:
|
|
201
|
+
self.raw: dict[int, bytes] = {}
|
|
202
|
+
reader = Reader(data)
|
|
203
|
+
while reader.remaining:
|
|
204
|
+
identifier = reader.varint()
|
|
205
|
+
self.raw[identifier] = reader.take(reader.varint())
|
|
206
|
+
|
|
207
|
+
def integer(self, parameter: Parameter, default: int) -> int:
|
|
208
|
+
value = self.raw.get(parameter)
|
|
209
|
+
return default if value is None else Reader(value).varint()
|
|
210
|
+
|
|
211
|
+
def connection_id(self, parameter: Parameter) -> bytes | None:
|
|
212
|
+
return self.raw.get(parameter)
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
@final
|
|
216
|
+
@dataclass(frozen=True, slots=True)
|
|
217
|
+
class StreamDataReceived:
|
|
218
|
+
stream_id: int
|
|
219
|
+
data: bytes
|
|
220
|
+
end: bool
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
@final
|
|
224
|
+
@dataclass(frozen=True, slots=True)
|
|
225
|
+
class StreamReset:
|
|
226
|
+
stream_id: int
|
|
227
|
+
error: int
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
@final
|
|
231
|
+
@dataclass(frozen=True, slots=True)
|
|
232
|
+
class StopSendingReceived:
|
|
233
|
+
stream_id: int
|
|
234
|
+
error: int
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
@final
|
|
238
|
+
@dataclass(frozen=True, slots=True)
|
|
239
|
+
class HandshakeCompleted:
|
|
240
|
+
pass
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
@final
|
|
244
|
+
@dataclass(frozen=True, slots=True)
|
|
245
|
+
class ConnectionTerminated:
|
|
246
|
+
error: int
|
|
247
|
+
reason: str
|
|
248
|
+
application: bool
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
@dataclass(frozen=True, slots=True)
|
|
252
|
+
class VersionNegotiated:
|
|
253
|
+
version: int
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
@dataclass(frozen=True, slots=True)
|
|
257
|
+
class EarlyDataAvailable:
|
|
258
|
+
pass
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
@dataclass(frozen=True, slots=True)
|
|
262
|
+
class EarlyDataRejected:
|
|
263
|
+
pass
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
@final
|
|
267
|
+
@dataclass(frozen=True, slots=True)
|
|
268
|
+
class DatagramReceived:
|
|
269
|
+
data: bytes
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
type Event = (
|
|
273
|
+
StreamDataReceived
|
|
274
|
+
| StreamReset
|
|
275
|
+
| StopSendingReceived
|
|
276
|
+
| HandshakeCompleted
|
|
277
|
+
| ConnectionTerminated
|
|
278
|
+
| DatagramReceived
|
|
279
|
+
| VersionNegotiated
|
|
280
|
+
| EarlyDataAvailable
|
|
281
|
+
| EarlyDataRejected
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
@final
|
|
286
|
+
class QuicConnection:
|
|
287
|
+
def __init__(
|
|
288
|
+
self,
|
|
289
|
+
context: ClientContext,
|
|
290
|
+
server_name: str,
|
|
291
|
+
parameters: TransportParameters,
|
|
292
|
+
now: float,
|
|
293
|
+
*,
|
|
294
|
+
version: int = QUIC_V1,
|
|
295
|
+
token: bytes = b"",
|
|
296
|
+
remembered: bytes = b"",
|
|
297
|
+
) -> None:
|
|
298
|
+
self._version = version
|
|
299
|
+
self._parameters = parameters
|
|
300
|
+
self._source_id = PacketProtection.random(8)
|
|
301
|
+
self._destination_id = PacketProtection.random(8)
|
|
302
|
+
self._original_destination = self._destination_id
|
|
303
|
+
self._retry_source: bytes | None = None
|
|
304
|
+
self._token = token
|
|
305
|
+
self._server_name = server_name
|
|
306
|
+
self._context = context
|
|
307
|
+
self._handshake = QuicHandshake(
|
|
308
|
+
context,
|
|
309
|
+
server_name,
|
|
310
|
+
parameters.encode(self._source_id, version, (version,)),
|
|
311
|
+
remembered,
|
|
312
|
+
)
|
|
313
|
+
self._recovery = Recovery(DATAGRAM_SIZE)
|
|
314
|
+
send, receive = PacketProtection.initial(version, self._destination_id)
|
|
315
|
+
self._keys: dict[
|
|
316
|
+
EncryptionLevel, tuple[PacketProtection | None, PacketProtection | None]
|
|
317
|
+
] = {EncryptionLevel.INITIAL: (receive, send)}
|
|
318
|
+
self._next_keys: PacketProtection | None = None
|
|
319
|
+
self._key_phase = False
|
|
320
|
+
self._crypto_send: dict[EncryptionLevel, SendStream] = {
|
|
321
|
+
level: SendStream(CRYPTO_STREAM, 1 << 62) for level in EncryptionLevel
|
|
322
|
+
}
|
|
323
|
+
self._crypto_receive: dict[EncryptionLevel, ReceiveStream] = {
|
|
324
|
+
level: ReceiveStream(CRYPTO_STREAM, 1 << 62) for level in EncryptionLevel
|
|
325
|
+
}
|
|
326
|
+
self._send_streams: dict[int, SendStream] = {}
|
|
327
|
+
self._receive_streams: dict[int, ReceiveStream] = {}
|
|
328
|
+
self._next_bidi = 0
|
|
329
|
+
self._next_uni = 2
|
|
330
|
+
self._peer_max_streams = {True: 0, False: 0}
|
|
331
|
+
self._local_max_streams = {
|
|
332
|
+
True: parameters.initial_max_streams_bidi,
|
|
333
|
+
False: parameters.initial_max_streams_uni,
|
|
334
|
+
}
|
|
335
|
+
self._peer_opened = {True: 0, False: 0}
|
|
336
|
+
self._peer: PeerParameters | None = None
|
|
337
|
+
self._peer_max_data = 0
|
|
338
|
+
self._data_sent = 0
|
|
339
|
+
self._local_max_data = parameters.initial_max_data
|
|
340
|
+
self._data_received = 0
|
|
341
|
+
self._pending: dict[EncryptionLevel, list[Frame]] = {
|
|
342
|
+
level: [] for level in EncryptionLevel
|
|
343
|
+
}
|
|
344
|
+
self._events: deque[Event] = deque()
|
|
345
|
+
self._handshake_done = False
|
|
346
|
+
self._handshake_confirmed = False
|
|
347
|
+
self._closing: ConnectionClose | None = None
|
|
348
|
+
self._close_sent = False
|
|
349
|
+
self._terminated = False
|
|
350
|
+
self._idle_timeout = parameters.max_idle_timeout / 1000
|
|
351
|
+
self._last_activity = now
|
|
352
|
+
self._ack_timer: float | None = None
|
|
353
|
+
self._peer_connection_ids: dict[int, tuple[bytes, bytes]] = {}
|
|
354
|
+
self._retire_prior_to = 0
|
|
355
|
+
self._datagrams_out: deque[bytes] = deque()
|
|
356
|
+
self._probe_levels: deque[EncryptionLevel] = deque()
|
|
357
|
+
self._initial_hello = b""
|
|
358
|
+
self.new_token: bytes = b""
|
|
359
|
+
self.alpn: NextProtocol | bytes | None = None
|
|
360
|
+
self.early_data = self._handshake.early_data_offered
|
|
361
|
+
if self.early_data:
|
|
362
|
+
# 0-RTT runs under the limits the server granted last time until the handshake brings the new ones
|
|
363
|
+
granted = PeerParameters(self._handshake.remembered_transport_parameters)
|
|
364
|
+
for parameter in NEVER_REMEMBERED:
|
|
365
|
+
_ = granted.raw.pop(parameter, None)
|
|
366
|
+
self._apply_peer_parameters(granted)
|
|
367
|
+
self._drive_handshake()
|
|
368
|
+
|
|
369
|
+
@property
|
|
370
|
+
def handshake_complete(self) -> bool:
|
|
371
|
+
return self._handshake_done
|
|
372
|
+
|
|
373
|
+
@property
|
|
374
|
+
def early_data_pending(self) -> bool:
|
|
375
|
+
return self.early_data and not self._handshake_done
|
|
376
|
+
|
|
377
|
+
@property
|
|
378
|
+
def peer_transport_parameters(self) -> bytes:
|
|
379
|
+
return (
|
|
380
|
+
self._handshake.peer_transport_parameters if self._handshake_done else b""
|
|
381
|
+
)
|
|
382
|
+
|
|
383
|
+
@property
|
|
384
|
+
def terminated(self) -> bool:
|
|
385
|
+
return self._terminated
|
|
386
|
+
|
|
387
|
+
@property
|
|
388
|
+
def peer_certificate(self) -> bytes | None:
|
|
389
|
+
return self._handshake.buffer.getpeercert()
|
|
390
|
+
|
|
391
|
+
def next_event(self) -> Event | None:
|
|
392
|
+
return self._events.popleft() if self._events else None
|
|
393
|
+
|
|
394
|
+
def can_open_stream(self, *, bidirectional: bool = True) -> bool:
|
|
395
|
+
return (
|
|
396
|
+
self._next_bidi if bidirectional else self._next_uni
|
|
397
|
+
) // 4 < self._peer_max_streams[bidirectional]
|
|
398
|
+
|
|
399
|
+
def open_stream(self, *, bidirectional: bool = True) -> int:
|
|
400
|
+
if not self.can_open_stream(bidirectional=bidirectional):
|
|
401
|
+
raise ConnectError("the server allows no more streams right now")
|
|
402
|
+
if bidirectional:
|
|
403
|
+
stream_id, self._next_bidi = self._next_bidi, self._next_bidi + 4
|
|
404
|
+
else:
|
|
405
|
+
stream_id, self._next_uni = self._next_uni, self._next_uni + 4
|
|
406
|
+
limit = self._peer_parameter(
|
|
407
|
+
Parameter.INITIAL_MAX_STREAM_DATA_BIDI_REMOTE
|
|
408
|
+
if bidirectional
|
|
409
|
+
else Parameter.INITIAL_MAX_STREAM_DATA_UNI,
|
|
410
|
+
0,
|
|
411
|
+
)
|
|
412
|
+
self._send_streams[stream_id] = SendStream(stream_id, limit)
|
|
413
|
+
if bidirectional:
|
|
414
|
+
self._receive_streams[stream_id] = ReceiveStream(
|
|
415
|
+
stream_id, self._parameters.initial_max_stream_data_bidi_local
|
|
416
|
+
)
|
|
417
|
+
return stream_id
|
|
418
|
+
|
|
419
|
+
def send_stream_data(
|
|
420
|
+
self, stream_id: int, data: bytes, *, end: bool = False
|
|
421
|
+
) -> None:
|
|
422
|
+
stream = self._send_streams[stream_id]
|
|
423
|
+
stream.write(data)
|
|
424
|
+
if end:
|
|
425
|
+
stream.end()
|
|
426
|
+
|
|
427
|
+
def reset_stream(self, stream_id: int, error: int) -> None:
|
|
428
|
+
stream = self._send_streams.get(stream_id)
|
|
429
|
+
if stream is not None and not stream.reset_sent:
|
|
430
|
+
stream.reset_error = error
|
|
431
|
+
stream.reset_sent = True
|
|
432
|
+
stream.pending.clear()
|
|
433
|
+
self._pending[EncryptionLevel.APPLICATION].append(
|
|
434
|
+
ResetStream(stream_id, error, stream.offset)
|
|
435
|
+
)
|
|
436
|
+
|
|
437
|
+
def stop_sending(self, stream_id: int, error: int) -> None:
|
|
438
|
+
stream = self._receive_streams.get(stream_id)
|
|
439
|
+
if stream is not None and not stream.stop_sent:
|
|
440
|
+
stream.stop_sent = True
|
|
441
|
+
self._pending[EncryptionLevel.APPLICATION].append(
|
|
442
|
+
StopSending(stream_id, error)
|
|
443
|
+
)
|
|
444
|
+
|
|
445
|
+
def send_datagram(self, data: bytes) -> None:
|
|
446
|
+
self._pending[EncryptionLevel.APPLICATION].append(Datagram(data))
|
|
447
|
+
|
|
448
|
+
def close(
|
|
449
|
+
self, error: int = 0, reason: str = "", *, application: bool = True
|
|
450
|
+
) -> None:
|
|
451
|
+
if self._closing is None and not self._terminated:
|
|
452
|
+
self._closing = ConnectionClose(error, None, reason.encode(), application)
|
|
453
|
+
|
|
454
|
+
def datagram_received(self, data: bytes, now: float) -> None:
|
|
455
|
+
if self._terminated:
|
|
456
|
+
return
|
|
457
|
+
self._last_activity = now
|
|
458
|
+
offset = 0
|
|
459
|
+
while offset < len(data):
|
|
460
|
+
try:
|
|
461
|
+
header = Header.parse(data, offset, len(self._source_id))
|
|
462
|
+
except ProtocolError:
|
|
463
|
+
return
|
|
464
|
+
if header.packet_type is PacketType.VERSION_NEGOTIATION:
|
|
465
|
+
self._on_version_negotiation(header)
|
|
466
|
+
return
|
|
467
|
+
if header.packet_type is PacketType.RETRY:
|
|
468
|
+
self._on_retry(header, data[offset:])
|
|
469
|
+
return
|
|
470
|
+
if (
|
|
471
|
+
header.packet_type is not PacketType.ONE_RTT
|
|
472
|
+
and header.version != self._version
|
|
473
|
+
):
|
|
474
|
+
return
|
|
475
|
+
level = LEVEL_OF[header.packet_type]
|
|
476
|
+
keys = self._keys.get(level)
|
|
477
|
+
if keys is None or keys[0] is None:
|
|
478
|
+
offset = header.end
|
|
479
|
+
continue
|
|
480
|
+
self._receive_packet(header, data, offset, level, keys[0], now)
|
|
481
|
+
offset = header.end
|
|
482
|
+
|
|
483
|
+
def datagrams_to_send(self, now: float) -> list[bytes]:
|
|
484
|
+
if self._terminated:
|
|
485
|
+
return []
|
|
486
|
+
if self._closing is not None:
|
|
487
|
+
if self._close_sent:
|
|
488
|
+
return []
|
|
489
|
+
self._close_sent = True
|
|
490
|
+
datagram = self._close_datagram(now)
|
|
491
|
+
self._terminated = True
|
|
492
|
+
self._events.append(
|
|
493
|
+
ConnectionTerminated(
|
|
494
|
+
self._closing.error,
|
|
495
|
+
self._closing.reason.decode("utf-8", "replace"),
|
|
496
|
+
self._closing.application,
|
|
497
|
+
)
|
|
498
|
+
)
|
|
499
|
+
return [datagram] if datagram else []
|
|
500
|
+
datagrams: list[bytes] = []
|
|
501
|
+
while len(datagrams) < MAX_BURST:
|
|
502
|
+
datagram = self._build_datagram(now)
|
|
503
|
+
if datagram is None:
|
|
504
|
+
break
|
|
505
|
+
datagrams.append(datagram)
|
|
506
|
+
if datagrams:
|
|
507
|
+
self._last_activity = now
|
|
508
|
+
return datagrams
|
|
509
|
+
|
|
510
|
+
def pacing_delay(self, size: int) -> float:
|
|
511
|
+
# RFC 9002 section 7.7: spread a window over a round trip instead of sending it as one burst
|
|
512
|
+
recovery = self._recovery
|
|
513
|
+
rate = (
|
|
514
|
+
PACING_GAIN
|
|
515
|
+
* max(recovery.window, 2 * DATAGRAM_SIZE)
|
|
516
|
+
/ max(recovery.smoothed_rtt, GRANULARITY)
|
|
517
|
+
)
|
|
518
|
+
return size / rate
|
|
519
|
+
|
|
520
|
+
def next_timer(self) -> float | None:
|
|
521
|
+
candidates = [self._last_activity + self._idle_timeout]
|
|
522
|
+
if self._ack_timer is not None:
|
|
523
|
+
candidates.append(self._ack_timer)
|
|
524
|
+
timer = self._recovery.loss_timer(EncryptionLevel.HANDSHAKE in self._keys)
|
|
525
|
+
if timer is not None:
|
|
526
|
+
candidates.append(timer[0])
|
|
527
|
+
return min(candidates)
|
|
528
|
+
|
|
529
|
+
def handle_timer(self, now: float) -> None:
|
|
530
|
+
if self._terminated:
|
|
531
|
+
return
|
|
532
|
+
if now >= self._last_activity + self._idle_timeout:
|
|
533
|
+
self._terminated = True
|
|
534
|
+
self._events.append(
|
|
535
|
+
ConnectionTerminated(TransportError.NO_ERROR, "idle timeout", False)
|
|
536
|
+
)
|
|
537
|
+
return
|
|
538
|
+
if self._ack_timer is not None and now >= self._ack_timer:
|
|
539
|
+
self._ack_timer = None
|
|
540
|
+
self._recovery.space(EncryptionLevel.APPLICATION).ack_pending = True
|
|
541
|
+
timer = self._recovery.loss_timer(EncryptionLevel.HANDSHAKE in self._keys)
|
|
542
|
+
if timer is None or now < timer[0]:
|
|
543
|
+
return
|
|
544
|
+
_, level, is_loss = timer
|
|
545
|
+
if is_loss:
|
|
546
|
+
for packet in self._recovery.detect_lost(level, now):
|
|
547
|
+
self._on_lost(EncryptionLevel(level), packet)
|
|
548
|
+
return
|
|
549
|
+
self._recovery.on_pto()
|
|
550
|
+
wanted = EncryptionLevel(level)
|
|
551
|
+
if (
|
|
552
|
+
wanted is EncryptionLevel.APPLICATION
|
|
553
|
+
and wanted not in self._keys
|
|
554
|
+
and EncryptionLevel.EARLY_DATA in self._keys
|
|
555
|
+
):
|
|
556
|
+
wanted = EncryptionLevel.EARLY_DATA
|
|
557
|
+
probe_level = wanted if wanted in self._keys else next(iter(self._keys))
|
|
558
|
+
self._resend_unacknowledged(probe_level)
|
|
559
|
+
self._probe_levels.extend((probe_level, probe_level))
|
|
560
|
+
|
|
561
|
+
def _resend_unacknowledged(self, level: EncryptionLevel) -> None:
|
|
562
|
+
# RFC 9002 section 6.2.4: a probe carries unacknowledged data again; a bare PING tells a server that lost the first flight nothing
|
|
563
|
+
space = self._recovery.space(self._space_of(level))
|
|
564
|
+
unacknowledged = [
|
|
565
|
+
packet for _, packet in sorted(space.sent.items()) if packet.ack_eliciting
|
|
566
|
+
]
|
|
567
|
+
# the handshake flights are small and go out whole; afterwards the oldest packet alone is enough
|
|
568
|
+
for packet in (
|
|
569
|
+
unacknowledged
|
|
570
|
+
if level is not EncryptionLevel.APPLICATION
|
|
571
|
+
else unacknowledged[:1]
|
|
572
|
+
):
|
|
573
|
+
del space.sent[packet.number]
|
|
574
|
+
if packet.in_flight:
|
|
575
|
+
self._recovery.bytes_in_flight -= packet.size
|
|
576
|
+
self._on_lost(self._space_of(level), packet)
|
|
577
|
+
|
|
578
|
+
def _receive_packet(
|
|
579
|
+
self,
|
|
580
|
+
header: Header,
|
|
581
|
+
data: bytes,
|
|
582
|
+
offset: int,
|
|
583
|
+
level: EncryptionLevel,
|
|
584
|
+
keys: PacketProtection,
|
|
585
|
+
now: float,
|
|
586
|
+
) -> None:
|
|
587
|
+
number_offset = header.number_offset
|
|
588
|
+
if header.end - number_offset < 4 + SAMPLE_LENGTH:
|
|
589
|
+
return
|
|
590
|
+
sample = data[number_offset + 4 : number_offset + 4 + SAMPLE_LENGTH]
|
|
591
|
+
mask = keys.mask(sample)
|
|
592
|
+
first = data[offset] ^ (mask[0] & (0x0F if data[offset] & 0x80 else 0x1F))
|
|
593
|
+
length = (first & 0x03) + 1
|
|
594
|
+
number_bytes = bytes(
|
|
595
|
+
byte ^ mask[1 + index]
|
|
596
|
+
for index, byte in enumerate(data[number_offset : number_offset + length])
|
|
597
|
+
)
|
|
598
|
+
space = self._recovery.space(level)
|
|
599
|
+
number = space.decode_number(int.from_bytes(number_bytes, "big"), length * 8)
|
|
600
|
+
protected = bytes((first,)) + data[offset + 1 : number_offset] + number_bytes
|
|
601
|
+
ciphertext = data[number_offset + length : header.end]
|
|
602
|
+
if (
|
|
603
|
+
level is EncryptionLevel.APPLICATION
|
|
604
|
+
and bool(first & 0x04) != self._key_phase
|
|
605
|
+
):
|
|
606
|
+
candidate = self._next_keys or keys.update()
|
|
607
|
+
try:
|
|
608
|
+
payload = candidate.open(number, protected, ciphertext)
|
|
609
|
+
except ValueError:
|
|
610
|
+
return
|
|
611
|
+
self._next_keys = None
|
|
612
|
+
self._key_phase = not self._key_phase
|
|
613
|
+
send = self._keys[level][1]
|
|
614
|
+
self._keys[level] = (candidate, send.update() if send is not None else None)
|
|
615
|
+
else:
|
|
616
|
+
try:
|
|
617
|
+
payload = keys.open(number, protected, ciphertext)
|
|
618
|
+
except ValueError:
|
|
619
|
+
return
|
|
620
|
+
if (
|
|
621
|
+
level is EncryptionLevel.INITIAL
|
|
622
|
+
and not self._handshake_done
|
|
623
|
+
and self._destination_id in (self._original_destination, self._retry_source)
|
|
624
|
+
):
|
|
625
|
+
self._destination_id = header.source
|
|
626
|
+
self._process_payload(level, number, payload, now)
|
|
627
|
+
|
|
628
|
+
def _process_payload(
|
|
629
|
+
self, level: EncryptionLevel, number: int, payload: bytes, now: float
|
|
630
|
+
) -> None:
|
|
631
|
+
reader = Reader(payload)
|
|
632
|
+
ack_eliciting = False
|
|
633
|
+
while reader.remaining:
|
|
634
|
+
frame = reader.frame()
|
|
635
|
+
if not isinstance(frame, ACK_ELICITING_EXCLUSIONS):
|
|
636
|
+
ack_eliciting = True
|
|
637
|
+
match frame:
|
|
638
|
+
case Ack():
|
|
639
|
+
self._on_ack(level, frame, now)
|
|
640
|
+
case Crypto(offset, data):
|
|
641
|
+
receive = self._crypto_receive[level]
|
|
642
|
+
_ = receive.on_frame(Stream(CRYPTO_STREAM, offset, data, False))
|
|
643
|
+
if receive.readable:
|
|
644
|
+
self._handshake.provide(level, receive.read())
|
|
645
|
+
if self._handshake_done:
|
|
646
|
+
# NewSessionTicket and the other post-handshake messages; this is what fills the session cache
|
|
647
|
+
self._handshake.process_post_handshake()
|
|
648
|
+
else:
|
|
649
|
+
self._drive_handshake()
|
|
650
|
+
case Stream():
|
|
651
|
+
self._on_stream(frame)
|
|
652
|
+
case MaxData(maximum):
|
|
653
|
+
self._peer_max_data = max(self._peer_max_data, maximum)
|
|
654
|
+
case MaxStreamData(stream_id, maximum):
|
|
655
|
+
stream = self._send_streams.get(stream_id)
|
|
656
|
+
if stream is not None:
|
|
657
|
+
stream.max_data = max(stream.max_data, maximum)
|
|
658
|
+
case MaxStreams(bidirectional, maximum):
|
|
659
|
+
self._peer_max_streams[bidirectional] = max(
|
|
660
|
+
self._peer_max_streams[bidirectional], maximum
|
|
661
|
+
)
|
|
662
|
+
case NewConnectionId(
|
|
663
|
+
sequence, retire_prior_to, connection_id, reset_token
|
|
664
|
+
):
|
|
665
|
+
self._peer_connection_ids[sequence] = (connection_id, reset_token)
|
|
666
|
+
for old in [
|
|
667
|
+
key
|
|
668
|
+
for key in self._peer_connection_ids
|
|
669
|
+
if key < retire_prior_to
|
|
670
|
+
]:
|
|
671
|
+
del self._peer_connection_ids[old]
|
|
672
|
+
self._pending[EncryptionLevel.APPLICATION].append(
|
|
673
|
+
RetireConnectionId(old)
|
|
674
|
+
)
|
|
675
|
+
case ResetStream(stream_id, error, _):
|
|
676
|
+
stream = self._receive_streams.pop(stream_id, None)
|
|
677
|
+
if stream is not None:
|
|
678
|
+
self._events.append(StreamReset(stream_id, error))
|
|
679
|
+
case StopSending(stream_id, error):
|
|
680
|
+
self.reset_stream(stream_id, error)
|
|
681
|
+
self._events.append(StopSendingReceived(stream_id, error))
|
|
682
|
+
case NewToken(token):
|
|
683
|
+
self.new_token = token
|
|
684
|
+
case PathChallenge(challenge):
|
|
685
|
+
self._pending[EncryptionLevel.APPLICATION].append(
|
|
686
|
+
PathResponse(challenge)
|
|
687
|
+
)
|
|
688
|
+
case ConnectionClose(error, _, reason, application):
|
|
689
|
+
self._terminated = True
|
|
690
|
+
self._events.append(
|
|
691
|
+
ConnectionTerminated(
|
|
692
|
+
error, reason.decode("utf-8", "replace"), application
|
|
693
|
+
)
|
|
694
|
+
)
|
|
695
|
+
return
|
|
696
|
+
case HandshakeDone():
|
|
697
|
+
self._confirm_handshake()
|
|
698
|
+
case Datagram(data):
|
|
699
|
+
self._events.append(DatagramReceived(data))
|
|
700
|
+
case (
|
|
701
|
+
Padding()
|
|
702
|
+
| Ping()
|
|
703
|
+
| PathResponse()
|
|
704
|
+
| DataBlocked()
|
|
705
|
+
| StreamDataBlocked()
|
|
706
|
+
| StreamsBlocked()
|
|
707
|
+
| RetireConnectionId()
|
|
708
|
+
):
|
|
709
|
+
pass
|
|
710
|
+
space = self._recovery.space(level)
|
|
711
|
+
in_order = space.record_received(number, now, ack_eliciting)
|
|
712
|
+
if ack_eliciting:
|
|
713
|
+
# RFC 9000 section 13.2.1: an ACK after every second ack-eliciting packet, at once when one arrives out of order
|
|
714
|
+
if (
|
|
715
|
+
level is not EncryptionLevel.APPLICATION
|
|
716
|
+
or not in_order
|
|
717
|
+
or space.ack_eliciting_received >= ACK_THRESHOLD
|
|
718
|
+
):
|
|
719
|
+
space.ack_pending = True
|
|
720
|
+
self._ack_timer = None
|
|
721
|
+
elif self._ack_timer is None:
|
|
722
|
+
self._ack_timer = now + self._parameters.max_ack_delay / 1000
|
|
723
|
+
|
|
724
|
+
def _on_ack(self, level: EncryptionLevel, ack: Ack, now: float) -> None:
|
|
725
|
+
exponent = self._peer_parameter(Parameter.ACK_DELAY_EXPONENT, 3)
|
|
726
|
+
acked, lost = self._recovery.on_ack_received(level, ack, now, exponent)
|
|
727
|
+
space = self._recovery.space(level)
|
|
728
|
+
for packet in acked:
|
|
729
|
+
for frame in packet.frames:
|
|
730
|
+
if isinstance(frame, Ack):
|
|
731
|
+
space.forget(frame.largest)
|
|
732
|
+
elif isinstance(frame, Stream):
|
|
733
|
+
self._stream_acked(frame.stream_id)
|
|
734
|
+
elif isinstance(frame, ResetStream):
|
|
735
|
+
_ = self._send_streams.pop(frame.stream_id, None)
|
|
736
|
+
for packet in lost:
|
|
737
|
+
self._on_lost(level, packet)
|
|
738
|
+
# RFC 9001 section 4.1.2: an acknowledgement of a 1-RTT packet confirms the handshake; one of a 0-RTT packet does not
|
|
739
|
+
if (
|
|
740
|
+
level is EncryptionLevel.APPLICATION
|
|
741
|
+
and self._handshake_done
|
|
742
|
+
and not self._handshake_confirmed
|
|
743
|
+
and any(not packet.early for packet in acked)
|
|
744
|
+
):
|
|
745
|
+
self._confirm_handshake()
|
|
746
|
+
|
|
747
|
+
def _stream_acked(self, stream_id: int) -> None:
|
|
748
|
+
stream = self._send_streams.get(stream_id)
|
|
749
|
+
if stream is None:
|
|
750
|
+
return
|
|
751
|
+
stream.unacked -= 1
|
|
752
|
+
if stream.fin_sent and not stream.pending and stream.unacked == 0:
|
|
753
|
+
del self._send_streams[stream_id]
|
|
754
|
+
|
|
755
|
+
def _on_lost(self, level: EncryptionLevel, packet: SentPacket) -> None:
|
|
756
|
+
for frame in packet.frames:
|
|
757
|
+
match frame:
|
|
758
|
+
case Crypto(offset, data):
|
|
759
|
+
self._crypto_send[level].requeue(
|
|
760
|
+
Stream(CRYPTO_STREAM, offset, data, False)
|
|
761
|
+
)
|
|
762
|
+
case Stream():
|
|
763
|
+
stream = self._send_streams.get(frame.stream_id)
|
|
764
|
+
if stream is not None and not stream.reset_sent:
|
|
765
|
+
stream.requeue(frame)
|
|
766
|
+
case Ack() | Ping() | Padding() | PathResponse() | Datagram():
|
|
767
|
+
pass
|
|
768
|
+
case _:
|
|
769
|
+
self._pending[level].append(frame)
|
|
770
|
+
|
|
771
|
+
def _on_stream(self, frame: Stream) -> None:
|
|
772
|
+
stream_id = frame.stream_id
|
|
773
|
+
client_initiated = stream_id % 2 == 0
|
|
774
|
+
bidirectional = stream_id % 4 < 2
|
|
775
|
+
if client_initiated and not bidirectional:
|
|
776
|
+
raise ProtocolError(
|
|
777
|
+
f"the server sent data on our unidirectional stream {stream_id}"
|
|
778
|
+
)
|
|
779
|
+
receive = self._receive_streams.get(stream_id)
|
|
780
|
+
if receive is None:
|
|
781
|
+
if client_initiated:
|
|
782
|
+
return
|
|
783
|
+
if stream_id // 4 >= self._local_max_streams[bidirectional]:
|
|
784
|
+
raise ProtocolError("the server opened more streams than allowed")
|
|
785
|
+
limit = (
|
|
786
|
+
self._parameters.initial_max_stream_data_bidi_remote
|
|
787
|
+
if bidirectional
|
|
788
|
+
else self._parameters.initial_max_stream_data_uni
|
|
789
|
+
)
|
|
790
|
+
receive = self._receive_streams[stream_id] = ReceiveStream(stream_id, limit)
|
|
791
|
+
self._peer_opened[bidirectional] = max(
|
|
792
|
+
self._peer_opened[bidirectional], stream_id // 4 + 1
|
|
793
|
+
)
|
|
794
|
+
if bidirectional:
|
|
795
|
+
self._send_streams[stream_id] = SendStream(
|
|
796
|
+
stream_id,
|
|
797
|
+
self._peer_parameter(
|
|
798
|
+
Parameter.INITIAL_MAX_STREAM_DATA_BIDI_LOCAL, 0
|
|
799
|
+
),
|
|
800
|
+
)
|
|
801
|
+
if (
|
|
802
|
+
self._peer_opened[bidirectional] * 2
|
|
803
|
+
> self._local_max_streams[bidirectional]
|
|
804
|
+
):
|
|
805
|
+
self._local_max_streams[bidirectional] += (
|
|
806
|
+
self._parameters.initial_max_streams_bidi
|
|
807
|
+
if bidirectional
|
|
808
|
+
else self._parameters.initial_max_streams_uni
|
|
809
|
+
)
|
|
810
|
+
self._pending[EncryptionLevel.APPLICATION].append(
|
|
811
|
+
MaxStreams(bidirectional, self._local_max_streams[bidirectional])
|
|
812
|
+
)
|
|
813
|
+
before = receive.next_offset
|
|
814
|
+
after = receive.on_frame(frame)
|
|
815
|
+
self._data_received += max(after - before, 0)
|
|
816
|
+
if receive.readable:
|
|
817
|
+
data = receive.read()
|
|
818
|
+
end = (
|
|
819
|
+
receive.final_size is not None
|
|
820
|
+
and receive.next_offset == receive.final_size
|
|
821
|
+
)
|
|
822
|
+
if data or end:
|
|
823
|
+
self._events.append(StreamDataReceived(stream_id, data, end))
|
|
824
|
+
window = (
|
|
825
|
+
self._parameters.initial_max_stream_data_bidi_local
|
|
826
|
+
if bidirectional
|
|
827
|
+
else self._parameters.initial_max_stream_data_uni
|
|
828
|
+
)
|
|
829
|
+
if receive.max_data - receive.consumed < window // 2 and not end:
|
|
830
|
+
receive.max_data = receive.consumed + window
|
|
831
|
+
self._pending[EncryptionLevel.APPLICATION].append(
|
|
832
|
+
MaxStreamData(stream_id, receive.max_data)
|
|
833
|
+
)
|
|
834
|
+
if end:
|
|
835
|
+
del self._receive_streams[stream_id]
|
|
836
|
+
if (
|
|
837
|
+
self._data_received
|
|
838
|
+
> self._local_max_data - self._parameters.initial_max_data // 2
|
|
839
|
+
):
|
|
840
|
+
self._local_max_data = (
|
|
841
|
+
self._data_received + self._parameters.initial_max_data
|
|
842
|
+
)
|
|
843
|
+
self._pending[EncryptionLevel.APPLICATION].append(
|
|
844
|
+
MaxData(self._local_max_data)
|
|
845
|
+
)
|
|
846
|
+
|
|
847
|
+
def _drive_handshake(self) -> None:
|
|
848
|
+
try:
|
|
849
|
+
done = self._handshake.advance()
|
|
850
|
+
except TLSError as error:
|
|
851
|
+
# the peer gets the TLS alert in a CONNECTION_CLOSE and the caller gets the reason when it goes out
|
|
852
|
+
alert = (
|
|
853
|
+
self._handshake.alert[1]
|
|
854
|
+
if self._handshake.alert is not None
|
|
855
|
+
else INTERNAL_ERROR_ALERT
|
|
856
|
+
)
|
|
857
|
+
self._closing = ConnectionClose(
|
|
858
|
+
TransportError.CRYPTO_ERROR + alert,
|
|
859
|
+
None,
|
|
860
|
+
f"TLS handshake failed: {error}".encode(),
|
|
861
|
+
False,
|
|
862
|
+
)
|
|
863
|
+
return
|
|
864
|
+
for level, is_write, suite, secret in self._handshake.take_secrets():
|
|
865
|
+
protection = PacketProtection(suite, secret, self._version)
|
|
866
|
+
receive, send = self._keys.get(level, (None, None))
|
|
867
|
+
self._keys[level] = (
|
|
868
|
+
(receive, protection) if is_write else (protection, send)
|
|
869
|
+
)
|
|
870
|
+
if level is EncryptionLevel.EARLY_DATA:
|
|
871
|
+
self._events.append(EarlyDataAvailable())
|
|
872
|
+
for level in EncryptionLevel:
|
|
873
|
+
data = self._handshake.take_outgoing(level)
|
|
874
|
+
if data:
|
|
875
|
+
self._crypto_send[level].write(data)
|
|
876
|
+
if level is EncryptionLevel.INITIAL:
|
|
877
|
+
self._initial_hello += data
|
|
878
|
+
if self._handshake.alert is not None:
|
|
879
|
+
level, alert = self._handshake.alert
|
|
880
|
+
self._closing = ConnectionClose(
|
|
881
|
+
TransportError.CRYPTO_ERROR + alert, None, b"TLS alert", False
|
|
882
|
+
)
|
|
883
|
+
if done and not self._handshake_done:
|
|
884
|
+
self._handshake_done = True
|
|
885
|
+
peer = PeerParameters(self._handshake.peer_transport_parameters)
|
|
886
|
+
if (
|
|
887
|
+
peer.connection_id(Parameter.ORIGINAL_DESTINATION_CONNECTION_ID)
|
|
888
|
+
!= self._original_destination
|
|
889
|
+
):
|
|
890
|
+
raise ProtocolError(
|
|
891
|
+
"the server's original_destination_connection_id does not match"
|
|
892
|
+
)
|
|
893
|
+
if (
|
|
894
|
+
peer.connection_id(Parameter.INITIAL_SOURCE_CONNECTION_ID)
|
|
895
|
+
!= self._destination_id
|
|
896
|
+
):
|
|
897
|
+
raise ProtocolError(
|
|
898
|
+
"the server's initial_source_connection_id does not match"
|
|
899
|
+
)
|
|
900
|
+
if (
|
|
901
|
+
self._retry_source is not None
|
|
902
|
+
and peer.connection_id(Parameter.RETRY_SOURCE_CONNECTION_ID)
|
|
903
|
+
!= self._retry_source
|
|
904
|
+
):
|
|
905
|
+
raise ProtocolError(
|
|
906
|
+
"the server's retry_source_connection_id does not match"
|
|
907
|
+
)
|
|
908
|
+
if self._handshake.early_data_accepted and self._peer is not None:
|
|
909
|
+
# RFC 9000 section 7.4.1: accepted 0-RTT data keeps the limits it was sent under
|
|
910
|
+
for parameter in EARLY_DATA_LIMITS:
|
|
911
|
+
if peer.integer(parameter, 0) < self._peer.integer(parameter, 0):
|
|
912
|
+
raise ProtocolError(
|
|
913
|
+
f"the server lowered {parameter.name} below the value it granted for early data"
|
|
914
|
+
)
|
|
915
|
+
self._apply_peer_parameters(peer)
|
|
916
|
+
self.alpn = self._handshake.buffer.negotiated_protocol()
|
|
917
|
+
if self._handshake.early_data_rejected:
|
|
918
|
+
self._reject_early_data()
|
|
919
|
+
if self.early_data:
|
|
920
|
+
self._discard(EncryptionLevel.EARLY_DATA)
|
|
921
|
+
self._events.append(HandshakeCompleted())
|
|
922
|
+
|
|
923
|
+
def _apply_peer_parameters(self, peer: PeerParameters) -> None:
|
|
924
|
+
self._peer = peer
|
|
925
|
+
self._peer_max_data = peer.integer(Parameter.INITIAL_MAX_DATA, 0)
|
|
926
|
+
self._peer_max_streams[True] = peer.integer(
|
|
927
|
+
Parameter.INITIAL_MAX_STREAMS_BIDI, 0
|
|
928
|
+
)
|
|
929
|
+
self._peer_max_streams[False] = peer.integer(
|
|
930
|
+
Parameter.INITIAL_MAX_STREAMS_UNI, 0
|
|
931
|
+
)
|
|
932
|
+
peer_idle = peer.integer(Parameter.MAX_IDLE_TIMEOUT, 0) / 1000
|
|
933
|
+
own_idle = self._parameters.max_idle_timeout / 1000
|
|
934
|
+
self._idle_timeout = min(own_idle, peer_idle) if peer_idle else own_idle
|
|
935
|
+
self._recovery.max_ack_delay = peer.integer(Parameter.MAX_ACK_DELAY, 25) / 1000
|
|
936
|
+
|
|
937
|
+
def _reject_early_data(self) -> None:
|
|
938
|
+
self._requeue_early_packets()
|
|
939
|
+
self._events.append(EarlyDataRejected())
|
|
940
|
+
|
|
941
|
+
def _requeue_early_packets(self) -> None:
|
|
942
|
+
# RFC 9001 section 4.6.2: everything that went out in 0-RTT packets is sent again under the 1-RTT keys
|
|
943
|
+
space = self._recovery.space(EncryptionLevel.APPLICATION)
|
|
944
|
+
for packet in [packet for packet in space.sent.values() if packet.early]:
|
|
945
|
+
del space.sent[packet.number]
|
|
946
|
+
if packet.in_flight:
|
|
947
|
+
self._recovery.bytes_in_flight -= packet.size
|
|
948
|
+
self._on_lost(EncryptionLevel.APPLICATION, packet)
|
|
949
|
+
|
|
950
|
+
def _confirm_handshake(self) -> None:
|
|
951
|
+
if self._handshake_confirmed:
|
|
952
|
+
return
|
|
953
|
+
self._handshake_confirmed = True
|
|
954
|
+
self._recovery.handshake_confirmed = True
|
|
955
|
+
self._discard(EncryptionLevel.HANDSHAKE)
|
|
956
|
+
|
|
957
|
+
def _discard(self, level: EncryptionLevel) -> None:
|
|
958
|
+
_ = self._keys.pop(level, None)
|
|
959
|
+
self._recovery.discard_space(level)
|
|
960
|
+
self._crypto_send[level].pending.clear()
|
|
961
|
+
self._pending[level].clear()
|
|
962
|
+
|
|
963
|
+
def _peer_parameter(self, parameter: Parameter, default: int) -> int:
|
|
964
|
+
return default if self._peer is None else self._peer.integer(parameter, default)
|
|
965
|
+
|
|
966
|
+
def _on_version_negotiation(self, header: Header) -> None:
|
|
967
|
+
if self._version in header.supported_versions or self._handshake_done:
|
|
968
|
+
return
|
|
969
|
+
for candidate in VERSIONS:
|
|
970
|
+
if candidate in header.supported_versions and candidate != self._version:
|
|
971
|
+
self._terminated = True
|
|
972
|
+
self._events.append(VersionNegotiated(candidate))
|
|
973
|
+
return
|
|
974
|
+
self._terminated = True
|
|
975
|
+
self._events.append(
|
|
976
|
+
ConnectionTerminated(
|
|
977
|
+
TransportError.NO_ERROR, "no common QUIC version", False
|
|
978
|
+
)
|
|
979
|
+
)
|
|
980
|
+
|
|
981
|
+
def _on_retry(self, header: Header, packet: bytes) -> None:
|
|
982
|
+
if (
|
|
983
|
+
self._retry_source is not None
|
|
984
|
+
or self._handshake_done
|
|
985
|
+
or header.destination != self._source_id
|
|
986
|
+
):
|
|
987
|
+
return
|
|
988
|
+
if (
|
|
989
|
+
PacketProtection.retry_tag(
|
|
990
|
+
self._version, self._destination_id, packet[:-TAG_LENGTH]
|
|
991
|
+
)
|
|
992
|
+
!= header.retry_tag
|
|
993
|
+
):
|
|
994
|
+
return
|
|
995
|
+
self._retry_source = header.source
|
|
996
|
+
self._token = header.retry_token
|
|
997
|
+
self._destination_id = header.source
|
|
998
|
+
send, receive = PacketProtection.initial(self._version, self._destination_id)
|
|
999
|
+
self._keys[EncryptionLevel.INITIAL] = (receive, send)
|
|
1000
|
+
self._recovery.discard_space(EncryptionLevel.INITIAL)
|
|
1001
|
+
self._crypto_send[EncryptionLevel.INITIAL] = SendStream(CRYPTO_STREAM, 1 << 62)
|
|
1002
|
+
self._crypto_send[EncryptionLevel.INITIAL].write(self._initial_hello)
|
|
1003
|
+
# the server dropped the 0-RTT packets along with the Initial; they go again with the new connection id
|
|
1004
|
+
self._requeue_early_packets()
|
|
1005
|
+
|
|
1006
|
+
def _build_datagram(self, now: float) -> bytes | None:
|
|
1007
|
+
packets: list[tuple[EncryptionLevel, bytes, SentPacket]] = []
|
|
1008
|
+
remaining = DATAGRAM_SIZE
|
|
1009
|
+
for level in (
|
|
1010
|
+
EncryptionLevel.INITIAL,
|
|
1011
|
+
EncryptionLevel.EARLY_DATA,
|
|
1012
|
+
EncryptionLevel.HANDSHAKE,
|
|
1013
|
+
EncryptionLevel.APPLICATION,
|
|
1014
|
+
):
|
|
1015
|
+
keys = self._keys.get(level)
|
|
1016
|
+
if keys is None or keys[1] is None:
|
|
1017
|
+
continue
|
|
1018
|
+
built = self._build_packet(level, keys[1], remaining, now, pad=False)
|
|
1019
|
+
if built is not None:
|
|
1020
|
+
packets.append((level, built[0], built[1]))
|
|
1021
|
+
remaining -= len(built[0])
|
|
1022
|
+
if not packets:
|
|
1023
|
+
return None
|
|
1024
|
+
if packets[0][0] is EncryptionLevel.INITIAL and remaining > 0:
|
|
1025
|
+
level, _, record = packets[0]
|
|
1026
|
+
keys = self._keys[level]
|
|
1027
|
+
if keys[1] is not None:
|
|
1028
|
+
self._recovery.space(level).next_number -= 1
|
|
1029
|
+
rebuilt = self._build_packet(
|
|
1030
|
+
level,
|
|
1031
|
+
keys[1],
|
|
1032
|
+
DATAGRAM_SIZE - sum(len(packet) for _, packet, _ in packets[1:]),
|
|
1033
|
+
now,
|
|
1034
|
+
pad=True,
|
|
1035
|
+
frames=record.frames,
|
|
1036
|
+
)
|
|
1037
|
+
if rebuilt is not None:
|
|
1038
|
+
packets[0] = (level, rebuilt[0], rebuilt[1])
|
|
1039
|
+
for level, _, record in packets:
|
|
1040
|
+
self._recovery.on_packet_sent(self._space_of(level), record)
|
|
1041
|
+
if (
|
|
1042
|
+
level is EncryptionLevel.HANDSHAKE
|
|
1043
|
+
and EncryptionLevel.INITIAL in self._keys
|
|
1044
|
+
):
|
|
1045
|
+
self._discard(EncryptionLevel.INITIAL)
|
|
1046
|
+
return b"".join(packet for _, packet, _ in packets)
|
|
1047
|
+
|
|
1048
|
+
@staticmethod
|
|
1049
|
+
def _space_of(level: EncryptionLevel) -> EncryptionLevel:
|
|
1050
|
+
# 0-RTT and 1-RTT packets share one packet number space
|
|
1051
|
+
return (
|
|
1052
|
+
EncryptionLevel.APPLICATION
|
|
1053
|
+
if level is EncryptionLevel.EARLY_DATA
|
|
1054
|
+
else level
|
|
1055
|
+
)
|
|
1056
|
+
|
|
1057
|
+
def _build_packet(
|
|
1058
|
+
self,
|
|
1059
|
+
level: EncryptionLevel,
|
|
1060
|
+
keys: PacketProtection,
|
|
1061
|
+
space_left: int,
|
|
1062
|
+
now: float,
|
|
1063
|
+
*,
|
|
1064
|
+
pad: bool,
|
|
1065
|
+
frames: tuple[Frame, ...] | None = None,
|
|
1066
|
+
) -> tuple[bytes, SentPacket] | None:
|
|
1067
|
+
space = self._recovery.space(self._space_of(level))
|
|
1068
|
+
number = space.next_number
|
|
1069
|
+
number_bytes = space.encode_number(number)
|
|
1070
|
+
overhead = self._header_length(level, len(number_bytes)) + TAG_LENGTH
|
|
1071
|
+
if space_left <= overhead + 4:
|
|
1072
|
+
return None
|
|
1073
|
+
capacity = space_left - overhead
|
|
1074
|
+
collected = (
|
|
1075
|
+
list(frames)
|
|
1076
|
+
if frames is not None
|
|
1077
|
+
else self._collect_frames(level, capacity, now)
|
|
1078
|
+
)
|
|
1079
|
+
if not collected:
|
|
1080
|
+
return None
|
|
1081
|
+
writer = Writer()
|
|
1082
|
+
for frame in collected:
|
|
1083
|
+
writer.frame(frame)
|
|
1084
|
+
if pad and len(writer) < capacity:
|
|
1085
|
+
collected.append(Padding(capacity - len(writer)))
|
|
1086
|
+
writer.frame(collected[-1])
|
|
1087
|
+
if len(writer) + len(number_bytes) < 4:
|
|
1088
|
+
padding = Padding(4 - len(writer) - len(number_bytes))
|
|
1089
|
+
collected.append(padding)
|
|
1090
|
+
writer.frame(padding)
|
|
1091
|
+
payload = writer.finish()
|
|
1092
|
+
header = self._header(level, number_bytes, len(payload) + TAG_LENGTH)
|
|
1093
|
+
sealed = keys.seal(number, header, payload)
|
|
1094
|
+
mask = keys.mask(
|
|
1095
|
+
sealed[4 - len(number_bytes) : 4 - len(number_bytes) + SAMPLE_LENGTH]
|
|
1096
|
+
)
|
|
1097
|
+
protected = bytearray(header)
|
|
1098
|
+
protected[0] ^= mask[0] & (0x0F if header[0] & 0x80 else 0x1F)
|
|
1099
|
+
for index in range(len(number_bytes)):
|
|
1100
|
+
protected[len(header) - len(number_bytes) + index] ^= mask[1 + index]
|
|
1101
|
+
packet = bytes(protected) + sealed
|
|
1102
|
+
space.next_number += 1
|
|
1103
|
+
ack_eliciting = any(
|
|
1104
|
+
not isinstance(frame, ACK_ELICITING_EXCLUSIONS) for frame in collected
|
|
1105
|
+
)
|
|
1106
|
+
in_flight = ack_eliciting or any(
|
|
1107
|
+
isinstance(frame, Padding) for frame in collected
|
|
1108
|
+
)
|
|
1109
|
+
return packet, SentPacket(
|
|
1110
|
+
number,
|
|
1111
|
+
now,
|
|
1112
|
+
ack_eliciting,
|
|
1113
|
+
in_flight,
|
|
1114
|
+
len(packet),
|
|
1115
|
+
tuple(collected),
|
|
1116
|
+
level is EncryptionLevel.EARLY_DATA,
|
|
1117
|
+
)
|
|
1118
|
+
|
|
1119
|
+
def _collect_frames(
|
|
1120
|
+
self, level: EncryptionLevel, capacity: int, now: float
|
|
1121
|
+
) -> list[Frame]:
|
|
1122
|
+
frames: list[Frame] = []
|
|
1123
|
+
used = 0
|
|
1124
|
+
early = level is EncryptionLevel.EARLY_DATA
|
|
1125
|
+
space = self._recovery.space(self._space_of(level))
|
|
1126
|
+
exponent = self._parameters.ack_delay_exponent
|
|
1127
|
+
# RFC 9000 section 12.5: a 0-RTT packet never carries an ACK frame
|
|
1128
|
+
if not early and (
|
|
1129
|
+
space.ack_pending
|
|
1130
|
+
or (
|
|
1131
|
+
space.received
|
|
1132
|
+
and level is not EncryptionLevel.APPLICATION
|
|
1133
|
+
and space.ack_eliciting_received
|
|
1134
|
+
)
|
|
1135
|
+
):
|
|
1136
|
+
ack = space.ack_frame(now, exponent)
|
|
1137
|
+
if ack is not None:
|
|
1138
|
+
frames.append(ack)
|
|
1139
|
+
used += self._frame_length(ack)
|
|
1140
|
+
space.ack_pending = False
|
|
1141
|
+
space.ack_eliciting_received = 0
|
|
1142
|
+
if self._probe_levels and self._probe_levels[0] is level:
|
|
1143
|
+
_ = self._probe_levels.popleft()
|
|
1144
|
+
frames.append(Ping())
|
|
1145
|
+
used += 1
|
|
1146
|
+
pending = self._pending[self._space_of(level)]
|
|
1147
|
+
for frame in list(pending):
|
|
1148
|
+
length = self._frame_length(frame)
|
|
1149
|
+
if used + length > capacity:
|
|
1150
|
+
break
|
|
1151
|
+
frames.append(frame)
|
|
1152
|
+
pending.remove(frame)
|
|
1153
|
+
used += length
|
|
1154
|
+
crypto = self._crypto_send[level]
|
|
1155
|
+
while crypto.active and used + 8 < capacity:
|
|
1156
|
+
frame = crypto.next_frame(capacity - used - 8, 1 << 62)
|
|
1157
|
+
if frame is None:
|
|
1158
|
+
break
|
|
1159
|
+
crypto_frame = Crypto(frame.offset, frame.data)
|
|
1160
|
+
frames.append(crypto_frame)
|
|
1161
|
+
used += self._frame_length(crypto_frame)
|
|
1162
|
+
if early or (level is EncryptionLevel.APPLICATION and self._handshake_done):
|
|
1163
|
+
for stream in list(self._send_streams.values()):
|
|
1164
|
+
if not stream.active or stream.reset_sent:
|
|
1165
|
+
continue
|
|
1166
|
+
if not self._recovery.can_send(used + 32):
|
|
1167
|
+
break
|
|
1168
|
+
while stream.active and used + 12 < capacity:
|
|
1169
|
+
high = stream.sent_offset
|
|
1170
|
+
frame = stream.next_frame(
|
|
1171
|
+
capacity - used - 12, self._peer_max_data - self._data_sent
|
|
1172
|
+
)
|
|
1173
|
+
if frame is None:
|
|
1174
|
+
if stream.blocked:
|
|
1175
|
+
blocked = StreamDataBlocked(
|
|
1176
|
+
stream.stream_id, stream.max_data
|
|
1177
|
+
)
|
|
1178
|
+
frames.append(blocked)
|
|
1179
|
+
used += self._frame_length(blocked)
|
|
1180
|
+
stream.blocked = False
|
|
1181
|
+
break
|
|
1182
|
+
frames.append(frame)
|
|
1183
|
+
# a retransmission was charged to the connection window when it first went out
|
|
1184
|
+
self._data_sent += stream.sent_offset - high
|
|
1185
|
+
used += self._frame_length(frame)
|
|
1186
|
+
return frames
|
|
1187
|
+
|
|
1188
|
+
def _close_datagram(self, now: float) -> bytes:
|
|
1189
|
+
if self._closing is None:
|
|
1190
|
+
return b""
|
|
1191
|
+
for level in (
|
|
1192
|
+
EncryptionLevel.APPLICATION,
|
|
1193
|
+
EncryptionLevel.HANDSHAKE,
|
|
1194
|
+
EncryptionLevel.INITIAL,
|
|
1195
|
+
):
|
|
1196
|
+
keys = self._keys.get(level)
|
|
1197
|
+
if keys is None or keys[1] is None:
|
|
1198
|
+
continue
|
|
1199
|
+
close = (
|
|
1200
|
+
self._closing
|
|
1201
|
+
if level is EncryptionLevel.APPLICATION
|
|
1202
|
+
else ConnectionClose(
|
|
1203
|
+
TransportError.APPLICATION_ERROR
|
|
1204
|
+
if self._closing.application
|
|
1205
|
+
else self._closing.error,
|
|
1206
|
+
None,
|
|
1207
|
+
b"",
|
|
1208
|
+
False,
|
|
1209
|
+
)
|
|
1210
|
+
)
|
|
1211
|
+
built = self._build_packet(
|
|
1212
|
+
level,
|
|
1213
|
+
keys[1],
|
|
1214
|
+
DATAGRAM_SIZE,
|
|
1215
|
+
now,
|
|
1216
|
+
pad=level is EncryptionLevel.INITIAL,
|
|
1217
|
+
frames=(close,),
|
|
1218
|
+
)
|
|
1219
|
+
if built is not None:
|
|
1220
|
+
return built[0]
|
|
1221
|
+
return b""
|
|
1222
|
+
|
|
1223
|
+
def _header_length(self, level: EncryptionLevel, number_length: int) -> int:
|
|
1224
|
+
if level is EncryptionLevel.APPLICATION:
|
|
1225
|
+
return 1 + len(self._destination_id) + number_length
|
|
1226
|
+
token = (
|
|
1227
|
+
len(Writer.encode_varint(len(self._token))) + len(self._token)
|
|
1228
|
+
if level is EncryptionLevel.INITIAL
|
|
1229
|
+
else 0
|
|
1230
|
+
)
|
|
1231
|
+
return (
|
|
1232
|
+
7
|
|
1233
|
+
+ len(self._destination_id)
|
|
1234
|
+
+ len(self._source_id)
|
|
1235
|
+
+ token
|
|
1236
|
+
+ 2
|
|
1237
|
+
+ number_length
|
|
1238
|
+
)
|
|
1239
|
+
|
|
1240
|
+
def _header(
|
|
1241
|
+
self, level: EncryptionLevel, number_bytes: bytes, remaining_length: int
|
|
1242
|
+
) -> bytes:
|
|
1243
|
+
if level is EncryptionLevel.APPLICATION:
|
|
1244
|
+
return Header.build_short(
|
|
1245
|
+
self._destination_id, number_bytes, self._key_phase
|
|
1246
|
+
)
|
|
1247
|
+
return Header.build(
|
|
1248
|
+
PACKET_TYPE_OF[level],
|
|
1249
|
+
self._version,
|
|
1250
|
+
self._destination_id,
|
|
1251
|
+
self._source_id,
|
|
1252
|
+
self._token,
|
|
1253
|
+
number_bytes,
|
|
1254
|
+
remaining_length,
|
|
1255
|
+
)
|
|
1256
|
+
|
|
1257
|
+
@staticmethod
|
|
1258
|
+
def _frame_length(frame: Frame) -> int:
|
|
1259
|
+
writer = Writer()
|
|
1260
|
+
writer.frame(frame)
|
|
1261
|
+
return len(writer)
|