1 /*
2 *
3 * Copyright 2017 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 <grpc/support/port_platform.h>
20
21 #include "src/core/lib/compression/stream_compression_gzip.h"
22
23 #include <grpc/support/alloc.h>
24 #include <grpc/support/log.h>
25
26 #include "src/core/lib/iomgr/exec_ctx.h"
27 #include "src/core/lib/slice/slice_internal.h"
28
29 #define OUTPUT_BLOCK_SIZE (1024)
30
31 typedef struct grpc_stream_compression_context_gzip {
32 grpc_stream_compression_context base;
33
34 z_stream zs;
35 int (*flate)(z_stream* zs, int flush);
36 } grpc_stream_compression_context_gzip;
37
gzip_flate(grpc_stream_compression_context_gzip * ctx,grpc_slice_buffer * in,grpc_slice_buffer * out,size_t * output_size,size_t max_output_size,int flush,bool * end_of_context)38 static bool gzip_flate(grpc_stream_compression_context_gzip* ctx,
39 grpc_slice_buffer* in, grpc_slice_buffer* out,
40 size_t* output_size, size_t max_output_size, int flush,
41 bool* end_of_context) {
42 GPR_ASSERT(flush == 0 || flush == Z_SYNC_FLUSH || flush == Z_FINISH);
43 /* Full flush is not allowed when inflating. */
44 GPR_ASSERT(!(ctx->flate == inflate && (flush == Z_FINISH)));
45
46 grpc_core::ExecCtx exec_ctx;
47 int r;
48 bool eoc = false;
49 size_t original_max_output_size = max_output_size;
50 while (max_output_size > 0 && (in->length > 0 || flush) && !eoc) {
51 size_t slice_size = max_output_size < OUTPUT_BLOCK_SIZE ? max_output_size
52 : OUTPUT_BLOCK_SIZE;
53 grpc_slice slice_out = GRPC_SLICE_MALLOC(slice_size);
54 ctx->zs.avail_out = static_cast<uInt>(slice_size);
55 ctx->zs.next_out = GRPC_SLICE_START_PTR(slice_out);
56 while (ctx->zs.avail_out > 0 && in->length > 0 && !eoc) {
57 grpc_slice* slice = grpc_slice_buffer_peek_first(in);
58 ctx->zs.avail_in = static_cast<uInt> GRPC_SLICE_LENGTH(*slice);
59 ctx->zs.next_in = GRPC_SLICE_START_PTR(*slice);
60 r = ctx->flate(&ctx->zs, Z_NO_FLUSH);
61 if (r < 0 && r != Z_BUF_ERROR) {
62 gpr_log(GPR_ERROR, "zlib error (%d)", r);
63 grpc_slice_unref_internal(slice_out);
64 grpc_slice_buffer_remove_first(in);
65 return false;
66 } else if (r == Z_STREAM_END && ctx->flate == inflate) {
67 eoc = true;
68 }
69 if (ctx->zs.avail_in > 0) {
70 grpc_slice_buffer_sub_first(
71 in, GRPC_SLICE_LENGTH(*slice) - ctx->zs.avail_in,
72 GRPC_SLICE_LENGTH(*slice));
73 } else {
74 grpc_slice_buffer_remove_first(in);
75 }
76 }
77 if (flush != 0 && ctx->zs.avail_out > 0 && !eoc) {
78 GPR_ASSERT(in->length == 0);
79 r = ctx->flate(&ctx->zs, flush);
80 if (flush == Z_SYNC_FLUSH) {
81 switch (r) {
82 case Z_OK:
83 /* Maybe flush is not complete; just made some partial progress. */
84 if (ctx->zs.avail_out > 0) {
85 flush = 0;
86 }
87 break;
88 case Z_BUF_ERROR:
89 case Z_STREAM_END:
90 flush = 0;
91 break;
92 default:
93 gpr_log(GPR_ERROR, "zlib error (%d)", r);
94 grpc_slice_unref_internal(slice_out);
95
96 return false;
97 }
98 } else if (flush == Z_FINISH) {
99 switch (r) {
100 case Z_OK:
101 case Z_BUF_ERROR:
102 /* Wait for the next loop to assign additional output space. */
103 GPR_ASSERT(ctx->zs.avail_out == 0);
104 break;
105 case Z_STREAM_END:
106 flush = 0;
107 break;
108 default:
109 gpr_log(GPR_ERROR, "zlib error (%d)", r);
110 grpc_slice_unref_internal(slice_out);
111
112 return false;
113 }
114 }
115 }
116
117 if (ctx->zs.avail_out == 0) {
118 grpc_slice_buffer_add(out, slice_out);
119 } else if (ctx->zs.avail_out < slice_size) {
120 size_t len = GRPC_SLICE_LENGTH(slice_out);
121 GRPC_SLICE_SET_LENGTH(slice_out, len - ctx->zs.avail_out);
122 grpc_slice_buffer_add(out, slice_out);
123 } else {
124 grpc_slice_unref_internal(slice_out);
125 }
126 max_output_size -= (slice_size - ctx->zs.avail_out);
127 }
128
129 if (end_of_context) {
130 *end_of_context = eoc;
131 }
132 if (output_size) {
133 *output_size = original_max_output_size - max_output_size;
134 }
135 return true;
136 }
137
grpc_stream_compress_gzip(grpc_stream_compression_context * ctx,grpc_slice_buffer * in,grpc_slice_buffer * out,size_t * output_size,size_t max_output_size,grpc_stream_compression_flush flush)138 static bool grpc_stream_compress_gzip(grpc_stream_compression_context* ctx,
139 grpc_slice_buffer* in,
140 grpc_slice_buffer* out,
141 size_t* output_size,
142 size_t max_output_size,
143 grpc_stream_compression_flush flush) {
144 if (ctx == nullptr) {
145 return false;
146 }
147 grpc_stream_compression_context_gzip* gzip_ctx =
148 reinterpret_cast<grpc_stream_compression_context_gzip*>(ctx);
149 GPR_ASSERT(gzip_ctx->flate == deflate);
150 int gzip_flush;
151 switch (flush) {
152 case GRPC_STREAM_COMPRESSION_FLUSH_NONE:
153 gzip_flush = 0;
154 break;
155 case GRPC_STREAM_COMPRESSION_FLUSH_SYNC:
156 gzip_flush = Z_SYNC_FLUSH;
157 break;
158 case GRPC_STREAM_COMPRESSION_FLUSH_FINISH:
159 gzip_flush = Z_FINISH;
160 break;
161 default:
162 gzip_flush = 0;
163 }
164 return gzip_flate(gzip_ctx, in, out, output_size, max_output_size, gzip_flush,
165 nullptr);
166 }
167
grpc_stream_decompress_gzip(grpc_stream_compression_context * ctx,grpc_slice_buffer * in,grpc_slice_buffer * out,size_t * output_size,size_t max_output_size,bool * end_of_context)168 static bool grpc_stream_decompress_gzip(grpc_stream_compression_context* ctx,
169 grpc_slice_buffer* in,
170 grpc_slice_buffer* out,
171 size_t* output_size,
172 size_t max_output_size,
173 bool* end_of_context) {
174 if (ctx == nullptr) {
175 return false;
176 }
177 grpc_stream_compression_context_gzip* gzip_ctx =
178 reinterpret_cast<grpc_stream_compression_context_gzip*>(ctx);
179 GPR_ASSERT(gzip_ctx->flate == inflate);
180 return gzip_flate(gzip_ctx, in, out, output_size, max_output_size,
181 Z_SYNC_FLUSH, end_of_context);
182 }
183
184 static grpc_stream_compression_context*
grpc_stream_compression_context_create_gzip(grpc_stream_compression_method method)185 grpc_stream_compression_context_create_gzip(
186 grpc_stream_compression_method method) {
187 GPR_ASSERT(method == GRPC_STREAM_COMPRESSION_GZIP_COMPRESS ||
188 method == GRPC_STREAM_COMPRESSION_GZIP_DECOMPRESS);
189 grpc_stream_compression_context_gzip* gzip_ctx =
190 static_cast<grpc_stream_compression_context_gzip*>(
191 gpr_zalloc(sizeof(grpc_stream_compression_context_gzip)));
192 int r;
193 if (gzip_ctx == nullptr) {
194 return nullptr;
195 }
196 if (method == GRPC_STREAM_COMPRESSION_GZIP_DECOMPRESS) {
197 r = inflateInit2(&gzip_ctx->zs, 0x1F);
198 gzip_ctx->flate = inflate;
199 } else {
200 r = deflateInit2(&gzip_ctx->zs, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 0x1F, 8,
201 Z_DEFAULT_STRATEGY);
202 gzip_ctx->flate = deflate;
203 }
204 if (r != Z_OK) {
205 gpr_free(gzip_ctx);
206 return nullptr;
207 }
208
209 gzip_ctx->base.vtable = &grpc_stream_compression_gzip_vtable;
210 return reinterpret_cast<grpc_stream_compression_context*>(gzip_ctx);
211 }
212
grpc_stream_compression_context_destroy_gzip(grpc_stream_compression_context * ctx)213 static void grpc_stream_compression_context_destroy_gzip(
214 grpc_stream_compression_context* ctx) {
215 if (ctx == nullptr) {
216 return;
217 }
218 grpc_stream_compression_context_gzip* gzip_ctx =
219 reinterpret_cast<grpc_stream_compression_context_gzip*>(ctx);
220 if (gzip_ctx->flate == inflate) {
221 inflateEnd(&gzip_ctx->zs);
222 } else {
223 deflateEnd(&gzip_ctx->zs);
224 }
225 gpr_free(ctx);
226 }
227
228 const grpc_stream_compression_vtable grpc_stream_compression_gzip_vtable = {
229 grpc_stream_compress_gzip, grpc_stream_decompress_gzip,
230 grpc_stream_compression_context_create_gzip,
231 grpc_stream_compression_context_destroy_gzip};
232