lsquic_frame_writer.c revision 5392f7a3
1/* Copyright (c) 2017 - 2019 LiteSpeed Technologies Inc.  See LICENSE. */
2/*
3 * lsquic_frame_writer.c -- write frames to HEADERS stream.
4 *
5 * The frame is first written to list of frame_buf's (frabs) and then
6 * out to the stream.  This is done because frame's size is written out
7 * to the stream and we may not have enough room in the stream to fit
8 * the whole frame.
9 */
10
11#ifndef WIN32
12#include <arpa/inet.h>
13#endif
14#include <assert.h>
15#include <errno.h>
16#include <inttypes.h>
17#include <stdlib.h>
18#include <string.h>
19#include <sys/queue.h>
20
21#include "lshpack.h"
22#include "lsquic_mm.h"
23#include "lsquic.h"
24#include "lsquic_int_types.h"
25#include "lsquic_hash.h"
26#include "lsquic_conn.h"
27
28#include "lsquic_frame_writer.h"
29#include "lsquic_frame_common.h"
30#include "lsquic_frab_list.h"
31#include "lsquic_ev_log.h"
32
33#define LSQUIC_LOGGER_MODULE LSQLM_FRAME_WRITER
34#define LSQUIC_LOG_CONN_ID lsquic_conn_log_cid(\
35                                        lsquic_stream_conn(fw->fw_stream))
36#include "lsquic_logger.h"
37
38
39struct lsquic_frame_writer
40{
41    struct lsquic_stream       *fw_stream;
42    fw_writef_f                 fw_writef;
43    struct lsquic_mm           *fw_mm;
44    struct lshpack_enc         *fw_henc;
45#if LSQUIC_CONN_STATS
46    struct conn_stats          *fw_conn_stats;
47#endif
48    struct frab_list            fw_fral;
49    unsigned                    fw_max_frame_sz;
50    uint32_t                    fw_max_header_list_sz;  /* 0 means unlimited */
51    enum {
52        FW_SERVER   = (1 << 0),
53    }                           fw_flags;
54};
55
56
57/* RFC 7540, Section 4.2 */
58#define MIN_MAX_FRAME_SIZE  (1 << 14)
59#define MAX_MAX_FRAME_SIZE ((1 << 24) - 1)
60
61#define MAX(a, b) ((a) > (b) ? (a) : (b))
62#define SETTINGS_FRAME_SZ 6
63#define ABS_MIN_FRAME_SIZE MAX(SETTINGS_FRAME_SZ, \
64                                            sizeof(struct http_prio_frame))
65
66static void *
67fw_alloc (void *ctx, size_t size)
68{
69    return lsquic_mm_get_4k(ctx);
70}
71
72
73struct lsquic_frame_writer *
74lsquic_frame_writer_new (struct lsquic_mm *mm, struct lsquic_stream *stream,
75     unsigned max_frame_sz, struct lshpack_enc *henc, fw_writef_f writef,
76#if LSQUIC_CONN_STATS
77     struct conn_stats *conn_stats,
78#endif
79     int is_server)
80{
81    struct lsquic_frame_writer *fw;
82
83    /* When frame writer is instantiated, limit the maximum size to
84     * MIN_MAX_FRAME_SIZE.  The reference implementation has this value
85     * hardcoded and QUIC does not provide a mechanism to advertise a
86     * different value.
87     */
88    if (0 == max_frame_sz)
89        max_frame_sz = MIN_MAX_FRAME_SIZE;
90    else
91        LSQ_LOG1(LSQ_LOG_WARN, "max frame size specified to be %u bytes "
92            "-- this better be test code!", max_frame_sz);
93
94    if (!is_server && max_frame_sz < ABS_MIN_FRAME_SIZE)
95    {
96        LSQ_LOG1(LSQ_LOG_ERROR, "max frame size must be at least %zd bytes, "
97            "which is the size of priority information that client always "
98            "writes", ABS_MIN_FRAME_SIZE);
99        return NULL;
100    }
101
102    fw = malloc(sizeof(*fw));
103    if (!fw)
104        return NULL;
105
106    fw->fw_mm           = mm;
107    fw->fw_henc         = henc;
108    fw->fw_stream       = stream;
109    fw->fw_writef       = writef;
110    fw->fw_max_frame_sz = max_frame_sz;
111    fw->fw_max_header_list_sz = 0;
112    if (is_server)
113        fw->fw_flags    = FW_SERVER;
114    else
115        fw->fw_flags    = 0;
116#if LSQUIC_CONN_STATS
117    fw->fw_conn_stats   = conn_stats;
118#endif
119    lsquic_frab_list_init(&fw->fw_fral, 0x1000, fw_alloc,
120        (void (*)(void *, void *)) lsquic_mm_put_4k, mm);
121    return fw;
122}
123
124
125void
126lsquic_frame_writer_destroy (struct lsquic_frame_writer *fw)
127{
128    lsquic_frab_list_cleanup(&fw->fw_fral);
129    free(fw);
130}
131
132
133int
134lsquic_frame_writer_have_leftovers (const struct lsquic_frame_writer *fw)
135{
136    return !lsquic_frab_list_empty(&fw->fw_fral);
137}
138
139
140int
141lsquic_frame_writer_flush (struct lsquic_frame_writer *fw)
142{
143    struct lsquic_reader reader = {
144        .lsqr_read  = lsquic_frab_list_read,
145        .lsqr_size  = lsquic_frab_list_size,
146        .lsqr_ctx   = &fw->fw_fral,
147    };
148    ssize_t nw;
149
150    nw = fw->fw_writef(fw->fw_stream, &reader);
151
152    if (nw >= 0)
153        return 0;
154    else
155        return -1;
156}
157
158
159struct header_framer_ctx
160{
161    struct lsquic_frame_writer
162               *hfc_fw;
163    struct {
164        struct frame_buf   *frab;
165        unsigned short      off;
166    }           hfc_header_ptr;     /* Points to byte *after* current frame header */
167    unsigned    hfc_max_frame_sz;   /* Maximum frame size.  We always fill it. */
168    unsigned    hfc_cur_sz;         /* Number of bytes in the current frame. */
169    unsigned    hfc_n_frames;       /* Number of frames written. */
170    lsquic_stream_id_t
171                hfc_stream_id;      /* Stream ID */
172    enum http_frame_header_flags
173                hfc_first_flags;
174    enum http_frame_type
175                hfc_frame_type;
176};
177
178
179static void
180hfc_init (struct header_framer_ctx *hfc, struct lsquic_frame_writer *fw,
181      unsigned max_frame_sz, enum http_frame_type frame_type,
182      lsquic_stream_id_t stream_id, enum http_frame_header_flags first_flags)
183{
184    memset(hfc, 0, sizeof(*hfc));
185    hfc->hfc_fw           = fw;
186    hfc->hfc_frame_type   = frame_type;
187    hfc->hfc_stream_id    = stream_id;
188    hfc->hfc_first_flags  = first_flags;
189    hfc->hfc_max_frame_sz = max_frame_sz;
190    hfc->hfc_cur_sz       = max_frame_sz;
191}
192
193
194static void
195hfc_save_ptr (struct header_framer_ctx *hfc)
196{
197    hfc->hfc_header_ptr.frab = TAILQ_LAST(&hfc->hfc_fw->fw_fral.fl_frabs, frame_buf_head);
198    hfc->hfc_header_ptr.off = hfc->hfc_header_ptr.frab->frab_size;
199}
200
201
202static void
203hfc_terminate_frame (struct header_framer_ctx *hfc,
204                     enum http_frame_header_flags flags)
205{
206    union {
207        struct http_frame_header fh;
208        unsigned char            buf[ sizeof(struct http_frame_header) ];
209    } u;
210    uint32_t stream_id;
211    struct frame_buf *frab;
212
213    /* Construct the frame */
214    u.fh.hfh_length[0] = hfc->hfc_cur_sz >> 16;
215    u.fh.hfh_length[1] = hfc->hfc_cur_sz >> 8;
216    u.fh.hfh_length[2] = hfc->hfc_cur_sz;
217    u.fh.hfh_flags     = flags;
218    if (1 == hfc->hfc_n_frames)
219    {
220        u.fh.hfh_type  = hfc->hfc_frame_type;
221        u.fh.hfh_flags |= hfc->hfc_first_flags;
222    }
223    else
224        u.fh.hfh_type  = HTTP_FRAME_CONTINUATION;
225    stream_id = htonl(hfc->hfc_stream_id);
226    memcpy(u.fh.hfh_stream_id, &stream_id, sizeof(stream_id));
227
228    if (hfc->hfc_header_ptr.off >= sizeof(u.fh))
229    {   /* Write in a single chunk */
230        assert(0 == memcmp("123456789", hfc->hfc_header_ptr.frab->frab_buf +
231                    hfc->hfc_header_ptr.off - sizeof(u.buf), sizeof(u.buf)));
232        memcpy(hfc->hfc_header_ptr.frab->frab_buf + hfc->hfc_header_ptr.off -
233                    sizeof(u.buf), u.buf, sizeof(u.buf));
234    }
235    else
236    {   /* Write across frab boundary */
237        memcpy(hfc->hfc_header_ptr.frab->frab_buf,
238            u.buf + sizeof(u.buf) - hfc->hfc_header_ptr.off,
239            hfc->hfc_header_ptr.off);
240        frab = TAILQ_PREV(hfc->hfc_header_ptr.frab, frame_buf_head, frab_next);
241        memcpy(frab->frab_buf + frab->frab_size - sizeof(u.buf) +
242            hfc->hfc_header_ptr.off, u.buf,
243            sizeof(u.buf) - hfc->hfc_header_ptr.off);
244    }
245}
246
247
248static int
249hfc_write (struct header_framer_ctx *hfc, const void *buf, size_t sz)
250{
251    const unsigned char *p = buf;
252    unsigned avail;
253    int s;
254
255    while (sz > 0)
256    {
257        if (hfc->hfc_max_frame_sz == hfc->hfc_cur_sz)
258        {
259            if (hfc->hfc_n_frames > 0)
260                hfc_terminate_frame(hfc, 0);
261            s = lsquic_frab_list_write(&hfc->hfc_fw->fw_fral, "123456789",
262                                        sizeof(struct http_frame_header));
263            if (s < 0)
264                return s;
265            ++hfc->hfc_n_frames;
266            hfc_save_ptr(hfc);
267            hfc->hfc_cur_sz = 0;
268        }
269
270        avail = hfc->hfc_max_frame_sz - hfc->hfc_cur_sz;
271        if (sz < avail)
272            avail = sz;
273        if (avail)
274        {
275            s = lsquic_frab_list_write(&hfc->hfc_fw->fw_fral, p, avail);
276            if (s < 0)
277                return s;
278            hfc->hfc_cur_sz += avail;
279            sz -= avail;
280            p += avail;
281        }
282    }
283
284    return 0;
285}
286
287
288static unsigned
289count_uppercase (const unsigned char *buf, size_t sz)
290{
291    static const unsigned char uppercase[0x100] = {
292        ['A'] = 1, ['B'] = 1, ['C'] = 1, ['D'] = 1, ['E'] = 1, ['F'] = 1,
293        ['G'] = 1, ['H'] = 1, ['I'] = 1, ['J'] = 1, ['K'] = 1, ['L'] = 1,
294        ['M'] = 1, ['N'] = 1, ['O'] = 1, ['P'] = 1, ['Q'] = 1, ['R'] = 1,
295        ['S'] = 1, ['T'] = 1, ['U'] = 1, ['V'] = 1, ['W'] = 1, ['X'] = 1,
296        ['Y'] = 1, ['Z'] = 1,
297    };
298    unsigned n_uppercase, i;
299    n_uppercase = 0;
300    for (i = 0; i < sz; ++i)
301        n_uppercase += uppercase[ buf[i] ];
302    return n_uppercase;
303}
304
305
306static uint32_t
307calc_headers_size (const struct lsquic_http_headers *headers)
308{
309    int i;
310    uint32_t size = 0;
311    for (i = 0; i < headers->count; ++i)
312        size += 32 + headers->headers[i].name.iov_len +
313                     headers->headers[i].value.iov_len;
314    return size;
315}
316
317
318static int
319have_oversize_strings (const struct lsquic_http_headers *headers)
320{
321    int i, have;
322    for (i = 0, have = 0; i < headers->count; ++i)
323    {
324        have |= headers->headers[i].name.iov_len  > LSHPACK_MAX_STRLEN;
325        have |= headers->headers[i].value.iov_len > LSHPACK_MAX_STRLEN;
326    }
327    return have;
328}
329
330
331static int
332check_headers_size (const struct lsquic_frame_writer *fw,
333                    const struct lsquic_http_headers *headers,
334                    const struct lsquic_http_headers *extra_headers)
335{
336    uint32_t headers_sz;
337    headers_sz = calc_headers_size(headers);
338    if (extra_headers)
339        headers_sz += calc_headers_size(extra_headers);
340
341    if (headers_sz <= fw->fw_max_header_list_sz)
342        return 0;
343    else if (fw->fw_flags & FW_SERVER)
344    {
345        LSQ_INFO("Sending headers larger (%u bytes) than max allowed (%u)",
346            headers_sz, fw->fw_max_header_list_sz);
347        return 0;
348    }
349    else
350    {
351        LSQ_INFO("Headers size %u is larger than max allowed (%u)",
352            headers_sz, fw->fw_max_header_list_sz);
353        errno = EMSGSIZE;
354        return -1;
355    }
356}
357
358
359static int
360check_headers_case (const struct lsquic_frame_writer *fw,
361                    const struct lsquic_http_headers *headers)
362{
363    unsigned n_uppercase;
364    int i;
365    n_uppercase = 0;
366    for (i = 0; i < headers->count; ++i)
367        n_uppercase += count_uppercase(headers->headers[i].name.iov_base,
368                                        headers->headers[i].name.iov_len);
369    if (n_uppercase)
370    {
371        LSQ_INFO("Uppercase letters in header names");
372        errno = EINVAL;
373        return -1;
374    }
375    return 0;
376}
377
378
379static int
380write_headers (struct lsquic_frame_writer *fw,
381               const struct lsquic_http_headers *headers,
382               struct header_framer_ctx *hfc, unsigned char *buf,
383               const unsigned buf_sz)
384{
385    unsigned char *end;
386    int i, s;
387
388    for (i = 0; i < headers->count; ++i)
389    {
390        end = lshpack_enc_encode(fw->fw_henc, buf, buf + buf_sz,
391                                 LSHPACK_HDR_UNKNOWN,
392                                 (const lshpack_header_t *)&headers->headers[i],
393                                 0);
394        if (end > buf)
395        {
396            s = hfc_write(hfc, buf, end - buf);
397            if (s < 0)
398                return s;
399#if LSQUIC_CONN_STATS
400            fw->fw_conn_stats->out.headers_uncomp +=
401                headers->headers[i].name.iov_len
402                    + headers->headers[i].value.iov_len;
403            fw->fw_conn_stats->out.headers_comp += end - buf;
404#endif
405        }
406        else
407        {
408            /* Ignore errors, matching HTTP2 behavior in our server code */
409        }
410    }
411
412    return 0;
413}
414
415
416int
417lsquic_frame_writer_write_headers (struct lsquic_frame_writer *fw,
418                                   lsquic_stream_id_t stream_id,
419                                   const struct lsquic_http_headers *headers,
420                                   int eos, unsigned weight)
421{
422    struct header_framer_ctx hfc;
423    int s;
424    struct http_prio_frame prio_frame;
425    enum http_frame_header_flags flags;
426    unsigned char *buf;
427
428    /* Internal function: weight must be valid here */
429    assert(weight >= 1 && weight <= 256);
430
431    if (fw->fw_max_header_list_sz && 0 != check_headers_size(fw, headers, NULL))
432        return -1;
433
434    if (0 != check_headers_case(fw, headers))
435        return -1;
436
437    if (have_oversize_strings(headers))
438        return -1;
439
440    if (eos)
441        flags = HFHF_END_STREAM;
442    else
443        flags = 0;
444
445    if (!(fw->fw_flags & FW_SERVER))
446        flags |= HFHF_PRIORITY;
447
448    hfc_init(&hfc, fw, fw->fw_max_frame_sz, HTTP_FRAME_HEADERS, stream_id,
449                                                                        flags);
450
451    if (!(fw->fw_flags & FW_SERVER))
452    {
453        memset(&prio_frame.hpf_stream_id, 0, sizeof(prio_frame.hpf_stream_id));
454        prio_frame.hpf_weight = weight - 1;
455        s = hfc_write(&hfc, &prio_frame, sizeof(struct http_prio_frame));
456        if (s < 0)
457            return s;
458    }
459
460    buf = malloc(MAX_HEADERS_SIZE);
461    if (!buf)
462        return -1;
463    s = write_headers(fw, headers, &hfc, buf, MAX_HEADERS_SIZE);
464    free(buf);
465    if (0 == s)
466    {
467        EV_LOG_GENERATED_HTTP_HEADERS(LSQUIC_LOG_CONN_ID, stream_id,
468                            fw->fw_flags & FW_SERVER, &prio_frame, headers);
469        hfc_terminate_frame(&hfc, HFHF_END_HEADERS);
470        return lsquic_frame_writer_flush(fw);
471    }
472    else
473        return s;
474}
475
476
477int
478lsquic_frame_writer_write_promise (struct lsquic_frame_writer *fw,
479    lsquic_stream_id_t stream_id64, lsquic_stream_id_t promised_stream_id64,
480    const struct iovec *path, const struct iovec *host,
481    const struct lsquic_http_headers *extra_headers)
482{
483    uint32_t stream_id = stream_id64;
484    uint32_t promised_stream_id = promised_stream_id64;
485    struct header_framer_ctx hfc;
486    struct http_push_promise_frame push_frame;
487    lsquic_http_header_t mpas_headers[4];
488    struct lsquic_http_headers mpas = {    /* method, path, authority, scheme */
489        .headers = mpas_headers,
490        .count   = 4,
491    };
492    unsigned char *buf;
493    int s;
494
495    mpas_headers[0].name. iov_base    = ":method";
496    mpas_headers[0].name. iov_len     = 7;
497    mpas_headers[0].value.iov_base    = "GET";
498    mpas_headers[0].value.iov_len     = 3;
499    mpas_headers[1].name .iov_base    = ":path";
500    mpas_headers[1].name .iov_len     = 5;
501    mpas_headers[1].value             = *path;
502    mpas_headers[2].name .iov_base    = ":authority";
503    mpas_headers[2].name .iov_len     = 10;
504    mpas_headers[2].value             = *host;
505    mpas_headers[3].name. iov_base    = ":scheme";
506    mpas_headers[3].name. iov_len     = 7;
507    mpas_headers[3].value.iov_base    = "https";
508    mpas_headers[3].value.iov_len     = 5;
509
510    if (fw->fw_max_header_list_sz &&
511                    0 != check_headers_size(fw, &mpas, extra_headers))
512        return -1;
513
514    if (extra_headers && 0 != check_headers_case(fw, extra_headers))
515        return -1;
516
517    if (have_oversize_strings(&mpas))
518        return -1;
519
520    if (extra_headers && have_oversize_strings(extra_headers))
521        return -1;
522
523    hfc_init(&hfc, fw, fw->fw_max_frame_sz, HTTP_FRAME_PUSH_PROMISE,
524                                                            stream_id, 0);
525
526    promised_stream_id = htonl(promised_stream_id);
527    memcpy(push_frame.hppf_promised_id, &promised_stream_id, 4);
528    s = hfc_write(&hfc, &push_frame, sizeof(struct http_push_promise_frame));
529    if (s < 0)
530        return s;
531
532    buf = malloc(MAX_HEADERS_SIZE);
533    if (!buf)
534        return -1;
535
536    s = write_headers(fw, &mpas, &hfc, buf, MAX_HEADERS_SIZE);
537    if (s != 0)
538    {
539        free(buf);
540        return -1;
541    }
542
543    if (extra_headers)
544        s = write_headers(fw, extra_headers, &hfc, buf, MAX_HEADERS_SIZE);
545
546    free(buf);
547
548    if (0 == s)
549    {
550        EV_LOG_GENERATED_HTTP_PUSH_PROMISE(LSQUIC_LOG_CONN_ID, stream_id,
551                            htonl(promised_stream_id), &mpas, extra_headers);
552        hfc_terminate_frame(&hfc, HFHF_END_HEADERS);
553        return lsquic_frame_writer_flush(fw);
554    }
555    else
556        return -1;
557}
558
559
560void
561lsquic_frame_writer_max_header_list_size (struct lsquic_frame_writer *fw,
562                                          uint32_t max_size)
563{
564    LSQ_DEBUG("set max_header_list_sz to %u", max_size);
565    fw->fw_max_header_list_sz = max_size;
566}
567
568
569static int
570write_settings (struct lsquic_frame_writer *fw,
571    const struct lsquic_http2_setting *settings, unsigned n_settings)
572{
573    struct http_frame_header fh;
574    unsigned payload_length;
575    uint32_t val;
576    uint16_t id;
577    int s;
578
579    payload_length = n_settings * 6;
580
581    memset(&fh, 0, sizeof(fh));
582    fh.hfh_type  = HTTP_FRAME_SETTINGS;
583    fh.hfh_length[0] = payload_length >> 16;
584    fh.hfh_length[1] = payload_length >> 8;
585    fh.hfh_length[2] = payload_length;
586
587    s = lsquic_frab_list_write(&fw->fw_fral, &fh, sizeof(fh));
588    if (s != 0)
589        return s;
590
591    do
592    {
593        id  = htons(settings->id);
594        val = htonl(settings->value);
595        if (0 != (s = lsquic_frab_list_write(&fw->fw_fral, &id, sizeof(id))) ||
596            0 != (s = lsquic_frab_list_write(&fw->fw_fral, &val, sizeof(val))))
597            return s;
598        EV_LOG_CONN_EVENT(LSQUIC_LOG_CONN_ID, "wrote HTTP SETTINGS frame: "
599            "%s=%"PRIu32, lsquic_http_setting_id2str(settings->id),
600            settings->value);
601        ++settings;
602    }
603    while (--n_settings);
604
605    return 0;
606}
607
608int
609lsquic_frame_writer_write_settings (struct lsquic_frame_writer *fw,
610    const struct lsquic_http2_setting *settings, unsigned n_settings)
611{
612    unsigned settings_per_frame;
613    unsigned n;
614
615    if (0 == n_settings)
616    {
617        errno = EINVAL;
618        return -1;
619    }
620
621    settings_per_frame = fw->fw_max_frame_sz / SETTINGS_FRAME_SZ;
622    n = 0;
623
624    do {
625        if (settings_per_frame > n_settings - n)
626            settings_per_frame = n_settings - n;
627        if (0 != write_settings(fw, &settings[n], settings_per_frame))
628            return -1;
629        n += settings_per_frame;
630    } while (n < n_settings);
631
632    return lsquic_frame_writer_flush(fw);
633}
634
635
636int
637lsquic_frame_writer_write_priority (struct lsquic_frame_writer *fw,
638        lsquic_stream_id_t stream_id64, int exclusive,
639        lsquic_stream_id_t stream_dep_id64, unsigned weight)
640{
641    uint32_t stream_id = stream_id64;
642    uint32_t stream_dep_id = stream_dep_id64;
643    unsigned char buf[ sizeof(struct http_frame_header) +
644                                        sizeof(struct http_prio_frame) ];
645    struct http_frame_header *fh         = (void *) &buf[0];
646    struct http_prio_frame   *prio_frame = (void *) &buf[sizeof(*fh)];
647    int s;
648
649    if (stream_dep_id & (1UL << 31))
650    {
651        LSQ_WARN("stream ID too high (%u): cannot write PRIORITY frame",
652            stream_dep_id);
653        return -1;
654    }
655
656    if (weight < 1 || weight > 256)
657        return -1;
658
659    memset(fh, 0, sizeof(*fh));
660    fh->hfh_type      = HTTP_FRAME_PRIORITY;
661    fh->hfh_length[2] = sizeof(struct http_prio_frame);
662    stream_id = htonl(stream_id);
663    memcpy(fh->hfh_stream_id, &stream_id, 4);
664
665    stream_dep_id |= !!exclusive << 31;
666    stream_id = htonl(stream_dep_id);
667    memcpy(prio_frame->hpf_stream_id, &stream_id, 4);
668    prio_frame->hpf_weight = weight - 1;
669
670    s = lsquic_frab_list_write(&fw->fw_fral, buf, sizeof(buf));
671    if (s != 0)
672        return s;
673
674    EV_LOG_CONN_EVENT(LSQUIC_LOG_CONN_ID, "wrote HTTP PRIORITY frame: "
675        "stream %"PRIu32"; weight: %u; exclusive: %d",
676        htonl(stream_id), weight, !!exclusive);
677
678    return lsquic_frame_writer_flush(fw);
679}
680
681
682size_t
683lsquic_frame_writer_mem_used (const struct lsquic_frame_writer *fw)
684{
685    size_t size;
686
687    size = sizeof(*fw)
688         + lsquic_frab_list_mem_used(&fw->fw_fral)
689         - sizeof(fw->fw_fral);
690
691    return size;
692}
693