blob: dc3acc89bb7147fc4bce0fdd23e74cf60fbe4511 [file] [log] [blame]
/*
*
* Copyright 2015 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
#include "channel.h"
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <php.h>
#include <php_ini.h>
#include <ext/standard/info.h>
#include <ext/standard/php_var.h>
#include <ext/standard/sha1.h>
#if PHP_MAJOR_VERSION < 7
#include <ext/standard/php_smart_str.h>
#else
#include <zend_smart_str.h>
#endif
#include <ext/spl/spl_exceptions.h>
#include "php_grpc.h"
#include <zend_exceptions.h>
#include <stdbool.h>
#include <grpc/grpc.h>
#include <grpc/grpc_security.h>
#include "completion_queue.h"
#include "channel_credentials.h"
#include "server.h"
#include "timeval.h"
zend_class_entry *grpc_ce_channel;
#if PHP_MAJOR_VERSION >= 7
static zend_object_handlers channel_ce_handlers;
#endif
static gpr_mu global_persistent_list_mu;
int le_plink;
/* Frees and destroys an instance of wrapped_grpc_channel */
PHP_GRPC_FREE_WRAPPED_FUNC_START(wrapped_grpc_channel)
if (p->wrapper != NULL) {
gpr_mu_lock(&p->wrapper->mu);
if (p->wrapper->wrapped != NULL) {
php_grpc_zend_resource *rsrc;
php_grpc_int key_len = strlen(p->wrapper->key);
// only destroy the channel here if not found in the persistent list
gpr_mu_lock(&global_persistent_list_mu);
if (!(PHP_GRPC_PERSISTENT_LIST_FIND(&EG(persistent_list), p->wrapper->key,
key_len, rsrc))) {
grpc_channel_destroy(p->wrapper->wrapped);
free(p->wrapper->target);
free(p->wrapper->args_hashstr);
}
gpr_mu_unlock(&global_persistent_list_mu);
}
gpr_mu_unlock(&p->wrapper->mu);
}
PHP_GRPC_FREE_WRAPPED_FUNC_END()
/* Initializes an instance of wrapped_grpc_channel to be associated with an
* object of a class specified by class_type */
php_grpc_zend_object create_wrapped_grpc_channel(zend_class_entry *class_type
TSRMLS_DC) {
PHP_GRPC_ALLOC_CLASS_OBJECT(wrapped_grpc_channel);
zend_object_std_init(&intern->std, class_type TSRMLS_CC);
object_properties_init(&intern->std, class_type);
PHP_GRPC_FREE_CLASS_OBJECT(wrapped_grpc_channel, channel_ce_handlers);
}
int php_grpc_read_args_array(zval *args_array,
grpc_channel_args *args TSRMLS_DC) {
HashTable *array_hash;
int args_index;
array_hash = Z_ARRVAL_P(args_array);
if (!array_hash) {
zend_throw_exception(spl_ce_InvalidArgumentException,
"array_hash is NULL", 1 TSRMLS_CC);
return FAILURE;
}
args->num_args = zend_hash_num_elements(array_hash);
args->args = ecalloc(args->num_args, sizeof(grpc_arg));
args_index = 0;
char *key = NULL;
zval *data;
int key_type;
PHP_GRPC_HASH_FOREACH_STR_KEY_VAL_START(array_hash, key, key_type, data)
if (key_type != HASH_KEY_IS_STRING) {
zend_throw_exception(spl_ce_InvalidArgumentException,
"args keys must be strings", 1 TSRMLS_CC);
return FAILURE;
}
args->args[args_index].key = key;
switch (Z_TYPE_P(data)) {
case IS_LONG:
args->args[args_index].value.integer = (int)Z_LVAL_P(data);
args->args[args_index].type = GRPC_ARG_INTEGER;
break;
case IS_STRING:
args->args[args_index].value.string = Z_STRVAL_P(data);
args->args[args_index].type = GRPC_ARG_STRING;
break;
default:
zend_throw_exception(spl_ce_InvalidArgumentException,
"args values must be int or string", 1 TSRMLS_CC);
return FAILURE;
}
args_index++;
PHP_GRPC_HASH_FOREACH_END()
return SUCCESS;
}
void generate_sha1_str(char *sha1str, char *str, php_grpc_int len) {
PHP_SHA1_CTX context;
unsigned char digest[20];
sha1str[0] = '\0';
PHP_SHA1Init(&context);
PHP_GRPC_SHA1Update(&context, str, len);
PHP_SHA1Final(digest, &context);
make_sha1_digest(sha1str, digest);
}
void create_channel(
wrapped_grpc_channel *channel,
char *target,
grpc_channel_args args,
wrapped_grpc_channel_credentials *creds) {
if (creds == NULL) {
channel->wrapper->wrapped = grpc_insecure_channel_create(target, &args,
NULL);
} else {
channel->wrapper->wrapped =
grpc_secure_channel_create(creds->wrapped, target, &args, NULL);
}
efree(args.args);
}
void 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 TSRMLS_DC) {
php_grpc_zend_resource new_rsrc;
channel_persistent_le_t *le;
// this links each persistent list entry to a destructor
new_rsrc.type = le_plink;
le = malloc(sizeof(channel_persistent_le_t));
create_channel(channel, target, args, creds);
le->channel = channel->wrapper;
new_rsrc.ptr = le;
gpr_mu_lock(&global_persistent_list_mu);
PHP_GRPC_PERSISTENT_LIST_UPDATE(&EG(persistent_list), key, key_len,
(void *)&new_rsrc);
gpr_mu_unlock(&global_persistent_list_mu);
}
/**
* Construct an instance of the Channel class.
*
* By default, the underlying grpc_channel is "persistent". That is, given
* the same set of parameters passed to the constructor, the same underlying
* grpc_channel will be returned.
*
* If the $args array contains a "credentials" key mapping to a
* ChannelCredentials object, a secure channel will be created with those
* credentials.
*
* If the $args array contains a "force_new" key mapping to a boolean value
* of "true", a new and separate underlying grpc_channel will be created
* and returned. This will not affect existing channels.
*
* @param string $target The hostname to associate with this channel
* @param array $args_array The arguments to pass to the Channel
*/
PHP_METHOD(Channel, __construct) {
wrapped_grpc_channel *channel = Z_WRAPPED_GRPC_CHANNEL_P(getThis());
zval *creds_obj = NULL;
char *target;
php_grpc_int target_length;
zval *args_array = NULL;
grpc_channel_args args;
HashTable *array_hash;
wrapped_grpc_channel_credentials *creds = NULL;
php_grpc_zend_resource *rsrc;
bool force_new = false;
zval *force_new_obj = NULL;
/* "sa" == 1 string, 1 array */
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sa", &target,
&target_length, &args_array) == FAILURE) {
zend_throw_exception(spl_ce_InvalidArgumentException,
"Channel expects a string and an array", 1 TSRMLS_CC);
return;
}
array_hash = Z_ARRVAL_P(args_array);
if (php_grpc_zend_hash_find(array_hash, "credentials", sizeof("credentials"),
(void **)&creds_obj) == SUCCESS) {
if (Z_TYPE_P(creds_obj) == IS_NULL) {
creds = NULL;
php_grpc_zend_hash_del(array_hash, "credentials", sizeof("credentials"));
} else if (PHP_GRPC_GET_CLASS_ENTRY(creds_obj) !=
grpc_ce_channel_credentials) {
zend_throw_exception(spl_ce_InvalidArgumentException,
"credentials must be a ChannelCredentials object",
1 TSRMLS_CC);
return;
} else {
creds = Z_WRAPPED_GRPC_CHANNEL_CREDS_P(creds_obj);
php_grpc_zend_hash_del(array_hash, "credentials", sizeof("credentials"));
}
}
if (php_grpc_zend_hash_find(array_hash, "force_new", sizeof("force_new"),
(void **)&force_new_obj) == SUCCESS) {
if (PHP_GRPC_BVAL_IS_TRUE(force_new_obj)) {
force_new = true;
}
php_grpc_zend_hash_del(array_hash, "force_new", sizeof("force_new"));
}
// parse the rest of the channel args array
if (php_grpc_read_args_array(args_array, &args TSRMLS_CC) == FAILURE) {
return;
}
// Construct a hashkey for the persistent channel
// Currently, the hashkey contains 3 parts:
// 1. hostname
// 2. hash value of the channel args array (excluding "credentials"
// and "force_new")
// 3. (optional) hash value of the ChannelCredentials object
php_serialize_data_t var_hash;
smart_str buf = {0};
PHP_VAR_SERIALIZE_INIT(var_hash);
PHP_GRPC_VAR_SERIALIZE(&buf, args_array, &var_hash);
PHP_VAR_SERIALIZE_DESTROY(var_hash);
char sha1str[41];
generate_sha1_str(sha1str, PHP_GRPC_SERIALIZED_BUF_STR(buf),
PHP_GRPC_SERIALIZED_BUF_LEN(buf));
php_grpc_int key_len = target_length + strlen(sha1str);
if (creds != NULL && creds->hashstr != NULL) {
key_len += strlen(creds->hashstr);
}
char *key = malloc(key_len + 1);
strcpy(key, target);
strcat(key, sha1str);
if (creds != NULL && creds->hashstr != NULL) {
strcat(key, creds->hashstr);
}
channel->wrapper = malloc(sizeof(grpc_channel_wrapper));
channel->wrapper->key = key;
channel->wrapper->target = strdup(target);
channel->wrapper->args_hashstr = strdup(sha1str);
if (creds != NULL && creds->hashstr != NULL) {
channel->wrapper->creds_hashstr = creds->hashstr;
}
gpr_mu_init(&channel->wrapper->mu);
smart_str_free(&buf);
if (force_new || (creds != NULL && creds->has_call_creds)) {
// If the ChannelCredentials object was composed with a CallCredentials
// object, there is no way we can tell them apart. Do NOT persist
// them. They should be individually destroyed.
create_channel(channel, target, args, creds);
} else if (!(PHP_GRPC_PERSISTENT_LIST_FIND(&EG(persistent_list), key,
key_len, rsrc))) {
create_and_add_channel_to_persistent_list(
channel, target, args, creds, key, key_len TSRMLS_CC);
} else {
// Found a previously stored channel in the persistent list
channel_persistent_le_t *le = (channel_persistent_le_t *)rsrc->ptr;
if (strcmp(target, le->channel->target) != 0 ||
strcmp(sha1str, le->channel->args_hashstr) != 0 ||
(creds != NULL && creds->hashstr != NULL &&
strcmp(creds->hashstr, le->channel->creds_hashstr) != 0)) {
// somehow hash collision
create_and_add_channel_to_persistent_list(
channel, target, args, creds, key, key_len TSRMLS_CC);
} else {
channel->wrapper = le->channel;
}
}
}
/**
* Get the endpoint this call/stream is connected to
* @return string The URI of the endpoint
*/
PHP_METHOD(Channel, getTarget) {
wrapped_grpc_channel *channel = Z_WRAPPED_GRPC_CHANNEL_P(getThis());
gpr_mu_lock(&channel->wrapper->mu);
if (channel->wrapper->wrapped == NULL) {
zend_throw_exception(spl_ce_RuntimeException,
"Channel already closed", 1 TSRMLS_CC);
gpr_mu_unlock(&channel->wrapper->mu);
return;
}
char *target = grpc_channel_get_target(channel->wrapper->wrapped);
gpr_mu_unlock(&channel->wrapper->mu);
PHP_GRPC_RETURN_STRING(target, 1);
}
/**
* Get the connectivity state of the channel
* @param bool $try_to_connect Try to connect on the channel (optional)
* @return long The grpc connectivity state
*/
PHP_METHOD(Channel, getConnectivityState) {
wrapped_grpc_channel *channel = Z_WRAPPED_GRPC_CHANNEL_P(getThis());
gpr_mu_lock(&channel->wrapper->mu);
if (channel->wrapper->wrapped == NULL) {
zend_throw_exception(spl_ce_RuntimeException,
"Channel already closed", 1 TSRMLS_CC);
gpr_mu_unlock(&channel->wrapper->mu);
return;
}
bool try_to_connect = false;
/* "|b" == 1 optional bool */
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|b", &try_to_connect)
== FAILURE) {
zend_throw_exception(spl_ce_InvalidArgumentException,
"getConnectivityState expects a bool", 1 TSRMLS_CC);
gpr_mu_unlock(&channel->wrapper->mu);
return;
}
int state = grpc_channel_check_connectivity_state(channel->wrapper->wrapped,
(int)try_to_connect);
// this can happen if another shared Channel object close the underlying
// channel
if (state == GRPC_CHANNEL_SHUTDOWN) {
channel->wrapper->wrapped = NULL;
}
gpr_mu_unlock(&channel->wrapper->mu);
RETURN_LONG(state);
}
/**
* Watch the connectivity state of the channel until it changed
* @param long $last_state The previous connectivity state of the channel
* @param Timeval $deadline_obj The deadline this function should wait until
* @return bool If the connectivity state changes from last_state
* before deadline
*/
PHP_METHOD(Channel, watchConnectivityState) {
wrapped_grpc_channel *channel = Z_WRAPPED_GRPC_CHANNEL_P(getThis());
gpr_mu_lock(&channel->wrapper->mu);
if (channel->wrapper->wrapped == NULL) {
zend_throw_exception(spl_ce_RuntimeException,
"Channel already closed", 1 TSRMLS_CC);
gpr_mu_unlock(&channel->wrapper->mu);
return;
}
php_grpc_long last_state;
zval *deadline_obj;
/* "lO" == 1 long 1 object */
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "lO",
&last_state, &deadline_obj,
grpc_ce_timeval) == FAILURE) {
zend_throw_exception(spl_ce_InvalidArgumentException,
"watchConnectivityState expects 1 long 1 timeval",
1 TSRMLS_CC);
gpr_mu_unlock(&channel->wrapper->mu);
return;
}
wrapped_grpc_timeval *deadline = Z_WRAPPED_GRPC_TIMEVAL_P(deadline_obj);
grpc_channel_watch_connectivity_state(channel->wrapper->wrapped,
(grpc_connectivity_state)last_state,
deadline->wrapped, completion_queue,
NULL);
grpc_event event =
grpc_completion_queue_pluck(completion_queue, NULL,
gpr_inf_future(GPR_CLOCK_REALTIME), NULL);
gpr_mu_unlock(&channel->wrapper->mu);
RETURN_BOOL(event.success);
}
/**
* Close the channel
* @return void
*/
PHP_METHOD(Channel, close) {
wrapped_grpc_channel *channel = Z_WRAPPED_GRPC_CHANNEL_P(getThis());
gpr_mu_lock(&channel->wrapper->mu);
if (channel->wrapper->wrapped != NULL) {
grpc_channel_destroy(channel->wrapper->wrapped);
free(channel->wrapper->target);
free(channel->wrapper->args_hashstr);
channel->wrapper->wrapped = NULL;
php_grpc_delete_persistent_list_entry(channel->wrapper->key,
strlen(channel->wrapper->key)
TSRMLS_CC);
}
gpr_mu_unlock(&channel->wrapper->mu);
}
// Delete an entry from the persistent list
// Note: this does not destroy or close the underlying grpc_channel
void php_grpc_delete_persistent_list_entry(char *key, php_grpc_int key_len
TSRMLS_DC) {
php_grpc_zend_resource *rsrc;
gpr_mu_lock(&global_persistent_list_mu);
if (PHP_GRPC_PERSISTENT_LIST_FIND(&EG(persistent_list), key,
key_len, rsrc)) {
channel_persistent_le_t *le;
le = (channel_persistent_le_t *)rsrc->ptr;
le->channel = NULL;
php_grpc_zend_hash_del(&EG(persistent_list), key, key_len+1);
}
gpr_mu_unlock(&global_persistent_list_mu);
}
// A destructor associated with each list entry from the persistent list
static void php_grpc_channel_plink_dtor(php_grpc_zend_resource *rsrc
TSRMLS_DC) {
channel_persistent_le_t *le = (channel_persistent_le_t *)rsrc->ptr;
if (le->channel != NULL) {
gpr_mu_lock(&le->channel->mu);
if (le->channel->wrapped != NULL) {
grpc_channel_destroy(le->channel->wrapped);
free(le->channel->target);
free(le->channel->args_hashstr);
}
gpr_mu_unlock(&le->channel->mu);
}
}
ZEND_BEGIN_ARG_INFO_EX(arginfo_construct, 0, 0, 2)
ZEND_ARG_INFO(0, target)
ZEND_ARG_INFO(0, args)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_getTarget, 0, 0, 0)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_getConnectivityState, 0, 0, 0)
ZEND_ARG_INFO(0, try_to_connect)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_watchConnectivityState, 0, 0, 2)
ZEND_ARG_INFO(0, last_state)
ZEND_ARG_INFO(0, deadline)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_close, 0, 0, 0)
ZEND_END_ARG_INFO()
static zend_function_entry channel_methods[] = {
PHP_ME(Channel, __construct, arginfo_construct,
ZEND_ACC_PUBLIC | ZEND_ACC_CTOR)
PHP_ME(Channel, getTarget, arginfo_getTarget,
ZEND_ACC_PUBLIC)
PHP_ME(Channel, getConnectivityState, arginfo_getConnectivityState,
ZEND_ACC_PUBLIC)
PHP_ME(Channel, watchConnectivityState, arginfo_watchConnectivityState,
ZEND_ACC_PUBLIC)
PHP_ME(Channel, close, arginfo_close,
ZEND_ACC_PUBLIC)
PHP_FE_END
};
GRPC_STARTUP_FUNCTION(channel) {
zend_class_entry ce;
INIT_CLASS_ENTRY(ce, "Grpc\\Channel", channel_methods);
ce.create_object = create_wrapped_grpc_channel;
grpc_ce_channel = zend_register_internal_class(&ce TSRMLS_CC);
gpr_mu_init(&global_persistent_list_mu);
le_plink = zend_register_list_destructors_ex(
NULL, php_grpc_channel_plink_dtor, "Persistent Channel", module_number);
PHP_GRPC_INIT_HANDLER(wrapped_grpc_channel, channel_ce_handlers);
return SUCCESS;
}