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