blob: af21ed794df30e171a562c3ec14e5addfc3cb811 [file] [log] [blame]
David Garcia Quintas55b4ea12015-06-16 14:27:32 -07001/*
2 *
Craig Tiller6169d5f2016-03-31 07:46:18 -07003 * Copyright 2015, Google Inc.
David Garcia Quintas55b4ea12015-06-16 14:27:32 -07004 * All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions are
8 * met:
9 *
10 * * Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * * Redistributions in binary form must reproduce the above
13 * copyright notice, this list of conditions and the following disclaimer
14 * in the documentation and/or other materials provided with the
15 * distribution.
16 * * Neither the name of Google Inc. nor the names of its
17 * contributors may be used to endorse or promote products derived from
18 * this software without specific prior written permission.
19 *
20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 *
32 */
33
David Garcia Quintasd16af0e2015-06-22 22:39:21 -070034#include <assert.h>
David Garcia Quintas55b4ea12015-06-16 14:27:32 -070035#include <string.h>
36
David Garcia Quintas55b4ea12015-06-16 14:27:32 -070037#include <grpc/compression.h>
David Garcia Quintase091af82015-07-15 21:37:02 -070038#include <grpc/support/alloc.h>
David Garcia Quintas55b4ea12015-06-16 14:27:32 -070039#include <grpc/support/log.h>
40#include <grpc/support/slice_buffer.h>
41
Craig Tiller9533d042016-03-25 17:11:06 -070042#include "src/core/lib/channel/channel_args.h"
43#include "src/core/lib/channel/compress_filter.h"
44#include "src/core/lib/compression/algorithm_metadata.h"
45#include "src/core/lib/compression/message_compress.h"
46#include "src/core/lib/profiling/timers.h"
47#include "src/core/lib/support/string.h"
48#include "src/core/lib/transport/static_metadata.h"
David Garcia Quintas20afd462015-07-06 23:01:39 -070049
David Garcia Quintas46123372016-05-09 15:28:42 -070050int grpc_compression_trace = 0;
David Garcia Quintascce51fe2016-04-26 21:36:29 -070051
Craig Tillera82950e2015-09-22 12:33:20 -070052typedef struct call_data {
David Garcia Quintas7c4fdb52015-07-17 14:21:15 -070053 gpr_slice_buffer slices; /**< Buffers up input slices to be compressed */
David Garcia Quintasd16af0e2015-06-22 22:39:21 -070054 grpc_linked_mdelem compression_algorithm_storage;
David Garcia Quintase091af82015-07-15 21:37:02 -070055 grpc_linked_mdelem accept_encoding_storage;
Craig Tiller7536af02015-12-22 13:49:30 -080056 uint32_t remaining_slice_bytes;
David Garcia Quintas7c4fdb52015-07-17 14:21:15 -070057 /** Compression algorithm we'll try to use. It may be given by incoming
58 * metadata, or by the channel's default compression settings. */
David Garcia Quintasd16af0e2015-06-22 22:39:21 -070059 grpc_compression_algorithm compression_algorithm;
Craig Tillerd6c98df2015-08-18 09:33:44 -070060 /** If true, contents of \a compression_algorithm are authoritative */
David Garcia Quintas7c4fdb52015-07-17 14:21:15 -070061 int has_compression_algorithm;
Craig Tiller577c9b22015-11-02 14:11:15 -080062
63 grpc_transport_stream_op send_op;
Craig Tiller7536af02015-12-22 13:49:30 -080064 uint32_t send_length;
65 uint32_t send_flags;
Craig Tiller577c9b22015-11-02 14:11:15 -080066 gpr_slice incoming_slice;
67 grpc_slice_buffer_stream replacement_stream;
68 grpc_closure *post_send;
69 grpc_closure send_done;
70 grpc_closure got_slice;
David Garcia Quintas55b4ea12015-06-16 14:27:32 -070071} call_data;
72
Craig Tillera82950e2015-09-22 12:33:20 -070073typedef struct channel_data {
David Garcia Quintas7c4fdb52015-07-17 14:21:15 -070074 /** The default, channel-level, compression algorithm */
David Garcia Quintasd16af0e2015-06-22 22:39:21 -070075 grpc_compression_algorithm default_compression_algorithm;
David Garcia Quintas9e9f7b62016-05-16 19:12:12 -070076 /** Bitset of enabled algorithms */
77 uint32_t enabled_algorithms_bitset;
Craig Tillerebdef9d2015-11-19 17:09:49 -080078 /** Supported compression algorithms */
Craig Tiller7536af02015-12-22 13:49:30 -080079 uint32_t supported_compression_algorithms;
David Garcia Quintas55b4ea12015-06-16 14:27:32 -070080} channel_data;
81
David Garcia Quintasd16af0e2015-06-22 22:39:21 -070082/** For each \a md element from the incoming metadata, filter out the entry for
David Garcia Quintasd7d9ce22015-06-30 23:29:03 -070083 * "grpc-encoding", using its value to populate the call data's
David Garcia Quintasd16af0e2015-06-22 22:39:21 -070084 * compression_algorithm field. */
Craig Tillera82950e2015-09-22 12:33:20 -070085static grpc_mdelem *compression_md_filter(void *user_data, grpc_mdelem *md) {
David Garcia Quintasd16af0e2015-06-22 22:39:21 -070086 grpc_call_element *elem = user_data;
87 call_data *calld = elem->call_data;
88 channel_data *channeld = elem->channel_data;
89
Craig Tillerebdef9d2015-11-19 17:09:49 -080090 if (md->key == GRPC_MDSTR_GRPC_INTERNAL_ENCODING_REQUEST) {
Craig Tillera82950e2015-09-22 12:33:20 -070091 const char *md_c_str = grpc_mdstr_as_c_string(md->value);
92 if (!grpc_compression_algorithm_parse(md_c_str, strlen(md_c_str),
93 &calld->compression_algorithm)) {
94 gpr_log(GPR_ERROR,
95 "Invalid compression algorithm: '%s' (unknown). Ignoring.",
96 md_c_str);
97 calld->compression_algorithm = GRPC_COMPRESS_NONE;
David Garcia Quintasbeac88c2015-08-10 13:39:52 -070098 }
David Garcia Quintas9e9f7b62016-05-16 19:12:12 -070099 if (!GPR_BITGET(channeld->enabled_algorithms_bitset,
100 calld->compression_algorithm)) {
Craig Tillera82950e2015-09-22 12:33:20 -0700101 gpr_log(GPR_ERROR,
102 "Invalid compression algorithm: '%s' (previously disabled). "
103 "Ignoring.",
104 md_c_str);
105 calld->compression_algorithm = GRPC_COMPRESS_NONE;
106 }
107 calld->has_compression_algorithm = 1;
108 return NULL;
109 }
David Garcia Quintasd16af0e2015-06-22 22:39:21 -0700110
111 return md;
112}
113
Craig Tiller577c9b22015-11-02 14:11:15 -0800114static int skip_compression(grpc_call_element *elem) {
115 call_data *calld = elem->call_data;
116 channel_data *channeld = elem->channel_data;
Craig Tillera82950e2015-09-22 12:33:20 -0700117 if (calld->has_compression_algorithm) {
118 if (calld->compression_algorithm == GRPC_COMPRESS_NONE) {
119 return 1;
Craig Tillerd6c98df2015-08-18 09:33:44 -0700120 }
Craig Tillera82950e2015-09-22 12:33:20 -0700121 return 0; /* we have an actual call-specific algorithm */
122 }
David Garcia Quintasd16af0e2015-06-22 22:39:21 -0700123 /* no per-call compression override */
124 return channeld->default_compression_algorithm == GRPC_COMPRESS_NONE;
125}
126
Craig Tiller577c9b22015-11-02 14:11:15 -0800127/** Filter initial metadata */
128static void process_send_initial_metadata(
129 grpc_call_element *elem, grpc_metadata_batch *initial_metadata) {
David Garcia Quintas55b4ea12015-06-16 14:27:32 -0700130 call_data *calld = elem->call_data;
131 channel_data *channeld = elem->channel_data;
Craig Tiller577c9b22015-11-02 14:11:15 -0800132 /* Parse incoming request for compression. If any, it'll be available
133 * at calld->compression_algorithm */
134 grpc_metadata_batch_filter(initial_metadata, compression_md_filter, elem);
135 if (!calld->has_compression_algorithm) {
136 /* If no algorithm was found in the metadata and we aren't
137 * exceptionally skipping compression, fall back to the channel
138 * default */
139 calld->compression_algorithm = channeld->default_compression_algorithm;
140 calld->has_compression_algorithm = 1; /* GPR_TRUE */
Craig Tillera82950e2015-09-22 12:33:20 -0700141 }
Craig Tiller577c9b22015-11-02 14:11:15 -0800142 /* hint compression algorithm */
143 grpc_metadata_batch_add_tail(
144 initial_metadata, &calld->compression_algorithm_storage,
Craig Tillerebdef9d2015-11-19 17:09:49 -0800145 grpc_compression_encoding_mdelem(calld->compression_algorithm));
David Garcia Quintasd16af0e2015-06-22 22:39:21 -0700146
Craig Tiller577c9b22015-11-02 14:11:15 -0800147 /* convey supported compression algorithms */
Craig Tillerb2b42612015-11-20 12:02:17 -0800148 grpc_metadata_batch_add_tail(initial_metadata,
149 &calld->accept_encoding_storage,
150 GRPC_MDELEM_ACCEPT_ENCODING_FOR_ALGORITHMS(
151 channeld->supported_compression_algorithms));
Craig Tiller577c9b22015-11-02 14:11:15 -0800152}
153
154static void continue_send_message(grpc_exec_ctx *exec_ctx,
155 grpc_call_element *elem);
156
Craig Tillerc027e772016-05-03 16:27:00 -0700157static void send_done(grpc_exec_ctx *exec_ctx, void *elemp, grpc_error *error) {
Craig Tiller577c9b22015-11-02 14:11:15 -0800158 grpc_call_element *elem = elemp;
159 call_data *calld = elem->call_data;
160 gpr_slice_buffer_reset_and_unref(&calld->slices);
Craig Tillerc027e772016-05-03 16:27:00 -0700161 calld->post_send->cb(exec_ctx, calld->post_send->cb_arg, error);
Craig Tiller577c9b22015-11-02 14:11:15 -0800162}
163
164static void finish_send_message(grpc_exec_ctx *exec_ctx,
165 grpc_call_element *elem) {
166 call_data *calld = elem->call_data;
167 int did_compress;
168 gpr_slice_buffer tmp;
169 gpr_slice_buffer_init(&tmp);
170 did_compress =
171 grpc_msg_compress(calld->compression_algorithm, &calld->slices, &tmp);
Craig Tillera82950e2015-09-22 12:33:20 -0700172 if (did_compress) {
David Garcia Quintas46123372016-05-09 15:28:42 -0700173 if (grpc_compression_trace) {
David Garcia Quintascce51fe2016-04-26 21:36:29 -0700174 char *algo_name;
175 const size_t before_size = calld->slices.length;
176 const size_t after_size = tmp.length;
177 const float savings_ratio = 1.0f - (float)after_size / (float)before_size;
178 GPR_ASSERT(grpc_compression_algorithm_name(calld->compression_algorithm,
179 &algo_name));
Yuchen Zeng64c0e8d2016-06-10 11:19:51 -0700180 gpr_log(GPR_DEBUG, "Compressed[%s] %" PRIuPTR " bytes vs. %" PRIuPTR
181 " bytes (%.2f%% savings)",
David Garcia Quintascce51fe2016-04-26 21:36:29 -0700182 algo_name, before_size, after_size, 100 * savings_ratio);
183 }
Craig Tiller577c9b22015-11-02 14:11:15 -0800184 gpr_slice_buffer_swap(&calld->slices, &tmp);
185 calld->send_flags |= GRPC_WRITE_INTERNAL_COMPRESS;
David Garcia Quintascce51fe2016-04-26 21:36:29 -0700186 } else {
David Garcia Quintas46123372016-05-09 15:28:42 -0700187 if (grpc_compression_trace) {
David Garcia Quintascce51fe2016-04-26 21:36:29 -0700188 char *algo_name;
189 GPR_ASSERT(grpc_compression_algorithm_name(calld->compression_algorithm,
190 &algo_name));
Yuchen Zeng64c0e8d2016-06-10 11:19:51 -0700191 gpr_log(GPR_DEBUG,
192 "Algorithm '%s' enabled but decided not to compress. Input size: "
193 "%" PRIuPTR,
194 algo_name, calld->slices.length);
David Garcia Quintascce51fe2016-04-26 21:36:29 -0700195 }
Craig Tiller577c9b22015-11-02 14:11:15 -0800196 }
David Garcia Quintascce51fe2016-04-26 21:36:29 -0700197
Craig Tiller577c9b22015-11-02 14:11:15 -0800198 gpr_slice_buffer_destroy(&tmp);
199
200 grpc_slice_buffer_stream_init(&calld->replacement_stream, &calld->slices,
201 calld->send_flags);
202 calld->send_op.send_message = &calld->replacement_stream.base;
203 calld->post_send = calld->send_op.on_complete;
204 calld->send_op.on_complete = &calld->send_done;
205
206 grpc_call_next_op(exec_ctx, elem, &calld->send_op);
207}
208
Craig Tillerc027e772016-05-03 16:27:00 -0700209static void got_slice(grpc_exec_ctx *exec_ctx, void *elemp, grpc_error *error) {
Craig Tiller577c9b22015-11-02 14:11:15 -0800210 grpc_call_element *elem = elemp;
211 call_data *calld = elem->call_data;
212 gpr_slice_buffer_add(&calld->slices, calld->incoming_slice);
213 if (calld->send_length == calld->slices.length) {
214 finish_send_message(exec_ctx, elem);
215 } else {
216 continue_send_message(exec_ctx, elem);
Craig Tillera82950e2015-09-22 12:33:20 -0700217 }
David Garcia Quintas55b4ea12015-06-16 14:27:32 -0700218}
219
Craig Tiller577c9b22015-11-02 14:11:15 -0800220static void continue_send_message(grpc_exec_ctx *exec_ctx,
221 grpc_call_element *elem) {
222 call_data *calld = elem->call_data;
223 while (grpc_byte_stream_next(exec_ctx, calld->send_op.send_message,
224 &calld->incoming_slice, ~(size_t)0,
225 &calld->got_slice)) {
226 gpr_slice_buffer_add(&calld->slices, calld->incoming_slice);
227 if (calld->send_length == calld->slices.length) {
228 finish_send_message(exec_ctx, elem);
229 break;
230 }
231 }
232}
233
Craig Tillera82950e2015-09-22 12:33:20 -0700234static void compress_start_transport_stream_op(grpc_exec_ctx *exec_ctx,
235 grpc_call_element *elem,
236 grpc_transport_stream_op *op) {
Craig Tiller577c9b22015-11-02 14:11:15 -0800237 call_data *calld = elem->call_data;
238
Craig Tiller0ba432d2015-10-09 16:57:11 -0700239 GPR_TIMER_BEGIN("compress_start_transport_stream_op", 0);
Craig Tiller1f41b6b2015-10-09 15:07:02 -0700240
Craig Tiller577c9b22015-11-02 14:11:15 -0800241 if (op->send_initial_metadata) {
242 process_send_initial_metadata(elem, op->send_initial_metadata);
243 }
244 if (op->send_message != NULL && !skip_compression(elem) &&
245 0 == (op->send_message->flags & GRPC_WRITE_NO_COMPRESS)) {
246 calld->send_op = *op;
247 calld->send_length = op->send_message->length;
248 calld->send_flags = op->send_message->flags;
249 continue_send_message(exec_ctx, elem);
250 } else {
251 /* pass control down the stack */
252 grpc_call_next_op(exec_ctx, elem, op);
Craig Tillera82950e2015-09-22 12:33:20 -0700253 }
David Garcia Quintas55b4ea12015-06-16 14:27:32 -0700254
Craig Tiller0ba432d2015-10-09 16:57:11 -0700255 GPR_TIMER_END("compress_start_transport_stream_op", 0);
David Garcia Quintas55b4ea12015-06-16 14:27:32 -0700256}
257
David Garcia Quintas55b4ea12015-06-16 14:27:32 -0700258/* Constructor for call_data */
Craig Tillera82950e2015-09-22 12:33:20 -0700259static void init_call_elem(grpc_exec_ctx *exec_ctx, grpc_call_element *elem,
Craig Tiller577c9b22015-11-02 14:11:15 -0800260 grpc_call_element_args *args) {
David Garcia Quintas55b4ea12015-06-16 14:27:32 -0700261 /* grab pointers to our data from the call element */
262 call_data *calld = elem->call_data;
263
264 /* initialize members */
Craig Tillera82950e2015-09-22 12:33:20 -0700265 gpr_slice_buffer_init(&calld->slices);
David Garcia Quintasd16af0e2015-06-22 22:39:21 -0700266 calld->has_compression_algorithm = 0;
Craig Tiller577c9b22015-11-02 14:11:15 -0800267 grpc_closure_init(&calld->got_slice, got_slice, elem);
268 grpc_closure_init(&calld->send_done, send_done, elem);
David Garcia Quintas55b4ea12015-06-16 14:27:32 -0700269}
270
271/* Destructor for call_data */
Craig Tiller2c8063c2016-03-22 22:12:15 -0700272static void destroy_call_elem(grpc_exec_ctx *exec_ctx, grpc_call_element *elem,
David Garcia Quintas5dde14c2016-07-28 17:29:27 -0700273 const grpc_call_final_info *final_info,
274 void *ignored) {
David Garcia Quintas55b4ea12015-06-16 14:27:32 -0700275 /* grab pointers to our data from the call element */
276 call_data *calld = elem->call_data;
Craig Tillera82950e2015-09-22 12:33:20 -0700277 gpr_slice_buffer_destroy(&calld->slices);
David Garcia Quintas55b4ea12015-06-16 14:27:32 -0700278}
279
280/* Constructor for channel_data */
Craig Tillera82950e2015-09-22 12:33:20 -0700281static void init_channel_elem(grpc_exec_ctx *exec_ctx,
Craig Tiller577c9b22015-11-02 14:11:15 -0800282 grpc_channel_element *elem,
283 grpc_channel_element_args *args) {
David Garcia Quintas55b4ea12015-06-16 14:27:32 -0700284 channel_data *channeld = elem->channel_data;
David Garcia Quintasf74a49e2015-06-18 17:22:45 -0700285
David Garcia Quintas9e9f7b62016-05-16 19:12:12 -0700286 channeld->enabled_algorithms_bitset =
287 grpc_channel_args_compression_algorithm_get_states(args->channel_args);
David Garcia Quintasbeac88c2015-08-10 13:39:52 -0700288
Craig Tillera82950e2015-09-22 12:33:20 -0700289 channeld->default_compression_algorithm =
Craig Tiller577c9b22015-11-02 14:11:15 -0800290 grpc_channel_args_get_compression_algorithm(args->channel_args);
David Garcia Quintasbeac88c2015-08-10 13:39:52 -0700291 /* Make sure the default isn't disabled. */
David Garcia Quintas9e9f7b62016-05-16 19:12:12 -0700292 if (!GPR_BITGET(channeld->enabled_algorithms_bitset,
293 channeld->default_compression_algorithm)) {
Craig Tillerd2906ad2016-04-24 22:45:28 -0700294 gpr_log(GPR_DEBUG,
295 "compression algorithm %d not enabled: switching to none",
296 channeld->default_compression_algorithm);
297 channeld->default_compression_algorithm = GRPC_COMPRESS_NONE;
298 }
David Garcia Quintasf74a49e2015-06-18 17:22:45 -0700299
David Garcia Quintasddefbb82016-05-18 17:15:11 -0700300 channeld->supported_compression_algorithms = 1; /* always support identity */
301 for (grpc_compression_algorithm algo_idx = 1;
David Garcia Quintas9e9f7b62016-05-16 19:12:12 -0700302 algo_idx < GRPC_COMPRESS_ALGORITHMS_COUNT; ++algo_idx) {
Craig Tillera82950e2015-09-22 12:33:20 -0700303 /* skip disabled algorithms */
David Garcia Quintas9e9f7b62016-05-16 19:12:12 -0700304 if (!GPR_BITGET(channeld->enabled_algorithms_bitset, algo_idx)) {
Craig Tillera82950e2015-09-22 12:33:20 -0700305 continue;
David Garcia Quintasbeac88c2015-08-10 13:39:52 -0700306 }
Craig Tillerebdef9d2015-11-19 17:09:49 -0800307 channeld->supported_compression_algorithms |= 1u << algo_idx;
Craig Tillera82950e2015-09-22 12:33:20 -0700308 }
David Garcia Quintas55b4ea12015-06-16 14:27:32 -0700309
Craig Tiller577c9b22015-11-02 14:11:15 -0800310 GPR_ASSERT(!args->is_last);
David Garcia Quintas55b4ea12015-06-16 14:27:32 -0700311}
312
313/* Destructor for channel data */
Craig Tillera82950e2015-09-22 12:33:20 -0700314static void destroy_channel_elem(grpc_exec_ctx *exec_ctx,
Craig Tillerab33b482015-11-21 08:11:04 -0800315 grpc_channel_element *elem) {}
David Garcia Quintas55b4ea12015-06-16 14:27:32 -0700316
David Garcia Quintasd317e752015-07-15 00:09:27 -0700317const grpc_channel_filter grpc_compress_filter = {
Craig Tillerf40df232016-03-25 13:38:14 -0700318 compress_start_transport_stream_op,
319 grpc_channel_next_op,
320 sizeof(call_data),
321 init_call_elem,
David Garcia Quintas4afce7e2016-04-18 16:25:17 -0700322 grpc_call_stack_ignore_set_pollset_or_pollset_set,
Craig Tillerf40df232016-03-25 13:38:14 -0700323 destroy_call_elem,
324 sizeof(channel_data),
325 init_channel_elem,
326 destroy_channel_elem,
327 grpc_call_next_get_peer,
328 "compress"};