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