1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
|
QUIC Frame-in-Flight Management
===============================
The QUIC frame-in-flight manager is responsible for tracking frames which were
sent which need to be regenerated if the packets they were placed into are
designated as lost by the ACK manager. The ACK manager works on the level of
packets, whereas the QUIC frame-in-flight manager (FIFM) works on the level of
frames.
The FIFM comprises three components, collectively known as the FIFM:
- the Control Frame Queue (CFQ);
- the Transmitted Packet Information Manager (TXPIM); and
- the Frame-in-Flight Dispatcher (FIFD).
![](images/quic-fifm-overview.png "QUIC FIFM Overview")
These are introduced in turn below, but first we discuss the various QUIC frame
types to establish the need for each component.
Analysis of QUIC Frame Retransmission Requirements
--------------------------------------------------
### Frame Types
Standard QUIC uses the following frame types:
```plain
HANDSHAKE_DONE GCR / REGEN
MAX_DATA REGEN
DATA_BLOCKED REGEN
MAX_STREAMS REGEN
STREAMS_BLOCKED REGEN
NEW_CONNECTION_ID GCR
RETIRE_CONNECTION_ID GCR
PATH_CHALLENGE -
PATH_RESPONSE -
ACK - (non-ACK-eliciting)
CONNECTION_CLOSE special (non-ACK-eliciting)
NEW_TOKEN GCR
CRYPTO GCR or special
RESET_STREAM REGEN
STOP_SENDING REGEN
MAX_STREAM_DATA REGEN
STREAM_DATA_BLOCKED REGEN
STREAM special
PING -
PADDING - (non-ACK-eliciting)
```
The different frame types require various different ways of handling
retransmission in the event of loss:
- **GCR** (Generic Control Frame Retransmission): The raw bytes of
the encoded frame can simply be sent again. This retransmission system does
not need to understand the specific frame type. A simple queue can be used,
with each queue entry being an octet string representing an encoded frame.
This queue can also be used for initial transmission of **GCR** frames, not
just retransmissions.
- **REGEN** (Regenerate): These frames can be marked for dynamic regeneration
when a packet containing them is lost. This has the advantage of using
up-to-date data at the time of transmission, so is preferred over `GCR` when
possible.
- Special — `STREAM`, `CRYPTO`: `STREAM` frame handling is handled as a
special case by the QUIC Send Stream Manager. `CRYPTO` frame retransmission
can also be handled using a QUIC Send Stream manager. (`CRYPTO` frames could
also be handled via GCR, though suboptimally. We choose to use proper send
stream management, just as for application data streams.)
- Some frame types do not need to be retransmitted even if lost (`PING`,
`PADDING`, `PATH_CHALLENGE`, `PATH_RESPONSE`).
- Special — `CONNECTION_CLOSE`: This frame is a special case and is not
retransmitted per se.
### Requirements
The following requirements are identified:
- Need for a generic control queue which can store encoded control frames.
This control queue will handle both initial transmission and retransmission of
most control frames which do not have special requirements.
- The ability to determine, when the ACK Manager determines that a packet has
been acknowledged, lost or discarded:
- What stream IDs were sent in a packet, and the logical ranges of application
data bytes for each (which may not be one contiguous range).
This is needed so that the QUIC Send Stream Manager for a given stream
can be informed of lost or acked ranges in the stream.
- The logical ranges of the CRYPTO stream which were sent in the packet
(which may not be one contiguous range), for similar reasons.
- Which stream IDs had a FIN bit set in the packet.
This is needed so that the QUIC Send Stream Manager can be informed for a
given stream whether a FIN was lost or acked.
- What control frames using the **GCR** strategy were sent in the packet
so that they can be requeued (if lost) or released (if acked or discarded).
- For each type of frame using the **REGEN** strategy, a flag as to whether
that frame type was contained in the packet (so that the flag can be set
again if the packet was lost).
The Control Frame Queue (CFQ)
-----------------------------
![](images/quic-fifm-cfq.png "QUIC CFQ Overview")
The CFQ (`QUIC_CFQ`) stores encoded frames which can be blindly retransmitted in
the event that they are lost. It facilitates the GCR retransmission strategy.
One logical CFQ instance will be needed per PN space per connection. As an
optimisation, these three CFQ instances per connection are all modelled by a
single `QUIC_CFQ` instance.
Each frame in the CFQ is a simple opaque byte buffer, which has the following
metadata associated with it:
- An integral priority value, used to maintain priority ordering.
- The frame type, which is provided by the caller along with the buffer.
This can be determined from the encoded frame buffer, but this saves the
CFQ's users from needing to decode it. The CFQ itself does not use this
value.
- A state, which is either `NEW` or `TX`. Frames added to the CFQ have
the `NEW` state initially. When the frame is transmitted, it is transitioned
to the `TX` state. If the packet it was sent in is subsequently lost,
it is transitioned back to the `NEW` state.
Frames in the `NEW` state participate in a priority queue (the NEW queue)
according to their priority and the CFQ's NEW queue can be iterated in priority
order by callers.
When a packet containing a CFQ item is acknowledged, the CFQ is informed and the
CFQ item is released. A free callback provided when the buffer was added to the
CFQ is called, providing an opportunity to free or reuse the buffer. Buffers
provided to the CFQ as part of a CFQ item must remain allocated for the duration
of their membership of the CFQ. The CFQ maintains memory allocation of CFQ items
themselves internally.
### API
```c
/*
* QUIC Control Frame Queue Item
* =============================
*
* The CFQ item structure has a public and a private part. This structure
* documents the public part.
*/
typedef struct quic_cfq_item_st QUIC_CFQ_ITEM;
struct quic_cfq_item_st {
/*
* These fields are not used by the CFQ, but are a convenience to assist the
* TXPIM in keeping a list of GCR control frames which were sent in a
* packet. They may be used for any purpose.
*/
QUIC_CFQ_ITEM *pkt_prev, *pkt_next;
/* All other fields are private; use ossl_quic_cfq_item_* accessors. */
};
#define QUIC_CFQ_STATE_NEW 0
#define QUIC_CFQ_STATE_TX 1
/* Returns the frame type of a CFQ item. */
uint64_t ossl_quic_cfq_item_get_frame_type(QUIC_CFQ_ITEM *item);
/* Returns a pointer to the encoded buffer of a CFQ item. */
const unsigned char *ossl_quic_cfq_item_get_encoded(QUIC_CFQ_ITEM *item);
/* Returns the length of the encoded buffer in bytes. */
size_t ossl_quic_cfq_item_get_encoded_len(QUIC_CFQ_ITEM *item);
/* Returns the CFQ item state, a QUIC_CFQ_STATE_* value. */
int ossl_quic_cfg_item_get_state(QUIC_CFQ_ITEM *item);
/* Returns the PN space for the CFQ item. */
int ossl_quic_cfg_item_get_pn_space(QUIC_CFQ_ITEM *item);
/*
* QUIC Control Frame Queue
* ========================
*/
typedef struct quic_cfq_st QUIC_CFQ;
QUIC_CFQ *ossl_quic_cfq_new(void);
void ossl_quic_cfq_free(QUIC_CFQ *cfq);
/*
* Input Side
* ----------
*/
/*
* Enqueue a frame to the CFQ. encoded points to the opaque encoded frame.
*
* free_cb is called by the CFQ when the buffer is no longer needed;
* free_cb_arg is an opaque value passed to free_cb.
*
* priority determines the relative ordering of control frames in a packet.
* Higher numerical values for priority mean that a frame should come earlier in
* a packet. pn_space is a QUIC_PN_SPACE_* value.
*
* On success, returns a QUIC_CFQ_ITEM pointer which acts as a handle to
* the queued frame. On failure, returns NULL.
*
* The frame is initially in the TX state, so there is no need to call
* ossl_quic_cfq_mark_tx() immediately after calling this function.
*
* The frame type is duplicated as the frame_type argument here, even though it
* is also encoded into the buffer. This allows the caller to determine the
* frame type if desired without having to decode the frame.
*/
typedef void (cfq_free_cb)(unsigned char *buf, size_t buf_len, void *arg);
QUIC_CFQ_ITEM *ossl_quic_cfq_add_frame(QUIC_CFQ *cfq,
uint32_t priority,
uint32_t pn_space,
uint64_t frame_type,
const unsigned char *encoded,
size_t encoded_len,
cfq_free_cb *free_cb,
void *free_cb_arg);
/*
* Effects an immediate transition of the given CFQ item to the TX state.
*/
void ossl_quic_cfq_mark_tx(QUIC_CFQ *cfq, QUIC_CFQ_ITEM *item);
/*
* Effects an immediate transition of the given CFQ item to the NEW state,
* allowing the frame to be retransmitted. If priority is not UINT32_MAX,
* the priority is changed to the given value.
*/
void ossl_quic_cfq_mark_lost(QUIC_CFQ *cfq, QUIC_CFQ_ITEM *item,
uint32_t priority);
/*
* Releases a CFQ item. The item may be in either state (NEW or TX) prior to the
* call. The QUIC_CFQ_ITEM pointer must not be used following this call.
*/
void ossl_quic_cfq_release(QUIC_CFQ *cfq, QUIC_CFQ_ITEM *item);
/*
* Output Side
* -----------
*/
/*
* Gets the highest priority CFQ item in the given PN space awaiting
* transmission. If there are none, returns NULL.
*/
QUIC_CFQ_ITEM *ossl_quic_cfq_get_priority_head(QUIC_CFQ *cfq, uint32_t pn_space);
/*
* Given a CFQ item, gets the next CFQ item awaiting transmission in priority
* order in the given PN space. In other words, given the return value of
* ossl_quic_cfq_get_priority_head(), returns the next-lower priority item.
* Returns NULL if the given item is the last item in priority order.
*/
QUIC_CFQ_ITEM *ossl_quic_cfq_item_get_priority_next(QUIC_CFQ_ITEM *item,
uint32_t pn_space);
```
The Transmitted Packet Information Manager (TXPIM)
--------------------------------------------------
![](images/quic-fifm-txpim.png "QUIC TXPIM Overview")
The Transmitted Packet Information Manager (`QUIC_TXPIM`) is responsible for
allocating and keeping bookkeeping structures for packets which have been
transmitted, but not yet acknowledged, deemed lost or discarded. It is a
self-contained memory pool handing out `QUIC_TXPIM_PKT` structures. Each
`QUIC_TXPIM_PKT` is a self-contained data structure intended for consumption by
the FIFM.
The `QUIC_TXPIM_PKT` structure can be used for:
- Keeping track of all GCR control frames which were transmitted
in each packet, via a linked list of `QUIC_CFQ_ITEM`s.
- Keeping track of all REGEN-strategy control frame types, via a flag
for each frame type indicating whether the packet contained
such a frame.
- Keeping track of all stream IDs sent in a given packet, and
what ranges of the logical stream were sent, and whether
a FIN was sent.
- Keeping track of what logical ranges of the CRYPTO stream were sent.
In order to avoid unnecessary allocations, the FIFM also incorporates the ACK
Manager's `QUIC_ACKM_TX_PKT` structure into its per-packet bookkeeping
structure. The intention is for the `QUIC_TXPIM_PKT` to be the principal
allocation made per transmitted packet. The TX packetiser will obtain
a `QUIC_TXPIM_PKT` structure from the TXPIM, fill in the structure including
the ACK Manager data, and submit it via the FIFD which we introduce below.
The TXPIM does do not anything with the `QUIC_TXPIM_PKT` structure itself other
than managing its allocation and manipulation. Constructive use of the data kept
in the TXPIM is made by the FIFD.
### API
```c
/*
* QUIC Transmitted Packet Information Manager
* ===========================================
*/
typedef struct quic_txpim_st QUIC_TXPIM;
typedef struct quic_txpim_pkt_st {
/* ACKM-specific data. Caller should fill this. */
QUIC_ACKM_TX_PKT ackm_pkt;
/* Linked list of CFQ items in this packet. */
QUIC_CFQ_ITEM *retx_head;
/* Reserved for FIFD use. */
QUIC_FIFD *fifd;
/* Regenerate-strategy frames. */
unsigned int had_handshake_done : 1;
unsigned int had_max_data_frame : 1;
unsigned int had_max_streams_bidi_frame : 1;
unsigned int had_max_streams_uni_frame : 1;
unsigned int had_ack_frame : 1;
/* Private data follows. */
} QUIC_TXPIM_PKT;
/* Represents a range of bytes in an application or CRYPTO stream. */
typedef struct quic_txpim_chunk_st {
/* The stream ID, or UINT64_MAX for the CRYPTO stream. */
uint64_t stream_id;
/*
* The inclusive range of bytes in the stream. Exceptionally, if end <
* start, designates a frame of zero length (used for FIN-only frames).
*/
uint64_t start, end;
/*
* Whether a FIN was sent for this stream in the packet. Not valid for
* CRYPTO stream.
*/
unsigned int has_fin : 1;
} QUIC_TXPIM_CHUNK;
QUIC_TXPIM *ossl_quic_txpim_new(void);
void ossl_quic_txpim_free(QUIC_TXPIM *txpim);
/*
* Allocates a new QUIC_TXPIM_PKT structure from the pool. Returns NULL on
* failure. The returned structure is cleared of all data and is in a fresh
* initial state.
*/
QUIC_TXPIM_PKT *ossl_quic_txpim_pkt_alloc(QUIC_TXPIM *txpim);
/*
* Releases the TXPIM packet, returning it to the pool.
*/
void ossl_quic_txpim_pkt_release(QUIC_TXPIM *txpim, QUIC_TXPIM_PKT *fpkt);
/* Clears the chunk list of the packet, removing all entries. */
void ossl_quic_txpim_pkt_clear_chunks(QUIC_TXPIM_PKT *fpkt);
/* Appends a chunk to the packet. The structure is copied. */
int ossl_quic_txpim_pkt_append_chunk(QUIC_TXPIM_PKT *fpkt,
const QUIC_TXPIM_CHUNK *chunk);
/* Adds a CFQ item to the packet by prepending it to the retx_head list. */
void ossl_quic_txpim_pkt_add_cfq_item(QUIC_TXPIM_PKT *fpkt,
QUIC_CFQ_ITEM *item);
/*
* Returns a pointer to an array of stream chunk information structures for the
* given packet. The caller must call ossl_quic_txpim_pkt_get_num_chunks() to
* determine the length of this array.
*
* The chunks are sorted by (stream_id, start) in ascending order.
*/
const QUIC_TXPIM_CHUNK *ossl_quic_txpim_pkt_get_chunks(QUIC_TXPIM_PKT *fpkt);
/*
* Returns the number of entries in the array returned by
* ossl_quic_txpim_pkt_get_chunks().
*/
size_t ossl_quic_txpim_pkt_get_num_chunks(QUIC_TXPIM_PKT *fpkt);
/*
* Returns the number of QUIC_TXPIM_PKTs allocated by the given TXPIM that have
* yet to be returned to the TXPIM.
*/
size_t ossl_quic_txpim_get_in_use(QUIC_TXPIM *txpim);
```
The Frame-in-Flight Dispatcher (FIFD)
-------------------------------------
Finally, the CFQ, TXPIM and some interfaces to the ACKM are tied together via
the FIFD (`QUIC_FIFD`). The FIFD is completely stateless and provides reasonable
implementations for the on-loss, on-acked and on-discarded callbacks issued by
the ACK Manager.
The FIFD is used by obtaining a packet structure from the TXPIM, filling it in,
and then calling `ossl_quic_fifd_pkt_commit()`. The FIFD submits the packet to
the ACK Manager as a transmitted packet and provides its own callback
implementations to the ACK Manager for the packet. Note that the
`QUIC_TXPIM_PKT` is returned to the free pool once any of these callbacks occur;
once a packet's fate is known (acked, lost or discarded), use is immediately
made of the information in the `QUIC_TXPIM_PKT` and the `QUIC_TXPIM_PKT` is
immediately released. CFQ items may be freed (on ACK or discard) or transitioned
back to the NEW state (on loss).
The FIFD consumes various dependencies so that it can inform the appropriate
subsystems in the event of a packet being acked, lost or discarded. In
particular:
- It references a CFQ used to manage CFQ items;
- It references an ACK manager which it informs of transmitted packets;
- It references a TXPIM which manages each `QUIC_TXPIM_PKT`;
- It is provided with a callback to obtain a QUIC Send Stream based on a stream
ID. Thus the caller of the FIFD may implement whatever strategy it likes
to map stream IDs to QUIC Send Stream instances.
- It is provided with a callback which is called when it thinks a frame
should be regenerated using the REGEN strategy. Some of these are specific
to a given stream, in which case a stream ID is specified.
All of the state is in the dependencies referenced by the FIFD. The FIFD itself
simply glues all of these parts together.
### API
```c
typedef struct quic_fifd_st {
/* (internals) */
} QUIC_FIFD;
int ossl_quic_fifd_init(QUIC_FIFD *fifd,
QUIC_CFQ *cfq,
QUIC_ACKM *ackm,
QUIC_TXPIM *txpim,
/* stream_id is UINT64_MAX for the crypto stream */
OSSL_QSS *(*get_qss_by_id)(uint64_t stream_id,
void *arg),
void *get_qss_by_id_arg,
/* stream_id is UINT64_MAX if not applicable */
void (*regen_frame)(uint64_t frame_type,
uint64_t stream_id,
void *arg),
void *regen_frame_arg);
void ossl_quic_fifd_cleanup(QUIC_FIFD *fifd); /* (no-op) */
int ossl_quic_fifd_pkt_commit(QUIC_FIFD *fifd, QUIC_TXPIM_PKT *pkt);
```
Typical Intended TX Packetiser Usage
------------------------------------
- TX Packetiser maintains flags for each REGEN-strategy frame type.
It sets this flag when the regenerate callback is issued by the FIFD
and clears it when transmitting a packet containing such a frame.
- TX Packetiser obtains a `QUIC_TXPIM_PKT` structure by calling
`ossl_quic_txpim_pkt_alloc()`.
- TX Packetiser fills in the ACKM part of the `QUIC_TXPIM_PKT`
(`QUIC_ACKM_TX_PKT`), except for the callback fields, which are handled by the
FIFD.
- TX Packetiser queries the ACK Manager to determine if an ACK frame
is desired, and if so adds it to the packet.
- TX Packetiser queries the CFQ to determine what control frames it places
in a packet. It does this before adding STREAM or CRYPTO frames (i.e.,
all CFQ frames are considered of higher priority). For each such frame
it places in a packet, it:
- calls `ossl_quic_txpim_pkt_add_cfq_item()` on the TXPIM to log the CFQ item
as having been transmitted in the given packet, so that the CFQ item can be
released or requeued depending on the ultimate fate of the packet.
- For each STREAM or CRYPTO frame included in a packet, the TX Packetiser:
- informs the QUIC Send Stream instance for that stream that a range of bytes
has been transmitted;
- also informs the QUIC Send Stream instance if FIN was set on a STREAM frame.
- calls `ossl_quic_txpim_pkt_append_chunk()` to log a logical range of
the given application or crypto stream as having been sent, so that it can
be subsequently marked as acknowledged or lost depending on the ultimate
fate of the packet.
- TX Packetiser calls `ossl_quic_fifd_pkt_commit()`. The FIFD takes care
of submitting the packet to the ACK Manager and provides its own callback
implementation. It also takes care of informing the CFQ that any CFQ items
which were added via `ossl_quic_txpim_pkt_add_cfq_item()` have been
transmitted.
In the event of packet loss, ACK or discard, the appropriate QUIC Send Stream,
CFQ and regenerate callback calls are made. Regardless of the outcome, the
TXPIM is released.
|