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