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