blob: be7969f92772c07e5a2fe4c5b4a9b9daebecf52a [file] [log] [blame]
#include "call.h"
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "php.h"
#include "php_ini.h"
#include "ext/standard/info.h"
#include "ext/spl/spl_exceptions.h"
#include "php_grpc.h"
#include "zend_exceptions.h"
#include "zend_hash.h"
#include <stdbool.h>
#include "grpc/support/log.h"
#include "grpc/grpc.h"
#include "timeval.h"
#include "channel.h"
#include "completion_queue.h"
#include "byte_buffer.h"
/* Frees and destroys an instance of wrapped_grpc_call */
void free_wrapped_grpc_call(void *object TSRMLS_DC){
wrapped_grpc_call *call = (wrapped_grpc_call*)object;
if(call->wrapped != NULL){
grpc_call_destroy(call->wrapped);
}
efree(call);
}
/* Initializes an instance of wrapped_grpc_call to be associated with an object
* of a class specified by class_type */
zend_object_value create_wrapped_grpc_call(
zend_class_entry *class_type TSRMLS_DC){
zend_object_value retval;
wrapped_grpc_call *intern;
intern = (wrapped_grpc_call*)emalloc(sizeof(wrapped_grpc_call));
memset(intern, 0, sizeof(wrapped_grpc_call));
zend_object_std_init(&intern->std, class_type TSRMLS_CC);
object_properties_init(&intern->std, class_type);
retval.handle = zend_objects_store_put(
intern,
(zend_objects_store_dtor_t) zend_objects_destroy_object,
free_wrapped_grpc_call,
NULL TSRMLS_CC);
retval.handlers = zend_get_std_object_handlers();
return retval;
}
zval *grpc_php_wrap_call(grpc_call *wrapped){
zval *call_object;
MAKE_STD_ZVAL(call_object);
object_init_ex(call_object, grpc_ce_call);
wrapped_grpc_call *call = (wrapped_grpc_call*)zend_object_store_get_object(
call_object TSRMLS_CC);
call->wrapped = wrapped;
return call_object;
}
zval *grpc_call_create_metadata_array(int count, grpc_metadata *elements){
int i;
zval *array;
zval **data = NULL;
HashTable *array_hash;
zval *inner_array;
char *str_key;
char *str_val;
size_t key_len;
MAKE_STD_ZVAL(array);
array_init(array);
array_hash = Z_ARRVAL_P(array);
grpc_metadata *elem;
for(i=0; i<count; i++){
elem = &elements[i];
key_len = strlen(elem->key);
str_key = ecalloc(key_len+1, sizeof(char));
memcpy(str_key, elem->key, key_len);
str_val = ecalloc(elem->value_length+1, sizeof(char));
memcpy(str_val, elem->value, elem->value_length);
if(zend_hash_find(array_hash,
str_key,
key_len,
(void**)data) == SUCCESS){
switch(Z_TYPE_P(*data)){
case IS_STRING:
MAKE_STD_ZVAL(inner_array);
array_init(inner_array);
add_next_index_zval(inner_array, *data);
add_assoc_zval(array, str_key, inner_array);
break;
case IS_ARRAY:
inner_array = *data;
break;
default:
zend_throw_exception(zend_exception_get_default(),
"Metadata hash somehow contains wrong types.",
1 TSRMLS_CC);
efree(str_key);
efree(str_val);
return NULL;
}
add_next_index_stringl(inner_array,
str_val,
elem->value_length,
false);
} else {
add_assoc_stringl(array,
str_key,
str_val,
elem->value_length,
false);
}
}
return array;
}
int php_grpc_call_add_metadata_array_walk(void *elem TSRMLS_DC,
int num_args,
va_list args,
zend_hash_key *hash_key){
zval **data = (zval**)elem;
grpc_metadata metadata;
grpc_call *call = va_arg(args, grpc_call*);
gpr_uint32 flags = va_arg(args, gpr_uint32);
const char *key;
HashTable *inner_hash;
/* We assume that either two args were passed, and we are in the recursive
case (and the second argument is the key), or one arg was passed and
hash_key is the string key. */
if(num_args > 2){
key = va_arg(args, const char*);
} else {
/* TODO(mlumish): If possible, check that hash_key is a string */
key = hash_key->arKey;
}
switch(Z_TYPE_P(*data)){
case IS_STRING:
metadata.key = (char*)key;
metadata.value = Z_STRVAL_P(*data);
metadata.value_length = Z_STRLEN_P(*data);
grpc_call_add_metadata(call, &metadata, 0u);
break;
case IS_ARRAY:
inner_hash = Z_ARRVAL_P(*data);
zend_hash_apply_with_arguments(inner_hash TSRMLS_CC,
php_grpc_call_add_metadata_array_walk,
3,
call,
flags,
key);
break;
default:
zend_throw_exception(zend_exception_get_default(),
"Metadata hash somehow contains wrong types.",
1 TSRMLS_CC);
}
return ZEND_HASH_APPLY_KEEP;
}
/**
* Constructs a new instance of the Call class.
* @param Channel $channel The channel to associate the call with. Must not be
* closed.
* @param string $method The method to call
* @param Timeval $absolute_deadline The deadline for completing the call
*/
PHP_METHOD(Call, __construct){
wrapped_grpc_call *call = (wrapped_grpc_call*)zend_object_store_get_object(
getThis() TSRMLS_CC);
zval *channel_obj;
char *method;
int method_len;
zval *deadline_obj;
/* "OsO" == 1 Object, 1 string, 1 Object */
if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC,
"OsO",
&channel_obj, grpc_ce_channel,
&method, &method_len,
&deadline_obj, grpc_ce_timeval) == FAILURE){
zend_throw_exception(spl_ce_InvalidArgumentException,
"Call expects a Channel, a String, and a Timeval",
1 TSRMLS_CC);
return;
}
wrapped_grpc_channel *channel =
(wrapped_grpc_channel*)zend_object_store_get_object(channel_obj TSRMLS_CC);
if(channel->wrapped == NULL) {
zend_throw_exception(spl_ce_InvalidArgumentException,
"Call cannot be constructed from a closed Channel",
1 TSRMLS_CC);
return;
}
add_property_zval(getThis(), "channel", channel_obj);
wrapped_grpc_timeval *deadline =
(wrapped_grpc_timeval*)zend_object_store_get_object(deadline_obj TSRMLS_CC);
call->wrapped = grpc_channel_create_call(channel->wrapped,
method,
channel->target,
deadline->wrapped);
}
/**
* Add metadata to the call. All array keys must be strings. If the value is a
* string, it is added as a key/value pair. If it is an array, each value is
* added paired with the same string
* @param array $metadata The metadata to add
* @param long $flags A bitwise combination of the Grpc\WRITE_* constants
* (optional)
* @return Void
*/
PHP_METHOD(Call, add_metadata){
wrapped_grpc_call *call = (wrapped_grpc_call*)zend_object_store_get_object(
getThis() TSRMLS_CC);
zval *array;
HashTable *array_hash;
long flags = 0;
/* "a|l" == 1 array, 1 optional long */
if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC,
"a|l",
&array,
&flags) == FAILURE){
zend_throw_exception(spl_ce_InvalidArgumentException,
"add_metadata expects an array and an optional long",
1 TSRMLS_CC);
return;
}
array_hash = Z_ARRVAL_P(array);
zend_hash_apply_with_arguments(array_hash TSRMLS_CC,
php_grpc_call_add_metadata_array_walk,
2,
call->wrapped,
(gpr_uint32)flags);
}
/**
* Invoke the RPC. Starts sending metadata and request headers over the wire
* @param CompletionQueue $queue The completion queue to use with this call
* @param long $invoke_accepted_tag The tag to associate with this invocation
* @param long $metadata_tag The tag to associate with returned metadata
* @param long $finished_tag The tag to associate with the finished event
* @param long $flags A bitwise combination of the Grpc\WRITE_* constants
* (optional)
* @return long Error code
*/
PHP_METHOD(Call, start_invoke){
long tag1;
long tag2;
long tag3;
zval *queue_obj;
long flags = 0;
/* "Olll|l" == 1 Object, 3 mandatory longs, 1 optional long */
if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC,
"Olll|l",
&queue_obj, grpc_ce_completion_queue,
&tag1,
&tag2,
&tag3,
&flags) == FAILURE){
zend_throw_exception(
spl_ce_InvalidArgumentException,
"start_invoke needs a CompletionQueue, 3 longs, and an optional long",
1 TSRMLS_CC);
return;
}
add_property_zval(getThis(), "completion_queue", queue_obj);
wrapped_grpc_call *call = (wrapped_grpc_call*)zend_object_store_get_object(
getThis() TSRMLS_CC);
wrapped_grpc_completion_queue *queue =
(wrapped_grpc_completion_queue*)zend_object_store_get_object(
queue_obj TSRMLS_CC);
RETURN_LONG(grpc_call_start_invoke(call->wrapped,
queue->wrapped,
(void*)tag1,
(void*)tag2,
(void*)tag3,
(gpr_uint32)flags));
}
/**
* Accept an incoming RPC, binding a completion queue to it. To be called after
* adding metadata to the call, but before sending messages. Can only be called
* on the server
* @param CompletionQueue $queue The completion queue to use with this call
* @param long $finished_tag The tag to associate with the finished event
* @param long $flags A bitwise combination of the Grpc\WRITE_* constants
* (optional)
* @return long Error code
*/
PHP_METHOD(Call, accept){
long tag;
zval *queue_obj;
long flags = 0;
/* "Ol|l" == 1 Object, 1 mandatory long, 1 optional long */
if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC,
"Ol|l",
&queue_obj, grpc_ce_completion_queue,
&tag,
&flags) == FAILURE){
zend_throw_exception(
spl_ce_InvalidArgumentException,
"accept expects a CompletionQueue, a long, and an optional long",
1 TSRMLS_CC);
return;
}
add_property_zval(getThis(), "completion_queue", queue_obj);
wrapped_grpc_call *call = (wrapped_grpc_call*)zend_object_store_get_object(
getThis() TSRMLS_CC);
wrapped_grpc_completion_queue *queue =
(wrapped_grpc_completion_queue*)zend_object_store_get_object(
queue_obj TSRMLS_CC);
RETURN_LONG(grpc_call_accept(call->wrapped,
queue->wrapped,
(void*)tag,
(gpr_uint32)flags));
}
/**
* Called by clients to cancel an RPC on the server.
* @return long Error code
*/
PHP_METHOD(Call, cancel){
wrapped_grpc_call *call = (wrapped_grpc_call*)zend_object_store_get_object(
getThis() TSRMLS_CC);
RETURN_LONG(grpc_call_cancel(call->wrapped));
}
/**
* Queue a byte buffer for writing
* @param string $buffer The buffer to queue for writing
* @param long $tag The tag to associate with this write
* @param long $flags A bitwise combination of the Grpc\WRITE_* constants
* (optional)
* @return long Error code
*/
PHP_METHOD(Call, start_write){
wrapped_grpc_call *call = (wrapped_grpc_call*)zend_object_store_get_object(
getThis() TSRMLS_CC);
char *buffer;
int buffer_len;
long tag;
long flags = 0;
/* "Ol|l" == 1 Object, 1 mandatory long, 1 optional long */
if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC,
"sl|l",
&buffer, &buffer_len,
&tag,
&flags) == FAILURE){
zend_throw_exception(
spl_ce_InvalidArgumentException,
"start_write expects a string and an optional long",
1 TSRMLS_CC);
return;
}
RETURN_LONG(grpc_call_start_write(call->wrapped,
string_to_byte_buffer(buffer, buffer_len),
(void*)tag,
(gpr_uint32)flags));
}
/**
* Queue a status for writing
* @param long $status_code The status code to send
* @param string $status_details The status details to send
* @param long $tag The tag to associate with this status
* @return long Error code
*/
PHP_METHOD(Call, start_write_status){
wrapped_grpc_call *call = (wrapped_grpc_call*)zend_object_store_get_object(
getThis() TSRMLS_CC);
long status_code;
int status_details_length;
long tag;
grpc_status status;
/* "lsl" == 1 long, 1 string, 1 long */
if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC,
"lsl",
&status_code,
&status.details, &status_details_length,
&tag) == FAILURE){
zend_throw_exception(
spl_ce_InvalidArgumentException,
"start_write_status expects a long, a string, and a long",
1 TSRMLS_CC);
return;
}
status.code = (gpr_uint32)status_code;
RETURN_LONG(grpc_call_start_write_status(call->wrapped,
status,
(void*)tag));
}
/**
* Indicate that there are no more messages to send
* @return long Error code
*/
PHP_METHOD(Call, writes_done){
wrapped_grpc_call *call = (wrapped_grpc_call*)zend_object_store_get_object(
getThis() TSRMLS_CC);
long tag;
/* "l" == 1 long */
if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l", &tag) == FAILURE){
zend_throw_exception(spl_ce_InvalidArgumentException,
"writes_done expects a long",
1 TSRMLS_CC);
return;
}
RETURN_LONG(grpc_call_writes_done(call->wrapped, (void*)tag));
}
/**
* Initiate a read on a call. Output event contains a byte buffer with the
* result of the read
* @param long $tag The tag to associate with this read
* @return long Error code
*/
PHP_METHOD(Call, start_read){
wrapped_grpc_call *call = (wrapped_grpc_call*)zend_object_store_get_object(
getThis() TSRMLS_CC);
long tag;
/* "l" == 1 long */
if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l", &tag) == FAILURE){
zend_throw_exception(spl_ce_InvalidArgumentException,
"start_read expects a long",
1 TSRMLS_CC);
return;
}
RETURN_LONG(grpc_call_start_read(call->wrapped, (void*)tag));
}
static zend_function_entry call_methods[] = {
PHP_ME(Call, __construct, NULL, ZEND_ACC_PUBLIC|ZEND_ACC_CTOR)
PHP_ME(Call, accept, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Call, add_metadata, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Call, cancel, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Call, start_invoke, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Call, start_read, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Call, start_write, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Call, start_write_status, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Call, writes_done, NULL, ZEND_ACC_PUBLIC)
PHP_FE_END
};
void grpc_init_call(TSRMLS_D){
zend_class_entry ce;
INIT_CLASS_ENTRY(ce, "Grpc\\Call", call_methods);
ce.create_object = create_wrapped_grpc_call;
grpc_ce_call = zend_register_internal_class(&ce TSRMLS_CC);
}