lsquic_qdec_hdl.c revision f4841319
1/* Copyright (c) 2017 - 2020 LiteSpeed Technologies Inc. See LICENSE. */ 2/* 3 * lsquic_qdec_hdl.c -- QPACK decoder streams handler 4 */ 5 6#include <assert.h> 7#include <errno.h> 8#include <inttypes.h> 9#include <stdlib.h> 10#include <string.h> 11#include <sys/queue.h> 12 13#include "lsquic.h" 14#include "lsquic_types.h" 15#include "lsquic_int_types.h" 16#include "lsquic_sfcw.h" 17#include "lsquic_varint.h" 18#include "lsquic_hq.h" 19#include "lsquic_hash.h" 20#include "lsquic_stream.h" 21#include "lsquic_frab_list.h" 22#include "lsqpack.h" 23#include "lsquic_http1x_if.h" 24#include "lsquic_qdec_hdl.h" 25#include "lsquic_mm.h" 26#include "lsquic_engine_public.h" 27#include "lsquic_headers.h" 28#include "lsquic_conn.h" 29 30#define LSQUIC_LOGGER_MODULE LSQLM_QDEC_HDL 31#define LSQUIC_LOG_CONN_ID lsquic_conn_log_cid(qdh->qdh_conn) 32#include "lsquic_logger.h" 33 34static void 35qdh_hblock_unblocked (void *); 36 37 38static int 39qdh_write_decoder (struct qpack_dec_hdl *qdh, const unsigned char *buf, 40 size_t sz) 41{ 42 ssize_t nw; 43 44 if (!(qdh->qdh_dec_sm_out && lsquic_frab_list_empty(&qdh->qdh_fral))) 45 { 46 write_to_frab: 47 if (0 == lsquic_frab_list_write(&qdh->qdh_fral, 48 (unsigned char *) buf, sz)) 49 { 50 LSQ_DEBUG("wrote %zu bytes to frab list", sz); 51 lsquic_stream_wantwrite(qdh->qdh_dec_sm_out, 1); 52 return 0; 53 } 54 else 55 { 56 LSQ_INFO("error writing to frab list"); 57 return -1; 58 } 59 } 60 61 nw = lsquic_stream_write(qdh->qdh_dec_sm_out, buf, sz); 62 if (nw < 0) 63 { 64 LSQ_INFO("error writing to outgoing QPACK decoder stream: %s", 65 strerror(errno)); 66 return -1; 67 } 68 LSQ_DEBUG("wrote %zd bytes to outgoing QPACK decoder stream", nw); 69 70 if ((size_t) nw == sz) 71 return 0; 72 73 buf = buf + nw; 74 sz -= (size_t) nw; 75 goto write_to_frab; 76} 77 78 79static int 80qdh_write_type (struct qpack_dec_hdl *qdh) 81{ 82 int s; 83 84#ifndef NDEBUG 85 const char *env = getenv("LSQUIC_RND_VARINT_LEN"); 86 if (env && atoi(env)) 87 { 88 s = rand() & 3; 89 LSQ_DEBUG("writing %d-byte stream type", 1 << s); 90 } 91 else 92#endif 93 s = 0; 94 95 switch (s) 96 { 97 case 0: 98 return qdh_write_decoder(qdh, 99 (unsigned char []) { HQUST_QPACK_DEC }, 1); 100 case 1: 101 return qdh_write_decoder(qdh, 102 (unsigned char []) { 0x40, HQUST_QPACK_DEC }, 2); 103 case 2: 104 return qdh_write_decoder(qdh, 105 (unsigned char []) { 0x80, 0x00, 0x00, HQUST_QPACK_DEC }, 4); 106 default: 107 return qdh_write_decoder(qdh, 108 (unsigned char []) { 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 109 HQUST_QPACK_DEC }, 8); 110 } 111} 112 113 114static void 115qdh_begin_out (struct qpack_dec_hdl *qdh) 116{ 117 if (0 != qdh_write_type(qdh)) 118 { 119 LSQ_WARN("%s: could not write to decoder", __func__); 120 qdh->qdh_conn->cn_if->ci_internal_error(qdh->qdh_conn, 121 "cannot write to decoder stream"); 122 } 123} 124 125 126int 127lsquic_qdh_init (struct qpack_dec_hdl *qdh, struct lsquic_conn *conn, 128 int is_server, const struct lsquic_engine_public *enpub, 129 unsigned dyn_table_size, unsigned max_risked_streams) 130{ 131 qdh->qdh_conn = conn; 132 lsquic_frab_list_init(&qdh->qdh_fral, 0x400, NULL, NULL, NULL); 133 lsqpack_dec_init(&qdh->qdh_decoder, (void *) conn, dyn_table_size, 134 max_risked_streams, qdh_hblock_unblocked); 135 qdh->qdh_flags |= QDH_INITIALIZED; 136 qdh->qdh_enpub = enpub; 137 if (qdh->qdh_enpub->enp_hsi_if == lsquic_http1x_if) 138 { 139 qdh->qdh_h1x_ctor_ctx = (struct http1x_ctor_ctx) { 140 .conn = conn, 141 .max_headers_sz = MAX_HTTP1X_HEADERS_SIZE, 142 .is_server = is_server, 143 }; 144 qdh->qdh_hsi_ctx = &qdh->qdh_h1x_ctor_ctx; 145 } 146 else 147 qdh->qdh_hsi_ctx = qdh->qdh_enpub->enp_hsi_ctx; 148 if (qdh->qdh_dec_sm_out) 149 qdh_begin_out(qdh); 150 if (qdh->qdh_enc_sm_in) 151 lsquic_stream_wantread(qdh->qdh_enc_sm_in, 1); 152 LSQ_DEBUG("initialized"); 153 return 0; 154} 155 156 157void 158lsquic_qdh_cleanup (struct qpack_dec_hdl *qdh) 159{ 160 if (qdh->qdh_flags & QDH_INITIALIZED) 161 { 162 LSQ_DEBUG("cleanup"); 163 lsqpack_dec_cleanup(&qdh->qdh_decoder); 164 lsquic_frab_list_cleanup(&qdh->qdh_fral); 165 qdh->qdh_flags &= ~QDH_INITIALIZED; 166 } 167} 168 169static lsquic_stream_ctx_t * 170qdh_out_on_new (void *stream_if_ctx, struct lsquic_stream *stream) 171{ 172 struct qpack_dec_hdl *const qdh = stream_if_ctx; 173 qdh->qdh_dec_sm_out = stream; 174 if (qdh->qdh_flags & QDH_INITIALIZED) 175 qdh_begin_out(qdh); 176 LSQ_DEBUG("initialized outgoing decoder stream"); 177 return (void *) qdh; 178} 179 180 181static void 182qdh_out_on_write (struct lsquic_stream *stream, lsquic_stream_ctx_t *ctx) 183{ 184 struct qpack_dec_hdl *const qdh = (void *) ctx; 185 struct lsquic_reader reader; 186 ssize_t nw; 187 unsigned char buf[LSQPACK_LONGEST_ICI]; 188 189 if (lsqpack_dec_ici_pending(&qdh->qdh_decoder)) 190 { 191 nw = lsqpack_dec_write_ici(&qdh->qdh_decoder, buf, sizeof(buf)); 192 if (nw > 0) 193 { 194 if (0 == qdh_write_decoder(qdh, buf, nw)) 195 LSQ_DEBUG("wrote %zd-byte TSS instruction", nw); 196 else 197 goto err; 198 } 199 else if (nw < 0) 200 { 201 LSQ_WARN("could not generate TSS instruction"); 202 goto err; 203 } 204 } 205 206 if (lsquic_frab_list_empty(&qdh->qdh_fral)) 207 { 208 LSQ_DEBUG("%s: nothing to write", __func__); 209 lsquic_stream_wantwrite(stream, 0); 210 return; 211 } 212 213 reader = (struct lsquic_reader) { 214 .lsqr_read = lsquic_frab_list_read, 215 .lsqr_size = lsquic_frab_list_size, 216 .lsqr_ctx = &qdh->qdh_fral, 217 }; 218 219 nw = lsquic_stream_writef(stream, &reader); 220 if (nw >= 0) 221 { 222 LSQ_DEBUG("wrote %zd bytes to stream", nw); 223 (void) lsquic_stream_flush(stream); 224 if (lsquic_frab_list_empty(&qdh->qdh_fral)) 225 { 226 lsquic_stream_wantwrite(stream, 0); 227 if (qdh->qdh_on_dec_sent_func) 228 { 229 LSQ_DEBUG("buffered data written: call callback"); 230 qdh->qdh_on_dec_sent_func(qdh->qdh_on_dec_sent_ctx); 231 qdh->qdh_on_dec_sent_func = NULL; 232 qdh->qdh_on_dec_sent_ctx = NULL; 233 } 234 } 235 } 236 else 237 { 238 LSQ_WARN("cannot write to stream: %s", strerror(errno)); 239 err: 240 lsquic_stream_wantwrite(stream, 0); 241 qdh->qdh_conn->cn_if->ci_internal_error(qdh->qdh_conn, 242 "cannot write to stream"); 243 } 244} 245 246 247static void 248qdh_out_on_close (struct lsquic_stream *stream, lsquic_stream_ctx_t *ctx) 249{ 250 struct qpack_dec_hdl *const qdh = (void *) ctx; 251 qdh->qdh_dec_sm_out = NULL; 252 LSQ_DEBUG("closed outgoing decoder stream"); 253} 254 255 256static void 257qdh_out_on_read (struct lsquic_stream *stream, lsquic_stream_ctx_t *ctx) 258{ 259 assert(0); 260} 261 262 263static const struct lsquic_stream_if qdh_dec_sm_out_if = 264{ 265 .on_new_stream = qdh_out_on_new, 266 .on_read = qdh_out_on_read, 267 .on_write = qdh_out_on_write, 268 .on_close = qdh_out_on_close, 269}; 270const struct lsquic_stream_if *const lsquic_qdh_dec_sm_out_if = 271 &qdh_dec_sm_out_if; 272 273 274static lsquic_stream_ctx_t * 275qdh_in_on_new (void *stream_if_ctx, struct lsquic_stream *stream) 276{ 277 struct qpack_dec_hdl *const qdh = stream_if_ctx; 278 qdh->qdh_enc_sm_in = stream; 279 if (qdh->qdh_flags & QDH_INITIALIZED) 280 lsquic_stream_wantread(qdh->qdh_enc_sm_in, 1); 281 LSQ_DEBUG("initialized incoming encoder stream"); 282 return (void *) qdh; 283} 284 285 286static size_t 287qdh_read_encoder_stream (void *ctx, const unsigned char *buf, size_t sz, 288 int fin) 289{ 290 struct qpack_dec_hdl *const qdh = (void *) ctx; 291 const struct lsqpack_dec_err *qerr; 292 int s; 293 294 if (fin) 295 { 296 LSQ_INFO("encoder stream is closed"); 297 qdh->qdh_conn->cn_if->ci_abort_error(qdh->qdh_conn, 1, 298 HEC_CLOSED_CRITICAL_STREAM, "Peer closed QPACK encoder stream"); 299 goto end; 300 } 301 302 s = lsqpack_dec_enc_in(&qdh->qdh_decoder, buf, sz); 303 if (s != 0) 304 { 305 LSQ_INFO("error reading encoder stream"); 306 qerr = lsqpack_dec_get_err_info(&qdh->qdh_decoder); 307 qdh->qdh_conn->cn_if->ci_abort_error(qdh->qdh_conn, 1, 308 HEC_QPACK_DECODER_STREAM_ERROR, "Error interpreting QPACK encoder " 309 "stream; offset %"PRIu64", line %d", qerr->off, qerr->line); 310 goto end; 311 } 312 if (qdh->qdh_dec_sm_out 313 && lsqpack_dec_ici_pending(&qdh->qdh_decoder)) 314 lsquic_stream_wantwrite(qdh->qdh_dec_sm_out, 1); 315 316 LSQ_DEBUG("successfully fed %zu bytes to QPACK decoder", sz); 317 318 end: 319 return sz; 320} 321 322 323static void 324qdh_in_on_read (struct lsquic_stream *stream, lsquic_stream_ctx_t *ctx) 325{ 326 struct qpack_dec_hdl *const qdh = (void *) ctx; 327 ssize_t nread; 328 329 nread = lsquic_stream_readf(stream, qdh_read_encoder_stream, qdh); 330 if (nread <= 0) 331 { 332 if (nread < 0) 333 { 334 LSQ_WARN("cannot read from encoder stream: %s", strerror(errno)); 335 qdh->qdh_conn->cn_if->ci_internal_error(qdh->qdh_conn, 336 "cannot read from encoder stream"); 337 } 338 else 339 { 340 LSQ_INFO("encoder stream closed by peer: abort connection"); 341 qdh->qdh_conn->cn_if->ci_abort_error(qdh->qdh_conn, 1, 342 HEC_CLOSED_CRITICAL_STREAM, "encoder stream closed"); 343 } 344 lsquic_stream_wantread(stream, 0); 345 } 346} 347 348 349static void 350qdh_in_on_close (struct lsquic_stream *stream, lsquic_stream_ctx_t *ctx) 351{ 352 struct qpack_dec_hdl *const qdh = (void *) ctx; 353 LSQ_DEBUG("closed incoming encoder stream"); 354 qdh->qdh_enc_sm_in = NULL; 355} 356 357 358static void 359qdh_in_on_write (struct lsquic_stream *stream, lsquic_stream_ctx_t *ctx) 360{ 361 assert(0); 362} 363 364 365static const struct lsquic_stream_if qdh_enc_sm_in_if = 366{ 367 .on_new_stream = qdh_in_on_new, 368 .on_read = qdh_in_on_read, 369 .on_write = qdh_in_on_write, 370 .on_close = qdh_in_on_close, 371}; 372const struct lsquic_stream_if *const lsquic_qdh_enc_sm_in_if = 373 &qdh_enc_sm_in_if; 374 375 376static void 377qdh_hblock_unblocked (void *stream_p) 378{ 379 struct lsquic_stream *const stream = stream_p; 380 struct qpack_dec_hdl *const qdh = lsquic_stream_get_qdh(stream); 381 382 LSQ_DEBUG("header block for stream %"PRIu64" unblocked", stream->id); 383 lsquic_stream_qdec_unblocked(stream); 384} 385 386 387struct cont_len 388{ 389 unsigned long long value; 390 int has; /* 1: set, 0: not set, -1: invalid */ 391}; 392 393 394static void 395process_content_length (const struct qpack_dec_hdl *qdh /* for logging */, 396 struct cont_len *cl, const char *val /* not NUL-terminated */, 397 unsigned len) 398{ 399 char *endcl, cont_len_buf[30]; 400 401 if (0 == cl->has) 402 { 403 if (len >= sizeof(cont_len_buf)) 404 { 405 LSQ_DEBUG("content-length has invalid value `%.*s'", 406 (int) len, val); 407 cl->has = -1; 408 return; 409 } 410 memcpy(cont_len_buf, val, len); 411 cont_len_buf[len] = '\0'; 412 cl->value = strtoull(cont_len_buf, &endcl, 10); 413 if (*endcl == '\0' && !(ULLONG_MAX == cl->value && ERANGE == errno)) 414 { 415 cl->has = 1; 416 LSQ_DEBUG("content length is %llu", cl->value); 417 } 418 else 419 { 420 cl->has = -1; 421 LSQ_DEBUG("content-length has invalid value `%.*s'", 422 (int) len, val); 423 } 424 } 425 else if (cl->has > 0) 426 { 427 LSQ_DEBUG("header set has two content-length: ambiguous, " 428 "turn off checking"); 429 cl->has = -1; 430 } 431} 432 433 434static int 435is_content_length (const struct lsqpack_header *header) 436{ 437 return ((header->qh_flags & QH_ID_SET) && header->qh_static_id == 4) 438 || (header->qh_name_len == 14 && header->qh_name[0] == 'c' 439 && 0 == memcmp(header->qh_name + 1, "ontent-length", 13)) 440 ; 441} 442 443 444static int 445qdh_supply_hset_to_stream (struct qpack_dec_hdl *qdh, 446 struct lsquic_stream *stream, struct lsqpack_header_list *qlist) 447{ 448 const struct lsquic_hset_if *const hset_if = qdh->qdh_enpub->enp_hsi_if; 449 const unsigned hpack_static_table_size = 61; 450 struct uncompressed_headers *uh = NULL; 451 const struct lsqpack_header *header; 452 enum lsquic_header_status st; 453 int push_promise; 454 unsigned i; 455 void *hset; 456 struct cont_len cl; 457 458 push_promise = lsquic_stream_header_is_pp(stream); 459 hset = hset_if->hsi_create_header_set(qdh->qdh_hsi_ctx, push_promise); 460 if (!hset) 461 { 462 LSQ_INFO("call to hsi_create_header_set failed"); 463 return -1; 464 } 465 466 LSQ_DEBUG("got header set for stream %"PRIu64, stream->id); 467 468 cl.has = 0; 469 for (i = 0; i < qlist->qhl_count; ++i) 470 { 471 header = qlist->qhl_headers[i]; 472 LSQ_DEBUG("%.*s: %.*s", header->qh_name_len, header->qh_name, 473 header->qh_value_len, header->qh_value); 474 st = hset_if->hsi_process_header(hset, 475 header->qh_flags & QH_ID_SET ? 476 hpack_static_table_size + 1 + header->qh_static_id : 0, 477 header->qh_name, header->qh_name_len, 478 header->qh_value, header->qh_value_len); 479 if (st != LSQUIC_HDR_OK) 480 { 481 LSQ_INFO("header process returned non-OK code %u", (unsigned) st); 482 goto err; 483 } 484 if (is_content_length(header)) 485 process_content_length(qdh, &cl, header->qh_value, 486 header->qh_value_len); 487 } 488 489 lsqpack_dec_destroy_header_list(qlist); 490 qlist = NULL; 491 st = hset_if->hsi_process_header(hset, 0, 0, 0, 0, 0); 492 if (st != LSQUIC_HDR_OK) 493 goto err; 494 495 uh = calloc(1, sizeof(*uh)); 496 if (!uh) 497 goto err; 498 uh->uh_stream_id = stream->id; 499 uh->uh_oth_stream_id = 0; 500 uh->uh_weight = 0; 501 uh->uh_exclusive = -1; 502 if (hset_if == lsquic_http1x_if) 503 uh->uh_flags |= UH_H1H; 504 uh->uh_hset = hset; 505 if (0 != lsquic_stream_uh_in(stream, uh)) 506 goto err; 507 LSQ_DEBUG("converted qlist to hset and gave it to stream %"PRIu64, 508 stream->id); 509 if (cl.has > 0) 510 (void) lsquic_stream_verify_len(stream, cl.value); 511 return 0; 512 513 err: 514 if (qlist) 515 lsqpack_dec_destroy_header_list(qlist); 516 hset_if->hsi_discard_header_set(hset); 517 free(uh); 518 return -1; 519} 520 521 522/* Releases qlist */ 523static int 524qdh_process_qlist (struct qpack_dec_hdl *qdh, 525 struct lsquic_stream *stream, struct lsqpack_header_list *qlist) 526{ 527 if (!lsquic_stream_header_is_trailer(stream)) 528 return qdh_supply_hset_to_stream(qdh, stream, qlist); 529 else 530 { 531 LSQ_DEBUG("discard trailer header set"); 532 lsqpack_dec_destroy_header_list(qlist); 533 return 0; 534 } 535} 536 537 538static enum lsqpack_read_header_status 539qdh_header_read_results (struct qpack_dec_hdl *qdh, 540 struct lsquic_stream *stream, enum lsqpack_read_header_status rhs, 541 struct lsqpack_header_list *qlist, const unsigned char *dec_buf, 542 size_t dec_buf_sz) 543{ 544 const struct lsqpack_dec_err *qerr; 545 546 if (rhs == LQRHS_DONE) 547 { 548 if (qlist) 549 { 550 if (0 != qdh_process_qlist(qdh, stream, qlist)) 551 return LQRHS_ERROR; 552 if (qdh->qdh_dec_sm_out) 553 { 554 if (dec_buf_sz 555 && 0 != qdh_write_decoder(qdh, dec_buf, dec_buf_sz)) 556 { 557 return LQRHS_ERROR; 558 } 559 if (dec_buf_sz || lsqpack_dec_ici_pending(&qdh->qdh_decoder)) 560 lsquic_stream_wantwrite(qdh->qdh_dec_sm_out, 1); 561 } 562 } 563 else 564 { 565 LSQ_WARN("read header status is DONE but header list is not set"); 566 assert(0); 567 return LQRHS_ERROR; 568 } 569 } 570 else if (rhs == LQRHS_ERROR) 571 { 572 qerr = lsqpack_dec_get_err_info(&qdh->qdh_decoder); 573 qdh->qdh_conn->cn_if->ci_abort_error(qdh->qdh_conn, 1, 574 HEC_QPACK_DECOMPRESSION_FAILED, "QPACK decompression error; " 575 "stream %"PRIu64", offset %"PRIu64", line %d", qerr->stream_id, 576 qerr->off, qerr->line); 577 } 578 579 return rhs; 580} 581 582 583enum lsqpack_read_header_status 584lsquic_qdh_header_in_begin (struct qpack_dec_hdl *qdh, 585 struct lsquic_stream *stream, uint64_t header_size, 586 const unsigned char **buf, size_t bufsz) 587{ 588 enum lsqpack_read_header_status rhs; 589 struct lsqpack_header_list *qlist; 590 size_t dec_buf_sz; 591 unsigned char dec_buf[LSQPACK_LONGEST_HEADER_ACK]; 592 593 if (qdh->qdh_flags & QDH_INITIALIZED) 594 { 595 dec_buf_sz = sizeof(dec_buf); 596 rhs = lsqpack_dec_header_in(&qdh->qdh_decoder, stream, stream->id, 597 header_size, buf, bufsz, &qlist, dec_buf, &dec_buf_sz); 598 return qdh_header_read_results(qdh, stream, rhs, qlist, dec_buf, 599 dec_buf_sz); 600 } 601 else 602 { 603 LSQ_WARN("not initialized: cannot process header block"); 604 return LQRHS_ERROR; 605 } 606 607} 608 609 610enum lsqpack_read_header_status 611lsquic_qdh_header_in_continue (struct qpack_dec_hdl *qdh, 612 struct lsquic_stream *stream, const unsigned char **buf, size_t bufsz) 613{ 614 enum lsqpack_read_header_status rhs; 615 struct lsqpack_header_list *qlist; 616 size_t dec_buf_sz; 617 unsigned char dec_buf[LSQPACK_LONGEST_HEADER_ACK]; 618 619 if (qdh->qdh_flags & QDH_INITIALIZED) 620 { 621 dec_buf_sz = sizeof(dec_buf); 622 rhs = lsqpack_dec_header_read(&qdh->qdh_decoder, stream, 623 buf, bufsz, &qlist, dec_buf, &dec_buf_sz); 624 return qdh_header_read_results(qdh, stream, rhs, qlist, dec_buf, 625 dec_buf_sz); 626 } 627 else 628 { 629 LSQ_WARN("not initialized: cannot process header block"); 630 return LQRHS_ERROR; 631 } 632} 633 634 635void 636lsquic_qdh_unref_stream (struct qpack_dec_hdl *qdh, 637 struct lsquic_stream *stream) 638{ 639 if (0 == lsqpack_dec_unref_stream(&qdh->qdh_decoder, stream)) 640 LSQ_DEBUG("unreffed stream %"PRIu64, stream->id); 641 else 642 LSQ_WARN("cannot unref stream %"PRIu64, stream->id); 643} 644 645 646void 647lsquic_qdh_cancel_stream (struct qpack_dec_hdl *qdh, 648 struct lsquic_stream *stream) 649{ 650 ssize_t nw; 651 unsigned char buf[LSQPACK_LONGEST_CANCEL]; 652 653 nw = lsqpack_dec_cancel_stream(&qdh->qdh_decoder, stream, buf, sizeof(buf)); 654 if (nw > 0) 655 { 656 if (0 == qdh_write_decoder(qdh, buf, nw)) 657 LSQ_DEBUG("cancelled stream %"PRIu64" and wrote %zd-byte Cancel " 658 "Stream instruction to the decoder stream", stream->id, nw); 659 } 660 else if (nw == 0) 661 LSQ_WARN("cannot cancel stream %"PRIu64" -- not found", stream->id); 662 else 663 { 664 LSQ_WARN("cannot cancel stream %"PRIu64" -- not enough buffer space " 665 "to encode Cancel Stream instructin", stream->id); 666 lsquic_qdh_unref_stream(qdh, stream); 667 } 668} 669 670 671int 672lsquic_qdh_arm_if_unsent (struct qpack_dec_hdl *qdh, void (*func)(void *), 673 void *ctx) 674{ 675 size_t bytes; 676 677 /* Use size of a single frab list buffer as an arbitrary threshold */ 678 bytes = lsquic_frab_list_size(&qdh->qdh_fral); 679 if (bytes <= qdh->qdh_fral.fl_buf_size) 680 return 0; 681 else 682 { 683 LSQ_DEBUG("have %zu bytes of unsent QPACK decoder stream data: set " 684 "up callback", bytes); 685 qdh->qdh_on_dec_sent_func = func; 686 qdh->qdh_on_dec_sent_ctx = ctx; 687 return 1; 688 } 689} 690