xref: /aosp_15_r20/external/grpc-grpc/test/core/transport/chttp2/hpack_encoder_test.cc (revision cc02d7e222339f7a4f6ba5f422e6413f4bd931f2)
1 //
2 //
3 // Copyright 2015 gRPC authors.
4 //
5 // Licensed under the Apache License, Version 2.0 (the "License");
6 // you may not use this file except in compliance with the License.
7 // You may obtain a copy of the License at
8 //
9 //     http://www.apache.org/licenses/LICENSE-2.0
10 //
11 // Unless required by applicable law or agreed to in writing, software
12 // distributed under the License is distributed on an "AS IS" BASIS,
13 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 // See the License for the specific language governing permissions and
15 // limitations under the License.
16 //
17 //
18 
19 #include "src/core/ext/transport/chttp2/transport/hpack_encoder.h"
20 
21 #include <stdlib.h>
22 #include <string.h>
23 
24 #include <memory>
25 #include <string>
26 
27 #include "gmock/gmock.h"
28 #include "gtest/gtest.h"
29 
30 #include <grpc/event_engine/memory_allocator.h>
31 #include <grpc/slice_buffer.h>
32 #include <grpc/support/log.h>
33 
34 #include "src/core/ext/transport/chttp2/transport/legacy_frame.h"
35 #include "src/core/lib/gprpp/ref_counted_ptr.h"
36 #include "src/core/lib/iomgr/exec_ctx.h"
37 #include "src/core/lib/resource_quota/arena.h"
38 #include "src/core/lib/resource_quota/memory_quota.h"
39 #include "src/core/lib/resource_quota/resource_quota.h"
40 #include "test/core/util/parse_hexstring.h"
41 #include "test/core/util/slice_splitter.h"
42 #include "test/core/util/test_config.h"
43 
44 grpc_core::HPackCompressor* g_compressor;
45 
46 typedef struct {
47   bool eof;
48   bool use_true_binary_metadata;
49 } verify_params;
50 
51 // verify that the output frames that are generated by encoding the stream
52 // have sensible type and flags values
verify_frames(grpc_slice_buffer & output,bool header_is_eof)53 static void verify_frames(grpc_slice_buffer& output, bool header_is_eof) {
54   // per the HTTP/2 spec:
55   //   All frames begin with a fixed 9-octet header followed by a
56   //   variable-length payload.
57 
58   //   +-----------------------------------------------+
59   //   |                 Length (24)                   |
60   //   +---------------+---------------+---------------+
61   //   |   Type (8)    |   Flags (8)   |
62   //   +-+-------------+---------------+-------------------------------+
63   //   |R|                 Stream Identifier (31)                      |
64   //   +=+=============================================================+
65   //   |                   Frame Payload (0...)                      ...
66   //   +---------------------------------------------------------------+
67   //
68   uint8_t type = 0xff, flags = 0xff;
69   size_t i, merged_length, frame_size;
70   bool first_frame = false;
71   bool in_header = false;
72   bool end_header = false;
73   bool is_closed = false;
74   for (i = 0; i < output.count;) {
75     first_frame = i == 0;
76     grpc_slice* slice = &output.slices[i++];
77 
78     // Read gRPC frame header
79     uint8_t* p = GRPC_SLICE_START_PTR(*slice);
80     frame_size = 0;
81     frame_size |= static_cast<uint32_t>(p[0]) << 16;
82     frame_size |= static_cast<uint32_t>(p[1]) << 8;
83     frame_size |= static_cast<uint32_t>(p[2]);
84     type = p[3];
85     flags = p[4];
86 
87     // Read remainder of the gRPC frame
88     merged_length = GRPC_SLICE_LENGTH(*slice);
89     while (merged_length < frame_size + 9) {  // including 9 byte frame header
90       grpc_slice* slice = &output.slices[i++];
91       merged_length += GRPC_SLICE_LENGTH(*slice);
92     }
93 
94     // Verifications
95     if (first_frame && type != GRPC_CHTTP2_FRAME_HEADER) {
96       gpr_log(GPR_ERROR, "expected first frame to be of type header");
97       gpr_log(GPR_ERROR, "EXPECT: 0x%x", GRPC_CHTTP2_FRAME_HEADER);
98       gpr_log(GPR_ERROR, "GOT:    0x%x", type);
99       EXPECT_TRUE(false);
100     } else if (first_frame && header_is_eof &&
101                !(flags & GRPC_CHTTP2_DATA_FLAG_END_STREAM)) {
102       gpr_log(GPR_ERROR, "missing END_STREAM flag in HEADER frame");
103       EXPECT_TRUE(false);
104     }
105     if (is_closed &&
106         (type == GRPC_CHTTP2_FRAME_DATA || type == GRPC_CHTTP2_FRAME_HEADER)) {
107       gpr_log(GPR_ERROR,
108               "stream is closed; new frame headers and data are not allowed");
109       EXPECT_TRUE(false);
110     }
111     if (end_header && (type == GRPC_CHTTP2_FRAME_HEADER ||
112                        type == GRPC_CHTTP2_FRAME_CONTINUATION)) {
113       gpr_log(GPR_ERROR,
114               "frame header is ended; new headers and continuations are not "
115               "allowed");
116       EXPECT_TRUE(false);
117     }
118     if (in_header &&
119         (type == GRPC_CHTTP2_FRAME_DATA || type == GRPC_CHTTP2_FRAME_HEADER)) {
120       gpr_log(GPR_ERROR,
121               "parsing frame header; new headers and data are not allowed");
122       EXPECT_TRUE(false);
123     }
124     if (flags & ~(GRPC_CHTTP2_DATA_FLAG_END_STREAM |
125                   GRPC_CHTTP2_DATA_FLAG_END_HEADERS)) {
126       gpr_log(GPR_ERROR, "unexpected frame flags: 0x%x", flags);
127       EXPECT_TRUE(false);
128     }
129 
130     // Update state
131     if (flags & GRPC_CHTTP2_DATA_FLAG_END_HEADERS) {
132       in_header = false;
133       end_header = true;
134     } else if (type == GRPC_CHTTP2_DATA_FLAG_END_HEADERS) {
135       in_header = true;
136     }
137     if (flags & GRPC_CHTTP2_DATA_FLAG_END_STREAM) {
138       is_closed = true;
139       if (type == GRPC_CHTTP2_FRAME_CONTINUATION) {
140         gpr_log(GPR_ERROR, "unexpected END_STREAM flag in CONTINUATION frame");
141         EXPECT_TRUE(false);
142       }
143     }
144   }
145 }
146 
CrashOnAppendError(absl::string_view,const grpc_core::Slice &)147 static void CrashOnAppendError(absl::string_view, const grpc_core::Slice&) {
148   abort();
149 }
150 
EncodeHeaderIntoBytes(bool is_eof,const std::vector<std::pair<std::string,std::string>> & header_fields)151 grpc_slice EncodeHeaderIntoBytes(
152     bool is_eof,
153     const std::vector<std::pair<std::string, std::string>>& header_fields) {
154   std::unique_ptr<grpc_core::HPackCompressor> compressor =
155       std::make_unique<grpc_core::HPackCompressor>();
156 
157   grpc_core::MemoryAllocator memory_allocator =
158       grpc_core::MemoryAllocator(grpc_core::ResourceQuota::Default()
159                                      ->memory_quota()
160                                      ->CreateMemoryAllocator("test"));
161   auto arena = grpc_core::MakeScopedArena(1024, &memory_allocator);
162   grpc_metadata_batch b;
163 
164   for (const auto& field : header_fields) {
165     b.Append(field.first,
166              grpc_core::Slice::FromStaticString(field.second.c_str()),
167              CrashOnAppendError);
168   }
169 
170   grpc_transport_one_way_stats stats = {};
171   grpc_core::HPackCompressor::EncodeHeaderOptions hopt{
172       0xdeadbeef,  // stream_id
173       is_eof,      // is_eof
174       false,       // use_true_binary_metadata
175       16384,       // max_frame_size
176       &stats       // stats
177   };
178   grpc_slice_buffer output;
179   grpc_slice_buffer_init(&output);
180 
181   compressor->EncodeHeaders(hopt, b, &output);
182   verify_frames(output, is_eof);
183 
184   grpc_slice ret = grpc_slice_merge(output.slices, output.count);
185   grpc_slice_buffer_destroy(&output);
186 
187   return ret;
188 }
189 
190 // verify that the output generated by encoding the stream matches the
191 // hexstring passed in
verify(bool is_eof,const char * expected,const std::vector<std::pair<std::string,std::string>> & header_fields)192 static void verify(
193     bool is_eof, const char* expected,
194     const std::vector<std::pair<std::string, std::string>>& header_fields) {
195   const grpc_core::Slice merged(EncodeHeaderIntoBytes(is_eof, header_fields));
196   const grpc_core::Slice expect(grpc_core::ParseHexstring(expected));
197 
198   EXPECT_EQ(merged, expect);
199 }
200 
TEST(HpackEncoderTest,TestBasicHeaders)201 TEST(HpackEncoderTest, TestBasicHeaders) {
202   grpc_core::ExecCtx exec_ctx;
203   g_compressor = new grpc_core::HPackCompressor();
204 
205   verify(false, "000005 0104 deadbeef 00 0161 0161", {{"a", "a"}});
206   verify(false, "00000a 0104 deadbeef 00 0161 0161 00 0162 0163",
207          {{"a", "a"}, {"b", "c"}});
208 
209   delete g_compressor;
210 }
211 
212 MATCHER(HasLiteralHeaderFieldNewNameFlagIncrementalIndexing, "") {
213   constexpr size_t kHttp2FrameHeaderSize = 9u;
214   /// Reference: https://httpwg.org/specs/rfc7541.html#rfc.section.6.2.1
215   /// The first byte of a literal header field with incremental indexing should
216   /// be 0x40.
217   constexpr uint8_t kLiteralHeaderFieldNewNameFlagIncrementalIndexing = 0x40;
218   return (GRPC_SLICE_START_PTR(arg)[kHttp2FrameHeaderSize] ==
219           kLiteralHeaderFieldNewNameFlagIncrementalIndexing);
220 }
221 
222 MATCHER(HasLiteralHeaderFieldNewNameFlagNoIndexing, "") {
223   constexpr size_t kHttp2FrameHeaderSize = 9u;
224   /// Reference: https://httpwg.org/specs/rfc7541.html#rfc.section.6.2.2
225   /// The first byte of a literal header field without indexing should be 0x0.
226   constexpr uint8_t kLiteralHeaderFieldNewNameFlagNoIndexing = 0x00;
227   return (GRPC_SLICE_START_PTR(arg)[kHttp2FrameHeaderSize] ==
228           kLiteralHeaderFieldNewNameFlagNoIndexing);
229 }
230 
TEST(HpackEncoderTest,GrpcTraceBinMetadataIndexing)231 TEST(HpackEncoderTest, GrpcTraceBinMetadataIndexing) {
232   grpc_core::ExecCtx exec_ctx;
233 
234   const grpc_slice encoded_header = EncodeHeaderIntoBytes(
235       false, {{grpc_core::GrpcTraceBinMetadata::key().data(), "value"}});
236   EXPECT_THAT(encoded_header,
237               HasLiteralHeaderFieldNewNameFlagIncrementalIndexing());
238 
239   grpc_slice_unref(encoded_header);
240 }
241 
TEST(HpackEncoderTest,GrpcTraceBinMetadataNoIndexing)242 TEST(HpackEncoderTest, GrpcTraceBinMetadataNoIndexing) {
243   grpc_core::ExecCtx exec_ctx;
244 
245   /// needs to be greater than `HPackEncoderTable::MaxEntrySize()`
246   constexpr size_t long_value_size = 70000u;
247   const grpc_slice encoded_header = EncodeHeaderIntoBytes(
248       false, {{grpc_core::GrpcTraceBinMetadata::key().data(),
249                std::string(long_value_size, 'a')}});
250   EXPECT_THAT(encoded_header, HasLiteralHeaderFieldNewNameFlagNoIndexing());
251 
252   grpc_slice_unref(encoded_header);
253 }
254 
TEST(HpackEncoderTest,TestGrpcTagsBinMetadataIndexing)255 TEST(HpackEncoderTest, TestGrpcTagsBinMetadataIndexing) {
256   grpc_core::ExecCtx exec_ctx;
257 
258   const grpc_slice encoded_header = EncodeHeaderIntoBytes(
259       false,
260       {{grpc_core::GrpcTagsBinMetadata::key().data(), std::string("value")}});
261   EXPECT_THAT(encoded_header,
262               HasLiteralHeaderFieldNewNameFlagIncrementalIndexing());
263 
264   grpc_slice_unref(encoded_header);
265 }
266 
TEST(HpackEncoderTest,TestGrpcTagsBinMetadataNoIndexing)267 TEST(HpackEncoderTest, TestGrpcTagsBinMetadataNoIndexing) {
268   grpc_core::ExecCtx exec_ctx;
269 
270   /// needs to be greater than `HPackEncoderTable::MaxEntrySize()`
271   constexpr size_t long_value_size = 70000u;
272   const grpc_slice encoded_header = EncodeHeaderIntoBytes(
273       false, {{grpc_core::GrpcTagsBinMetadata::key().data(),
274                std::string(long_value_size, 'a')}});
275   EXPECT_THAT(encoded_header, HasLiteralHeaderFieldNewNameFlagNoIndexing());
276 
277   grpc_slice_unref(encoded_header);
278 }
279 
TEST(HpackEncoderTest,UserAgentMetadataIndexing)280 TEST(HpackEncoderTest, UserAgentMetadataIndexing) {
281   grpc_core::ExecCtx exec_ctx;
282 
283   const grpc_slice encoded_header = EncodeHeaderIntoBytes(
284       false, {{grpc_core::UserAgentMetadata::key().data(), "value"}});
285   EXPECT_THAT(encoded_header,
286               HasLiteralHeaderFieldNewNameFlagIncrementalIndexing());
287 
288   grpc_slice_unref(encoded_header);
289 }
290 
TEST(HpackEncoderTest,UserAgentMetadataNoIndexing)291 TEST(HpackEncoderTest, UserAgentMetadataNoIndexing) {
292   grpc_core::ExecCtx exec_ctx;
293 
294   /// needs to be greater than `HPackEncoderTable::MaxEntrySize()`
295   constexpr size_t long_value_size = 70000u;
296   const grpc_slice encoded_header =
297       EncodeHeaderIntoBytes(false, {{grpc_core::UserAgentMetadata::key().data(),
298                                      std::string(long_value_size, 'a')}});
299   EXPECT_THAT(encoded_header, HasLiteralHeaderFieldNewNameFlagNoIndexing());
300 
301   grpc_slice_unref(encoded_header);
302 }
303 
verify_continuation_headers(const char * key,const char * value,bool is_eof)304 static void verify_continuation_headers(const char* key, const char* value,
305                                         bool is_eof) {
306   grpc_core::MemoryAllocator memory_allocator =
307       grpc_core::MemoryAllocator(grpc_core::ResourceQuota::Default()
308                                      ->memory_quota()
309                                      ->CreateMemoryAllocator("test"));
310   auto arena = grpc_core::MakeScopedArena(1024, &memory_allocator);
311   grpc_slice_buffer output;
312   grpc_metadata_batch b;
313   b.Append(key, grpc_core::Slice::FromStaticString(value), CrashOnAppendError);
314   grpc_slice_buffer_init(&output);
315 
316   grpc_transport_one_way_stats stats;
317   stats = {};
318   grpc_core::HPackCompressor::EncodeHeaderOptions hopt = {
319       0xdeadbeef,  // stream_id
320       is_eof,      // is_eof
321       false,       // use_true_binary_metadata
322       150,         // max_frame_size
323       &stats /* stats */};
324   g_compressor->EncodeHeaders(hopt, b, &output);
325   verify_frames(output, is_eof);
326   grpc_slice_buffer_destroy(&output);
327 }
328 
TEST(HpackEncoderTest,TestContinuationHeaders)329 TEST(HpackEncoderTest, TestContinuationHeaders) {
330   grpc_core::ExecCtx exec_ctx;
331   g_compressor = new grpc_core::HPackCompressor();
332 
333   char value[200];
334   memset(value, 'a', 200);
335   value[199] = 0;  // null terminator
336   verify_continuation_headers("key", value, true);
337 
338   char value2[400];
339   memset(value2, 'b', 400);
340   value2[399] = 0;  // null terminator
341   verify_continuation_headers("key2", value2, true);
342 
343   delete g_compressor;
344 }
345 
TEST(HpackEncoderTest,EncodeBinaryAsBase64)346 TEST(HpackEncoderTest, EncodeBinaryAsBase64) {
347   grpc_core::MemoryAllocator memory_allocator =
348       grpc_core::MemoryAllocator(grpc_core::ResourceQuota::Default()
349                                      ->memory_quota()
350                                      ->CreateMemoryAllocator("test"));
351   auto arena = grpc_core::MakeScopedArena(1024, &memory_allocator);
352   grpc_metadata_batch b;
353   // Haiku by Bard
354   b.Append("grpc-trace-bin",
355            grpc_core::Slice::FromStaticString(
356                "Base64, a tool\nTo encode binary data into "
357                "text\nSo it can be shared."),
358            CrashOnAppendError);
359   grpc_transport_one_way_stats stats;
360   stats = {};
361   grpc_slice_buffer output;
362   grpc_slice_buffer_init(&output);
363   grpc_core::HPackCompressor::EncodeHeaderOptions hopt = {
364       0xdeadbeef,  // stream_id
365       true,        // is_eof
366       false,       // use_true_binary_metadata
367       150,         // max_frame_size
368       &stats};
369   grpc_core::HPackCompressor compressor;
370   compressor.EncodeHeaders(hopt, b, &output);
371   grpc_slice_buffer_destroy(&output);
372 
373   EXPECT_EQ(compressor.test_only_table_size(), 136);
374 }
375 
TEST(HpackEncoderTest,EncodeBinaryAsTrueBinary)376 TEST(HpackEncoderTest, EncodeBinaryAsTrueBinary) {
377   grpc_core::MemoryAllocator memory_allocator =
378       grpc_core::MemoryAllocator(grpc_core::ResourceQuota::Default()
379                                      ->memory_quota()
380                                      ->CreateMemoryAllocator("test"));
381   auto arena = grpc_core::MakeScopedArena(1024, &memory_allocator);
382   grpc_metadata_batch b;
383   // Haiku by Bard
384   b.Append("grpc-trace-bin",
385            grpc_core::Slice::FromStaticString(
386                "Base64, a tool\nTo encode binary data into "
387                "text\nSo it can be shared."),
388            CrashOnAppendError);
389   grpc_transport_one_way_stats stats;
390   stats = {};
391   grpc_slice_buffer output;
392   grpc_slice_buffer_init(&output);
393   grpc_core::HPackCompressor::EncodeHeaderOptions hopt = {
394       0xdeadbeef,  // stream_id
395       true,        // is_eof
396       true,        // use_true_binary_metadata
397       150,         // max_frame_size
398       &stats};
399   grpc_core::HPackCompressor compressor;
400   compressor.EncodeHeaders(hopt, b, &output);
401   grpc_slice_buffer_destroy(&output);
402 
403   EXPECT_EQ(compressor.test_only_table_size(), 114);
404 }
405 
main(int argc,char ** argv)406 int main(int argc, char** argv) {
407   grpc::testing::TestEnvironment env(&argc, argv);
408   ::testing::InitGoogleTest(&argc, argv);
409   grpc::testing::TestGrpcScope grpc_scope;
410   return RUN_ALL_TESTS();
411 }
412