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 "channel.h"
20 
21 #include <ext/standard/php_var.h>
22 #include <ext/standard/sha1.h>
23 #if PHP_MAJOR_VERSION < 7
24 #include <ext/standard/php_smart_str.h>
25 #else
26 #include <zend_smart_str.h>
27 #endif
28 #include <ext/spl/spl_exceptions.h>
29 #include <zend_exceptions.h>
30 
31 #include <grpc/grpc_security.h>
32 #include <grpc/support/alloc.h>
33 
34 #include "completion_queue.h"
35 #include "channel_credentials.h"
36 #include "timeval.h"
37 
38 zend_class_entry *grpc_ce_channel;
39 PHP_GRPC_DECLARE_OBJECT_HANDLER(channel_ce_handlers)
40 static gpr_mu global_persistent_list_mu;
41 int le_plink;
42 int le_bound;
43 extern HashTable grpc_persistent_list;
44 extern HashTable grpc_target_upper_bound_map;
45 
free_grpc_channel_wrapper(grpc_channel_wrapper * channel,bool free_channel)46 void free_grpc_channel_wrapper(grpc_channel_wrapper* channel, bool free_channel) {
47   if (free_channel) {
48     grpc_channel_destroy(channel->wrapped);
49     channel->wrapped = NULL;
50   }
51   free(channel->target);
52   free(channel->args_hashstr);
53   free(channel->creds_hashstr);
54   free(channel->key);
55   channel->target = NULL;
56   channel->args_hashstr = NULL;
57   channel->creds_hashstr = NULL;
58   channel->key = NULL;
59 }
60 
php_grpc_channel_ref(grpc_channel_wrapper * wrapper)61 void php_grpc_channel_ref(grpc_channel_wrapper* wrapper) {
62   gpr_mu_lock(&wrapper->mu);
63   wrapper->ref_count += 1;
64   gpr_mu_unlock(&wrapper->mu);
65 }
66 
php_grpc_channel_unref(grpc_channel_wrapper * wrapper)67 void php_grpc_channel_unref(grpc_channel_wrapper* wrapper) {
68   gpr_mu_lock(&wrapper->mu);
69   wrapper->ref_count -= 1;
70   if (wrapper->ref_count == 0) {
71     free_grpc_channel_wrapper(wrapper, true);
72     gpr_mu_unlock(&wrapper->mu);
73     free(wrapper);
74     wrapper = NULL;
75     return;
76   }
77   gpr_mu_unlock(&wrapper->mu);
78 }
79 
80 /* Frees and destroys an instance of wrapped_grpc_channel */
81 PHP_GRPC_FREE_WRAPPED_FUNC_START(wrapped_grpc_channel)
82   // In_persistent_list is used when the user don't close the channel,
83   // In this case, channels not in the list should be freed.
84   if (p->wrapper != NULL) {
85     php_grpc_channel_unref(p->wrapper);
86     p->wrapper = NULL;
87   }
PHP_GRPC_FREE_WRAPPED_FUNC_END()88 PHP_GRPC_FREE_WRAPPED_FUNC_END()
89 
90 /* Initializes an instance of wrapped_grpc_channel to be associated with an
91  * object of a class specified by class_type */
92 php_grpc_zend_object create_wrapped_grpc_channel(zend_class_entry *class_type
93                                                  TSRMLS_DC) {
94   PHP_GRPC_ALLOC_CLASS_OBJECT(wrapped_grpc_channel);
95   zend_object_std_init(&intern->std, class_type TSRMLS_CC);
96   object_properties_init(&intern->std, class_type);
97   PHP_GRPC_FREE_CLASS_OBJECT(wrapped_grpc_channel, channel_ce_handlers);
98 }
99 
php_grpc_read_args_array(zval * args_array,grpc_channel_args * args TSRMLS_DC)100 int php_grpc_read_args_array(zval *args_array,
101                              grpc_channel_args *args TSRMLS_DC) {
102   HashTable *array_hash;
103   int args_index;
104   array_hash = Z_ARRVAL_P(args_array);
105   if (!array_hash) {
106     zend_throw_exception(spl_ce_InvalidArgumentException,
107                          "array_hash is NULL", 1 TSRMLS_CC);
108     return FAILURE;
109   }
110   args->num_args = zend_hash_num_elements(array_hash);
111   args->args = ecalloc(args->num_args, sizeof(grpc_arg));
112   args_index = 0;
113 
114   char *key = NULL;
115   zval *data;
116   int key_type;
117 
118   PHP_GRPC_HASH_FOREACH_STR_KEY_VAL_START(array_hash, key, key_type, data)
119     if (key_type != HASH_KEY_IS_STRING) {
120       zend_throw_exception(spl_ce_InvalidArgumentException,
121                            "args keys must be strings", 1 TSRMLS_CC);
122       return FAILURE;
123     }
124     args->args[args_index].key = key;
125     switch (Z_TYPE_P(data)) {
126     case IS_LONG:
127       args->args[args_index].value.integer = (int)Z_LVAL_P(data);
128       args->args[args_index].type = GRPC_ARG_INTEGER;
129       break;
130     case IS_STRING:
131       args->args[args_index].value.string = Z_STRVAL_P(data);
132       args->args[args_index].type = GRPC_ARG_STRING;
133       break;
134     default:
135       zend_throw_exception(spl_ce_InvalidArgumentException,
136                            "args values must be int or string", 1 TSRMLS_CC);
137       return FAILURE;
138     }
139     args_index++;
140   PHP_GRPC_HASH_FOREACH_END()
141   return SUCCESS;
142 }
143 
generate_sha1_str(char * sha1str,char * str,php_grpc_int len)144 void generate_sha1_str(char *sha1str, char *str, php_grpc_int len) {
145   PHP_SHA1_CTX context;
146   unsigned char digest[20];
147   sha1str[0] = '\0';
148   PHP_SHA1Init(&context);
149   PHP_GRPC_SHA1Update(&context, str, len);
150   PHP_SHA1Final(digest, &context);
151   make_sha1_digest(sha1str, digest);
152 }
153 
php_grpc_persistent_list_delete_unused_channel(char * target,target_bound_le_t * target_bound_status TSRMLS_DC)154 bool php_grpc_persistent_list_delete_unused_channel(
155     char* target,
156     target_bound_le_t* target_bound_status TSRMLS_DC) {
157   zval *data;
158   PHP_GRPC_HASH_FOREACH_VAL_START(&grpc_persistent_list, data)
159     php_grpc_zend_resource *rsrc  = (php_grpc_zend_resource*) PHP_GRPC_HASH_VALPTR_TO_VAL(data)
160     if (rsrc == NULL) {
161       break;
162     }
163     channel_persistent_le_t* le = rsrc->ptr;
164     // Find the channel sharing the same target.
165     if (strcmp(le->channel->target, target) == 0) {
166       // ref_count=1 means that only the map holds the reference to the channel.
167       if (le->channel->ref_count == 1) {
168         php_grpc_delete_persistent_list_entry(le->channel->key,
169                                               strlen(le->channel->key)
170                                               TSRMLS_CC);
171         target_bound_status->current_count -= 1;
172         if (target_bound_status->current_count < target_bound_status->upper_bound) {
173           return true;
174         }
175       }
176     }
177   PHP_GRPC_HASH_FOREACH_END()
178   return false;
179 }
180 
update_and_get_target_upper_bound(char * target,int bound)181 target_bound_le_t* update_and_get_target_upper_bound(char* target, int bound) {
182   php_grpc_zend_resource *rsrc;
183   target_bound_le_t* target_bound_status;
184   php_grpc_int key_len = strlen(target);
185   if (!(PHP_GRPC_PERSISTENT_LIST_FIND(&grpc_target_upper_bound_map, target,
186       key_len, rsrc))) {
187     // Target is not not persisted.
188     php_grpc_zend_resource new_rsrc;
189     target_bound_status = malloc(sizeof(target_bound_le_t));
190     if (bound == -1) {
191       // If the bound is not set, use 1 as default.s
192       bound = 1;
193     }
194     target_bound_status->upper_bound = bound;
195     // Init current_count with 1. It should be add 1 when the channel is successfully
196     // created and minus 1 when it is removed from the persistent list.
197     target_bound_status->current_count = 0;
198     new_rsrc.type = le_bound;
199     new_rsrc.ptr = target_bound_status;
200     gpr_mu_lock(&global_persistent_list_mu);
201     PHP_GRPC_PERSISTENT_LIST_UPDATE(&grpc_target_upper_bound_map,
202                                     target, key_len, (void *)&new_rsrc);
203     gpr_mu_unlock(&global_persistent_list_mu);
204   } else {
205     // The target already in the map recording the upper bound.
206     // If no newer bound set, use the original now.
207     target_bound_status = (target_bound_le_t *)rsrc->ptr;
208     if (bound != -1) {
209       target_bound_status->upper_bound = bound;
210     }
211   }
212   return target_bound_status;
213 }
214 
create_channel(wrapped_grpc_channel * channel,char * target,grpc_channel_args args,wrapped_grpc_channel_credentials * creds)215 void create_channel(
216     wrapped_grpc_channel *channel,
217     char *target,
218     grpc_channel_args args,
219     wrapped_grpc_channel_credentials *creds) {
220   if (creds == NULL) {
221     channel->wrapper->wrapped = grpc_insecure_channel_create(target, &args,
222                                                              NULL);
223   } else {
224     channel->wrapper->wrapped =
225         grpc_secure_channel_create(creds->wrapped, target, &args, NULL);
226   }
227   // There is an Grpc\Channel object refer to it.
228   php_grpc_channel_ref(channel->wrapper);
229   efree(args.args);
230 }
231 
create_and_add_channel_to_persistent_list(wrapped_grpc_channel * channel,char * target,grpc_channel_args args,wrapped_grpc_channel_credentials * creds,char * key,php_grpc_int key_len,int target_upper_bound TSRMLS_DC)232 void create_and_add_channel_to_persistent_list(
233     wrapped_grpc_channel *channel,
234     char *target,
235     grpc_channel_args args,
236     wrapped_grpc_channel_credentials *creds,
237     char *key,
238     php_grpc_int key_len,
239     int target_upper_bound TSRMLS_DC) {
240   target_bound_le_t* target_bound_status =
241     update_and_get_target_upper_bound(target, target_upper_bound);
242   // Check the upper bound status before inserting to the persistent map.
243   if (target_bound_status->current_count >=
244       target_bound_status->upper_bound) {
245     if (!php_grpc_persistent_list_delete_unused_channel(
246           target, target_bound_status TSRMLS_CC)) {
247       // If no channel can be deleted from the persistent map,
248       // do not persist this one.
249       create_channel(channel, target, args, creds);
250       php_printf("[Warning] The number of channel for the"
251                  " target %s is maxed out bounded.\n", target);
252       php_printf("[Warning] Target upper bound: %d. Current size: %d.\n",
253                  target_bound_status->upper_bound,
254                  target_bound_status->current_count);
255       php_printf("[Warning] Target %s will not be persisted.\n", target);
256       return;
257     }
258   }
259   // There is space in the persistent map.
260   php_grpc_zend_resource new_rsrc;
261   channel_persistent_le_t *le;
262   // this links each persistent list entry to a destructor
263   new_rsrc.type = le_plink;
264   le = malloc(sizeof(channel_persistent_le_t));
265 
266   create_channel(channel, target, args, creds);
267   target_bound_status->current_count += 1;
268 
269   le->channel = channel->wrapper;
270   new_rsrc.ptr = le;
271   gpr_mu_lock(&global_persistent_list_mu);
272   PHP_GRPC_PERSISTENT_LIST_UPDATE(&grpc_persistent_list, key, key_len,
273                                   (void *)&new_rsrc);
274   // Persistent map refer to it.
275   php_grpc_channel_ref(channel->wrapper);
276   gpr_mu_unlock(&global_persistent_list_mu);
277 }
278 
279 /**
280  * Construct an instance of the Channel class.
281  *
282  * By default, the underlying grpc_channel is "persistent". That is, given
283  * the same set of parameters passed to the constructor, the same underlying
284  * grpc_channel will be returned.
285  *
286  * If the $args array contains a "credentials" key mapping to a
287  * ChannelCredentials object, a secure channel will be created with those
288  * credentials.
289  *
290  * If the $args array contains a "force_new" key mapping to a boolean value
291  * of "true", a new and separate underlying grpc_channel will be created
292  * and returned. This will not affect existing channels.
293  *
294  * @param string $target The hostname to associate with this channel
295  * @param array $args_array The arguments to pass to the Channel
296  */
PHP_METHOD(Channel,__construct)297 PHP_METHOD(Channel, __construct) {
298   wrapped_grpc_channel *channel =
299     PHP_GRPC_GET_WRAPPED_OBJECT(wrapped_grpc_channel, getThis());
300   zval *creds_obj = NULL;
301   char *target;
302   php_grpc_int target_length;
303   zval *args_array = NULL;
304   grpc_channel_args args;
305   HashTable *array_hash;
306   wrapped_grpc_channel_credentials *creds = NULL;
307   php_grpc_zend_resource *rsrc;
308   bool force_new = false;
309   zval *force_new_obj = NULL;
310   int target_upper_bound = -1;
311 
312   /* "sa" == 1 string, 1 array */
313   if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sa", &target,
314                             &target_length, &args_array) == FAILURE) {
315     zend_throw_exception(spl_ce_InvalidArgumentException,
316                          "Channel expects a string and an array", 1 TSRMLS_CC);
317     return;
318   }
319   array_hash = Z_ARRVAL_P(args_array);
320   if (php_grpc_zend_hash_find(array_hash, "credentials", sizeof("credentials"),
321                               (void **)&creds_obj) == SUCCESS) {
322     if (Z_TYPE_P(creds_obj) == IS_NULL) {
323       creds = NULL;
324       php_grpc_zend_hash_del(array_hash, "credentials", sizeof("credentials"));
325     } else if (PHP_GRPC_GET_CLASS_ENTRY(creds_obj) !=
326                grpc_ce_channel_credentials) {
327       zend_throw_exception(spl_ce_InvalidArgumentException,
328                            "credentials must be a ChannelCredentials object",
329                            1 TSRMLS_CC);
330       return;
331     } else {
332       creds = PHP_GRPC_GET_WRAPPED_OBJECT(wrapped_grpc_channel_credentials,
333                                           creds_obj);
334       php_grpc_zend_hash_del(array_hash, "credentials", sizeof("credentials"));
335     }
336   }
337   if (php_grpc_zend_hash_find(array_hash, "force_new", sizeof("force_new"),
338                               (void **)&force_new_obj) == SUCCESS) {
339     if (PHP_GRPC_BVAL_IS_TRUE(force_new_obj)) {
340       force_new = true;
341     }
342     php_grpc_zend_hash_del(array_hash, "force_new", sizeof("force_new"));
343   }
344 
345   if (php_grpc_zend_hash_find(array_hash, "grpc_target_persist_bound",
346                               sizeof("grpc_target_persist_bound"),
347                               (void **)&force_new_obj) == SUCCESS) {
348     if (Z_TYPE_P(force_new_obj) != IS_LONG) {
349       zend_throw_exception(spl_ce_InvalidArgumentException,
350                            "plist_bound must be a number",
351                            1 TSRMLS_CC);
352     }
353     target_upper_bound = (int)Z_LVAL_P(force_new_obj);
354     php_grpc_zend_hash_del(array_hash, "grpc_target_persist_bound",
355                            sizeof("grpc_target_persist_bound"));
356   }
357 
358   // parse the rest of the channel args array
359   if (php_grpc_read_args_array(args_array, &args TSRMLS_CC) == FAILURE) {
360     efree(args.args);
361     return;
362   }
363 
364   // Construct a hashkey for the persistent channel
365   // Currently, the hashkey contains 3 parts:
366   // 1. hostname
367   // 2. hash value of the channel args array (excluding "credentials"
368   //    and "force_new")
369   // 3. (optional) hash value of the ChannelCredentials object
370   php_serialize_data_t var_hash;
371   smart_str buf = {0};
372   PHP_VAR_SERIALIZE_INIT(var_hash);
373   PHP_GRPC_VAR_SERIALIZE(&buf, args_array, &var_hash);
374   PHP_VAR_SERIALIZE_DESTROY(var_hash);
375 
376   char sha1str[41];
377   generate_sha1_str(sha1str, PHP_GRPC_SERIALIZED_BUF_STR(buf),
378                     PHP_GRPC_SERIALIZED_BUF_LEN(buf));
379 
380   php_grpc_int key_len = target_length + strlen(sha1str);
381   if (creds != NULL && creds->hashstr != NULL) {
382     key_len += strlen(creds->hashstr);
383   }
384   char *key = malloc(key_len + 1);
385   strcpy(key, target);
386   strcat(key, sha1str);
387   if (creds != NULL && creds->hashstr != NULL) {
388     strcat(key, creds->hashstr);
389   }
390   channel->wrapper = malloc(sizeof(grpc_channel_wrapper));
391   channel->wrapper->ref_count = 0;
392   channel->wrapper->key = key;
393   channel->wrapper->target = strdup(target);
394   channel->wrapper->args_hashstr = strdup(sha1str);
395   channel->wrapper->creds_hashstr = NULL;
396   if (creds != NULL && creds->hashstr != NULL) {
397     php_grpc_int creds_hashstr_len = strlen(creds->hashstr);
398     char *channel_creds_hashstr = malloc(creds_hashstr_len + 1);
399     strcpy(channel_creds_hashstr, creds->hashstr);
400     channel->wrapper->creds_hashstr = channel_creds_hashstr;
401   }
402 
403   gpr_mu_init(&channel->wrapper->mu);
404   smart_str_free(&buf);
405   if (force_new || (creds != NULL && creds->has_call_creds)) {
406     // If the ChannelCredentials object was composed with a CallCredentials
407     // object, there is no way we can tell them apart. Do NOT persist
408     // them. They should be individually destroyed.
409     create_channel(channel, target, args, creds);
410   } else if (!(PHP_GRPC_PERSISTENT_LIST_FIND(&grpc_persistent_list, key,
411                                              key_len, rsrc))) {
412     create_and_add_channel_to_persistent_list(
413         channel, target, args, creds, key, key_len, target_upper_bound TSRMLS_CC);
414   } else {
415     // Found a previously stored channel in the persistent list
416     channel_persistent_le_t *le = (channel_persistent_le_t *)rsrc->ptr;
417     if (strcmp(target, le->channel->target) != 0 ||
418         strcmp(sha1str, le->channel->args_hashstr) != 0 ||
419         (creds != NULL && creds->hashstr != NULL &&
420          strcmp(creds->hashstr, le->channel->creds_hashstr) != 0)) {
421       // somehow hash collision
422       create_and_add_channel_to_persistent_list(
423           channel, target, args, creds, key, key_len, target_upper_bound TSRMLS_CC);
424     } else {
425       efree(args.args);
426       free_grpc_channel_wrapper(channel->wrapper, false);
427       gpr_mu_destroy(&channel->wrapper->mu);
428       free(channel->wrapper);
429       channel->wrapper = NULL;
430       channel->wrapper = le->channel;
431       // One more Grpc\Channel object refer to it.
432       php_grpc_channel_ref(channel->wrapper);
433       update_and_get_target_upper_bound(target, target_upper_bound);
434     }
435   }
436 }
437 
438 /**
439  * Get the endpoint this call/stream is connected to
440  * @return string The URI of the endpoint
441  */
PHP_METHOD(Channel,getTarget)442 PHP_METHOD(Channel, getTarget) {
443   wrapped_grpc_channel *channel =
444     PHP_GRPC_GET_WRAPPED_OBJECT(wrapped_grpc_channel, getThis());
445   if (channel->wrapper == NULL) {
446     zend_throw_exception(spl_ce_RuntimeException,
447                          "getTarget error."
448                          "Channel is already closed.", 1 TSRMLS_CC);
449     return;
450   }
451   gpr_mu_lock(&channel->wrapper->mu);
452   char *target = grpc_channel_get_target(channel->wrapper->wrapped);
453   gpr_mu_unlock(&channel->wrapper->mu);
454   PHP_GRPC_RETVAL_STRING(target, 1);
455   gpr_free(target);
456 }
457 
458 /**
459  * Get the connectivity state of the channel
460  * @param bool $try_to_connect Try to connect on the channel (optional)
461  * @return long The grpc connectivity state
462  */
PHP_METHOD(Channel,getConnectivityState)463 PHP_METHOD(Channel, getConnectivityState) {
464   wrapped_grpc_channel *channel =
465     PHP_GRPC_GET_WRAPPED_OBJECT(wrapped_grpc_channel, getThis());
466   if (channel->wrapper == NULL) {
467     zend_throw_exception(spl_ce_RuntimeException,
468                          "getConnectivityState error."
469                          "Channel is already closed.", 1 TSRMLS_CC);
470     return;
471   }
472   gpr_mu_lock(&channel->wrapper->mu);
473   bool try_to_connect = false;
474   /* "|b" == 1 optional bool */
475   if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|b", &try_to_connect)
476       == FAILURE) {
477     zend_throw_exception(spl_ce_InvalidArgumentException,
478                          "getConnectivityState expects a bool", 1 TSRMLS_CC);
479     gpr_mu_unlock(&channel->wrapper->mu);
480     return;
481   }
482   int state = grpc_channel_check_connectivity_state(channel->wrapper->wrapped,
483                                                     (int)try_to_connect);
484   gpr_mu_unlock(&channel->wrapper->mu);
485   RETURN_LONG(state);
486 }
487 
488 /**
489  * Watch the connectivity state of the channel until it changed
490  * @param long $last_state The previous connectivity state of the channel
491  * @param Timeval $deadline_obj The deadline this function should wait until
492  * @return bool If the connectivity state changes from last_state
493  *              before deadline
494  */
PHP_METHOD(Channel,watchConnectivityState)495 PHP_METHOD(Channel, watchConnectivityState) {
496   wrapped_grpc_channel *channel =
497     PHP_GRPC_GET_WRAPPED_OBJECT(wrapped_grpc_channel, getThis());
498   if (channel->wrapper == NULL) {
499     zend_throw_exception(spl_ce_RuntimeException,
500                          "watchConnectivityState error"
501                          "Channel is already closed.", 1 TSRMLS_CC);
502     return;
503   }
504   gpr_mu_lock(&channel->wrapper->mu);
505   php_grpc_long last_state;
506   zval *deadline_obj;
507 
508   /* "lO" == 1 long 1 object */
509   if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "lO",
510                             &last_state, &deadline_obj,
511                             grpc_ce_timeval) == FAILURE) {
512     zend_throw_exception(spl_ce_InvalidArgumentException,
513                          "watchConnectivityState expects 1 long 1 timeval",
514                          1 TSRMLS_CC);
515     gpr_mu_unlock(&channel->wrapper->mu);
516     return;
517   }
518 
519   wrapped_grpc_timeval *deadline =
520     PHP_GRPC_GET_WRAPPED_OBJECT(wrapped_grpc_timeval, deadline_obj);
521   grpc_channel_watch_connectivity_state(channel->wrapper->wrapped,
522                                         (grpc_connectivity_state)last_state,
523                                         deadline->wrapped, completion_queue,
524                                         NULL);
525   grpc_event event =
526       grpc_completion_queue_pluck(completion_queue, NULL,
527                                   gpr_inf_future(GPR_CLOCK_REALTIME), NULL);
528   gpr_mu_unlock(&channel->wrapper->mu);
529   RETURN_BOOL(event.success);
530 }
531 
532 /**
533  * Close the channel
534  * @return void
535  */
PHP_METHOD(Channel,close)536 PHP_METHOD(Channel, close) {
537   wrapped_grpc_channel *channel =
538     PHP_GRPC_GET_WRAPPED_OBJECT(wrapped_grpc_channel, getThis());
539   if (channel->wrapper != NULL) {
540     php_grpc_channel_unref(channel->wrapper);
541     channel->wrapper = NULL;
542   }
543 }
544 
545 // Delete an entry from the persistent list
546 // Note: this does not destroy or close the underlying grpc_channel
php_grpc_delete_persistent_list_entry(char * key,php_grpc_int key_len TSRMLS_DC)547 void php_grpc_delete_persistent_list_entry(char *key, php_grpc_int key_len
548                                            TSRMLS_DC) {
549   php_grpc_zend_resource *rsrc;
550   gpr_mu_lock(&global_persistent_list_mu);
551   if (PHP_GRPC_PERSISTENT_LIST_FIND(&grpc_persistent_list, key,
552                                     key_len, rsrc)) {
553     php_grpc_zend_hash_del(&grpc_persistent_list, key, key_len+1);
554   }
555   gpr_mu_unlock(&global_persistent_list_mu);
556 }
557 
558 // A destructor associated with each list entry from the persistent list
php_grpc_channel_plink_dtor(php_grpc_zend_resource * rsrc TSRMLS_DC)559 static void php_grpc_channel_plink_dtor(php_grpc_zend_resource *rsrc
560                                         TSRMLS_DC) {
561   channel_persistent_le_t *le = (channel_persistent_le_t *)rsrc->ptr;
562   if (le == NULL) {
563     return;
564   }
565   if (le->channel != NULL) {
566     php_grpc_channel_unref(le->channel);
567     le->channel = NULL;
568   }
569   free(le);
570   le = NULL;
571 }
572 
573 // A destructor associated with each list entry from the target_bound map
php_grpc_target_bound_dtor(php_grpc_zend_resource * rsrc TSRMLS_DC)574 static void php_grpc_target_bound_dtor(php_grpc_zend_resource *rsrc
575                                         TSRMLS_DC) {
576   target_bound_le_t *le = (target_bound_le_t *) rsrc->ptr;
577   if (le == NULL) {
578     return;
579   }
580   free(le);
581   le = NULL;
582 }
583 
584 #ifdef GRPC_PHP_DEBUG
585 
586 /**
587 * Clean all channels in the persistent. Test only.
588 * @return void
589 */
PHP_METHOD(Channel,cleanPersistentList)590 PHP_METHOD(Channel, cleanPersistentList) {
591   zend_hash_clean(&grpc_persistent_list);
592   zend_hash_clean(&grpc_target_upper_bound_map);
593 }
594 
grpc_connectivity_state_name(grpc_connectivity_state state)595 char *grpc_connectivity_state_name(grpc_connectivity_state state) {
596  switch (state) {
597    case GRPC_CHANNEL_IDLE:
598      return "IDLE";
599    case GRPC_CHANNEL_CONNECTING:
600      return "CONNECTING";
601    case GRPC_CHANNEL_READY:
602      return "READY";
603    case GRPC_CHANNEL_TRANSIENT_FAILURE:
604      return "TRANSIENT_FAILURE";
605    case GRPC_CHANNEL_SHUTDOWN:
606      return "SHUTDOWN";
607  }
608  return "UNKNOWN";
609 }
610 
611 /**
612 * Return the info about the current channel. Test only.
613 * @return array
614 */
PHP_METHOD(Channel,getChannelInfo)615 PHP_METHOD(Channel, getChannelInfo) {
616   wrapped_grpc_channel *channel =
617     PHP_GRPC_GET_WRAPPED_OBJECT(wrapped_grpc_channel, getThis());
618   array_init(return_value);
619    // Info about the target
620   PHP_GRPC_ADD_STRING_TO_ARRAY(return_value, "target",
621               sizeof("target"), channel->wrapper->target, true);
622   // Info about the upper bound for the target
623   target_bound_le_t* target_bound_status =
624     update_and_get_target_upper_bound(channel->wrapper->target, -1);
625   PHP_GRPC_ADD_LONG_TO_ARRAY(return_value, "target_upper_bound",
626     sizeof("target_upper_bound"), target_bound_status->upper_bound);
627   PHP_GRPC_ADD_LONG_TO_ARRAY(return_value, "target_current_size",
628     sizeof("target_current_size"), target_bound_status->current_count);
629   // Info about key
630   PHP_GRPC_ADD_STRING_TO_ARRAY(return_value, "key",
631               sizeof("key"), channel->wrapper->key, true);
632   // Info about persistent channel ref_count
633   PHP_GRPC_ADD_LONG_TO_ARRAY(return_value, "ref_count",
634               sizeof("ref_count"), channel->wrapper->ref_count);
635   // Info about connectivity status
636   int state =
637       grpc_channel_check_connectivity_state(channel->wrapper->wrapped, (int)0);
638   // It should be set to 'true' in PHP 5.6.33
639   PHP_GRPC_ADD_LONG_TO_ARRAY(return_value, "connectivity_status",
640               sizeof("connectivity_status"), state);
641   PHP_GRPC_ADD_STRING_TO_ARRAY(return_value, "ob",
642               sizeof("ob"),
643               grpc_connectivity_state_name(state), true);
644   // Info about the channel is closed or not
645   PHP_GRPC_ADD_BOOL_TO_ARRAY(return_value, "is_valid",
646               sizeof("is_valid"), (channel->wrapper == NULL));
647 }
648 
649 /**
650 * Return an array of all channels in the persistent list. Test only.
651 * @return array
652 */
PHP_METHOD(Channel,getPersistentList)653 PHP_METHOD(Channel, getPersistentList) {
654   array_init(return_value);
655   zval *data;
656   PHP_GRPC_HASH_FOREACH_VAL_START(&grpc_persistent_list, data)
657     php_grpc_zend_resource *rsrc  =
658                 (php_grpc_zend_resource*) PHP_GRPC_HASH_VALPTR_TO_VAL(data)
659     if (rsrc == NULL) {
660       break;
661     }
662     channel_persistent_le_t* le = rsrc->ptr;
663     zval* ret_arr;
664     PHP_GRPC_MAKE_STD_ZVAL(ret_arr);
665     array_init(ret_arr);
666     // Info about the target
667     PHP_GRPC_ADD_STRING_TO_ARRAY(ret_arr, "target",
668                 sizeof("target"), le->channel->target, true);
669     // Info about the upper bound for the target
670     target_bound_le_t* target_bound_status =
671       update_and_get_target_upper_bound(le->channel->target, -1);
672     PHP_GRPC_ADD_LONG_TO_ARRAY(ret_arr, "target_upper_bound",
673       sizeof("target_upper_bound"), target_bound_status->upper_bound);
674     PHP_GRPC_ADD_LONG_TO_ARRAY(ret_arr, "target_current_size",
675       sizeof("target_current_size"), target_bound_status->current_count);
676     // Info about key
677     PHP_GRPC_ADD_STRING_TO_ARRAY(ret_arr, "key",
678                 sizeof("key"), le->channel->key, true);
679     // Info about persistent channel ref_count
680     PHP_GRPC_ADD_LONG_TO_ARRAY(ret_arr, "ref_count",
681                 sizeof("ref_count"), le->channel->ref_count);
682     // Info about connectivity status
683     int state =
684         grpc_channel_check_connectivity_state(le->channel->wrapped, (int)0);
685     // It should be set to 'true' in PHP 5.6.33
686     PHP_GRPC_ADD_LONG_TO_ARRAY(ret_arr, "connectivity_status",
687                 sizeof("connectivity_status"), state);
688     PHP_GRPC_ADD_STRING_TO_ARRAY(ret_arr, "ob",
689                 sizeof("ob"),
690                 grpc_connectivity_state_name(state), true);
691     add_assoc_zval(return_value, le->channel->key, ret_arr);
692     PHP_GRPC_FREE_STD_ZVAL(ret_arr);
693   PHP_GRPC_HASH_FOREACH_END()
694 }
695 #endif
696 
697 
698 ZEND_BEGIN_ARG_INFO_EX(arginfo_construct, 0, 0, 2)
699   ZEND_ARG_INFO(0, target)
700   ZEND_ARG_INFO(0, args)
701 ZEND_END_ARG_INFO()
702 
703 ZEND_BEGIN_ARG_INFO_EX(arginfo_getTarget, 0, 0, 0)
704 ZEND_END_ARG_INFO()
705 
706 ZEND_BEGIN_ARG_INFO_EX(arginfo_getConnectivityState, 0, 0, 0)
707   ZEND_ARG_INFO(0, try_to_connect)
708 ZEND_END_ARG_INFO()
709 
710 ZEND_BEGIN_ARG_INFO_EX(arginfo_watchConnectivityState, 0, 0, 2)
711   ZEND_ARG_INFO(0, last_state)
712   ZEND_ARG_INFO(0, deadline)
713 ZEND_END_ARG_INFO()
714 
715 ZEND_BEGIN_ARG_INFO_EX(arginfo_close, 0, 0, 0)
716 ZEND_END_ARG_INFO()
717 
718 #ifdef GRPC_PHP_DEBUG
719 ZEND_BEGIN_ARG_INFO_EX(arginfo_getChannelInfo, 0, 0, 0)
720 ZEND_END_ARG_INFO()
721 
722 ZEND_BEGIN_ARG_INFO_EX(arginfo_cleanPersistentList, 0, 0, 0)
723 ZEND_END_ARG_INFO()
724 
725 ZEND_BEGIN_ARG_INFO_EX(arginfo_getPersistentList, 0, 0, 0)
726 ZEND_END_ARG_INFO()
727 #endif
728 
729 
730 static zend_function_entry channel_methods[] = {
731   PHP_ME(Channel, __construct, arginfo_construct,
732          ZEND_ACC_PUBLIC | ZEND_ACC_CTOR)
733   PHP_ME(Channel, getTarget, arginfo_getTarget,
734          ZEND_ACC_PUBLIC)
735   PHP_ME(Channel, getConnectivityState, arginfo_getConnectivityState,
736          ZEND_ACC_PUBLIC)
737   PHP_ME(Channel, watchConnectivityState, arginfo_watchConnectivityState,
738          ZEND_ACC_PUBLIC)
739   PHP_ME(Channel, close, arginfo_close,
740          ZEND_ACC_PUBLIC)
741   #ifdef GRPC_PHP_DEBUG
742   PHP_ME(Channel, getChannelInfo, arginfo_getChannelInfo,
743          ZEND_ACC_PUBLIC)
744   PHP_ME(Channel, cleanPersistentList, arginfo_cleanPersistentList,
745          ZEND_ACC_PUBLIC)
746   PHP_ME(Channel, getPersistentList, arginfo_getPersistentList,
747          ZEND_ACC_PUBLIC)
748   #endif
749   PHP_FE_END
750 };
751 
GRPC_STARTUP_FUNCTION(channel)752 GRPC_STARTUP_FUNCTION(channel) {
753   zend_class_entry ce;
754   INIT_CLASS_ENTRY(ce, "Grpc\\Channel", channel_methods);
755   ce.create_object = create_wrapped_grpc_channel;
756   grpc_ce_channel = zend_register_internal_class(&ce TSRMLS_CC);
757   gpr_mu_init(&global_persistent_list_mu);
758   le_plink = zend_register_list_destructors_ex(
759       NULL, php_grpc_channel_plink_dtor, "Persistent Channel", module_number);
760   zend_hash_init_ex(&grpc_persistent_list, 20, NULL,
761                     EG(persistent_list).pDestructor, 1, 0);
762   // Register the target->upper_bound map.
763   le_bound = zend_register_list_destructors_ex(
764       NULL, php_grpc_target_bound_dtor, "Target Bound", module_number);
765   zend_hash_init_ex(&grpc_target_upper_bound_map, 20, NULL,
766                     EG(persistent_list).pDestructor, 1, 0);
767 
768   PHP_GRPC_INIT_HANDLER(wrapped_grpc_channel, channel_ce_handlers);
769   return SUCCESS;
770 }
771