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