1/* 2 * QPACK Interop -- decode from intermediate format and output QIF 3 * 4 * https://github.com/quicwg/base-drafts/wiki/QPACK-Offline-Interop 5 */ 6 7#include <assert.h> 8 9#if defined(__FreeBSD__) || defined(__DragonFly__) || defined(__NetBSD__) 10#include <sys/endian.h> 11#define bswap_16 bswap16 12#define bswap_32 bswap32 13#define bswap_64 bswap64 14#elif defined(__APPLE__) 15#include <libkern/OSByteOrder.h> 16#define bswap_16 OSSwapInt16 17#define bswap_32 OSSwapInt32 18#define bswap_64 OSSwapInt64 19#elif defined(WIN32) 20#define bswap_16 _byteswap_ushort 21#define bswap_32 _byteswap_ulong 22#define bswap_64 _byteswap_uint64 23#else 24#include <byteswap.h> 25#endif 26 27#include <errno.h> 28#include <inttypes.h> 29#include <stdio.h> 30#include <stdlib.h> 31#include <string.h> 32#ifdef WIN32 33#include <getopt.h> 34#else 35#include <unistd.h> 36#endif 37#include <sys/queue.h> 38#include <sys/types.h> 39#include <sys/stat.h> 40#include <fcntl.h> 41 42#include "lsqpack.h" 43#include "lsxpack_header.h" 44#include "xxhash.h" 45#ifndef DEBUG 46#include "lsqpack-test.h" 47#endif 48 49#ifndef NDEBUG 50struct static_table_entry 51{ 52 const char *name; 53 const char *val; 54 unsigned name_len; 55 unsigned val_len; 56}; 57 58/* [draft-ietf-quic-qpack-03] Appendix A */ 59static const struct static_table_entry static_table[] = 60{ 61 {":authority", "", 10, 0,}, 62 {":path", "/", 5, 1,}, 63 {"age", "0", 3, 1,}, 64 {"content-disposition", "", 19, 0,}, 65 {"content-length", "0", 14, 1,}, 66 {"cookie", "", 6, 0,}, 67 {"date", "", 4, 0,}, 68 {"etag", "", 4, 0,}, 69 {"if-modified-since", "", 17, 0,}, 70 {"if-none-match", "", 13, 0,}, 71 {"last-modified", "", 13, 0,}, 72 {"link", "", 4, 0,}, 73 {"location", "", 8, 0,}, 74 {"referer", "", 7, 0,}, 75 {"set-cookie", "", 10, 0,}, 76 {":method", "CONNECT", 7, 7,}, 77 {":method", "DELETE", 7, 6,}, 78 {":method", "GET", 7, 3,}, 79 {":method", "HEAD", 7, 4,}, 80 {":method", "OPTIONS", 7, 7,}, 81 {":method", "POST", 7, 4,}, 82 {":method", "PUT", 7, 3,}, 83 {":scheme", "http", 7, 4,}, 84 {":scheme", "https", 7, 5,}, 85 {":status", "103", 7, 3,}, 86 {":status", "200", 7, 3,}, 87 {":status", "304", 7, 3,}, 88 {":status", "404", 7, 3,}, 89 {":status", "503", 7, 3,}, 90 {"accept", "*/*", 6, 3,}, 91 {"accept", "application/dns-message", 6, 23,}, 92 {"accept-encoding", "gzip, deflate, br", 15, 17,}, 93 {"accept-ranges", "bytes", 13, 5,}, 94 {"access-control-allow-headers", "cache-control", 28, 13,}, 95 {"access-control-allow-headers", "content-type", 28, 12,}, 96 {"access-control-allow-origin", "*", 27, 1,}, 97 {"cache-control", "max-age=0", 13, 9,}, 98 {"cache-control", "max-age=2592000", 13, 15,}, 99 {"cache-control", "max-age=604800", 13, 14,}, 100 {"cache-control", "no-cache", 13, 8,}, 101 {"cache-control", "no-store", 13, 8,}, 102 {"cache-control", "public, max-age=31536000", 13, 24,}, 103 {"content-encoding", "br", 16, 2,}, 104 {"content-encoding", "gzip", 16, 4,}, 105 {"content-type", "application/dns-message", 12, 23,}, 106 {"content-type", "application/javascript", 12, 22,}, 107 {"content-type", "application/json", 12, 16,}, 108 {"content-type", "application/x-www-form-urlencoded", 12, 33,}, 109 {"content-type", "image/gif", 12, 9,}, 110 {"content-type", "image/jpeg", 12, 10,}, 111 {"content-type", "image/png", 12, 9,}, 112 {"content-type", "text/css", 12, 8,}, 113 {"content-type", "text/html; charset=utf-8", 12, 24,}, 114 {"content-type", "text/plain", 12, 10,}, 115 {"content-type", "text/plain;charset=utf-8", 12, 24,}, 116 {"range", "bytes=0-", 5, 8,}, 117 {"strict-transport-security", "max-age=31536000", 25, 16,}, 118 {"strict-transport-security", "max-age=31536000; includesubdomains", 119 25, 35,}, 120 {"strict-transport-security", 121 "max-age=31536000; includesubdomains; preload", 25, 44,}, 122 {"vary", "accept-encoding", 4, 15,}, 123 {"vary", "origin", 4, 6,}, 124 {"x-content-type-options", "nosniff", 22, 7,}, 125 {"x-xss-protection", "1; mode=block", 16, 13,}, 126 {":status", "100", 7, 3,}, 127 {":status", "204", 7, 3,}, 128 {":status", "206", 7, 3,}, 129 {":status", "302", 7, 3,}, 130 {":status", "400", 7, 3,}, 131 {":status", "403", 7, 3,}, 132 {":status", "421", 7, 3,}, 133 {":status", "425", 7, 3,}, 134 {":status", "500", 7, 3,}, 135 {"accept-language", "", 15, 0,}, 136 {"access-control-allow-credentials", "FALSE", 32, 5,}, 137 {"access-control-allow-credentials", "TRUE", 32, 4,}, 138 {"access-control-allow-headers", "*", 28, 1,}, 139 {"access-control-allow-methods", "get", 28, 3,}, 140 {"access-control-allow-methods", "get, post, options", 28, 18,}, 141 {"access-control-allow-methods", "options", 28, 7,}, 142 {"access-control-expose-headers", "content-length", 29, 14,}, 143 {"access-control-request-headers", "content-type", 30, 12,}, 144 {"access-control-request-method", "get", 29, 3,}, 145 {"access-control-request-method", "post", 29, 4,}, 146 {"alt-svc", "clear", 7, 5,}, 147 {"authorization", "", 13, 0,}, 148 {"content-security-policy", 149 "script-src 'none'; object-src 'none'; base-uri 'none'", 23, 53,}, 150 {"early-data", "1", 10, 1,}, 151 {"expect-ct", "", 9, 0,}, 152 {"forwarded", "", 9, 0,}, 153 {"if-range", "", 8, 0,}, 154 {"origin", "", 6, 0,}, 155 {"purpose", "prefetch", 7, 8,}, 156 {"server", "", 6, 0,}, 157 {"timing-allow-origin", "*", 19, 1,}, 158 {"upgrade-insecure-requests", "1", 25, 1,}, 159 {"user-agent", "", 10, 0,}, 160 {"x-forwarded-for", "", 15, 0,}, 161 {"x-frame-options", "deny", 15, 4,}, 162 {"x-frame-options", "sameorigin", 15, 10,}, 163}; 164#endif 165 166static size_t s_max_read_size = SIZE_MAX; 167 168static int s_verbose; 169static enum lsqpack_dec_opts s_dec_opts = LSQPACK_DEC_OPT_HASH_NAME 170 | LSQPACK_DEC_OPT_HASH_NAMEVAL; 171 172static int s_check_unset_qpack_idx = 1; 173 174static FILE *s_out; 175 176#define MIN(a, b) ((a) < (b) ? (a) : (b)) 177 178static void 179usage (const char *name) 180{ 181 fprintf(stderr, 182"Usage: %s [options] [-i input] [-o output]\n" 183"\n" 184"Options:\n" 185" -i FILE Input file. If not specified or set to `-', the input is\n" 186" read from stdin.\n" 187" -o FILE Output file. If not spepcified or set to `-', the output\n" 188" is written to stdout.\n" 189" -r FILE Recipe file. Without a recipe, buffers are processed in\n" 190" order.\n" 191" -s NUMBER Maximum number of risked streams. Defaults to %u.\n" 192" -t NUMBER Dynamic table size. Defaults to %u.\n" 193" -m NUMBER Maximum read size. Defaults to %zu.\n" 194" -H [0|1] Use HTTP/1.x mode and test each header (defaults to `off').\n" 195" -v Verbose: print headers and table state to stderr.\n" 196" -S Don't swap encoder stream and header blocks.\n" 197" -Q Don't check static table when LSXPACK_QPACK_IDX is not set.\n" 198"\n" 199" -h Print this help screen and exit\n" 200 , name, LSQPACK_DEF_MAX_RISKED_STREAMS, LSQPACK_DEF_DYN_TABLE_SIZE, SIZE_MAX); 201} 202 203 204struct buf 205{ 206 TAILQ_ENTRY(buf) next_buf; 207 struct lsqpack_dec *dec; 208 uint64_t stream_id; /* Zero means encoder stream */ 209 size_t size; 210 size_t off; 211 size_t file_off; 212 213 /* A single header name/value pair is stored in xhdr_buf. When the 214 * header is done, the whole buffer can be used again for the next 215 * header. 216 */ 217 struct lsxpack_header xhdr; 218 unsigned xhdr_off; /* Used in xhdr_buf */ 219 unsigned xhdr_nalloc; /* Number of bytes allocated */ /* TODO: use it */ 220 221 /* Output is written to out_buf out header at a time and the printed all 222 * at once. This logic is not very important and we use a reasonably 223 * large fixed-size buffer. 224 */ 225 unsigned out_off; 226 char out_buf[0x1000]; 227 228 unsigned char buf[0]; 229}; 230 231 232TAILQ_HEAD(, buf) bufs = TAILQ_HEAD_INITIALIZER(bufs); 233 234static void 235hblock_unblocked (void *buf_p) 236{ 237 struct buf *buf = buf_p; 238 TAILQ_INSERT_HEAD(&bufs, buf, next_buf); 239} 240 241 242static struct lsxpack_header * 243prepare_decode (void *hblock_ctx, struct lsxpack_header *xhdr, size_t space) 244{ 245 struct buf *const buf = hblock_ctx; 246 char *new; 247 248 if (space > LSXPACK_MAX_STRLEN) 249 return NULL; 250 251 if (xhdr) 252 { 253 assert(xhdr == &buf->xhdr); 254 assert(space > xhdr->val_len); 255 new = realloc(xhdr->buf, space); 256 if (!new) 257 return NULL; 258 xhdr->buf = new; 259 xhdr->val_len = space; 260 return xhdr; 261 } 262 else 263 { 264 xhdr = &buf->xhdr; 265 new = malloc(space); 266 if (!new) 267 return NULL; 268 lsxpack_header_prepare_decode(xhdr, new, 0, space); 269 return xhdr; 270 } 271} 272 273 274static int 275process_header (void *hblock_ctx, struct lsxpack_header *xhdr) 276{ 277 struct buf *const buf = hblock_ctx; 278 const char *p; 279 const uint32_t seed = 39378473; 280 uint32_t hash, name_hash; 281 int nw; 282 283 if (s_dec_opts & LSQPACK_DEC_OPT_HTTP1X) 284 { 285 p = lsxpack_header_get_name(xhdr) + xhdr->name_len; 286 assert(0 == memcmp(p, ": ", 2)); 287 p += 2 + xhdr->val_len; 288 assert(0 == memcmp(p, "\r\n", 2)); 289 assert(xhdr->dec_overhead == 4); 290 } 291 else 292 assert(xhdr->dec_overhead == 0); 293 294 if (s_dec_opts & LSQPACK_DEC_OPT_HASH_NAME) 295 { 296 assert(xhdr->flags & LSXPACK_NAME_HASH); 297 hash = XXH32(lsxpack_header_get_name(xhdr), xhdr->name_len, seed); 298 assert(hash == xhdr->name_hash); 299 } 300 301 if (s_dec_opts & LSQPACK_DEC_OPT_HASH_NAME) 302 assert(xhdr->flags & LSXPACK_NAME_HASH); 303 304 if (xhdr->flags & LSXPACK_NAME_HASH) 305 { 306 name_hash = XXH32(lsxpack_header_get_name(xhdr), xhdr->name_len, seed); 307 assert(hash == xhdr->name_hash); 308 } 309#ifndef NDEBUG 310 else if (!(xhdr->flags & LSXPACK_QPACK_IDX)) 311 /* Calculate for upcoming "not in static table check" */ 312 name_hash = XXH32(lsxpack_header_get_name(xhdr), xhdr->name_len, seed); 313#endif 314 315 if (s_dec_opts & LSQPACK_DEC_OPT_HASH_NAMEVAL) 316 { 317 /* This is not required by the API, but internally, if the library 318 * calculates nameval hash, it should also set the name hash. 319 */ 320 assert(xhdr->flags & LSXPACK_NAME_HASH); 321 assert(xhdr->flags & LSXPACK_NAMEVAL_HASH); 322 } 323 324 if (xhdr->flags & LSXPACK_NAMEVAL_HASH) 325 { 326 hash = XXH32(lsxpack_header_get_name(xhdr), xhdr->name_len, seed); 327 hash = XXH32(lsxpack_header_get_value(xhdr), xhdr->val_len, hash); 328 assert(hash == xhdr->nameval_hash); 329 } 330 331#ifndef NDEBUG 332 if (xhdr->flags & LSXPACK_QPACK_IDX) 333 { 334 assert(xhdr->qpack_index < 335 sizeof(static_table) / sizeof(static_table[0])); 336 assert(static_table[xhdr->qpack_index].name_len == xhdr->name_len); 337 assert(0 == memcmp(lsxpack_header_get_name(xhdr), 338 static_table[xhdr->qpack_index].name, xhdr->name_len)); 339 } 340 else if (s_check_unset_qpack_idx) 341 { 342 /* The decoder does best effort: if the encoder did not use the 343 * static table, QPACK index is not set. However, since we are 344 * testing our decoder, we assume that the encoder always uses 345 * the static table when it can. 346 */ 347 int idx = lsqpack_find_in_static_headers(name_hash, 348 lsxpack_header_get_name(xhdr), xhdr->name_len); 349 assert(idx < 0); 350 } 351#endif 352 353 nw = snprintf(buf->out_buf + buf->out_off, 354 sizeof(buf->out_buf) - buf->out_off, 355 "%.*s\t%.*s\n", 356 (int) xhdr->name_len, lsxpack_header_get_name(xhdr), 357 (int) xhdr->val_len, lsxpack_header_get_value(xhdr)); 358 free(buf->xhdr.buf); 359 memset(&buf->xhdr, 0, sizeof(buf->xhdr)); 360 if (nw > 0 && (size_t) nw <= sizeof(buf->out_buf) - buf->out_off) 361 { 362 buf->out_off += (unsigned) nw; 363 return 0; 364 } 365 else 366 { 367 fprintf(stderr, "header list too long\n"); 368 return -1; 369 } 370} 371 372 373static const struct lsqpack_dec_hset_if hset_if = { 374 .dhi_unblocked = hblock_unblocked, 375 .dhi_prepare_decode = prepare_decode, 376 .dhi_process_header = process_header, 377}; 378 379 380static void 381header_block_done (const struct buf *buf) 382{ 383 fprintf(s_out, "# stream %"PRIu64"\n", buf->stream_id); 384 fprintf(s_out, "# (stream ID above is used for sorting)\n"); 385 fprintf(s_out, "%.*s\n", (int) buf->out_off, buf->out_buf); 386} 387 388 389int 390main (int argc, char **argv) 391{ 392 FILE *in = stdin; 393 FILE *recipe = NULL; 394 int opt; 395 unsigned dyn_table_size = LSQPACK_DEF_DYN_TABLE_SIZE, 396 max_risked_streams = LSQPACK_DEF_MAX_RISKED_STREAMS; 397 struct lsqpack_dec decoder; 398 const struct lsqpack_dec_err *err; 399 const unsigned char *p; 400 ssize_t nr; 401 int r; 402 uint64_t stream_id; 403 uint32_t size; 404 size_t off; /* For debugging */ 405 size_t file_off; 406 struct buf *buf; 407 unsigned lineno; 408 char *line, *end; 409 enum lsqpack_read_header_status rhs; 410 struct lsqpack_header_list *hlist; 411 int do_swap = 1; 412 char command[0x100]; 413 char line_buf[0x100]; 414 415 while (-1 != (opt = getopt(argc, argv, "i:o:r:s:t:m:hvH:SQ"))) 416 { 417 switch (opt) 418 { 419 case 'i': 420 if (0 != strcmp(optarg, "-")) 421 { 422 in = fopen(optarg, "rb"); 423 if (!in) 424 { 425 fprintf(stderr, "cannot open `%s' for reading: %s\n", 426 optarg, strerror(errno)); 427 exit(EXIT_FAILURE); 428 } 429 } 430 break; 431 case 'o': 432 if (0 != strcmp(optarg, "-")) 433 { 434 s_out = fopen(optarg, "w"); 435 if (!s_out) 436 { 437 fprintf(stderr, "cannot open `%s' for writing: %s\n", 438 optarg, strerror(errno)); 439 exit(EXIT_FAILURE); 440 } 441 } 442 break; 443 case 'r': 444 if (0 == strcmp(optarg, "-")) 445 recipe = stdin; 446 else 447 { 448 recipe = fopen(optarg, "r"); 449 if (!recipe) 450 { 451 fprintf(stderr, "cannot open `%s' for reading: %s\n", 452 optarg, strerror(errno)); 453 exit(EXIT_FAILURE); 454 } 455 } 456 break; 457 case 's': 458 max_risked_streams = atoi(optarg); 459 break; 460 case 't': 461 dyn_table_size = atoi(optarg); 462 break; 463 case 'm': 464 s_max_read_size = atoi(optarg); 465 break; 466 case 'h': 467 usage(argv[0]); 468 exit(EXIT_SUCCESS); 469 case 'v': 470 ++s_verbose; 471 break; 472 case 'S': 473 do_swap = 0; 474 break; 475 case 'H': 476 if (atoi(optarg)) 477 s_dec_opts |= LSQPACK_DEC_OPT_HTTP1X; 478 else 479 s_dec_opts &= ~LSQPACK_DEC_OPT_HTTP1X; 480 break; 481 case 'Q': 482 s_check_unset_qpack_idx = 0; 483 break; 484 default: 485 exit(EXIT_FAILURE); 486 } 487 } 488 489 if (!s_out) 490 s_out = stdout; 491 492 lsqpack_dec_init(&decoder, s_verbose ? stderr : NULL, dyn_table_size, 493 max_risked_streams, &hset_if, s_dec_opts); 494 495 off = 0; 496 while (1) 497 { 498 file_off = off; 499 nr = fread(&stream_id, 1, sizeof(stream_id), in); 500 if (nr == 0) 501 break; 502 if (nr != sizeof(stream_id)) 503 { 504 fprintf(stderr, "could not read %zu bytes (stream id) at " 505 "offset %zu: %s\n", sizeof(stream_id), off, strerror(errno)); 506 goto read_err; 507 } 508 off += nr; 509 nr = fread(&size, 1, sizeof(size), in); 510 if (nr != sizeof(size)) 511 { 512 fprintf(stderr, "could not read %zu bytes (size) at " 513 "offset %zu: %s\n", sizeof(size), off, strerror(errno)); 514 goto read_err; 515 } 516 off += nr; 517#if __BYTE_ORDER == __LITTLE_ENDIAN 518 stream_id = bswap_64(stream_id); 519 size = bswap_32(size); 520#endif 521 if (stream_id == 0 && size == 0) 522 continue; 523 buf = malloc(sizeof(*buf) + size); 524 if (!buf) 525 { 526 perror("malloc"); 527 exit(EXIT_FAILURE); 528 } 529 memset(buf, 0, sizeof(*buf)); 530 nr = fread(buf->buf, 1, size, in); 531 if (nr != (ssize_t) size) 532 { 533 fprintf(stderr, "could not read %"PRIu32" bytes (buffer) at " 534 "offset %zu: %s\n", size, off, strerror(errno)); 535 goto read_err; 536 } 537 off += nr; 538 buf->dec = &decoder; 539 buf->stream_id = stream_id; 540 buf->size = size; 541 buf->file_off = file_off; 542 if (buf->size == 0) 543 exit(EXIT_FAILURE); 544 TAILQ_INSERT_TAIL(&bufs, buf, next_buf); 545 } 546 (void) fclose(in); 547 in = NULL; 548 549 if (recipe) 550 { 551 lineno = 0; 552 while (line = fgets(line_buf, sizeof(line_buf), recipe), line != NULL) 553 { 554 ++lineno; 555 end = strchr(line, '\n'); 556 if (!end) 557 { 558 fprintf(stderr, "no newline on line %u\n", lineno); 559 exit(EXIT_FAILURE); 560 } 561 *end = '\0'; 562 563 if (*line == '#') 564 continue; 565 566 if (3 == sscanf(line, " %[s] %"PRIu64" %"PRIu32" ", command, &stream_id, &size)) 567 { 568 TAILQ_FOREACH(buf, &bufs, next_buf) 569 if (stream_id == buf->stream_id) 570 break; 571 if (!buf) 572 { 573 fprintf(stderr, "stream %"PRIu64" not found (recipe line %u)\n", 574 stream_id, lineno); 575 exit(EXIT_FAILURE); 576 } 577 p = buf->buf; 578 rhs = lsqpack_dec_header_in(&decoder, buf, stream_id, 579 buf->size, &p, 580 buf->size /* FIXME: this should be `size' */, 581 NULL, NULL); 582 switch (rhs) 583 { 584 case LQRHS_DONE: 585 assert(p == buf->buf + buf->size); 586 header_block_done(buf); 587 if (s_verbose) 588 fprintf(stderr, "compression ratio: %.3f\n", 589 lsqpack_dec_ratio(&decoder)); 590 TAILQ_REMOVE(&bufs, buf, next_buf); 591 free(buf); 592 break; 593 case LQRHS_BLOCKED: 594 buf->off += (unsigned) (p - buf->buf); 595 TAILQ_REMOVE(&bufs, buf, next_buf); 596 break; 597 case LQRHS_NEED: 598 buf->off += (unsigned) (p - buf->buf); 599 break; 600 default: 601 assert(rhs == LQRHS_ERROR); 602 fprintf(stderr, "recipe line %u: stream %"PRIu64": " 603 "header_in error\n", lineno, stream_id); 604 exit(EXIT_FAILURE); 605 } 606 } 607 else if (2 == sscanf(line, " %[z] %u ", command, &size)) 608 { 609 s_max_read_size = size; 610 } 611 else 612 { 613 perror("sscanf"); 614 exit(EXIT_FAILURE); 615 } 616 } 617 fclose(recipe); 618 } 619 else if (do_swap && max_risked_streams && dyn_table_size 620 && TAILQ_FIRST(&bufs) && TAILQ_FIRST(&bufs)->stream_id) 621 { 622 /* Swap header blocks and encoder stream bufs to exercise blocked 623 * header blocks logic. 624 */ 625 struct buf *saved_hblock = NULL, *next; 626 for (buf = TAILQ_FIRST(&bufs); buf; buf = next) 627 { 628 next = TAILQ_NEXT(buf, next_buf); 629 if (buf->stream_id) 630 continue; 631 if (saved_hblock) 632 TAILQ_INSERT_BEFORE(buf, saved_hblock, next_buf); 633 saved_hblock = buf; 634 TAILQ_REMOVE(&bufs, buf, next_buf); 635 } 636 TAILQ_INSERT_TAIL(&bufs, saved_hblock, next_buf); 637 } 638 639 while (buf = TAILQ_FIRST(&bufs), buf != NULL) 640 { 641 TAILQ_REMOVE(&bufs, buf, next_buf); 642 if (buf->stream_id == 0) 643 { 644 r = lsqpack_dec_enc_in(&decoder, buf->buf, buf->size - buf->off); 645 if (r != 0) 646 { 647 err = lsqpack_dec_get_err_info(buf->dec); 648 fprintf(stderr, "encoder_in error; off %"PRIu64", line %d\n", 649 err->off, err->line); 650 exit(EXIT_FAILURE); 651 } 652 if (s_verbose) 653 lsqpack_dec_print_table(&decoder, stderr); 654 free(buf); 655 } 656 else 657 { 658 dec_header: 659 p = buf->buf + buf->off; 660 if (buf->off == 0) 661 rhs = lsqpack_dec_header_in(&decoder, buf, buf->stream_id, 662 buf->size, &p, MIN(s_max_read_size, buf->size), 663 NULL, NULL); 664 else 665 rhs = lsqpack_dec_header_read(buf->dec, buf, &p, 666 MIN(s_max_read_size, (buf->size - buf->off)), 667 NULL, NULL); 668 switch (rhs) 669 { 670 case LQRHS_DONE: 671 assert(p == buf->buf + buf->size); 672 header_block_done(buf); 673 if (s_verbose) 674 fprintf(stderr, "compression ratio: %.3f\n", 675 lsqpack_dec_ratio(&decoder)); 676 free(buf); 677 break; 678 case LQRHS_BLOCKED: 679 buf->off = (unsigned) (p - buf->buf); 680 break; 681 case LQRHS_NEED: 682 buf->off = (unsigned) (p - buf->buf); 683 goto dec_header; 684 default: 685 assert(rhs == LQRHS_ERROR); 686 fprintf(stderr, "stream %"PRIu64": header block error " 687 "starting at off %zu\n", buf->stream_id, buf->off); 688 err = lsqpack_dec_get_err_info(&decoder); 689 fprintf(stderr, "encoder_in error; off %"PRIu64", line %d\n", 690 err->off, err->line); 691 exit(EXIT_FAILURE); 692 } 693 } 694 } 695 696 if (!TAILQ_EMPTY(&bufs)) 697 { 698 fprintf(stderr, "some streams reamain\n"); 699 exit(EXIT_FAILURE); 700 } 701 /* TODO: check if decoder has any stream references. That would be 702 * an error. 703 */ 704 705 if (s_verbose) 706 lsqpack_dec_print_table(&decoder, stderr); 707 708 lsqpack_dec_cleanup(&decoder); 709 710 assert(TAILQ_EMPTY(&bufs)); 711 712 if (s_out) 713 (void) fclose(s_out); 714 715 exit(EXIT_SUCCESS); 716 717 read_err: 718 if (nr < 0) 719 perror("read"); 720 else if (nr == 0) 721 fprintf(stderr, "unexpected EOF\n"); 722 else 723 fprintf(stderr, "not enough bytes read (%zu)\n", (size_t) nr); 724 exit(EXIT_FAILURE); 725} 726