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