lsquic_frame_writer.c revision a5fa05f9
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 struct lsxpack_header hdr; 393 for (i = 0; i < headers->count; ++i) 394 { 395 lsquic_http_header_t *h = &headers->headers[i]; 396 lsxpack_header_set_ptr(&hdr, h->name.iov_base, h->name.iov_len, 397 h->value.iov_base, h->value.iov_len); 398 end = lshpack_enc_encode(fw->fw_henc, buf, buf + buf_sz, &hdr); 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