src/proxy/http3/QPACK.cc (1,311 lines of code) (raw):

/** @file * * A brief file description * * @section license License * * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "proxy/hdrs/HTTP.h" #include "proxy/hdrs/XPACK.h" #include "proxy/http3/QPACK.h" #include "tscore/ink_defs.h" #include "tscore/ink_memory.h" #define QPACKDebug(fmt, ...) Dbg(dbg_ctl_qpack, "[%s] " fmt, this->_qc->cids().data(), ##__VA_ARGS__) #define QPACKDTDebug(fmt, ...) Dbg(dbg_ctl_qpack, "" fmt, ##__VA_ARGS__) namespace { DbgCtl dbg_ctl_qpack{"qpack"}; } // end anonymous namespace // qpack-05 Appendix A. const QPACK::Header QPACK::StaticTable::STATIC_HEADER_FIELDS[] = { {":authority", "" }, {":path", "/" }, {"age", "0" }, {"content-disposition", "" }, {"content-length", "0" }, {"cookie", "" }, {"date", "" }, {"etag", "" }, {"if-modified-since", "" }, {"if-none-match", "" }, {"last-modified", "" }, {"link", "" }, {"location", "" }, {"referer", "" }, {"set-cookie", "" }, {":method", "CONNECT" }, {":method", "DELETE" }, {":method", "GET" }, {":method", "HEAD" }, {":method", "OPTIONS" }, {":method", "POST" }, {":method", "PUT" }, {":scheme", "http" }, {":scheme", "https" }, {":status", "103" }, {":status", "200" }, {":status", "304" }, {":status", "404" }, {":status", "503" }, {"accept", "*/*" }, {"accept", "application/dns-message" }, {"accept-encoding", "gzip, deflate, br" }, {"accept-ranges", "bytes" }, {"access-control-allow-headers", "cache-control" }, {"access-control-allow-headers", "content-type" }, {"access-control-allow-origin", "*" }, {"cache-control", "max-age=0" }, {"cache-control", "max-age=2592000" }, {"cache-control", "max-age=604800" }, {"cache-control", "no-cache" }, {"cache-control", "no-store" }, {"cache-control", "public, max-age=31536000" }, {"content-encoding", "br" }, {"content-encoding", "gzip" }, {"content-type", "application/dns-message" }, {"content-type", "application/javascript" }, {"content-type", "application/json" }, {"content-type", "application/x-www-form-urlencoded" }, {"content-type", "image/gif" }, {"content-type", "image/jpeg" }, {"content-type", "image/png" }, {"content-type", "text/css" }, {"content-type", "text/html; charset=utf-8" }, {"content-type", "text/plain" }, {"content-type", "text/plain;charset=utf-8" }, {"range", "bytes=0-" }, {"strict-transport-security", "max-age=31536000" }, {"strict-transport-security", "max-age=31536000; includesubdomains" }, {"strict-transport-security", "max-age=31536000; includesubdomains; preload" }, {"vary", "accept-encoding" }, {"vary", "origin" }, {"x-content-type-options", "nosniff" }, {"x-xss-protection", "1; mode=block" }, {":status", "100" }, {":status", "204" }, {":status", "206" }, {":status", "302" }, {":status", "400" }, {":status", "403" }, {":status", "421" }, {":status", "425" }, {":status", "500" }, {"accept-language", "" }, {"access-control-allow-credentials", "FALSE" }, {"access-control-allow-credentials", "TRUE" }, {"access-control-allow-headers", "*" }, {"access-control-allow-methods", "get" }, {"access-control-allow-methods", "get, post, options" }, {"access-control-allow-methods", "options" }, {"access-control-expose-headers", "content-length" }, {"access-control-request-headers", "content-type" }, {"access-control-request-method", "get" }, {"access-control-request-method", "post" }, {"alt-svc", "clear" }, {"authorization", "" }, {"content-security-policy", "script-src 'none'; object-src 'none'; base-uri 'none'"}, {"early-data", "1" }, {"expect-ct", "" }, {"forwarded", "" }, {"if-range", "" }, {"origin", "" }, {"purpose", "prefetch" }, {"server", "" }, {"timing-allow-origin", "*" }, {"upgrade-insecure-requests", "1" }, {"user-agent", "" }, {"x-forwarded-for", "" }, {"x-frame-options", "deny" }, {"x-frame-options", "sameorigin" } }; QPACK::QPACK(QUICConnection *qc, uint32_t max_field_section_size, uint16_t max_table_size, uint16_t max_blocking_streams) : QUICApplication(qc), _dynamic_table(max_table_size), _max_field_section_size(max_field_section_size), _max_table_size(max_table_size), _max_blocking_streams(max_blocking_streams) { SET_HANDLER(&QPACK::event_handler); this->_encoder_stream_sending_instructions = new_MIOBuffer(BUFFER_SIZE_INDEX_1K); this->_decoder_stream_sending_instructions = new_MIOBuffer(BUFFER_SIZE_INDEX_1K); this->_encoder_stream_sending_instructions_reader = this->_encoder_stream_sending_instructions->alloc_reader(); this->_decoder_stream_sending_instructions_reader = this->_decoder_stream_sending_instructions->alloc_reader(); } QPACK::~QPACK() { free_MIOBuffer(_encoder_stream_sending_instructions); free_MIOBuffer(_decoder_stream_sending_instructions); } void QPACK::on_stream_open(QUICStream &stream) { auto *info = new QUICStreamVCAdapter::IOInfo(stream); switch (stream.direction()) { case QUICStreamDirection::BIDIRECTIONAL: // ink_assert(!"QPACK does not use bidirectional streams"); // QPACK offline interop uses stream 0 as a encoder stream. info->setup_write_vio(this); info->setup_read_vio(this); break; case QUICStreamDirection::SEND: info->setup_write_vio(this); break; case QUICStreamDirection::RECEIVE: info->setup_read_vio(this); break; default: ink_assert(false); break; } stream.set_io_adapter(&info->adapter); } void QPACK::on_stream_close(QUICStream & /* stream ATS_UNUSED */) { } int QPACK::event_handler(int event, Event *data) { VIO *vio = reinterpret_cast<VIO *>(data->cookie); QUICStreamVCAdapter *adapter = static_cast<QUICStreamVCAdapter *>(vio->vc_server); int ret; switch (event) { case VC_EVENT_READ_READY: adapter->clear_read_ready_event(data); ret = this->_on_read_ready(vio); break; case VC_EVENT_READ_COMPLETE: adapter->clear_read_complete_event(data); ret = EVENT_DONE; break; case VC_EVENT_WRITE_READY: adapter->clear_write_ready_event(data); ret = this->_on_write_ready(vio); break; case VC_EVENT_WRITE_COMPLETE: adapter->clear_write_complete_event(data); ret = EVENT_DONE; break; case VC_EVENT_EOS: adapter->clear_eos_event(data); ret = EVENT_DONE; break; default: ret = EVENT_DONE; } return ret; } int QPACK::encode(uint64_t stream_id, HTTPHdr &header_set, MIOBuffer *header_block, uint64_t &header_block_len) { if (!header_block) { return -1; } uint16_t base_index = this->_largest_known_received_index; // Compress headers and record the largest reference uint16_t referred_index = 0; uint16_t largest_reference = 0; uint16_t smallest_reference = 0; IOBufferBlock *compressed_headers = new_IOBufferBlock(); compressed_headers->alloc(BUFFER_SIZE_INDEX_2K); for (auto &field : header_set) { int ret = this->_encode_header(field, base_index, compressed_headers, referred_index); largest_reference = std::max(largest_reference, referred_index); smallest_reference = std::min(smallest_reference, referred_index); if (ret < 0) { compressed_headers->free(); return ret; } } struct EntryReference eref = {smallest_reference, largest_reference}; this->_references.emplace(stream_id, eref); // Make an IOBufferBlock for Header Data Prefix IOBufferBlock *header_data_prefix = new_IOBufferBlock(); header_data_prefix->alloc(BUFFER_SIZE_INDEX_128); this->_encode_prefix(largest_reference, base_index, header_data_prefix); header_block->append_block(header_data_prefix); header_block_len += header_data_prefix->size(); header_block->append_block(compressed_headers); header_block_len += compressed_headers->size(); return 0; } int QPACK::decode(uint64_t stream_id, const uint8_t *header_block, size_t header_block_len, HTTPHdr &hdr, Continuation *cont, EThread *thread) { if (!cont || !header_block) { return -1; } if (this->_invalid) { thread->schedule_imm(cont, QPACK_EVENT_DECODE_FAILED, nullptr); return -1; } uint64_t tmp = 0; int64_t ret = xpack_decode_integer(tmp, header_block, header_block + header_block_len, 8); if (ret < 0 && tmp > 0xFFFF) { return -1; } uint16_t largest_reference = tmp; if (largest_reference != 0 && (this->_dynamic_table.is_empty() || this->_dynamic_table.largest_index() < largest_reference)) { // Blocked if (this->_add_to_blocked_list( new DecodeRequest(largest_reference, thread, cont, stream_id, header_block, header_block_len, hdr))) { return 1; } else { // Number of blocked streams exceed the limit return -2; } } this->_decode(thread, cont, stream_id, header_block, header_block_len, hdr); return 0; } void QPACK::set_encoder_stream(QUICStreamId id) { this->_encoder_stream_id = id; } void QPACK::set_decoder_stream(QUICStreamId id) { this->_decoder_stream_id = id; } void QPACK::update_max_field_section_size(uint32_t max_field_section_size) { this->_max_field_section_size = max_field_section_size; } void QPACK::update_max_table_size(uint16_t max_table_size) { this->_max_table_size = max_table_size; } void QPACK::update_max_blocking_streams(uint16_t max_blocking_streams) { this->_max_blocking_streams = max_blocking_streams; } int QPACK::_encode_prefix(uint16_t largest_reference, uint16_t base_index, IOBufferBlock *prefix) { int ret; if ((ret = xpack_encode_integer(reinterpret_cast<uint8_t *>(prefix->end()), reinterpret_cast<uint8_t *>(prefix->end() + prefix->write_avail()), largest_reference, 8)) < 0) { return -1; } prefix->fill(ret); uint16_t delta; prefix->end()[0] = 0x0; if (base_index < largest_reference) { prefix->end()[0] |= 0x80; delta = largest_reference - base_index; } else { delta = base_index - largest_reference; } if ((ret = xpack_encode_integer(reinterpret_cast<uint8_t *>(prefix->end()), reinterpret_cast<uint8_t *>(prefix->end() + prefix->write_avail()), delta, 7)) < 0) { return -2; } prefix->fill(ret); QPACKDebug("Encoded Header Data Prefix: largest_ref=%d, base_index=%d, delta=%d", largest_reference, base_index, delta); return 0; } int QPACK::_encode_header(const MIMEField &field, uint16_t base_index, IOBufferBlock *compressed_header, uint16_t &referred_index) { auto name{field.name_get()}; char *lowered_name = this->_arena.str_store(name.data(), name.length()); for (size_t i = 0; i < name.length(); i++) { lowered_name[i] = ParseRules::ink_tolower(lowered_name[i]); } auto value{field.value_get()}; // TODO Set never_index flag on/off according to encoding headers bool never_index = false; // Find from tables, and insert / duplicate a entry prior to encode it XpackLookupResult lookup_result_static; XpackLookupResult lookup_result_dynamic; lookup_result_static = StaticTable::lookup(lowered_name, name.length(), value.data(), value.length()); if (lookup_result_static.match_type != XpackLookupResult::MatchType::EXACT) { lookup_result_dynamic = this->_dynamic_table.lookup(lowered_name, name.length(), value.data(), value.length()); if (lookup_result_dynamic.match_type == XpackLookupResult::MatchType::EXACT) { if (this->_dynamic_table.should_duplicate(lookup_result_dynamic.index)) { // Duplicate an entry and use the new entry uint16_t current_index = lookup_result_dynamic.index; lookup_result_dynamic = this->_dynamic_table.duplicate_entry(current_index); if (lookup_result_dynamic.match_type != XpackLookupResult::MatchType::NONE) { this->_write_duplicate(current_index); QPACKDebug("Wrote Duplicate: current_index=%d", current_index); this->_dynamic_table.ref_entry(current_index); } } } else if (lookup_result_static.match_type == XpackLookupResult::MatchType::NAME) { if (never_index) { // Name in static table is always available. Do nothing. } else { // Insert both the name and the value lookup_result_dynamic = this->_dynamic_table.insert_entry(lowered_name, name.length(), value.data(), value.length()); if (lookup_result_dynamic.match_type != XpackLookupResult::MatchType::NONE) { this->_write_insert_with_name_ref(lookup_result_static.index, false, value.data(), value.length()); QPACKDebug("Wrote Insert With Name Ref: index=%u, dynamic_table=%d value=%.*s", lookup_result_static.index, false, static_cast<int>(value.length()), value.data()); } } } else if (lookup_result_dynamic.match_type == XpackLookupResult::MatchType::NAME) { if (never_index) { if (this->_dynamic_table.should_duplicate(lookup_result_dynamic.index)) { // Duplicate an entry and use the new entry uint16_t current_index = lookup_result_dynamic.index; lookup_result_dynamic = this->_dynamic_table.duplicate_entry(current_index); if (lookup_result_dynamic.match_type != XpackLookupResult::MatchType::NONE) { this->_write_duplicate(current_index); QPACKDebug("Wrote Duplicate: current_index=%d", current_index); this->_dynamic_table.ref_entry(current_index); } } } else { if (this->_dynamic_table.should_duplicate(lookup_result_dynamic.index)) { // Duplicate an entry and use the new entry uint16_t current_index = lookup_result_dynamic.index; lookup_result_dynamic = this->_dynamic_table.duplicate_entry(current_index); if (lookup_result_dynamic.match_type != XpackLookupResult::MatchType::NONE) { this->_write_duplicate(current_index); QPACKDebug("Wrote Duplicate: current_index=%d", current_index); this->_dynamic_table.ref_entry(current_index); } } else { // Insert both the name and the value uint16_t current_index = lookup_result_dynamic.index; lookup_result_dynamic = this->_dynamic_table.insert_entry(lowered_name, name.length(), value.data(), value.length()); if (lookup_result_dynamic.match_type != XpackLookupResult::MatchType::NONE) { this->_write_insert_with_name_ref(current_index, true, value.data(), value.length()); QPACKDebug("Wrote Insert With Name Ref: index=%u, dynamic_table=%d, value=%.*s", current_index, true, static_cast<int>(value.length()), value.data()); } } } } else { if (never_index) { // Insert only the name lookup_result_dynamic = this->_dynamic_table.insert_entry(lowered_name, name.length(), "", 0); if (lookup_result_dynamic.match_type != XpackLookupResult::MatchType::NONE) { this->_write_insert_without_name_ref(lowered_name, name.length(), "", 0); QPACKDebug("Wrote Insert Without Name Ref: name=%.*s value=%.*s", static_cast<int>(name.length()), lowered_name, 0, ""); } } else { // Insert both the name and the value lookup_result_dynamic = this->_dynamic_table.insert_entry(lowered_name, name.length(), value.data(), value.length()); if (lookup_result_dynamic.match_type != XpackLookupResult::MatchType::NONE) { this->_write_insert_without_name_ref(lowered_name, name.length(), value.data(), value.length()); QPACKDebug("Wrote Insert Without Name Ref: name=%.*s value=%.*s", static_cast<int>(name.length()), lowered_name, static_cast<int>(value.length()), value.data()); } } } } // Encode if (lookup_result_static.match_type == XpackLookupResult::MatchType::EXACT) { this->_encode_indexed_header_field(lookup_result_static.index, base_index, false, compressed_header); QPACKDebug("Encoded Indexed Header Field: abs_index=%d, base_index=%d, dynamic_table=%d", lookup_result_static.index, base_index, false); referred_index = 0; } else if (lookup_result_dynamic.match_type == XpackLookupResult::MatchType::EXACT) { if (lookup_result_dynamic.index < this->_largest_known_received_index) { this->_encode_indexed_header_field(lookup_result_dynamic.index, base_index, true, compressed_header); QPACKDebug("Encoded Indexed Header Field: abs_index=%d, base_index=%d, dynamic_table=%d", lookup_result_dynamic.index, base_index, true); } else { this->_encode_indexed_header_field_with_postbase_index(lookup_result_dynamic.index, base_index, never_index, compressed_header); QPACKDebug("Encoded Indexed Header With Postbase Index: abs_index=%d, base_index=%d, never_index=%d", lookup_result_dynamic.index, base_index, never_index); } this->_dynamic_table.ref_entry(lookup_result_dynamic.index); referred_index = lookup_result_dynamic.index; } else if (lookup_result_static.match_type == XpackLookupResult::MatchType::NAME) { this->_encode_literal_header_field_with_name_ref(lookup_result_static.index, false, base_index, value.data(), value.length(), never_index, compressed_header); QPACKDebug( "Encoded Literal Header Field With Name Ref: abs_index=%d, base_index=%d, dynamic_table=%d, value=%.*s, never_index=%d", lookup_result_static.index, base_index, false, static_cast<int>(value.length()), value.data(), never_index); referred_index = 0; } else if (lookup_result_dynamic.match_type == XpackLookupResult::MatchType::NAME) { if (lookup_result_dynamic.index <= this->_largest_known_received_index) { this->_encode_literal_header_field_with_name_ref(lookup_result_dynamic.index, true, base_index, value.data(), value.length(), never_index, compressed_header); QPACKDebug( "Encoded Literal Header Field With Name Ref: abs_index=%d, base_index=%d, dynamic_table=%d, value=%.*s, never_index=%d", lookup_result_dynamic.index, base_index, true, static_cast<int>(value.length()), value.data(), never_index); } else { this->_encode_literal_header_field_with_postbase_name_ref(lookup_result_dynamic.index, base_index, value.data(), value.length(), never_index, compressed_header); QPACKDebug("Encoded Literal Header Field With Postbase Name Ref: abs_index=%d, base_index=%d, value=%.*s, never_index=%d", lookup_result_dynamic.index, base_index, static_cast<int>(value.length()), value.data(), never_index); } this->_dynamic_table.ref_entry(lookup_result_dynamic.index); referred_index = lookup_result_dynamic.index; } else { this->_encode_literal_header_field_without_name_ref(lowered_name, name.length(), value.data(), value.length(), never_index, compressed_header); QPACKDebug("Encoded Literal Header Field Without Name Ref: name=%.*s, value=%.*s, never_index=%d", static_cast<int>(name.length()), lowered_name, static_cast<int>(value.length()), value.data(), never_index); } this->_arena.str_free(lowered_name); return 0; } int QPACK::_encode_indexed_header_field(uint16_t index, uint16_t base_index, bool dynamic_table, IOBufferBlock *compressed_header) { char *buf = compressed_header->end(); char *buf_end = buf + compressed_header->write_avail(); int written = 0; // Indexed Header Field buf[0] = 0x80; // References static table or not if (dynamic_table) { // Use relative index if we refer Dynamic Table index = this->_calc_relative_index_from_absolute_index(base_index, index); } else { buf[0] |= 0x40; } // Index int ret; if ((ret = xpack_encode_integer(reinterpret_cast<uint8_t *>(buf + written), reinterpret_cast<uint8_t *>(buf_end), index, 6)) < 0) { return ret; } written += ret; // Finalize and Schedule to send compressed_header->fill(written); return 0; } int QPACK::_encode_indexed_header_field_with_postbase_index(uint16_t index, uint16_t base_index, bool /* never_index ATS_UNUSED */, IOBufferBlock *compressed_header) { char *buf = compressed_header->end(); char *buf_end = buf + compressed_header->write_avail(); int written = 0; // Indexed Header Field with Post-Base Index buf[0] = 0x10; // Index int ret; if ((ret = xpack_encode_integer(reinterpret_cast<uint8_t *>(buf + written), reinterpret_cast<uint8_t *>(buf_end), this->_calc_postbase_index_from_absolute_index(base_index, index), 4)) < 0) { return ret; } written += ret; // Finalize and Schedule to send compressed_header->fill(written); return 0; } int QPACK::_encode_literal_header_field_with_name_ref(uint16_t index, bool dynamic_table, uint16_t base_index, const char *value, int value_len, bool never_index, IOBufferBlock *compressed_header) { char *buf = compressed_header->end(); char *buf_end = buf + compressed_header->write_avail(); int written = 0; // Literal Header Field With Name Reference buf[0] = 0x40; if (never_index) { buf[0] |= 0x20; } // References static table or not if (dynamic_table) { // Use relative index if we refer Dynamic Table index = this->_calc_relative_index_from_absolute_index(base_index, index); } else { buf[0] |= 0x10; } // Index int ret; if ((ret = xpack_encode_integer(reinterpret_cast<uint8_t *>(buf + written), reinterpret_cast<uint8_t *>(buf_end), index, 4)) < 0) { return ret; } written += ret; // Value if ((ret = xpack_encode_string(reinterpret_cast<uint8_t *>(buf + written), reinterpret_cast<uint8_t *>(buf_end), value, value_len)) < 0) { return ret; } written += ret; // Finalize and Schedule to send compressed_header->fill(written); return 0; } int QPACK::_encode_literal_header_field_without_name_ref(const char *name, int name_len, const char *value, int value_len, bool never_index, IOBufferBlock *compressed_header) { char *buf = compressed_header->end(); char *buf_end = buf + compressed_header->write_avail(); int written = 0; // Literal Header Field Without Name Reference buf[0] = 0x20; if (never_index) { buf[0] |= 0x10; } // Name int ret; if ((ret = xpack_encode_string(reinterpret_cast<uint8_t *>(buf + written), reinterpret_cast<uint8_t *>(buf_end), name, name_len, 3)) < 0) { return ret; } written += ret; // Value if ((ret = xpack_encode_string(reinterpret_cast<uint8_t *>(buf + written), reinterpret_cast<uint8_t *>(buf_end), value, value_len, 7)) < 0) { return ret; } written += ret; // Finalize and Schedule to send compressed_header->fill(written); return 0; } int QPACK::_encode_literal_header_field_with_postbase_name_ref(uint16_t index, uint16_t base_index, const char *value, int value_len, bool never_index, IOBufferBlock *compressed_header) { char *buf = compressed_header->end(); char *buf_end = buf + compressed_header->write_avail(); int written = 0; // Literal Header Field With Post-Base Name Reference buf[0] = 0x00; if (never_index) { buf[0] |= 0x08; } // Index int ret; if ((ret = xpack_encode_integer(reinterpret_cast<uint8_t *>(buf + written), reinterpret_cast<uint8_t *>(buf_end), this->_calc_postbase_index_from_absolute_index(base_index, index), 3)) < 0) { return ret; } written += ret; // Value if ((ret = xpack_encode_string(reinterpret_cast<uint8_t *>(buf + written), reinterpret_cast<uint8_t *>(buf_end), value, value_len, 7)) < 0) { return ret; } written += ret; // Finalize and Schedule to send compressed_header->fill(written); return 0; } int QPACK::_decode_indexed_header_field(int16_t base_index, const uint8_t *buf, size_t buf_len, HTTPHdr &hdr, uint32_t &header_len) { // Read index field int len = 0; uint64_t index; int ret = xpack_decode_integer(index, buf, buf + buf_len, 6); if (ret < 0) { return -1; } len += ret; // Lookup a table const char *name = nullptr; size_t name_len = 0; const char *value = nullptr; size_t value_len = 0; XpackLookupResult result; if (buf[0] & 0x40) { // Static table result = StaticTable::lookup(index, &name, &name_len, &value, &value_len); } else { // Dynamic table result = this->_dynamic_table.lookup(this->_calc_absolute_index_from_relative_index(base_index, index), &name, &name_len, &value, &value_len); } if (result.match_type != XpackLookupResult::MatchType::EXACT) { return -1; } // Create and attach a header this->_attach_header(hdr, name, name_len, value, value_len, false); header_len = name_len + value_len; QPACKDebug("Decoded Indexed Header Field: base_index=%d, abs_index=%d, name=%.*s, value=%.*s", base_index, result.index, static_cast<int>(name_len), name, static_cast<int>(value_len), value); return len; } int QPACK::_decode_literal_header_field_with_name_ref(int16_t base_index, const uint8_t *buf, size_t buf_len, HTTPHdr &hdr, uint32_t &header_len) { int read_len = 0; // Never index field bool never_index = false; if (buf[0] & 0x20) { never_index = true; } // Read name index field uint64_t index; int ret = xpack_decode_integer(index, buf, buf + buf_len, 4); if (ret < 0) { return -1; } read_len += ret; // Lookup the name const char *name = nullptr; size_t name_len = 0; const char *dummy = nullptr; size_t dummy_len = 0; XpackLookupResult result; if (buf[0] & 0x10) { // Static table result = StaticTable::lookup(index, &name, &name_len, &dummy, &dummy_len); } else { // Dynamic table result = this->_dynamic_table.lookup(this->_calc_absolute_index_from_relative_index(base_index, index), &name, &name_len, &dummy, &dummy_len); } if (result.match_type != XpackLookupResult::MatchType::EXACT) { return -1; } // Read value char *value; uint64_t value_len; if ((ret = xpack_decode_string(this->_arena, &value, value_len, buf + read_len, buf + buf_len, 7)) < 0) { return -1; } read_len += ret; // Create and attach a header this->_attach_header(hdr, name, name_len, value, value_len, never_index); header_len = name_len + value_len; QPACKDebug("Decoded Literal Header Field With Name Ref: base_index=%d, abs_index=%d, name=%.*s, value=%.*s", base_index, result.index, static_cast<int>(name_len), name, static_cast<int>(value_len), value); this->_arena.str_free(value); return read_len; } int QPACK::_decode_literal_header_field_without_name_ref(const uint8_t *buf, size_t buf_len, HTTPHdr &hdr, uint32_t &header_len) { int read_len = 0; // Never index field bool never_index = false; if (buf[0] & 0x10) { never_index = true; } // Read name and value int64_t ret; char *name; uint64_t name_len; if ((ret = xpack_decode_string(this->_arena, &name, name_len, buf, buf + buf_len, 3)) < 0) { return -1; } read_len += ret; char *value; uint64_t value_len; if ((ret = xpack_decode_string(this->_arena, &value, value_len, buf + read_len, buf + buf_len, 7)) < 0) { return -1; } read_len += ret; // Create and attach a header this->_attach_header(hdr, name, name_len, value, value_len, never_index); header_len = name_len + value_len; QPACKDebug("Decoded Literal Header Field Without Name Ref: name=%.*s, value=%.*s", static_cast<uint16_t>(name_len), name, static_cast<uint16_t>(value_len), value); this->_arena.str_free(name); this->_arena.str_free(value); return read_len; } int QPACK::_decode_indexed_header_field_with_postbase_index(int16_t base_index, const uint8_t *buf, size_t buf_len, HTTPHdr &hdr, uint32_t &header_len) { // Read index field int len = 0; uint64_t index; int ret = xpack_decode_integer(index, buf, buf + buf_len, 4); if (ret < 0) { return -1; } len += ret; // Lookup a table const char *name = nullptr; size_t name_len = 0; const char *value = nullptr; size_t value_len = 0; XpackLookupResult result; result = this->_dynamic_table.lookup(this->_calc_absolute_index_from_postbase_index(base_index, index), &name, &name_len, &value, &value_len); if (result.match_type != XpackLookupResult::MatchType::EXACT) { return -1; } // Create and attach a header this->_attach_header(hdr, name, name_len, value, value_len, false); header_len = name_len + value_len; QPACKDebug("Decoded Indexed Header Field With Postbase Index: base_index=%d, abs_index=%d, name=%.*s, value=%.*s", base_index, result.index, static_cast<int>(name_len), name, static_cast<int>(value_len), value); return len; } int QPACK::_decode_literal_header_field_with_postbase_name_ref(int16_t base_index, const uint8_t *buf, size_t buf_len, HTTPHdr &hdr, uint32_t &header_len) { int read_len = 0; // Never index field bool never_index = false; if (buf[0] & 0x08) { never_index = true; } // Read name index field uint64_t index; int ret = xpack_decode_integer(index, buf, buf + buf_len, 3); if (ret < 0) { return -1; } read_len += ret; // Lookup the name const char *name = nullptr; size_t name_len = 0; const char *dummy = nullptr; size_t dummy_len = 0; XpackLookupResult result; result = this->_dynamic_table.lookup(this->_calc_absolute_index_from_postbase_index(base_index, index), &name, &name_len, &dummy, &dummy_len); if (result.match_type != XpackLookupResult::MatchType::EXACT) { return -1; } // Read value char *value; uint64_t value_len; if ((ret = xpack_decode_string(this->_arena, &value, value_len, buf + read_len, buf + buf_len, 7)) < 0) { return -1; } read_len += ret; // Create and attach a header this->_attach_header(hdr, name, name_len, value, value_len, never_index); header_len = name_len + value_len; QPACKDebug("Decoded Literal Header Field With Postbase Name Ref: base_index=%d, abs_index=%d, name=%.*s, value=%.*s", base_index, static_cast<uint16_t>(index), static_cast<int>(name_len), name, static_cast<int>(value_len), value); this->_arena.str_free(value); return read_len; } int QPACK::_decode_header(const uint8_t *header_block, size_t header_block_len, HTTPHdr &hdr) { const uint8_t *pos = header_block; size_t remain_len = header_block_len; int64_t ret; // Decode Header Data Prefix uint64_t tmp; if ((ret = xpack_decode_integer(tmp, pos, pos + remain_len, 8)) < 0 && tmp > 0xFFFF) { return -1; } pos += ret; uint16_t largest_reference = tmp; uint64_t delta_base_index; uint16_t base_index; if ((ret = xpack_decode_integer(delta_base_index, pos, pos + remain_len, 7)) < 0 && delta_base_index < 0xFFFF) { return -2; } if (pos[0] & 0x80) { if (delta_base_index == 0) { return -3; } base_index = largest_reference - delta_base_index; } else { base_index = largest_reference + delta_base_index; } pos += ret; uint32_t decoded_header_list_size = 0; // Decode Instructions while (pos < header_block + header_block_len) { uint32_t header_len = 0; if (pos[0] & 0x80) { // Index Header Field ret = this->_decode_indexed_header_field(base_index, pos, remain_len, hdr, header_len); } else if (pos[0] & 0x40) { // Literal Header Field With Name Reference ret = this->_decode_literal_header_field_with_name_ref(base_index, pos, remain_len, hdr, header_len); } else if (pos[0] & 0x20) { // Literal Header Field Without Name Reference ret = this->_decode_literal_header_field_without_name_ref(pos, remain_len, hdr, header_len); } else if (pos[0] & 0x10) { // Indexed Header Field With Post-Base Index ret = this->_decode_indexed_header_field_with_postbase_index(base_index, pos, remain_len, hdr, header_len); } else { // Literal Header Field With Post-Base Name Reference ret = this->_decode_literal_header_field_with_postbase_name_ref(base_index, pos, remain_len, hdr, header_len); } if (ret < 0) { break; } decoded_header_list_size += header_len; if (decoded_header_list_size > this->_max_field_section_size) { ret = -2; break; } pos += ret; } return ret; } void QPACK::_decode(EThread *ethread, Continuation *cont, uint64_t stream_id, const uint8_t *header_block, size_t header_block_len, HTTPHdr &hdr) { int event; int res = this->_decode_header(header_block, header_block_len, hdr); if (res < 0) { event = QPACK_EVENT_DECODE_FAILED; QPACKDebug("decoding header failed (%d)", res); } else { event = QPACK_EVENT_DECODE_COMPLETE; this->_write_header_acknowledgement(stream_id); } ethread->schedule_imm(cont, event, &hdr); } bool QPACK::_add_to_blocked_list(DecodeRequest *decode_request) { if (this->_blocked_list.count() >= this->_max_blocking_streams) { return false; } this->_blocked_list.append(decode_request); return true; } void QPACK::_update_largest_known_received_index_by_insert_count(uint16_t insert_count) { this->_largest_known_received_index += insert_count; } void QPACK::_update_largest_known_received_index_by_stream_id(uint64_t stream_id) { uint16_t largest_ref_index = this->_references[stream_id].largest; if (largest_ref_index > this->_largest_known_received_index) { this->_largest_known_received_index = largest_ref_index; } } void QPACK::_update_reference_counts(uint64_t stream_id) { uint16_t smallest_ref_index = this->_references[stream_id].smallest; if (smallest_ref_index) { this->_dynamic_table.unref_entry(smallest_ref_index); } } void QPACK::_resume_decode() { DecodeRequest *r = this->_blocked_list.head(); while (r) { if (this->_largest_known_received_index >= r->largest_reference()) { this->_decode(r->thread(), r->continuation(), r->stream_id(), r->header_block(), r->header_block_len(), r->hdr()); DecodeRequest *tmp = r; r = DecodeRequest::Linkage::next_ptr(r); this->_blocked_list.erase(tmp); delete tmp; } else { r = DecodeRequest::Linkage::next_ptr(r); } } } void QPACK::_abort_decode() { this->_invalid = true; DecodeRequest *r = this->_blocked_list.head(); while (r) { if (this->_largest_known_received_index >= r->largest_reference()) { r->thread()->schedule_imm(r->continuation(), QPACK_EVENT_DECODE_FAILED, nullptr); DecodeRequest *tmp = r; r = DecodeRequest::Linkage::next_ptr(r); this->_blocked_list.erase(tmp); delete tmp; } else { r = DecodeRequest::Linkage::next_ptr(r); } } } int QPACK::_on_read_ready(VIO *vio) { int nread = 0; QUICStreamId stream_id = static_cast<QUICStreamVCAdapter *>(vio->vc_server)->stream().id(); if (stream_id == this->_decoder_stream_id) { nread = this->_on_decoder_stream_read_ready(*vio->get_reader()); } else if (stream_id == this->_encoder_stream_id) { nread = this->_on_encoder_stream_read_ready(*vio->get_reader()); } else { ink_assert(!"The stream ID must match either encoder stream id or decoder stream id"); } vio->ndone += nread; return EVENT_DONE; } int QPACK::_on_write_ready(VIO *vio) { QUICStreamId stream_id = static_cast<QUICStreamVCAdapter *>(vio->vc_server)->stream().id(); if (stream_id == this->_decoder_stream_id) { return this->_on_decoder_write_ready(*vio->get_writer()); } else if (stream_id == this->_encoder_stream_id) { return this->_on_encoder_write_ready(*vio->get_writer()); } else { ink_assert(!"The stream ID must match either decoder stream id or decoder stream id"); return EVENT_DONE; } } int QPACK::_on_decoder_stream_read_ready(IOBufferReader &reader) { if (reader.is_read_avail_more_than(0)) { uint8_t buf; reader.memcpy(&buf, 1); if (buf & 0x80) { // Header Acknowledgement uint64_t stream_id; if (this->_read_header_acknowledgement(reader, stream_id) >= 0) { QPACKDebug("Received Header Acknowledgement: stream_id=%" PRIu64, stream_id); this->_update_largest_known_received_index_by_stream_id(stream_id); this->_update_reference_counts(stream_id); this->_references.erase(stream_id); } } else if (buf & 0x40) { // Stream Cancellation uint64_t stream_id; if (this->_read_stream_cancellation(reader, stream_id) >= 0) { QPACKDebug("Received Stream Cancellation: stream_id=%" PRIu64, stream_id); this->_update_reference_counts(stream_id); this->_references.erase(stream_id); } } else { // Table State Synchronize uint16_t insert_count; if (this->_read_table_state_synchronize(reader, insert_count) >= 0) { QPACKDebug("Received Table State Synchronize: inserted_count=%d", insert_count); this->_update_largest_known_received_index_by_insert_count(insert_count); } } } return EVENT_DONE; } int QPACK::_on_encoder_stream_read_ready(IOBufferReader &reader) { while (reader.is_read_avail_more_than(0)) { uint8_t buf; reader.memcpy(&buf, 1); if (buf & 0x80) { // Insert With Name Reference bool is_static; uint16_t index; const char *name; size_t name_len; const char *dummy; size_t dummy_len; char *value; size_t value_len; if (this->_read_insert_with_name_ref(reader, is_static, index, this->_arena, &value, value_len) < 0) { this->_abort_decode(); return EVENT_DONE; } QPACKDebug("Received Insert With Name Ref: is_static=%d, index=%d, value=%.*s", is_static, index, static_cast<int>(value_len), value); StaticTable::lookup(index, &name, &name_len, &dummy, &dummy_len); this->_dynamic_table.insert_entry(name, name_len, value, value_len); this->_arena.str_free(value); } else if (buf & 0x40) { // Insert Without Name Reference char *name; size_t name_len; char *value; size_t value_len; if (this->_read_insert_without_name_ref(reader, this->_arena, &name, name_len, &value, value_len) < 0) { this->_abort_decode(); return EVENT_DONE; } QPACKDebug("Received Insert Without Name Ref: name=%.*s, value=%.*s", static_cast<int>(name_len), name, static_cast<int>(value_len), value); this->_dynamic_table.insert_entry(name, name_len, value, value_len); this->_arena.str_free(name); } else if (buf & 0x20) { // Dynamic Table Size Update uint16_t max_size; if (this->_read_dynamic_table_size_update(reader, max_size) < 0) { this->_abort_decode(); return EVENT_DONE; } QPACKDebug("Received Dynamic Table Size Update: max_size=%d", max_size); this->_dynamic_table.update_maximum_size(max_size); } else { // Duplicates uint16_t index; if (this->_read_duplicate(reader, index) < 0) { this->_abort_decode(); return EVENT_DONE; } QPACKDebug("Received Duplicate: index=%d", index); this->_dynamic_table.duplicate_entry(index); } this->_resume_decode(); } return EVENT_DONE; } int QPACK::_on_decoder_write_ready(MIOBuffer &writer) { int64_t written_len = writer.write(this->_decoder_stream_sending_instructions_reader, INT64_MAX); this->_decoder_stream_sending_instructions_reader->consume(written_len); return written_len; } int QPACK::_on_encoder_write_ready(MIOBuffer &writer) { int64_t written_len = writer.write(this->_encoder_stream_sending_instructions_reader, INT64_MAX); this->_encoder_stream_sending_instructions_reader->consume(written_len); return written_len; } size_t QPACK::estimate_header_block_size(const HTTPHdr & /* hdr ATS_UNUSED */) { // FIXME Estimate it return 128 * 1024 * 1024; } const XpackLookupResult QPACK::StaticTable::lookup(uint16_t index, const char **name, size_t *name_len, const char **value, size_t *value_len) { const Header &header = STATIC_HEADER_FIELDS[index]; *name = header.name; *name_len = header.name_len; *value = header.value; *value_len = header.value_len; return {index, XpackLookupResult::MatchType::EXACT}; } const XpackLookupResult QPACK::StaticTable::lookup(const char *name, size_t name_len, const char *value, size_t value_len) { XpackLookupResult::MatchType match_type = XpackLookupResult::MatchType::NONE; uint16_t i = 0; uint16_t candidate_index = 0; int n = countof(STATIC_HEADER_FIELDS); for (; i < n; ++i) { const Header &h = STATIC_HEADER_FIELDS[i]; if (h.name_len == name_len) { if (memcmp(name, h.name, name_len) == 0) { candidate_index = i; if (value_len == h.value_len && memcmp(value, h.value, value_len) == 0) { // Exact match match_type = XpackLookupResult::MatchType::EXACT; break; } else { // Name match -- Keep it for no exact matches match_type = XpackLookupResult::MatchType::NAME; } } } } return {candidate_index, match_type}; } uint16_t QPACK::_calc_absolute_index_from_relative_index(uint16_t base_index, uint16_t relative_index) { return base_index - relative_index; } uint16_t QPACK::_calc_absolute_index_from_postbase_index(uint16_t base_index, uint16_t postbase_index) { return base_index + postbase_index + 1; } uint16_t QPACK::_calc_relative_index_from_absolute_index(uint16_t base_index, uint16_t absolute_index) { return base_index - absolute_index; } uint16_t QPACK::_calc_postbase_index_from_absolute_index(uint16_t base_index, uint16_t absolute_index) { return absolute_index - base_index - 1; } void QPACK::_attach_header(HTTPHdr &hdr, const char *name, int name_len, const char *value, int value_len, bool /* never_index ATS_UNUSED */) { // TODO If never_index is true, we need to mark this header as sensitive to not index the header when passing it to the other side MIMEField *new_field = hdr.field_create(name, name_len); new_field->value_set(hdr.m_heap, hdr.m_mime, value, value_len); hdr.field_attach(new_field); } int QPACK::_write_insert_with_name_ref(uint16_t index, bool dynamic, const char *value, uint16_t value_len) { IOBufferBlock *instruction = new_IOBufferBlock(); instruction->alloc(TS_IOBUFFER_SIZE_INDEX_2K); char *buf = instruction->end(); char *buf_end = buf + instruction->write_avail(); int written = 0; // Insert With Name Reference buf[0] = 0x80; // References static table or not if (!dynamic) { buf[0] |= 0x40; } // Name Index int ret; if ((ret = xpack_encode_integer(reinterpret_cast<uint8_t *>(buf + written), reinterpret_cast<uint8_t *>(buf_end), index, 6)) < 0) { return ret; } written += ret; // Value if ((ret = xpack_encode_string(reinterpret_cast<uint8_t *>(buf + written), reinterpret_cast<uint8_t *>(buf_end), value, value_len, 7)) < 0) { return ret; } written += ret; // Finalize and Schedule to send instruction->fill(written); this->_encoder_stream_sending_instructions->append_block(instruction); return 0; } int QPACK::_write_insert_without_name_ref(const char *name, int name_len, const char *value, uint16_t value_len) { IOBufferBlock *instruction = new_IOBufferBlock(); instruction->alloc(TS_IOBUFFER_SIZE_INDEX_2K); char *buf = instruction->end(); char *buf_end = buf + instruction->write_avail(); int written = 0; // Insert Without Name Reference buf[0] = 0x40; // Name int ret; if ((ret = xpack_encode_string(reinterpret_cast<uint8_t *>(buf + written), reinterpret_cast<uint8_t *>(buf_end), name, name_len, 5)) < 0) { return ret; } written += ret; // Value if ((ret = xpack_encode_string(reinterpret_cast<uint8_t *>(buf + written), reinterpret_cast<uint8_t *>(buf_end), value, value_len, 7)) < 0) { return ret; } written += ret; // Finalize and Schedule to send instruction->fill(written); this->_encoder_stream_sending_instructions->append_block(instruction); return 0; } int QPACK::_write_duplicate(uint16_t index) { IOBufferBlock *instruction = new_IOBufferBlock(); instruction->alloc(TS_IOBUFFER_SIZE_INDEX_2K); char *buf = instruction->end(); char *buf_end = buf + instruction->write_avail(); int written = 0; // Index int ret; if ((ret = xpack_encode_integer(reinterpret_cast<uint8_t *>(buf + written), reinterpret_cast<uint8_t *>(buf_end), index, 5)) < 0) { return ret; } written += ret; // Finalize and Schedule to send instruction->fill(written); this->_encoder_stream_sending_instructions->append_block(instruction); return 0; } int QPACK::_write_dynamic_table_size_update(uint16_t max_size) { IOBufferBlock *instruction = new_IOBufferBlock(); instruction->alloc(TS_IOBUFFER_SIZE_INDEX_128); char *buf = instruction->end(); char *buf_end = buf + instruction->write_avail(); int written = 0; // Dynamic Table Size Update buf[0] = 0x20; // Max Size int ret; if ((ret = xpack_encode_integer(reinterpret_cast<uint8_t *>(buf + written), reinterpret_cast<uint8_t *>(buf_end), max_size, 5)) < 0) { return ret; } written += ret; // Finalize and Schedule to send instruction->fill(written); this->_encoder_stream_sending_instructions->append_block(instruction); return 0; } int QPACK::_write_table_state_synchronize(uint16_t insert_count) { IOBufferBlock *instruction = new_IOBufferBlock(); instruction->alloc(TS_IOBUFFER_SIZE_INDEX_128); char *buf = instruction->end(); char *buf_end = buf + instruction->write_avail(); int written = 0; // Insert Count int ret; if ((ret = xpack_encode_integer(reinterpret_cast<uint8_t *>(buf + written), reinterpret_cast<uint8_t *>(buf_end), insert_count, 6)) < 0) { return ret; } written += ret; // Finalize and Schedule to send instruction->fill(written); this->_encoder_stream_sending_instructions->append_block(instruction); return 0; } int QPACK::_write_header_acknowledgement(uint64_t stream_id) { IOBufferBlock *instruction = new_IOBufferBlock(); instruction->alloc(TS_IOBUFFER_SIZE_INDEX_128); char *buf = instruction->end(); char *buf_end = buf + instruction->write_avail(); int written = 0; // Header Acknowledgement buf[0] = 0x80; // Stream ID int ret; if ((ret = xpack_encode_integer(reinterpret_cast<uint8_t *>(buf + written), reinterpret_cast<uint8_t *>(buf_end), stream_id, 7)) < 0) { return ret; } written += ret; // Finalize and Schedule to send instruction->fill(written); this->_encoder_stream_sending_instructions->append_block(instruction); return 0; } int QPACK::_write_stream_cancellation(uint64_t stream_id) { IOBufferBlock *instruction = new_IOBufferBlock(); instruction->alloc(TS_IOBUFFER_SIZE_INDEX_128); char *buf = instruction->end(); char *buf_end = buf + instruction->write_avail(); int written = 0; // Stream Cancellation buf[0] = 0x40; // Stream ID int ret; if ((ret = xpack_encode_integer(reinterpret_cast<uint8_t *>(buf + written), reinterpret_cast<uint8_t *>(buf_end), stream_id, 7)) < 0) { return ret; } written += ret; // Finalize and Schedule to send instruction->fill(written); this->_encoder_stream_sending_instructions->append_block(instruction); return 0; } int QPACK::_read_insert_with_name_ref(IOBufferReader &reader, bool &is_static, uint16_t &index, Arena &arena, char **value, size_t &value_len) { size_t read_len = 0; int ret; uint8_t input[16384]; uint8_t *p = reinterpret_cast<uint8_t *>(reader.memcpy(input, sizeof(input))); int input_len = p - input; // S flag is_static = input[0] & 0x40; // Name Index uint64_t tmp; if ((ret = xpack_decode_integer(tmp, input, input + input_len, 6)) < 0 && tmp > 0xFFFF) { return -1; } index = tmp; read_len += ret; // Value if ((ret = xpack_decode_string(arena, value, tmp, input + read_len, input + input_len, 7)) < 0 && tmp > 0xFF) { return -1; } value_len = tmp; read_len += ret; reader.consume(read_len); return 0; } int QPACK::_read_insert_without_name_ref(IOBufferReader &reader, Arena &arena, char **name, size_t &name_len, char **value, size_t &value_len) { size_t read_len = 0; int ret; uint8_t input[16384]; uint8_t *p = reinterpret_cast<uint8_t *>(reader.memcpy(input, sizeof(input))); int input_len = p - input; // Name uint64_t tmp; if ((ret = xpack_decode_string(arena, name, tmp, input, input + input_len, 5)) < 0 && tmp > 0xFFFF) { return -1; } name_len = tmp; read_len += ret; // Value if ((ret = xpack_decode_string(arena, value, tmp, input + read_len, input + input_len, 7)) < 0 && tmp > 0xFFFF) { return -1; } value_len = tmp; read_len += ret; reader.consume(read_len); return 0; } int QPACK::_read_duplicate(IOBufferReader &reader, uint16_t &index) { size_t read_len = 0; int ret; uint8_t input[16]; uint8_t *p = reinterpret_cast<uint8_t *>(reader.memcpy(input, sizeof(input))); int input_len = p - input; // Index uint64_t tmp; if ((ret = xpack_decode_integer(tmp, input, input + input_len, 5)) < 0 && tmp > 0xFFFF) { return -1; } index = tmp; read_len += ret; reader.consume(read_len); return 0; } int QPACK::_read_dynamic_table_size_update(IOBufferReader &reader, uint16_t &max_size) { size_t read_len = 0; int ret; uint8_t input[16]; uint8_t *p = reinterpret_cast<uint8_t *>(reader.memcpy(input, sizeof(input))); int input_len = p - input; uint64_t tmp; // Max Size if ((ret = xpack_decode_integer(tmp, input, input + input_len, 5)) < 0 && tmp > 0xFFFF) { return -1; } max_size = tmp; read_len += ret; reader.consume(read_len); return 0; } int QPACK::_read_table_state_synchronize(IOBufferReader &reader, uint16_t &insert_count) { size_t read_len = 0; int ret; uint8_t input[16]; uint8_t *p = reinterpret_cast<uint8_t *>(reader.memcpy(input, sizeof(input))); int input_len = p - input; uint64_t tmp; // Insert Count if ((ret = xpack_decode_integer(tmp, input, input + input_len, 6)) < 0 && tmp > 0xFFFF) { return -1; } insert_count = tmp; read_len += ret; reader.consume(read_len); return 0; } int QPACK::_read_header_acknowledgement(IOBufferReader &reader, uint64_t &stream_id) { size_t read_len = 0; int ret; uint8_t input[16]; uint8_t *p = reinterpret_cast<uint8_t *>(reader.memcpy(input, sizeof(input))); int input_len = p - input; // Stream ID // FIXME xpack_decode_integer does not support uint64_t if ((ret = xpack_decode_integer(stream_id, input, input + input_len, 7)) < 0) { return -1; } read_len += ret; reader.consume(read_len); return 0; } int QPACK::_read_stream_cancellation(IOBufferReader &reader, uint64_t &stream_id) { size_t read_len = 0; int ret; uint8_t input[16]; uint8_t *p = reinterpret_cast<uint8_t *>(reader.memcpy(input, sizeof(input))); int input_len = p - input; // Stream ID // FIXME xpack_decode_integer does not support uint64_t if ((ret = xpack_decode_integer(stream_id, input, input + input_len, 6)) < 0) { return -1; } read_len += ret; reader.consume(read_len); return 0; }