Imported Upstream version 1.41.0
[platform/upstream/grpc.git] / src / core / lib / compression / stream_compression_gzip.cc
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
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
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
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*
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
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};