src/http3/qpack/xqc_decoder.c (310 lines of code) (raw):

/** * @copyright Copyright (c) 2022, Alibaba Group Holding Limited */ #include "src/http3/qpack/xqc_decoder.h" #include "src/http3/xqc_h3_header.h" typedef struct xqc_decoder_s { xqc_dtable_t *dtable; /* * max dtable capacity is the configured size of dynamic table, this value is used to decode * the Required Insert Count in encoded filed sections */ size_t max_ents; /* log handler */ xqc_log_t *log; } xqc_decoder_s; xqc_decoder_t * xqc_decoder_create(xqc_log_t *log, size_t max_dtable_cap) { xqc_decoder_t *dec = xqc_malloc(sizeof(xqc_decoder_t)); if (dec == NULL) { return NULL; } dec->dtable = xqc_dtable_create(XQC_QPACK_DEFAULT_HASH_TABLE_SIZE, log, 0); if (dec->dtable == NULL) { xqc_free(dec); return NULL; } dec->max_ents = xqc_dtable_max_entry_cnt(max_dtable_cap); dec->log = log; return dec; } void xqc_decoder_destroy(xqc_decoder_t *dec) { if (dec) { if (dec->dtable) { xqc_dtable_free(dec->dtable); } xqc_free(dec); } } xqc_int_t xqc_decoder_index(xqc_decoder_t *dec, xqc_flag_t t, uint64_t idx, xqc_var_buf_t *name_buf, xqc_var_buf_t *value_buf) { xqc_log(dec->log, XQC_LOG_DEBUG, "|decode indexed|t:%d|idx:%ui|", t, idx); if (name_buf->data_len != 0 || value_buf->data_len != 0) { xqc_log(dec->log, XQC_LOG_ERROR, "|nv not clear|name_len:%uz|value_len:%uz|", name_buf->data_len, value_buf->data_len); } xqc_var_buf_clear(name_buf); xqc_var_buf_clear(value_buf); if (t == XQC_DTABLE_FLAG) { return xqc_dtable_get_nv(dec->dtable, idx, name_buf, value_buf); } else { return xqc_stable_get_nv(idx, name_buf, value_buf); } } xqc_int_t xqc_decoder_name_index(xqc_decoder_t *dec, xqc_flag_t t, uint64_t idx, xqc_var_buf_t *name_buf) { xqc_log(dec->log, XQC_LOG_DEBUG, "|decode name indexed|t:%d|idx:%ui|", t, idx); if (name_buf->data_len != 0) { xqc_log(dec->log, XQC_LOG_ERROR, "|nv not clear|name_len:%uz|", name_buf->data_len); } xqc_var_buf_clear(name_buf); if (t == XQC_DTABLE_FLAG) { return xqc_dtable_get_nv(dec->dtable, idx, name_buf, NULL); } else { return xqc_stable_get_nv(idx, name_buf, NULL); } } xqc_int_t xqc_decoder_copy_header(xqc_http_header_t *hdr, xqc_var_buf_t *name, xqc_var_buf_t *value) { hdr->name.iov_len = name->data_len; xqc_int_t ret = xqc_var_buf_save_prepare(name, 1); if (ret != XQC_OK) { return ret; } hdr->name.iov_base = xqc_var_buf_take_over(name); hdr->value.iov_len = value->data_len; ret = xqc_var_buf_save_prepare(value, 1); if (ret != XQC_OK) { return ret; } hdr->value.iov_base = xqc_var_buf_take_over(value); *((unsigned char *)hdr->name.iov_base + hdr->name.iov_len) = '\0'; *((unsigned char *)hdr->value.iov_base + hdr->value.iov_len) = '\0'; return XQC_OK; } xqc_int_t xqc_decoder_save_hdr(xqc_decoder_t *dec, xqc_rep_ctx_t *ctx, xqc_http_header_t *hdr) { xqc_int_t ret; xqc_var_buf_t *name = ctx->name->value; xqc_var_buf_t *value = ctx->value->value; /* convert index to string */ switch (ctx->type) { case XQC_REP_TYPE_INDEXED: case XQC_REP_TYPE_POST_BASE_INDEXED: ret = xqc_decoder_index(dec, ctx->table, ctx->index.value, name, value); if (ret != XQC_OK) { xqc_log(dec->log, XQC_LOG_ERROR, "|decode indexed field line error|type:%d" "|base:%ui|ret:%d|", ctx->type, ctx->base.value, ret); return -XQC_QPACK_DECODER_ERROR; } /* restore never flag */ hdr->flags = ctx->never ? XQC_HTTP_HEADER_FLAG_NEVER_INDEX : XQC_HTTP_HEADER_FLAG_NONE; break; case XQC_REP_TYPE_NAME_REFERENCE: case XQC_REP_TYPE_POST_BASE_NAME_REFERENCE: ret = xqc_decoder_name_index(dec, ctx->table, ctx->index.value, name); if (ret != XQC_OK) { xqc_log(dec->log, XQC_LOG_ERROR, "|decode name indexed field line error|type:%d|" "base:%ui|ret:%d|", ctx->type, ctx->base.value, ret); return -XQC_QPACK_DECODER_ERROR; } /* restore never flag */ hdr->flags = ctx->never ? XQC_HTTP_HEADER_FLAG_NEVER_INDEX_VALUE : XQC_HTTP_HEADER_FLAG_NONE; break; case XQC_REP_TYPE_LITERAL: break; default: xqc_log(dec->log, XQC_LOG_ERROR, "|unknown field line|type:%d|", ctx->type); return -XQC_QPACK_DECODER_ERROR; } /* copy name and value */ return xqc_decoder_copy_header(hdr, name, value); } ssize_t xqc_decoder_dec_header(xqc_decoder_t *dec, xqc_rep_ctx_t *ctx, unsigned char *buf, size_t buf_len, xqc_http_header_t *hdr, xqc_bool_t *blocked) { ssize_t processed = 0; ssize_t read = 0; /* decode efs prefix first */ if (ctx->state < XQC_REP_DECODE_STATE_OPCODE) { read = xqc_rep_decode_prefix(ctx, dec->max_ents, xqc_dtable_get_insert_cnt(dec->dtable), buf, buf_len); if (read < 0) { xqc_log(dec->log, XQC_LOG_ERROR, "|decode prefix error|processed:%z|state:%d" "|max_ents:%uz|icnt:%ui|", read, ctx->state, dec->max_ents, xqc_dtable_get_insert_cnt(dec->dtable)); return -XQC_QPACK_DECODER_ERROR; } processed += read; if (ctx->state == XQC_REP_DECODE_STATE_OPCODE) { xqc_log(dec->log, XQC_LOG_DEBUG, "|encoded field section prefix|ric:%ui|s:%d|db:%ui|", ctx->ric.value, ctx->sign, ctx->base.value); xqc_log_event(dec->log, QPACK_HEADERS_DECODED, XQC_LOG_BLOCK_PREFIX, ctx->ric.value, ctx->base.value); } } /* ric not satisfied, blocked */ if (ctx->state > XQC_REP_DECODE_STATE_RICNT && ctx->ric.value > xqc_dtable_get_insert_cnt(dec->dtable)) { *blocked = XQC_TRUE; return processed; } if (ctx->state >= XQC_REP_DECODE_STATE_OPCODE) { /* decode one field line */ read = xqc_rep_decode_field_line(ctx, buf + processed, buf_len - processed); if (read < 0) { xqc_log(dec->log, XQC_LOG_ERROR, "|decode field line error|type:%d|state:%d|" "processed:%z|", ctx->type, ctx->state, read); if (ctx->state == XQC_REP_DECODE_STATE_NAME && ctx->name->huff_flag > 0) { xqc_log(dec->log, XQC_LOG_ERROR, "|decode name error|pre_state:%d|end:%d|bit:%d|", ctx->name->huff_ctx.pre_state, ctx->name->huff_ctx.end, ctx->name->huff_ctx.bit); } if (ctx->state == XQC_REP_DECODE_STATE_VALUE && ctx->value->huff_flag > 0) { xqc_log(dec->log, XQC_LOG_ERROR, "|decode value error|pre_state:%d|end:%d|bit:%d|", (unsigned int)ctx->value->huff_ctx.pre_state, (unsigned int)ctx->value->huff_ctx.end, (unsigned int)ctx->value->huff_ctx.bit); } return -XQC_QPACK_DECODER_ERROR; } processed += read; /* finish decoding a field line, copy result to header, and reset ctx */ if (ctx->state == XQC_REP_DECODE_STATE_FINISH) { xqc_log(dec->log, XQC_LOG_DEBUG, "|decode one field line|type:%d|", ctx->type); xqc_int_t ret = xqc_decoder_save_hdr(dec, ctx, hdr); if (ret != XQC_OK) { xqc_log(dec->log, XQC_LOG_ERROR, "|save header error|ret:%d|", ret); return ret; } xqc_log_event(dec->log, QPACK_HEADERS_DECODED, XQC_LOG_HEADER_BLOCK, ctx, hdr); } } return processed; } xqc_int_t xqc_decoder_set_dtable_cap(xqc_decoder_t *dec, uint64_t cap) { xqc_log(dec->log, XQC_LOG_DEBUG, "|on set dtable cap|cap:%ui|", cap); xqc_log_event(dec->log, QPACK_STATE_UPDATED, XQC_LOG_DECODER_EVENT, dec->dtable); return xqc_dtable_set_capacity(dec->dtable, cap); } xqc_int_t xqc_decoder_duplicate(xqc_decoder_t *dec, uint64_t idx) { uint64_t new_idx = 0; uint64_t base_idx = xqc_dtable_get_insert_cnt(dec->dtable); idx = xqc_brel2abs(base_idx, idx); xqc_int_t ret = xqc_dtable_duplicate(dec->dtable, idx, &new_idx); if (ret != XQC_OK) { xqc_log(dec->log, XQC_LOG_ERROR, "|duplicate entry error|ret:%d|idx:%ui|", ret, idx); return -XQC_QPACK_DECODER_ERROR; } xqc_log(dec->log, XQC_LOG_DEBUG, "|on duplicate|idx:%ui|new_idx:%ui|ret:%d|", idx, new_idx, ret); xqc_log_event(dec->log, QPACK_STATE_UPDATED, XQC_LOG_DECODER_EVENT, dec->dtable); return XQC_OK; } xqc_int_t xqc_decoder_insert_literal(xqc_decoder_t *dec, unsigned char *name, size_t nlen, unsigned char *value, size_t vlen) { uint64_t idx = XQC_INVALID_INDEX; xqc_int_t ret = xqc_dtable_add(dec->dtable, name, nlen, value, vlen, &idx); if (ret != XQC_OK) { xqc_log(dec->log, XQC_LOG_ERROR, "|insert entry error|ret:%d|name:%*s|value:%*s|", ret, (size_t) xqc_min(nlen, 512), name, (size_t) xqc_min(vlen, 512), value); return -XQC_QPACK_DECODER_ERROR; } xqc_log(dec->log, XQC_LOG_DEBUG, "|on insert literal|idx:%ui|ret:%d|nlen:%uz|name:%*s|vlen:%uz|" "value:%*s|", idx, ret, nlen, (size_t) xqc_min(nlen, 512), name, vlen, (size_t) xqc_min(vlen, 512), value); xqc_log_event(dec->log, QPACK_STATE_UPDATED, XQC_LOG_DECODER_EVENT, dec->dtable); return XQC_OK; } xqc_int_t xqc_decoder_insert_name_ref(xqc_decoder_t *dec, xqc_flag_t t, uint64_t nidx, unsigned char *value, size_t vlen) { uint64_t idx; xqc_var_buf_t *nbuf = xqc_var_buf_create(XQC_HTTP3_QPACK_MAX_NAMELEN); if (nbuf == NULL) { return -XQC_EMALLOC; } if (t == XQC_DTABLE_FLAG) { nidx = xqc_brel2abs(xqc_dtable_get_insert_cnt(dec->dtable), nidx); } xqc_int_t ret = xqc_decoder_name_index(dec, t, nidx, nbuf); if (ret != XQC_OK) { xqc_log(dec->log, XQC_LOG_ERROR, "|name index error|ret:%d|nidx:%ui|", ret, nidx); xqc_var_buf_free(nbuf); return -XQC_QPACK_DECODER_ERROR; } ret = xqc_dtable_add(dec->dtable, nbuf->data, nbuf->data_len, value, vlen, &idx); if (ret != XQC_OK) { xqc_log(dec->log, XQC_LOG_ERROR, "|insert entry error|ret:%d|nidx:%ui|value:%*s|", ret, nidx, (size_t) xqc_min(vlen, 512), value); xqc_var_buf_free(nbuf); return -XQC_QPACK_DECODER_ERROR; } xqc_log(dec->log, XQC_LOG_DEBUG, "|on insert name ref|nidx:%ui|value:%*s|idx:%ui|", nidx, (size_t) xqc_min(vlen, 512), value, idx); xqc_log_event(dec->log, QPACK_STATE_UPDATED, XQC_LOG_DECODER_EVENT, dec->dtable); xqc_var_buf_free(nbuf); return XQC_OK; } uint64_t xqc_decoder_get_insert_cnt(xqc_decoder_t *dec) { return xqc_dtable_get_insert_cnt(dec->dtable); } void xqc_log_QPACK_HEADERS_DECODED_callback(xqc_log_t *log, const char *func, ...) { va_list args; va_start(args, func); xqc_int_t type = va_arg(args, xqc_int_t); if (type == XQC_LOG_BLOCK_PREFIX) { uint64_t ricnt = va_arg(args, uint64_t); uint64_t base = va_arg(args, uint64_t); xqc_log_implement(log, QPACK_HEADERS_DECODED, func, "|prefix|ricnt:%ui|base:%ui|", ricnt, base); } else if (type == XQC_LOG_HEADER_BLOCK) { xqc_rep_ctx_t *ctx = va_arg(args, xqc_rep_ctx_t*); xqc_http_header_t *hdr = va_arg(args, xqc_http_header_t*); switch (ctx->type) { case XQC_REP_TYPE_INDEXED: case XQC_REP_TYPE_POST_BASE_INDEXED: { xqc_flag_t pb = ctx->type == XQC_REP_TYPE_POST_BASE_INDEXED; xqc_log_implement(log, QPACK_HEADERS_DECODED, func, "|header|indexed field line|%s%s|index:%ui|", ctx->table == XQC_DTABLE_FLAG ? "dtable" : "stable", pb ? "" : "|post base", ctx->index.value); break; } case XQC_REP_TYPE_NAME_REFERENCE: case XQC_REP_TYPE_POST_BASE_NAME_REFERENCE: { xqc_flag_t pb = ctx->type == XQC_REP_TYPE_POST_BASE_NAME_REFERENCE; xqc_log_implement(log, QPACK_HEADERS_DECODED, func, "|header|literal with name reference|%s%s|index:%ui|value:%*s|", ctx->table == XQC_DTABLE_FLAG ? "|dtable" : "|stable", pb ? "" : "|post base", ctx->index.value, (size_t) hdr->value.iov_len, hdr->value.iov_base); break; } case XQC_REP_TYPE_LITERAL: if (hdr->value.iov_len > 0) { xqc_log_implement(log, QPACK_HEADERS_DECODED, func, "|header|literal|name:%*s|value:%*s|", (size_t) hdr->name.iov_len, hdr->name.iov_base, (size_t) hdr->value.iov_len, hdr->value.iov_base); } else { xqc_log_implement(log, QPACK_HEADERS_DECODED, func, "|header|literal|name:%*s|", (size_t) hdr->name.iov_len, hdr->name.iov_base); } break; } } else { uint64_t stream_id = va_arg(args, uint64_t); uint64_t length = va_arg(args, uint64_t); xqc_log_implement(log, QPACK_HEADERS_DECODED, func, "|frame|stream_id:%ui|length:%ui|", stream_id, length); } va_end(args); }