mirror of https://github.com/grpc/grpc.git
Rebuild HPACK encoder table as C++ (#27192)
* Rebuild HPACK encoder table as C++ * move comment * incguards * build * Automated change: Fix sanity tests * c++ initialization ftw * Automated change: Fix sanity tests * Add missing header * Add missing header Co-authored-by: ctiller <ctiller@users.noreply.github.com>pull/27226/head
parent
4729f6fdcc
commit
999e36fb31
31 changed files with 612 additions and 310 deletions
@ -0,0 +1,41 @@ |
|||||||
|
// Copyright 2021 gRPC authors.
|
||||||
|
//
|
||||||
|
// Licensed 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.
|
||||||
|
|
||||||
|
#ifndef GRPC_CORE_EXT_TRANSPORT_CHTTP2_TRANSPORT_HPACK_CONSTANTS_H |
||||||
|
#define GRPC_CORE_EXT_TRANSPORT_CHTTP2_TRANSPORT_HPACK_CONSTANTS_H |
||||||
|
|
||||||
|
#include <grpc/impl/codegen/port_platform.h> |
||||||
|
|
||||||
|
#include <stdint.h> |
||||||
|
|
||||||
|
namespace grpc_core { |
||||||
|
namespace hpack_constants { |
||||||
|
// Per entry overhead bytes as per the spec
|
||||||
|
static constexpr uint32_t kEntryOverhead = 32; |
||||||
|
// Initial table size as per the spec
|
||||||
|
static constexpr uint32_t kInitialTableSize = 4096; |
||||||
|
|
||||||
|
// last index in the static table
|
||||||
|
static constexpr uint32_t kLastStaticEntry = 61; |
||||||
|
|
||||||
|
static constexpr uint32_t EntriesForBytes(uint32_t bytes) noexcept { |
||||||
|
return (bytes + kEntryOverhead - 1) / kEntryOverhead; |
||||||
|
} |
||||||
|
|
||||||
|
static constexpr uint32_t kInitialTableEntries = |
||||||
|
EntriesForBytes(kInitialTableSize); |
||||||
|
} // namespace hpack_constants
|
||||||
|
} // namespace grpc_core
|
||||||
|
|
||||||
|
#endif // GRPC_CORE_EXT_TRANSPORT_CHTTP2_TRANSPORT_HPACK_CONSTANTS_H
|
@ -0,0 +1,86 @@ |
|||||||
|
// Copyright 2021 gRPC authors.
|
||||||
|
//
|
||||||
|
// Licensed 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 <grpc/support/port_platform.h> |
||||||
|
|
||||||
|
#include <grpc/support/log.h> |
||||||
|
|
||||||
|
#include "src/core/ext/transport/chttp2/transport/hpack_encoder_table.h" |
||||||
|
|
||||||
|
namespace grpc_core { |
||||||
|
|
||||||
|
uint32_t HPackEncoderTable::AllocateIndex(size_t element_size) { |
||||||
|
uint32_t new_index = tail_remote_index_ + table_elems_ + 1; |
||||||
|
GPR_DEBUG_ASSERT(element_size < 65536); |
||||||
|
|
||||||
|
if (element_size > max_table_size_) { |
||||||
|
while (table_size_ > 0) { |
||||||
|
EvictOne(); |
||||||
|
} |
||||||
|
return 0; |
||||||
|
} |
||||||
|
|
||||||
|
// Reserve space for this element in the remote table: if this overflows
|
||||||
|
// the current table, drop elements until it fits, matching the decompressor
|
||||||
|
// algorithm.
|
||||||
|
while (table_size_ + element_size > max_table_size_) { |
||||||
|
EvictOne(); |
||||||
|
} |
||||||
|
GPR_ASSERT(table_elems_ < elem_size_.size()); |
||||||
|
elem_size_[new_index % elem_size_.size()] = |
||||||
|
static_cast<uint16_t>(element_size); |
||||||
|
table_size_ += element_size; |
||||||
|
table_elems_++; |
||||||
|
|
||||||
|
return new_index; |
||||||
|
} |
||||||
|
|
||||||
|
bool HPackEncoderTable::SetMaxSize(uint32_t max_table_size) { |
||||||
|
if (max_table_size == max_table_size_) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
while (table_size_ > 0 && table_size_ > max_table_size) { |
||||||
|
EvictOne(); |
||||||
|
} |
||||||
|
max_table_size_ = max_table_size; |
||||||
|
const size_t max_table_elems = |
||||||
|
hpack_constants::EntriesForBytes(max_table_size); |
||||||
|
// TODO(ctiller): integrate with ResourceQuota to rebuild smaller when we can.
|
||||||
|
if (max_table_elems > elem_size_.size()) { |
||||||
|
Rebuild(std::max(max_table_elems, 2 * elem_size_.size())); |
||||||
|
} |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
void HPackEncoderTable::EvictOne() { |
||||||
|
tail_remote_index_++; |
||||||
|
GPR_ASSERT(tail_remote_index_ > 0); |
||||||
|
GPR_ASSERT(table_elems_ > 0); |
||||||
|
auto removing_size = elem_size_[tail_remote_index_ % elem_size_.size()]; |
||||||
|
GPR_ASSERT(table_size_ >= removing_size); |
||||||
|
table_size_ -= removing_size; |
||||||
|
table_elems_--; |
||||||
|
} |
||||||
|
|
||||||
|
void HPackEncoderTable::Rebuild(uint32_t capacity) { |
||||||
|
decltype(elem_size_) new_elem_size(capacity); |
||||||
|
GPR_ASSERT(table_elems_ <= capacity); |
||||||
|
for (uint32_t i = 0; i < table_elems_; i++) { |
||||||
|
uint32_t ofs = tail_remote_index_ + i + 1; |
||||||
|
new_elem_size[ofs % capacity] = elem_size_[ofs % elem_size_.size()]; |
||||||
|
} |
||||||
|
elem_size_.swap(new_elem_size); |
||||||
|
} |
||||||
|
|
||||||
|
} // namespace grpc_core
|
@ -0,0 +1,68 @@ |
|||||||
|
// Copyright 2021 gRPC authors.
|
||||||
|
//
|
||||||
|
// Licensed 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.
|
||||||
|
|
||||||
|
#ifndef GRPC_CORE_EXT_TRANSPORT_CHTTP2_TRANSPORT_HPACK_ENCODER_TABLE_H |
||||||
|
#define GRPC_CORE_EXT_TRANSPORT_CHTTP2_TRANSPORT_HPACK_ENCODER_TABLE_H |
||||||
|
|
||||||
|
#include <grpc/support/port_platform.h> |
||||||
|
|
||||||
|
#include "absl/container/inlined_vector.h" |
||||||
|
#include "src/core/ext/transport/chttp2/transport/hpack_constants.h" |
||||||
|
|
||||||
|
namespace grpc_core { |
||||||
|
|
||||||
|
// Tracks the values available in the remote HPACK header table, and their
|
||||||
|
// sizes.
|
||||||
|
class HPackEncoderTable { |
||||||
|
public: |
||||||
|
HPackEncoderTable() : elem_size_(hpack_constants::kInitialTableEntries) {} |
||||||
|
|
||||||
|
// Reserve space in table for the new element, evict entries if needed.
|
||||||
|
// Return the new index of the element. Return 0 to indicate not adding to
|
||||||
|
// table.
|
||||||
|
uint32_t AllocateIndex(size_t element_size); |
||||||
|
// Set the maximum table size. Return true if it changed.
|
||||||
|
bool SetMaxSize(uint32_t max_table_size); |
||||||
|
// Get the current max table size
|
||||||
|
uint32_t max_size() const { return max_table_size_; } |
||||||
|
// Get the current table size
|
||||||
|
uint32_t test_only_table_size() const { return table_size_; } |
||||||
|
|
||||||
|
// Convert an element index into a dynamic index
|
||||||
|
uint32_t DynamicIndex(uint32_t index) const { |
||||||
|
return 1 + hpack_constants::kLastStaticEntry + tail_remote_index_ + |
||||||
|
table_elems_ - index; |
||||||
|
} |
||||||
|
// Check if an element index is convertable to a dynamic index
|
||||||
|
bool ConvertableToDynamicIndex(uint32_t index) const { |
||||||
|
return index > tail_remote_index_; |
||||||
|
} |
||||||
|
|
||||||
|
private: |
||||||
|
void EvictOne(); |
||||||
|
void Rebuild(uint32_t capacity); |
||||||
|
|
||||||
|
// one before the lowest usable table index
|
||||||
|
uint32_t tail_remote_index_ = 0; |
||||||
|
uint32_t max_table_size_ = hpack_constants::kInitialTableSize; |
||||||
|
uint32_t table_elems_ = 0; |
||||||
|
uint32_t table_size_ = 0; |
||||||
|
// The size of each element in the HPACK table.
|
||||||
|
absl::InlinedVector<uint16_t, hpack_constants::kInitialTableEntries> |
||||||
|
elem_size_; |
||||||
|
}; |
||||||
|
|
||||||
|
} // namespace grpc_core
|
||||||
|
|
||||||
|
#endif // GRPC_CORE_EXT_TRANSPORT_CHTTP2_TRANSPORT_HPACK_ENCODER_TABLE_H
|
@ -0,0 +1,45 @@ |
|||||||
|
// Copyright 2021 gRPC authors.
|
||||||
|
//
|
||||||
|
// Licensed 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 <grpc/support/port_platform.h> |
||||||
|
|
||||||
|
#include "src/core/ext/transport/chttp2/transport/hpack_utils.h" |
||||||
|
#include "src/core/lib/surface/validate_metadata.h" |
||||||
|
|
||||||
|
namespace grpc_core { |
||||||
|
|
||||||
|
namespace { |
||||||
|
size_t Base64EncodedSize(size_t raw_length) { |
||||||
|
static constexpr uint8_t tail_xtra[3] = {0, 2, 3}; |
||||||
|
return raw_length / 3 * 4 + tail_xtra[raw_length % 3]; |
||||||
|
} |
||||||
|
} // namespace
|
||||||
|
|
||||||
|
// Return the size occupied by some metadata in the HPACK table.
|
||||||
|
size_t MetadataSizeInHPackTable(grpc_mdelem elem, |
||||||
|
bool use_true_binary_metadata) { |
||||||
|
const uint8_t* key_buf = GRPC_SLICE_START_PTR(GRPC_MDKEY(elem)); |
||||||
|
size_t key_len = GRPC_SLICE_LENGTH(GRPC_MDKEY(elem)); |
||||||
|
size_t overhead_and_key = 32 + key_len; |
||||||
|
size_t value_len = GRPC_SLICE_LENGTH(GRPC_MDVALUE(elem)); |
||||||
|
if (grpc_key_is_binary_header(key_buf, key_len)) { |
||||||
|
return overhead_and_key + (use_true_binary_metadata |
||||||
|
? value_len + 1 |
||||||
|
: Base64EncodedSize(value_len)); |
||||||
|
} else { |
||||||
|
return overhead_and_key + value_len; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} // namespace grpc_core
|
@ -0,0 +1,30 @@ |
|||||||
|
// Copyright 2021 gRPC authors.
|
||||||
|
//
|
||||||
|
// Licensed 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.
|
||||||
|
|
||||||
|
#ifndef GRPC_CORE_EXT_TRANSPORT_CHTTP2_TRANSPORT_HPACK_UTILS_H |
||||||
|
#define GRPC_CORE_EXT_TRANSPORT_CHTTP2_TRANSPORT_HPACK_UTILS_H |
||||||
|
|
||||||
|
#include <grpc/support/port_platform.h> |
||||||
|
|
||||||
|
#include "src/core/lib/transport/metadata.h" |
||||||
|
|
||||||
|
namespace grpc_core { |
||||||
|
|
||||||
|
// Return the size occupied by some metadata in the HPACK table.
|
||||||
|
size_t MetadataSizeInHPackTable(grpc_mdelem elem, |
||||||
|
bool use_true_binary_metadata); |
||||||
|
|
||||||
|
} // namespace grpc_core
|
||||||
|
|
||||||
|
#endif // GRPC_CORE_EXT_TRANSPORT_CHTTP2_TRANSPORT_HPACK_UTILS_H
|
@ -0,0 +1,120 @@ |
|||||||
|
// Copyright 2021 gRPC authors.
|
||||||
|
//
|
||||||
|
// Licensed 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 <grpc/impl/codegen/port_platform.h> |
||||||
|
|
||||||
|
#include <gtest/gtest.h> |
||||||
|
#include <random> |
||||||
|
#include <unordered_map> |
||||||
|
#include "src/core/ext/transport/chttp2/transport/hpack_encoder_index.h" |
||||||
|
|
||||||
|
namespace grpc_core { |
||||||
|
namespace testing { |
||||||
|
|
||||||
|
static void VerifyAsciiHeaderSize(const char* key, const char* value, |
||||||
|
bool intern_key, bool intern_value) { |
||||||
|
grpc_mdelem elem = grpc_mdelem_from_slices( |
||||||
|
maybe_intern(grpc_slice_from_static_string(key), intern_key), |
||||||
|
maybe_intern(grpc_slice_from_static_string(value), intern_value)); |
||||||
|
size_t elem_size = grpc_core::MetadataSizeInHPackTable(elem, false); |
||||||
|
size_t expected_size = 32 + strlen(key) + strlen(value); |
||||||
|
GPR_ASSERT(expected_size == elem_size); |
||||||
|
GRPC_MDELEM_UNREF(elem); |
||||||
|
} |
||||||
|
|
||||||
|
static void VerifyBinaryHeaderSize(const char* key, const uint8_t* value, |
||||||
|
size_t value_len, bool intern_key, |
||||||
|
bool intern_value) { |
||||||
|
grpc_mdelem elem = grpc_mdelem_from_slices( |
||||||
|
maybe_intern(grpc_slice_from_static_string(key), intern_key), |
||||||
|
maybe_intern(grpc_slice_from_static_buffer(value, value_len), |
||||||
|
intern_value)); |
||||||
|
GPR_ASSERT(grpc_is_binary_header(GRPC_MDKEY(elem))); |
||||||
|
size_t elem_size = grpc_core::MetadataSizeInHPackTable(elem, false); |
||||||
|
grpc_slice value_slice = grpc_slice_from_copied_buffer( |
||||||
|
reinterpret_cast<const char*>(value), value_len); |
||||||
|
grpc_slice base64_encoded = grpc_chttp2_base64_encode(value_slice); |
||||||
|
size_t expected_size = 32 + strlen(key) + GRPC_SLICE_LENGTH(base64_encoded); |
||||||
|
GPR_ASSERT(expected_size == elem_size); |
||||||
|
grpc_slice_unref_internal(value_slice); |
||||||
|
grpc_slice_unref_internal(base64_encoded); |
||||||
|
GRPC_MDELEM_UNREF(elem); |
||||||
|
} |
||||||
|
|
||||||
|
struct Param { |
||||||
|
bool intern_key; |
||||||
|
bool intern_value; |
||||||
|
} |
||||||
|
|
||||||
|
class MetadataTest : public ::testing::TestWithParam<Param> { |
||||||
|
}; |
||||||
|
|
||||||
|
#define BUFFER_SIZE 64 |
||||||
|
TEST_P(MetadataTest, MetadataSize) { |
||||||
|
const bool intern_key = GetParam().intern_key; |
||||||
|
const bool intern_value = GetParam().intern_value; |
||||||
|
gpr_log(GPR_INFO, "test_mdelem_size: intern_key=%d intern_value=%d", |
||||||
|
intern_key, intern_value); |
||||||
|
grpc_init(); |
||||||
|
grpc_core::ExecCtx exec_ctx; |
||||||
|
|
||||||
|
uint8_t binary_value[BUFFER_SIZE] = {0}; |
||||||
|
for (uint8_t i = 0; i < BUFFER_SIZE; i++) { |
||||||
|
binary_value[i] = i; |
||||||
|
} |
||||||
|
|
||||||
|
verify_ascii_header_size("hello", "world", intern_key, intern_value); |
||||||
|
verify_ascii_header_size("hello", "worldxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", |
||||||
|
intern_key, intern_value); |
||||||
|
verify_ascii_header_size(":scheme", "http", intern_key, intern_value); |
||||||
|
|
||||||
|
for (uint8_t i = 0; i < BUFFER_SIZE; i++) { |
||||||
|
verify_binary_header_size("hello-bin", binary_value, i, intern_key, |
||||||
|
intern_value); |
||||||
|
} |
||||||
|
|
||||||
|
grpc_shutdown(); |
||||||
|
} |
||||||
|
|
||||||
|
INSTANTIATE_TEST_SUITE_P(MetadataTestSuite, MetadataTest, |
||||||
|
::testing::Values(Param{false, false}, |
||||||
|
Param{false, true}, |
||||||
|
Param{true, false}, |
||||||
|
Param{true, true})); |
||||||
|
|
||||||
|
TEST(HPackEncoderIndexTest, SetAndGet) { |
||||||
|
HPackEncoderIndex<TestKey, 64> index; |
||||||
|
std::default_random_engine rng; |
||||||
|
std::unordered_map<uint32_t, uint32_t> last_index; |
||||||
|
for (uint32_t i = 0; i < 10000; i++) { |
||||||
|
uint32_t key = rng(); |
||||||
|
index.Insert({key}, i); |
||||||
|
EXPECT_EQ(index.Lookup({key}), i); |
||||||
|
last_index[key] = i; |
||||||
|
} |
||||||
|
for (auto p : last_index) { |
||||||
|
auto r = index.Lookup({p.first}); |
||||||
|
if (r.has_value()) { |
||||||
|
EXPECT_EQ(*r, p.second); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} // namespace testing
|
||||||
|
} // namespace grpc_core
|
||||||
|
|
||||||
|
int main(int argc, char** argv) { |
||||||
|
::testing::InitGoogleTest(&argc, argv); |
||||||
|
return RUN_ALL_TESTS(); |
||||||
|
} |
Loading…
Reference in new issue