• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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