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