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.
@@ -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)