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 <ruby/ruby.h>
20
21 #include "rb_compression_options.h"
22
23 #include <grpc/compression.h>
24 #include <grpc/grpc.h>
25 #include <grpc/impl/codegen/compression_types.h>
26 #include <grpc/impl/grpc_types.h>
27 #include <grpc/support/alloc.h>
28 #include <grpc/support/log.h>
29 #include <grpc/support/string_util.h>
30 #include <string.h>
31
32 #include "rb_byte_buffer.h"
33 #include "rb_grpc.h"
34 #include "rb_grpc_imports.generated.h"
35
36 static VALUE grpc_rb_cCompressionOptions = Qnil;
37
38 /* Ruby Ids for the names of valid compression levels. */
39 static VALUE id_compress_level_none = Qnil;
40 static VALUE id_compress_level_low = Qnil;
41 static VALUE id_compress_level_medium = Qnil;
42 static VALUE id_compress_level_high = Qnil;
43
44 /* grpc_rb_compression_options wraps a grpc_compression_options.
45 * It can be used to get the channel argument key-values for specific
46 * compression settings. */
47
48 /* Note that ruby objects of this type don't carry any state in other
49 * Ruby objects and don't have a mark for GC. */
50 typedef struct grpc_rb_compression_options {
51 /* The actual compression options that's being wrapped */
52 grpc_compression_options* wrapped;
53 } grpc_rb_compression_options;
54
grpc_rb_compression_options_free_internal(void * p)55 static void grpc_rb_compression_options_free_internal(void* p) {
56 grpc_rb_compression_options* wrapper = NULL;
57 if (p == NULL) {
58 return;
59 };
60 wrapper = (grpc_rb_compression_options*)p;
61 if (wrapper->wrapped != NULL) {
62 gpr_free(wrapper->wrapped);
63 wrapper->wrapped = NULL;
64 }
65 xfree(p);
66 }
67
68 /* Destroys the compression options instances and free the
69 * wrapped grpc compression options. */
grpc_rb_compression_options_free(void * p)70 static void grpc_rb_compression_options_free(void* p) {
71 grpc_rb_compression_options_free_internal(p);
72 }
73
74 /* Ruby recognized data type for the CompressionOptions class. */
75 static rb_data_type_t grpc_rb_compression_options_data_type = {
76 "grpc_compression_options",
77 {NULL,
78 grpc_rb_compression_options_free,
79 GRPC_RB_MEMSIZE_UNAVAILABLE,
80 {NULL, NULL}},
81 NULL,
82 NULL,
83 #ifdef RUBY_TYPED_FREE_IMMEDIATELY
84 RUBY_TYPED_FREE_IMMEDIATELY
85 #endif
86 };
87
88 /* Allocates CompressionOptions instances.
89 Allocate the wrapped grpc compression options and
90 initialize it here too. */
grpc_rb_compression_options_alloc(VALUE cls)91 static VALUE grpc_rb_compression_options_alloc(VALUE cls) {
92 grpc_ruby_init();
93 grpc_rb_compression_options* wrapper = NULL;
94
95 wrapper = gpr_malloc(sizeof(grpc_rb_compression_options));
96 wrapper->wrapped = NULL;
97 wrapper->wrapped = gpr_malloc(sizeof(grpc_compression_options));
98 grpc_compression_options_init(wrapper->wrapped);
99
100 return TypedData_Wrap_Struct(cls, &grpc_rb_compression_options_data_type,
101 wrapper);
102 }
103
104 /* Disables a compression algorithm, given the GRPC core internal number of a
105 * compression algorithm. */
grpc_rb_compression_options_disable_compression_algorithm_internal(VALUE self,VALUE algorithm_to_disable)106 VALUE grpc_rb_compression_options_disable_compression_algorithm_internal(
107 VALUE self, VALUE algorithm_to_disable) {
108 grpc_compression_algorithm compression_algorithm = 0;
109 grpc_rb_compression_options* wrapper = NULL;
110
111 TypedData_Get_Struct(self, grpc_rb_compression_options,
112 &grpc_rb_compression_options_data_type, wrapper);
113 compression_algorithm =
114 (grpc_compression_algorithm)NUM2INT(algorithm_to_disable);
115
116 grpc_compression_options_disable_algorithm(wrapper->wrapped,
117 compression_algorithm);
118
119 return Qnil;
120 }
121
122 /* Gets the compression internal enum value of a compression level given its
123 * name. */
grpc_rb_compression_options_level_name_to_value_internal(VALUE level_name)124 grpc_compression_level grpc_rb_compression_options_level_name_to_value_internal(
125 VALUE level_name) {
126 Check_Type(level_name, T_SYMBOL);
127
128 /* Check the compression level of the name passed in, and see which macro
129 * from the GRPC core header files match. */
130 if (id_compress_level_none == SYM2ID(level_name)) {
131 return GRPC_COMPRESS_LEVEL_NONE;
132 } else if (id_compress_level_low == SYM2ID(level_name)) {
133 return GRPC_COMPRESS_LEVEL_LOW;
134 } else if (id_compress_level_medium == SYM2ID(level_name)) {
135 return GRPC_COMPRESS_LEVEL_MED;
136 } else if (id_compress_level_high == SYM2ID(level_name)) {
137 return GRPC_COMPRESS_LEVEL_HIGH;
138 }
139
140 rb_raise(rb_eArgError,
141 "Unrecognized compression level name."
142 "Valid compression level names are none, low, medium, and high.");
143
144 /* Phony return statement. */
145 return GRPC_COMPRESS_LEVEL_NONE;
146 }
147
148 /* Sets the default compression level, given the name of a compression level.
149 * Throws an error if no algorithm matched. */
grpc_rb_compression_options_set_default_level(grpc_compression_options * options,VALUE new_level_name)150 void grpc_rb_compression_options_set_default_level(
151 grpc_compression_options* options, VALUE new_level_name) {
152 options->default_level.level =
153 grpc_rb_compression_options_level_name_to_value_internal(new_level_name);
154 options->default_level.is_set = 1;
155 }
156
157 /* Gets the internal value of a compression algorithm suitable as the value
158 * in a GRPC core channel arguments hash.
159 * algorithm_value is an out parameter.
160 * Raises an error if the name of the algorithm passed in is invalid. */
grpc_rb_compression_options_algorithm_name_to_value_internal(grpc_compression_algorithm * algorithm_value,VALUE algorithm_name)161 void grpc_rb_compression_options_algorithm_name_to_value_internal(
162 grpc_compression_algorithm* algorithm_value, VALUE algorithm_name) {
163 grpc_slice name_slice;
164 VALUE algorithm_name_as_string = Qnil;
165
166 Check_Type(algorithm_name, T_SYMBOL);
167
168 /* Convert the algorithm symbol to a ruby string, so that we can get the
169 * correct C string out of it. */
170 algorithm_name_as_string = rb_funcall(algorithm_name, rb_intern("to_s"), 0);
171
172 name_slice =
173 grpc_slice_from_copied_buffer(RSTRING_PTR(algorithm_name_as_string),
174 RSTRING_LEN(algorithm_name_as_string));
175
176 /* Raise an error if the name isn't recognized as a compression algorithm by
177 * the algorithm parse function
178 * in GRPC core. */
179 if (!grpc_compression_algorithm_parse(name_slice, algorithm_value)) {
180 char* name_slice_str = grpc_slice_to_c_string(name_slice);
181 char* error_message_str = NULL;
182 VALUE error_message_ruby_str = Qnil;
183 GRPC_RUBY_ASSERT(gpr_asprintf(&error_message_str,
184 "Invalid compression algorithm name: %s",
185 name_slice_str) != -1);
186 gpr_free(name_slice_str);
187 error_message_ruby_str =
188 rb_str_new(error_message_str, strlen(error_message_str));
189 gpr_free(error_message_str);
190 rb_raise(rb_eNameError, "%s", StringValueCStr(error_message_ruby_str));
191 }
192
193 grpc_slice_unref(name_slice);
194 }
195
196 /* Indicates whether a given algorithm is enabled on this instance, given the
197 * readable algorithm name. */
grpc_rb_compression_options_is_algorithm_enabled(VALUE self,VALUE algorithm_name)198 VALUE grpc_rb_compression_options_is_algorithm_enabled(VALUE self,
199 VALUE algorithm_name) {
200 grpc_rb_compression_options* wrapper = NULL;
201 grpc_compression_algorithm internal_algorithm_value;
202
203 TypedData_Get_Struct(self, grpc_rb_compression_options,
204 &grpc_rb_compression_options_data_type, wrapper);
205 grpc_rb_compression_options_algorithm_name_to_value_internal(
206 &internal_algorithm_value, algorithm_name);
207
208 if (grpc_compression_options_is_algorithm_enabled(wrapper->wrapped,
209 internal_algorithm_value)) {
210 return Qtrue;
211 }
212 return Qfalse;
213 }
214
215 /* Sets the default algorithm to the name of the algorithm passed in.
216 * Raises an error if the name is not a valid compression algorithm name. */
grpc_rb_compression_options_set_default_algorithm(grpc_compression_options * options,VALUE algorithm_name)217 void grpc_rb_compression_options_set_default_algorithm(
218 grpc_compression_options* options, VALUE algorithm_name) {
219 grpc_rb_compression_options_algorithm_name_to_value_internal(
220 &options->default_algorithm.algorithm, algorithm_name);
221 options->default_algorithm.is_set = 1;
222 }
223
224 /* Disables an algorithm on the current instance, given the name of an
225 * algorithm.
226 * Fails if the algorithm name is invalid. */
grpc_rb_compression_options_disable_algorithm(grpc_compression_options * compression_options,VALUE algorithm_name)227 void grpc_rb_compression_options_disable_algorithm(
228 grpc_compression_options* compression_options, VALUE algorithm_name) {
229 grpc_compression_algorithm internal_algorithm_value;
230
231 grpc_rb_compression_options_algorithm_name_to_value_internal(
232 &internal_algorithm_value, algorithm_name);
233 grpc_compression_options_disable_algorithm(compression_options,
234 internal_algorithm_value);
235 }
236
237 /* Provides a ruby hash of GRPC core channel argument key-values that
238 * correspond to the compression settings on this instance. */
grpc_rb_compression_options_to_hash(VALUE self)239 VALUE grpc_rb_compression_options_to_hash(VALUE self) {
240 grpc_rb_compression_options* wrapper = NULL;
241 grpc_compression_options* compression_options = NULL;
242 VALUE channel_arg_hash = rb_hash_new();
243 VALUE key = Qnil;
244 VALUE value = Qnil;
245
246 TypedData_Get_Struct(self, grpc_rb_compression_options,
247 &grpc_rb_compression_options_data_type, wrapper);
248 compression_options = wrapper->wrapped;
249
250 /* Add key-value pairs to the new Ruby hash. It can be used
251 * as GRPC core channel arguments. */
252 if (compression_options->default_level.is_set) {
253 key = rb_str_new2(GRPC_COMPRESSION_CHANNEL_DEFAULT_LEVEL);
254 value = INT2NUM((int)compression_options->default_level.level);
255 rb_hash_aset(channel_arg_hash, key, value);
256 }
257
258 if (compression_options->default_algorithm.is_set) {
259 key = rb_str_new2(GRPC_COMPRESSION_CHANNEL_DEFAULT_ALGORITHM);
260 value = INT2NUM((int)compression_options->default_algorithm.algorithm);
261 rb_hash_aset(channel_arg_hash, key, value);
262 }
263
264 key = rb_str_new2(GRPC_COMPRESSION_CHANNEL_ENABLED_ALGORITHMS_BITSET);
265 value = INT2NUM((int)compression_options->enabled_algorithms_bitset);
266 rb_hash_aset(channel_arg_hash, key, value);
267
268 return channel_arg_hash;
269 }
270
271 /* Converts an internal enum level value to a readable level name.
272 * Fails if the level value is invalid. */
grpc_rb_compression_options_level_value_to_name_internal(grpc_compression_level compression_value)273 VALUE grpc_rb_compression_options_level_value_to_name_internal(
274 grpc_compression_level compression_value) {
275 switch (compression_value) {
276 case GRPC_COMPRESS_LEVEL_NONE:
277 return ID2SYM(id_compress_level_none);
278 case GRPC_COMPRESS_LEVEL_LOW:
279 return ID2SYM(id_compress_level_low);
280 case GRPC_COMPRESS_LEVEL_MED:
281 return ID2SYM(id_compress_level_medium);
282 case GRPC_COMPRESS_LEVEL_HIGH:
283 return ID2SYM(id_compress_level_high);
284 default:
285 rb_raise(
286 rb_eArgError,
287 "Failed to convert compression level value to name for value: %d",
288 (int)compression_value);
289 /* return something to avoid compiler error about no return */
290 return Qnil;
291 }
292 }
293
294 /* Converts an algorithm internal enum value to a readable name.
295 * Fails if the enum value is invalid. */
grpc_rb_compression_options_algorithm_value_to_name_internal(grpc_compression_algorithm internal_value)296 VALUE grpc_rb_compression_options_algorithm_value_to_name_internal(
297 grpc_compression_algorithm internal_value) {
298 const char* algorithm_name = NULL;
299
300 if (!grpc_compression_algorithm_name(internal_value, &algorithm_name)) {
301 rb_raise(rb_eArgError, "Failed to convert algorithm value to name");
302 }
303
304 return ID2SYM(rb_intern(algorithm_name));
305 }
306
307 /* Gets the readable name of the default algorithm if one has been set.
308 * Returns nil if no algorithm has been set. */
grpc_rb_compression_options_get_default_algorithm(VALUE self)309 VALUE grpc_rb_compression_options_get_default_algorithm(VALUE self) {
310 grpc_compression_algorithm internal_value;
311 grpc_rb_compression_options* wrapper = NULL;
312
313 TypedData_Get_Struct(self, grpc_rb_compression_options,
314 &grpc_rb_compression_options_data_type, wrapper);
315
316 if (wrapper->wrapped->default_algorithm.is_set) {
317 internal_value = wrapper->wrapped->default_algorithm.algorithm;
318 return grpc_rb_compression_options_algorithm_value_to_name_internal(
319 internal_value);
320 }
321
322 return Qnil;
323 }
324
325 /* Gets the internal value of the default compression level that is to be passed
326 * to the GRPC core as a channel argument value.
327 * A nil return value means that it hasn't been set. */
grpc_rb_compression_options_get_default_level(VALUE self)328 VALUE grpc_rb_compression_options_get_default_level(VALUE self) {
329 grpc_compression_level internal_value;
330 grpc_rb_compression_options* wrapper = NULL;
331
332 TypedData_Get_Struct(self, grpc_rb_compression_options,
333 &grpc_rb_compression_options_data_type, wrapper);
334
335 if (wrapper->wrapped->default_level.is_set) {
336 internal_value = wrapper->wrapped->default_level.level;
337 return grpc_rb_compression_options_level_value_to_name_internal(
338 internal_value);
339 }
340
341 return Qnil;
342 }
343
344 /* Gets a list of the disabled algorithms as readable names.
345 * Returns an empty list if no algorithms have been disabled. */
grpc_rb_compression_options_get_disabled_algorithms(VALUE self)346 VALUE grpc_rb_compression_options_get_disabled_algorithms(VALUE self) {
347 VALUE disabled_algorithms = rb_ary_new();
348 grpc_compression_algorithm internal_value;
349 grpc_rb_compression_options* wrapper = NULL;
350
351 TypedData_Get_Struct(self, grpc_rb_compression_options,
352 &grpc_rb_compression_options_data_type, wrapper);
353
354 for (internal_value = GRPC_COMPRESS_NONE;
355 internal_value < GRPC_COMPRESS_ALGORITHMS_COUNT; internal_value++) {
356 if (!grpc_compression_options_is_algorithm_enabled(wrapper->wrapped,
357 internal_value)) {
358 rb_ary_push(disabled_algorithms,
359 grpc_rb_compression_options_algorithm_value_to_name_internal(
360 internal_value));
361 }
362 }
363 return disabled_algorithms;
364 }
365
366 /* Initializes the compression options wrapper.
367 * Takes an optional hash parameter.
368 *
369 * Example call-seq:
370 * options = CompressionOptions.new(
371 * default_level: :none,
372 * disabled_algorithms: [:gzip]
373 * )
374 * channel_arg hash = Hash.new[...]
375 * channel_arg_hash_with_compression_options = channel_arg_hash.merge(options)
376 */
grpc_rb_compression_options_init(int argc,VALUE * argv,VALUE self)377 VALUE grpc_rb_compression_options_init(int argc, VALUE* argv, VALUE self) {
378 grpc_rb_compression_options* wrapper = NULL;
379 VALUE default_algorithm = Qnil;
380 VALUE default_level = Qnil;
381 VALUE disabled_algorithms = Qnil;
382 VALUE algorithm_name = Qnil;
383 VALUE hash_arg = Qnil;
384
385 rb_scan_args(argc, argv, "01", &hash_arg);
386
387 /* Check if the hash parameter was passed, or if invalid arguments were
388 * passed. */
389 if (hash_arg == Qnil) {
390 return self;
391 } else if (TYPE(hash_arg) != T_HASH || argc > 1) {
392 rb_raise(rb_eArgError,
393 "Invalid arguments. Expecting optional hash parameter");
394 }
395
396 TypedData_Get_Struct(self, grpc_rb_compression_options,
397 &grpc_rb_compression_options_data_type, wrapper);
398
399 /* Set the default algorithm if one was chosen. */
400 default_algorithm =
401 rb_hash_aref(hash_arg, ID2SYM(rb_intern("default_algorithm")));
402 if (default_algorithm != Qnil) {
403 grpc_rb_compression_options_set_default_algorithm(wrapper->wrapped,
404 default_algorithm);
405 }
406
407 /* Set the default level if one was chosen. */
408 default_level = rb_hash_aref(hash_arg, ID2SYM(rb_intern("default_level")));
409 if (default_level != Qnil) {
410 grpc_rb_compression_options_set_default_level(wrapper->wrapped,
411 default_level);
412 }
413
414 /* Set the disabled algorithms if any were chosen. */
415 disabled_algorithms =
416 rb_hash_aref(hash_arg, ID2SYM(rb_intern("disabled_algorithms")));
417 if (disabled_algorithms != Qnil) {
418 Check_Type(disabled_algorithms, T_ARRAY);
419
420 for (int i = 0; i < RARRAY_LEN(disabled_algorithms); i++) {
421 algorithm_name = rb_ary_entry(disabled_algorithms, i);
422 grpc_rb_compression_options_disable_algorithm(wrapper->wrapped,
423 algorithm_name);
424 }
425 }
426
427 return self;
428 }
429
Init_grpc_compression_options()430 void Init_grpc_compression_options() {
431 grpc_rb_cCompressionOptions = rb_define_class_under(
432 grpc_rb_mGrpcCore, "CompressionOptions", rb_cObject);
433
434 /* Allocates an object managed by the ruby runtime. */
435 rb_define_alloc_func(grpc_rb_cCompressionOptions,
436 grpc_rb_compression_options_alloc);
437
438 /* Initializes the ruby wrapper. #new method takes an optional hash argument.
439 */
440 rb_define_method(grpc_rb_cCompressionOptions, "initialize",
441 grpc_rb_compression_options_init, -1);
442
443 /* Methods for getting the default algorithm, default level, and disabled
444 * algorithms as readable names. */
445 rb_define_method(grpc_rb_cCompressionOptions, "default_algorithm",
446 grpc_rb_compression_options_get_default_algorithm, 0);
447 rb_define_method(grpc_rb_cCompressionOptions, "default_level",
448 grpc_rb_compression_options_get_default_level, 0);
449 rb_define_method(grpc_rb_cCompressionOptions, "disabled_algorithms",
450 grpc_rb_compression_options_get_disabled_algorithms, 0);
451
452 /* Determines whether or not an algorithm is enabled, given a readable
453 * algorithm name.*/
454 rb_define_method(grpc_rb_cCompressionOptions, "algorithm_enabled?",
455 grpc_rb_compression_options_is_algorithm_enabled, 1);
456
457 /* Provides a hash of the compression settings suitable
458 * for passing to server or channel args. */
459 rb_define_method(grpc_rb_cCompressionOptions, "to_hash",
460 grpc_rb_compression_options_to_hash, 0);
461 rb_define_alias(grpc_rb_cCompressionOptions, "to_channel_arg_hash",
462 "to_hash");
463
464 /* Ruby ids for the names of the different compression levels. */
465 id_compress_level_none = rb_intern("none");
466 id_compress_level_low = rb_intern("low");
467 id_compress_level_medium = rb_intern("medium");
468 id_compress_level_high = rb_intern("high");
469 }
470