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/lib/compression/message_compress.h"
20
21 #include <grpc/compression.h>
22 #include <grpc/slice_buffer.h>
23 #include <inttypes.h>
24 #include <stdlib.h>
25 #include <string.h>
26
27 #include <memory>
28
29 #include "absl/log/log.h"
30 #include "gtest/gtest.h"
31 #include "src/core/lib/iomgr/exec_ctx.h"
32 #include "src/core/util/useful.h"
33 #include "test/core/test_util/slice_splitter.h"
34 #include "test/core/test_util/test_config.h"
35
36 typedef enum { ONE_A = 0, ONE_KB_A, ONE_MB_A, TEST_VALUE_COUNT } test_value;
37
38 typedef enum {
39 SHOULD_NOT_COMPRESS,
40 SHOULD_COMPRESS,
41 MAYBE_COMPRESSES
42 } compressability;
43
assert_passthrough(grpc_slice value,grpc_compression_algorithm algorithm,grpc_slice_split_mode uncompressed_split_mode,grpc_slice_split_mode compressed_split_mode,compressability compress_result_check)44 static void assert_passthrough(grpc_slice value,
45 grpc_compression_algorithm algorithm,
46 grpc_slice_split_mode uncompressed_split_mode,
47 grpc_slice_split_mode compressed_split_mode,
48 compressability compress_result_check) {
49 grpc_slice_buffer input;
50 grpc_slice_buffer compressed_raw;
51 grpc_slice_buffer compressed;
52 grpc_slice_buffer output;
53 grpc_slice final;
54 int was_compressed;
55 const char* algorithm_name;
56
57 ASSERT_NE(grpc_compression_algorithm_name(algorithm, &algorithm_name), 0);
58 LOG(INFO) << "assert_passthrough: value_length=" << GRPC_SLICE_LENGTH(value)
59 << " algorithm='" << algorithm_name << "' uncompressed_split='"
60 << grpc_slice_split_mode_name(uncompressed_split_mode)
61 << "' compressed_split='"
62 << grpc_slice_split_mode_name(compressed_split_mode) << "'";
63
64 grpc_slice_buffer_init(&input);
65 grpc_slice_buffer_init(&compressed_raw);
66 grpc_slice_buffer_init(&compressed);
67 grpc_slice_buffer_init(&output);
68
69 grpc_split_slices_to_buffer(uncompressed_split_mode, &value, 1, &input);
70
71 {
72 grpc_core::ExecCtx exec_ctx;
73 was_compressed = grpc_msg_compress(algorithm, &input, &compressed_raw);
74 }
75 ASSERT_GT(input.count, 0);
76
77 switch (compress_result_check) {
78 case SHOULD_NOT_COMPRESS:
79 ASSERT_EQ(was_compressed, 0);
80 break;
81 case SHOULD_COMPRESS:
82 ASSERT_EQ(was_compressed, 1);
83 break;
84 case MAYBE_COMPRESSES:
85 // no check
86 break;
87 }
88
89 grpc_split_slice_buffer(compressed_split_mode, &compressed_raw, &compressed);
90
91 {
92 grpc_core::ExecCtx exec_ctx;
93 ASSERT_TRUE(grpc_msg_decompress(
94 was_compressed ? algorithm : GRPC_COMPRESS_NONE, &compressed, &output));
95 }
96
97 final = grpc_slice_merge(output.slices, output.count);
98 ASSERT_TRUE(grpc_slice_eq(value, final));
99
100 grpc_slice_buffer_destroy(&input);
101 grpc_slice_buffer_destroy(&compressed);
102 grpc_slice_buffer_destroy(&compressed_raw);
103 grpc_slice_buffer_destroy(&output);
104 grpc_slice_unref(final);
105 }
106
repeated(char c,size_t length)107 static grpc_slice repeated(char c, size_t length) {
108 grpc_slice out = grpc_slice_malloc(length);
109 memset(GRPC_SLICE_START_PTR(out), c, length);
110 return out;
111 }
112
get_compressability(test_value id,grpc_compression_algorithm algorithm)113 static compressability get_compressability(
114 test_value id, grpc_compression_algorithm algorithm) {
115 if (algorithm == GRPC_COMPRESS_NONE) return SHOULD_NOT_COMPRESS;
116 switch (id) {
117 case ONE_A:
118 return SHOULD_NOT_COMPRESS;
119 case ONE_KB_A:
120 case ONE_MB_A:
121 return SHOULD_COMPRESS;
122 case TEST_VALUE_COUNT:
123 abort();
124 }
125 return MAYBE_COMPRESSES;
126 }
127
create_test_value(test_value id)128 static grpc_slice create_test_value(test_value id) {
129 switch (id) {
130 case ONE_A:
131 return grpc_slice_from_copied_string("a");
132 case ONE_KB_A:
133 return repeated('a', 1024);
134 case ONE_MB_A:
135 return repeated('a', 1024 * 1024);
136 case TEST_VALUE_COUNT:
137 abort();
138 }
139 return grpc_slice_from_copied_string("bad value");
140 }
141
TEST(MessageCompressTest,TinyDataCompress)142 TEST(MessageCompressTest, TinyDataCompress) {
143 grpc_slice_buffer input;
144 grpc_slice_buffer output;
145
146 grpc_slice_buffer_init(&input);
147 grpc_slice_buffer_init(&output);
148 grpc_slice_buffer_add(&input, create_test_value(ONE_A));
149
150 for (int i = 0; i < GRPC_COMPRESS_ALGORITHMS_COUNT; i++) {
151 if (i == GRPC_COMPRESS_NONE) continue;
152 grpc_core::ExecCtx exec_ctx;
153 ASSERT_EQ(0, grpc_msg_compress(static_cast<grpc_compression_algorithm>(i),
154 &input, &output));
155 ASSERT_EQ(1, output.count);
156 }
157
158 grpc_slice_buffer_destroy(&input);
159 grpc_slice_buffer_destroy(&output);
160 }
161
TEST(MessageCompressTest,BadDecompressionDataCrc)162 TEST(MessageCompressTest, BadDecompressionDataCrc) {
163 grpc_slice_buffer input;
164 grpc_slice_buffer corrupted;
165 grpc_slice_buffer output;
166 size_t idx;
167 const uint32_t bad = 0xdeadbeef;
168
169 grpc_slice_buffer_init(&input);
170 grpc_slice_buffer_init(&corrupted);
171 grpc_slice_buffer_init(&output);
172 grpc_slice_buffer_add(&input, create_test_value(ONE_MB_A));
173
174 grpc_core::ExecCtx exec_ctx;
175 // compress it
176 grpc_msg_compress(GRPC_COMPRESS_GZIP, &input, &corrupted);
177 // corrupt the output by smashing the CRC
178 ASSERT_GT(corrupted.count, 1);
179 ASSERT_GT(GRPC_SLICE_LENGTH(corrupted.slices[1]), 8);
180 idx = GRPC_SLICE_LENGTH(corrupted.slices[1]) - 8;
181 memcpy(GRPC_SLICE_START_PTR(corrupted.slices[1]) + idx, &bad, 4);
182
183 // try (and fail) to decompress the corrupted compressed buffer
184 ASSERT_EQ(0, grpc_msg_decompress(GRPC_COMPRESS_GZIP, &corrupted, &output));
185
186 grpc_slice_buffer_destroy(&input);
187 grpc_slice_buffer_destroy(&corrupted);
188 grpc_slice_buffer_destroy(&output);
189 }
190
TEST(MessageCompressTest,BadDecompressionDataMissingTrailer)191 TEST(MessageCompressTest, BadDecompressionDataMissingTrailer) {
192 grpc_slice_buffer input;
193 grpc_slice_buffer decompressed;
194 grpc_slice_buffer garbage;
195 grpc_slice_buffer output;
196
197 grpc_slice_buffer_init(&input);
198 grpc_slice_buffer_init(&decompressed);
199 grpc_slice_buffer_init(&garbage);
200 grpc_slice_buffer_init(&output);
201 grpc_slice_buffer_add(&input, create_test_value(ONE_MB_A));
202
203 grpc_core::ExecCtx exec_ctx;
204 // compress it
205 grpc_msg_compress(GRPC_COMPRESS_GZIP, &input, &decompressed);
206 ASSERT_GT(decompressed.length, 8);
207 // Remove the footer from the decompressed message
208 grpc_slice_buffer_trim_end(&decompressed, 8, &garbage);
209 // try (and fail) to decompress the compressed buffer without the footer
210 ASSERT_EQ(0, grpc_msg_decompress(GRPC_COMPRESS_GZIP, &decompressed, &output));
211
212 grpc_slice_buffer_destroy(&input);
213 grpc_slice_buffer_destroy(&decompressed);
214 grpc_slice_buffer_destroy(&garbage);
215 grpc_slice_buffer_destroy(&output);
216 }
217
TEST(MessageCompressTest,BadDecompressionDataTrailingGarbage)218 TEST(MessageCompressTest, BadDecompressionDataTrailingGarbage) {
219 grpc_slice_buffer input;
220 grpc_slice_buffer output;
221
222 grpc_slice_buffer_init(&input);
223 grpc_slice_buffer_init(&output);
224 // append 0x99 to the end of an otherwise valid stream
225 grpc_slice_buffer_add(
226 &input, grpc_slice_from_copied_buffer(
227 "\x78\xda\x63\x60\x60\x60\x00\x00\x00\x04\x00\x01\x99", 13));
228
229 // try (and fail) to decompress the invalid compressed buffer
230 grpc_core::ExecCtx exec_ctx;
231 ASSERT_EQ(0, grpc_msg_decompress(GRPC_COMPRESS_DEFLATE, &input, &output));
232
233 grpc_slice_buffer_destroy(&input);
234 grpc_slice_buffer_destroy(&output);
235 }
236
TEST(MessageCompressTest,BadDecompressionDataStream)237 TEST(MessageCompressTest, BadDecompressionDataStream) {
238 grpc_slice_buffer input;
239 grpc_slice_buffer output;
240
241 grpc_slice_buffer_init(&input);
242 grpc_slice_buffer_init(&output);
243 grpc_slice_buffer_add(&input,
244 grpc_slice_from_copied_buffer("\x78\xda\xff\xff", 4));
245
246 // try (and fail) to decompress the invalid compressed buffer
247 grpc_core::ExecCtx exec_ctx;
248 ASSERT_EQ(0, grpc_msg_decompress(GRPC_COMPRESS_DEFLATE, &input, &output));
249
250 grpc_slice_buffer_destroy(&input);
251 grpc_slice_buffer_destroy(&output);
252 }
253
TEST(MessageCompressTest,BadCompressionAlgorithm)254 TEST(MessageCompressTest, BadCompressionAlgorithm) {
255 grpc_slice_buffer input;
256 grpc_slice_buffer output;
257 int was_compressed;
258
259 grpc_slice_buffer_init(&input);
260 grpc_slice_buffer_init(&output);
261 grpc_slice_buffer_add(
262 &input, grpc_slice_from_copied_string("Never gonna give you up"));
263
264 grpc_core::ExecCtx exec_ctx;
265 was_compressed =
266 grpc_msg_compress(GRPC_COMPRESS_ALGORITHMS_COUNT, &input, &output);
267 ASSERT_EQ(0, was_compressed);
268
269 was_compressed = grpc_msg_compress(static_cast<grpc_compression_algorithm>(
270 GRPC_COMPRESS_ALGORITHMS_COUNT + 123),
271 &input, &output);
272 ASSERT_EQ(0, was_compressed);
273
274 grpc_slice_buffer_destroy(&input);
275 grpc_slice_buffer_destroy(&output);
276 }
277
TEST(MessageCompressTest,BadDecompressionAlgorithm)278 TEST(MessageCompressTest, BadDecompressionAlgorithm) {
279 grpc_slice_buffer input;
280 grpc_slice_buffer output;
281 int was_decompressed;
282
283 grpc_slice_buffer_init(&input);
284 grpc_slice_buffer_init(&output);
285 grpc_slice_buffer_add(&input,
286 grpc_slice_from_copied_string(
287 "I'm not really compressed but it doesn't matter"));
288 grpc_core::ExecCtx exec_ctx;
289 was_decompressed =
290 grpc_msg_decompress(GRPC_COMPRESS_ALGORITHMS_COUNT, &input, &output);
291 ASSERT_EQ(0, was_decompressed);
292
293 was_decompressed =
294 grpc_msg_decompress(static_cast<grpc_compression_algorithm>(
295 GRPC_COMPRESS_ALGORITHMS_COUNT + 123),
296 &input, &output);
297 ASSERT_EQ(0, was_decompressed);
298
299 grpc_slice_buffer_destroy(&input);
300 grpc_slice_buffer_destroy(&output);
301 }
302
main(int argc,char ** argv)303 int main(int argc, char** argv) {
304 grpc::testing::TestEnvironment env(&argc, argv);
305 ::testing::InitGoogleTest(&argc, argv);
306 grpc::testing::TestGrpcScope grpc_scope;
307
308 unsigned i, j, k, m;
309 grpc_slice_split_mode uncompressed_split_modes[] = {
310 GRPC_SLICE_SPLIT_IDENTITY, GRPC_SLICE_SPLIT_ONE_BYTE};
311 grpc_slice_split_mode compressed_split_modes[] = {GRPC_SLICE_SPLIT_MERGE_ALL,
312 GRPC_SLICE_SPLIT_IDENTITY,
313 GRPC_SLICE_SPLIT_ONE_BYTE};
314 for (i = 0; i < GRPC_COMPRESS_ALGORITHMS_COUNT; i++) {
315 for (j = 0; j < GPR_ARRAY_SIZE(uncompressed_split_modes); j++) {
316 for (k = 0; k < GPR_ARRAY_SIZE(compressed_split_modes); k++) {
317 for (m = 0; m < TEST_VALUE_COUNT; m++) {
318 grpc_slice slice = create_test_value(static_cast<test_value>(m));
319 assert_passthrough(
320 slice, static_cast<grpc_compression_algorithm>(i),
321 static_cast<grpc_slice_split_mode>(j),
322 static_cast<grpc_slice_split_mode>(k),
323 get_compressability(static_cast<test_value>(m),
324 static_cast<grpc_compression_algorithm>(i)));
325 grpc_slice_unref(slice);
326 }
327 }
328 }
329 }
330
331 return RUN_ALL_TESTS();
332 }
333