diff --git a/src/php/.gitignore b/src/php/.gitignore new file mode 100755 index 0000000000000000000000000000000000000000..00fbd965dc25cb0221a1e23c2e03fc262c73d48b --- /dev/null +++ b/src/php/.gitignore @@ -0,0 +1,18 @@ +.libs/ +build/ +modules/ +autom4te.cache/ +*.lo +*.la +.deps +acinclude.m4 +aclocal.m4 +config.* +configure* +Makefile* +run-tests.php + +install-sh +libtool +missing +mkinstalldirs \ No newline at end of file diff --git a/src/php/README.md b/src/php/README.md new file mode 100755 index 0000000000000000000000000000000000000000..176bcfa0288796d8a290da10df7b07fcac17ee75 --- /dev/null +++ b/src/php/README.md @@ -0,0 +1,56 @@ +# PHP wrapper for the GRPC interfaces. + +## LAYOUT + +Directory structure is as generated by the PHP utility +[ext_skel](http://php.net/manual/en/internals2.buildsys.skeleton.php) + +## ENVIRONMENT + +To build a PHP environment that works with this extension, download and extract +PHP 5.5 (5.6 may also work), configure it, and install it: + +```bash +apt-get install libxml2 libxml2-dev +curl http://php.net/get/php-5.5.16.tar.gz +tar -xf php-5.5.16.tar.gz +cd php-5.5.16 +./configure --with-zlib=/usr --with-libxml-dir=ext/libxml --with-openssl=/usr/local/ssl +make +make install +``` + +To also download and install the patched protoc and PHP code generator: + +```bash +apt-get install -y procps +curl -sSL https://get.rvm.io | sudo bash -s stable --ruby +git clone sso://team/one-platform-grpc-team/protobuf +cd protobuf +./configure +make +make install +git clone sso://team/one-platform-grpc-team/grpc-php-protobuf-php +cd grpc-php-protobuf-php +rake pear:package version=1.0 +pear install Protobuf-1.0.tgz +``` + +## BUILDING + + 1. In ./ext/grpc, run the command `phpize` (distributed with PHP) + 2. Run `./ext/grpc/configure` + 3. In ./ext/grpc, run `make` and `sudo make install` + 4. In your php.ini file, add the line `extension=grpc.so` to load the + extension at PHP startup. + +## PHPUnit + +This repo now has PHPUnit tests, which can by run by executing +`./bin/run_tests.sh` after building. + +There is also a generated code test (`./bin/run_gen_code_test.sh`), which tests +the stub `./tests/generated_code/math.php` against a running localhost server +serving the math service. That stub is generated from +`./tests/generated_code/math.proto` with the head of the repo +`sso://team/one-platform-grpc-team/grpc-php-protobuf-php`. diff --git a/src/php/bin/run_gen_code_test.sh b/src/php/bin/run_gen_code_test.sh new file mode 100755 index 0000000000000000000000000000000000000000..ff1a618fa39caa3ed24fd086e762e39c984bd5c4 --- /dev/null +++ b/src/php/bin/run_gen_code_test.sh @@ -0,0 +1,5 @@ +# Runs the generated code test against the ruby server +cd $(dirname $0) +GRPC_TEST_HOST=localhost:7070 php -d extension_dir=../ext/grpc/modules/ \ + -d extension=grpc.so /usr/local/bin/phpunit -v --debug --strict \ + ../tests/generated_code/GeneratedCodeTest.php diff --git a/src/php/bin/run_tests.sh b/src/php/bin/run_tests.sh new file mode 100755 index 0000000000000000000000000000000000000000..cf4cc78a52e56390542ce088e07c9d8a129b1c55 --- /dev/null +++ b/src/php/bin/run_tests.sh @@ -0,0 +1,5 @@ +# Loads the local shared library, and runs all of the test cases in tests/ +# against it +cd $(dirname $0) +php -d extension_dir=../ext/grpc/modules/ -d extension=grpc.so \ + /usr/local/bin/phpunit -v --debug --strict ../tests/unit_tests diff --git a/src/php/ext/grpc/byte_buffer.c b/src/php/ext/grpc/byte_buffer.c new file mode 100755 index 0000000000000000000000000000000000000000..db018313a76f9ea3162974d461fba05637bf9bf2 --- /dev/null +++ b/src/php/ext/grpc/byte_buffer.c @@ -0,0 +1,38 @@ +#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 <string.h> + +#include "byte_buffer.h" + +#include "grpc/grpc.h" +#include "grpc/support/slice.h" + +grpc_byte_buffer *string_to_byte_buffer(char *string, size_t length) { + gpr_slice slice = gpr_slice_malloc(length); + memcpy(GPR_SLICE_START_PTR(slice), string, length); + return grpc_byte_buffer_create(&slice, 1); +} + +void byte_buffer_to_string(grpc_byte_buffer *buffer, + char **out_string, + size_t *out_length) { + size_t length = grpc_byte_buffer_length(buffer); + char *string = ecalloc(length+1, sizeof(char)); + size_t offset = 0; + grpc_byte_buffer_reader *reader = grpc_byte_buffer_reader_create(buffer); + gpr_slice next; + while(grpc_byte_buffer_reader_next(reader, &next) != 0) { + memcpy(string+offset, GPR_SLICE_START_PTR(next), GPR_SLICE_LENGTH(next)); + offset += GPR_SLICE_LENGTH(next); + } + *out_string = string; + *out_length = length; +} diff --git a/src/php/ext/grpc/byte_buffer.h b/src/php/ext/grpc/byte_buffer.h new file mode 100755 index 0000000000000000000000000000000000000000..1dd4769de24e61592ce48a241c65221579d3a0bb --- /dev/null +++ b/src/php/ext/grpc/byte_buffer.h @@ -0,0 +1,12 @@ +#ifndef NET_GRPC_PHP_GRPC_BYTE_BUFFER_H_ +#define NET_GRPC_PHP_GRPC_BYTE_BUFFER_H_ + +#include "grpc/grpc.h" + +grpc_byte_buffer *string_to_byte_buffer(char *string, size_t length); + +void byte_buffer_to_string(grpc_byte_buffer *buffer, + char **out_string, + size_t *out_length); + +#endif /* NET_GRPC_PHP_GRPC_BYTE_BUFFER_H_ */ diff --git a/src/php/ext/grpc/call.c b/src/php/ext/grpc/call.c new file mode 100755 index 0000000000000000000000000000000000000000..be7969f92772c07e5a2fe4c5b4a9b9daebecf52a --- /dev/null +++ b/src/php/ext/grpc/call.c @@ -0,0 +1,454 @@ +#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); +} diff --git a/src/php/ext/grpc/call.h b/src/php/ext/grpc/call.h new file mode 100755 index 0000000000000000000000000000000000000000..c433e6fee6c2993ca1be158b467510782afdc609 --- /dev/null +++ b/src/php/ext/grpc/call.h @@ -0,0 +1,35 @@ +#ifndef NET_GRPC_PHP_GRPC_CALL_H_ +#define NET_GRPC_PHP_GRPC_CALL_H_ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "php.h" +#include "php_ini.h" +#include "ext/standard/info.h" +#include "php_grpc.h" + +#include "grpc/grpc.h" + +/* Class entry for the Call PHP class */ +zend_class_entry *grpc_ce_call; + +/* Wrapper struct for grpc_call that can be associated with a PHP object */ +typedef struct wrapped_grpc_call { + zend_object std; + + grpc_call *wrapped; +} wrapped_grpc_call; + +/* Initializes the Call PHP class */ +void grpc_init_call(TSRMLS_D); + +/* Creates a Call object that wraps the given grpc_call struct */ +zval *grpc_php_wrap_call(grpc_call *wrapped); + +/* Creates and returns a PHP associative array of metadata from a C array of + * call metadata */ +zval *grpc_call_create_metadata_array(int count, grpc_metadata *elements); + +#endif /* NET_GRPC_PHP_GRPC_CHANNEL_H_ */ diff --git a/src/php/ext/grpc/channel.c b/src/php/ext/grpc/channel.c new file mode 100755 index 0000000000000000000000000000000000000000..c2847b99f1c7570237ba75a0275ab4663cfc31d9 --- /dev/null +++ b/src/php/ext/grpc/channel.c @@ -0,0 +1,182 @@ +#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/spl/spl_exceptions.h" +#include "php_grpc.h" + +#include "zend_exceptions.h" + +#include <stdbool.h> + +#include "grpc/grpc.h" +#include "grpc/support/log.h" +#include "grpc/grpc_security.h" + +#include "completion_queue.h" +#include "server.h" +#include "credentials.h" + +/* Frees and destroys an instance of wrapped_grpc_channel */ +void free_wrapped_grpc_channel(void *object TSRMLS_DC){ + wrapped_grpc_channel *channel = (wrapped_grpc_channel*)object; + if(channel->wrapped != NULL){ + grpc_channel_destroy(channel->wrapped); + } + efree(channel); +} + +/* Initializes an instance of wrapped_grpc_channel to be associated with an + * object of a class specified by class_type */ +zend_object_value create_wrapped_grpc_channel( + zend_class_entry *class_type TSRMLS_DC){ + zend_object_value retval; + wrapped_grpc_channel *intern; + intern = (wrapped_grpc_channel*)emalloc(sizeof(wrapped_grpc_channel)); + memset(intern, 0, sizeof(wrapped_grpc_channel)); + 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_channel, + NULL TSRMLS_CC); + retval.handlers = zend_get_std_object_handlers(); + return retval; +} + +void php_grpc_read_args_array(zval *args_array, grpc_channel_args *args){ + HashTable *array_hash; + HashPosition array_pointer; + int args_index; + zval **data; + char *key; + uint key_len; + ulong index; + array_hash = Z_ARRVAL_P(args_array); + args->num_args = zend_hash_num_elements(array_hash); + args->args = ecalloc(args->num_args, sizeof(grpc_arg)); + args_index = 0; + for(zend_hash_internal_pointer_reset_ex(array_hash, &array_pointer); + zend_hash_get_current_data_ex(array_hash, + (void**)&data, + &array_pointer) == SUCCESS; + zend_hash_move_forward_ex(array_hash, &array_pointer)){ + if(zend_hash_get_current_key_ex(array_hash, + &key, + &key_len, + &index, + 0, + &array_pointer) != HASH_KEY_IS_STRING){ + zend_throw_exception(spl_ce_InvalidArgumentException, + "args keys must be strings", + 1 TSRMLS_CC); + return; + } + 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); + break; + case IS_STRING: + args->args[args_index].value.string = Z_STRVAL_P(*data); + break; + default: + zend_throw_exception(spl_ce_InvalidArgumentException, + "args values must be int or string", + 1 TSRMLS_CC); + return; + } + args_index++; + } +} + +/** + * Construct an instance of the Channel class. If the $args array contains a + * "credentials" key mapping to a Credentials object, a secure channel will be + * created with those credentials. + * @param string $target The hostname to associate with this channel + * @param array $args The arguments to pass to the Channel (optional) + */ +PHP_METHOD(Channel, __construct){ + wrapped_grpc_channel *channel = + (wrapped_grpc_channel*)zend_object_store_get_object(getThis() TSRMLS_CC); + char *target; + int target_length; + zval *args_array = NULL; + grpc_channel_args args; + HashTable *array_hash; + zval **creds_obj = NULL; + wrapped_grpc_credentials *creds = NULL; + /* "s|a" == 1 string, 1 optional array */ + if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, + "s|a", + &target, &target_length, + &args_array) == FAILURE){ + zend_throw_exception(spl_ce_InvalidArgumentException, + "Channel expects a string and an array", + 1 TSRMLS_CC); + return; + } + if (args_array == NULL) { + channel->wrapped = grpc_channel_create(target, NULL); + } else { + array_hash = Z_ARRVAL_P(args_array); + if(zend_hash_find(array_hash, + "credentials", + sizeof("credentials"), + (void**)&creds_obj) == SUCCESS) { + if(zend_get_class_entry(*creds_obj TSRMLS_CC) != grpc_ce_credentials) { + zend_throw_exception(spl_ce_InvalidArgumentException, + "credentials must be a Credentials object", + 1 TSRMLS_CC); + return; + } + creds = (wrapped_grpc_credentials*)zend_object_store_get_object( + *creds_obj TSRMLS_CC); + zend_hash_del(array_hash, "credentials", 12); + } + php_grpc_read_args_array(args_array, &args); + if (creds == NULL) { + channel->wrapped = grpc_channel_create(target, &args); + } else { + gpr_log(GPR_DEBUG, "Initialized secure channel"); + channel->wrapped = grpc_secure_channel_create(creds->wrapped, + target, + &args); + } + efree(args.args); + } + channel->target = ecalloc(target_length+1, sizeof(char)); + memcpy(channel->target, target, target_length); +} + +/** + * Close the channel + */ +PHP_METHOD(Channel, close){ + wrapped_grpc_channel *channel = + (wrapped_grpc_channel*)zend_object_store_get_object(getThis() TSRMLS_CC); + if(channel->wrapped != NULL) { + grpc_channel_destroy(channel->wrapped); + channel->wrapped = NULL; + } +} + +static zend_function_entry channel_methods[] = { + PHP_ME(Channel, __construct, NULL, ZEND_ACC_PUBLIC|ZEND_ACC_CTOR) + PHP_ME(Channel, close, NULL, ZEND_ACC_PUBLIC) + PHP_FE_END +}; + +void grpc_init_channel(TSRMLS_D){ + 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); +} diff --git a/src/php/ext/grpc/channel.h b/src/php/ext/grpc/channel.h new file mode 100755 index 0000000000000000000000000000000000000000..e36f13022561e746d87f2d7259e920d5b88ecd0e --- /dev/null +++ b/src/php/ext/grpc/channel.h @@ -0,0 +1,32 @@ +#ifndef NET_GRPC_PHP_GRPC_CHANNEL_H_ +#define NET_GRPC_PHP_GRPC_CHANNEL_H_ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "php.h" +#include "php_ini.h" +#include "ext/standard/info.h" +#include "php_grpc.h" + +#include "grpc/grpc.h" + +/* Class entry for the PHP Channel class */ +zend_class_entry *grpc_ce_channel; + +/* Wrapper struct for grpc_channel that can be associated with a PHP object */ +typedef struct wrapped_grpc_channel { + zend_object std; + + grpc_channel *wrapped; + char *target; +} wrapped_grpc_channel; + +/* Initializes the Channel class */ +void grpc_init_channel(TSRMLS_D); + +/* Iterates through a PHP array and populates args with the contents */ +void php_grpc_read_args_array(zval *args_array, grpc_channel_args *args); + +#endif /* NET_GRPC_PHP_GRPC_CHANNEL_H_ */ diff --git a/src/php/ext/grpc/completion_queue.c b/src/php/ext/grpc/completion_queue.c new file mode 100755 index 0000000000000000000000000000000000000000..5b7bcfa976ad67044178819b258baabedececb97 --- /dev/null +++ b/src/php/ext/grpc/completion_queue.c @@ -0,0 +1,145 @@ +#include "completion_queue.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 <stdbool.h> + +#include "grpc/grpc.h" + +#include "event.h" +#include "timeval.h" + +/* Frees and destroys a wrapped instance of grpc_completion_queue */ +void free_wrapped_grpc_completion_queue(void *object TSRMLS_DC){ + wrapped_grpc_completion_queue *queue = NULL; + grpc_event *event; + queue = (wrapped_grpc_completion_queue*)object; + if(queue->wrapped != NULL){ + grpc_completion_queue_shutdown(queue->wrapped); + event = grpc_completion_queue_next(queue->wrapped, gpr_inf_future); + while(event != NULL){ + if(event->type == GRPC_QUEUE_SHUTDOWN){ + break; + } + event = grpc_completion_queue_next(queue->wrapped, gpr_inf_future); + } + grpc_completion_queue_destroy(queue->wrapped); + } + efree(queue); +} + +/* Initializes an instance of wrapped_grpc_channel to be associated with an + * object of a class specified by class_type */ +zend_object_value create_wrapped_grpc_completion_queue( + zend_class_entry *class_type TSRMLS_DC){ + zend_object_value retval; + wrapped_grpc_completion_queue *intern; + + intern = (wrapped_grpc_completion_queue*)emalloc( + sizeof(wrapped_grpc_completion_queue)); + memset(intern, 0, sizeof(wrapped_grpc_completion_queue)); + + 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_completion_queue, + NULL TSRMLS_CC); + retval.handlers = zend_get_std_object_handlers(); + return retval; +} + +/** + * Construct an instance of CompletionQueue + */ +PHP_METHOD(CompletionQueue, __construct){ + wrapped_grpc_completion_queue *queue = + (wrapped_grpc_completion_queue*)zend_object_store_get_object( + getThis() TSRMLS_CC); + queue->wrapped = grpc_completion_queue_create(); +} + +/** + * Blocks until an event is available, the completion queue is being shutdown, + * or timeout is reached. Returns NULL on timeout, otherwise the event that + * occurred. Callers should call event.finish once they have processed the + * event. + * @param Timeval $timeout The timeout for the event + * @return Event The event that occurred + */ +PHP_METHOD(CompletionQueue, next){ + zval *timeout; + /* "O" == 1 Object */ + if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, + "O", + &timeout, grpc_ce_timeval)==FAILURE){ + zend_throw_exception(spl_ce_InvalidArgumentException, + "next needs a Timeval", + 1 TSRMLS_CC); + return; + } + wrapped_grpc_completion_queue *completion_queue = + (wrapped_grpc_completion_queue*)zend_object_store_get_object( + getThis() TSRMLS_CC); + wrapped_grpc_timeval *wrapped_timeout = + (wrapped_grpc_timeval*)zend_object_store_get_object(timeout TSRMLS_CC); + grpc_event *event = grpc_completion_queue_next(completion_queue->wrapped, + wrapped_timeout->wrapped); + if(event == NULL){ + RETURN_NULL(); + } + zval *wrapped_event = grpc_php_wrap_event(event); + RETURN_DESTROY_ZVAL(wrapped_event); +} + +PHP_METHOD(CompletionQueue, pluck){ + long tag; + zval *timeout; + /* "lO" == 1 long, 1 Object */ + if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, + "lO", + &tag, + &timeout, grpc_ce_timeval)==FAILURE){ + zend_throw_exception(spl_ce_InvalidArgumentException, + "pluck needs a long and a Timeval", + 1 TSRMLS_CC); + } + wrapped_grpc_completion_queue *completion_queue = + (wrapped_grpc_completion_queue*)zend_object_store_get_object( + getThis() TSRMLS_CC); + wrapped_grpc_timeval *wrapped_timeout = + (wrapped_grpc_timeval*)zend_object_store_get_object(timeout TSRMLS_CC); + grpc_event *event = grpc_completion_queue_pluck(completion_queue->wrapped, + (void*)tag, + wrapped_timeout->wrapped); + if(event == NULL){ + RETURN_NULL(); + } + zval *wrapped_event = grpc_php_wrap_event(event); + RETURN_DESTROY_ZVAL(wrapped_event); +} + +static zend_function_entry completion_queue_methods[] = { + PHP_ME(CompletionQueue, __construct, NULL, ZEND_ACC_PUBLIC|ZEND_ACC_CTOR) + PHP_ME(CompletionQueue, next, NULL, ZEND_ACC_PUBLIC) + PHP_ME(CompletionQueue, pluck, NULL, ZEND_ACC_PUBLIC) + PHP_FE_END +}; + +void grpc_init_completion_queue(TSRMLS_D){ + zend_class_entry ce; + INIT_CLASS_ENTRY(ce, "Grpc\\CompletionQueue", completion_queue_methods); + ce.create_object = create_wrapped_grpc_completion_queue; + grpc_ce_completion_queue = zend_register_internal_class(&ce TSRMLS_CC); +} diff --git a/src/php/ext/grpc/completion_queue.h b/src/php/ext/grpc/completion_queue.h new file mode 100755 index 0000000000000000000000000000000000000000..6bf5b1612454c345079fa6dd4f926006e522cc16 --- /dev/null +++ b/src/php/ext/grpc/completion_queue.h @@ -0,0 +1,29 @@ +#ifndef NET_GRPC_PHP_GRPC_COMPLETION_QUEUE_H_ +#define NET_GRPC_PHP_GRPC_COMPLETION_QUEUE_H_ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "php.h" +#include "php_ini.h" +#include "ext/standard/info.h" +#include "php_grpc.h" + +#include "grpc/grpc.h" + +/* Class entry for the PHP CompletionQueue class */ +zend_class_entry *grpc_ce_completion_queue; + +/* Wrapper class for grpc_completion_queue that can be associated with a + PHP object */ +typedef struct wrapped_grpc_completion_queue { + zend_object std; + + grpc_completion_queue *wrapped; +} wrapped_grpc_completion_queue; + +/* Initialize the CompletionQueue class */ +void grpc_init_completion_queue(TSRMLS_D); + +#endif /* NET_GRPC_PHP_GRPC_COMPLETION_QUEUE_H_ */ diff --git a/src/php/ext/grpc/config.m4 b/src/php/ext/grpc/config.m4 new file mode 100755 index 0000000000000000000000000000000000000000..2d0db1a3e0f1a24a6b047dcb9e0d156f650c4bb1 --- /dev/null +++ b/src/php/ext/grpc/config.m4 @@ -0,0 +1,74 @@ +PHP_ARG_ENABLE(grpc, whether to enable grpc support, +[ --enable-grpc Enable grpc support]) + +if test "$PHP_GRPC" != "no"; then + dnl Write more examples of tests here... + + dnl # --with-grpc -> check with-path + SEARCH_PATH="/usr/local /usr $HOME/grpc_dev" # you might want to change this + SEARCH_FOR="include/grpc/grpc.h" # you most likely want to change this + if test -r $PHP_GRPC/$SEARCH_FOR; then # path given as parameter + GRPC_DIR=$PHP_GRPC + else # search default path list + AC_MSG_CHECKING([for grpc files in default path]) + for i in $SEARCH_PATH ; do + if test -r $i/$SEARCH_FOR; then + GRPC_DIR=$i + AC_MSG_RESULT(found in $i) + fi + done + fi + if test -z "$GRPC_DIR"; then + AC_MSG_RESULT([not found]) + AC_MSG_ERROR([Please reinstall the grpc distribution]) + fi + + dnl # --with-grpc -> add include path + PHP_ADD_INCLUDE($GRPC_DIR/include) + + LIBS="-lpthread $LIBS" + + dnl PHP_ADD_LIBRARY(pthread,,GRPC_SHARED_LIBADD) + GRPC_SHARED_LIBADD="-lpthread $GRPC_SHARED_LIBADD" + PHP_ADD_LIBRARY(pthread) + + PHP_ADD_LIBRARY(rt,,GRPC_SHARED_LIBADD) + PHP_ADD_LIBRARY(rt) + + PHP_ADD_LIBPATH($GRPC_DIR/lib) + + PHP_CHECK_LIBRARY(gpr,gpr_now, + [ + PHP_ADD_LIBRARY(gpr,,GRPC_SHARED_LIBADD) + PHP_ADD_LIBRARY(gpr) + AC_DEFINE(HAVE_GPRLIB,1,[ ]) + ],[ + AC_MSG_ERROR([wrong gpr lib version or lib not found]) + ],[ + -L$GRPC_DIR/lib + ]) + + PHP_ADD_LIBRARY(event,,GRPC_SHARED_LIBADD) + PHP_ADD_LIBRARY(event) + + PHP_ADD_LIBRARY(event_pthreads,,GRPC_SHARED_LIBADD) + PHP_ADD_LIBRARY(event_pthreads) + + PHP_ADD_LIBRARY(event_core,,GRPC_SHARED_LIBADD) + PHP_ADD_LIBRARY(event_core) + + PHP_CHECK_LIBRARY(grpc,grpc_channel_destroy, + [ + PHP_ADD_LIBRARY(grpc,,GRPC_SHARED_LIBADD) + dnl PHP_ADD_LIBRARY_WITH_PATH(grpc, $GRPC_DIR/lib, GRPC_SHARED_LIBADD) + AC_DEFINE(HAVE_GRPCLIB,1,[ ]) + ],[ + AC_MSG_ERROR([wrong grpc lib version or lib not found]) + ],[ + -L$GRPC_DIR/lib + ]) + + PHP_SUBST(GRPC_SHARED_LIBADD) + + PHP_NEW_EXTENSION(grpc, byte_buffer.c call.c channel.c completion_queue.c credentials.c event.c timeval.c server.c server_credentials.c php_grpc.c, $ext_shared, , -Wall -Werror -pedantic -std=c99) +fi diff --git a/src/php/ext/grpc/credentials.c b/src/php/ext/grpc/credentials.c new file mode 100755 index 0000000000000000000000000000000000000000..ffafddae5f26045b783b51795e51c2c60c1e483c --- /dev/null +++ b/src/php/ext/grpc/credentials.c @@ -0,0 +1,171 @@ +#include "credentials.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 "grpc/grpc.h" +#include "grpc/grpc_security.h" + +/* Frees and destroys an instance of wrapped_grpc_credentials */ +void free_wrapped_grpc_credentials(void *object TSRMLS_DC){ + wrapped_grpc_credentials *creds = (wrapped_grpc_credentials*)object; + if(creds->wrapped != NULL) { + grpc_credentials_release(creds->wrapped); + } + efree(creds); +} + +/* Initializes an instance of wrapped_grpc_credentials to be associated with an + * object of a class specified by class_type */ +zend_object_value create_wrapped_grpc_credentials( + zend_class_entry *class_type TSRMLS_DC){ + zend_object_value retval; + wrapped_grpc_credentials *intern; + + intern = (wrapped_grpc_credentials*)emalloc(sizeof(wrapped_grpc_credentials)); + memset(intern, 0, sizeof(wrapped_grpc_credentials)); + + 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_credentials, + NULL TSRMLS_CC); + retval.handlers = zend_get_std_object_handlers(); + return retval; +} + +zval *grpc_php_wrap_credentials(grpc_credentials *wrapped){ + zval *credentials_object; + MAKE_STD_ZVAL(credentials_object); + object_init_ex(credentials_object, grpc_ce_credentials); + wrapped_grpc_credentials *credentials = + (wrapped_grpc_credentials*)zend_object_store_get_object( + credentials_object TSRMLS_CC); + credentials->wrapped = wrapped; + return credentials_object; +} + +/** + * Create a default credentials object. + * @return Credentials The new default credentials object + */ +PHP_METHOD(Credentials, createDefault){ + grpc_credentials *creds = grpc_default_credentials_create(); + zval *creds_object = grpc_php_wrap_credentials(creds); + RETURN_DESTROY_ZVAL(creds_object); +} + +/** + * Create SSL credentials. + * @param string pem_root_certs PEM encoding of the server root certificates + * @param string pem_private_key PEM encoding of the client's private key + * (optional) + * @param string pem_cert_chain PEM encoding of the client's certificate chain + * (optional) + * @return Credentials The new SSL credentials object + */ +PHP_METHOD(Credentials, createSsl){ + char *pem_root_certs; + char *pem_private_key = NULL; + char *pem_cert_chain = NULL; + + int root_certs_length, private_key_length = 0, cert_chain_length = 0; + + /* "s|s!s! == 1 string, 2 optional nullable strings */ + if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, + "s|s!s!", + &pem_root_certs, &root_certs_length, + &pem_private_key, &private_key_length, + &pem_cert_chain, &cert_chain_length) == FAILURE) { + zend_throw_exception(spl_ce_InvalidArgumentException, + "createSsl expects 1 to 3 strings", + 1 TSRMLS_CC); + return; + } + grpc_credentials *creds = grpc_ssl_credentials_create( + (unsigned char*)pem_root_certs, (size_t)root_certs_length, + (unsigned char*)pem_private_key, (size_t)private_key_length, + (unsigned char*)pem_cert_chain, (size_t)cert_chain_length); + zval *creds_object = grpc_php_wrap_credentials(creds); + RETURN_DESTROY_ZVAL(creds_object); +} + +/** + * Create composite credentials from two existing credentials. + * @param Credentials cred1 The first credential + * @param Credentials cred2 The second credential + * @return Credentials The new composite credentials object + */ +PHP_METHOD(Credentials, createComposite){ + zval *cred1_obj; + zval *cred2_obj; + + /* "OO" == 3 Objects */ + if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, + "OO", + &cred1_obj, grpc_ce_credentials, + &cred2_obj, grpc_ce_credentials) == FAILURE) { + zend_throw_exception(spl_ce_InvalidArgumentException, + "createComposite expects 2 Credentials", + 1 TSRMLS_CC); + return; + } + wrapped_grpc_credentials *cred1 = + (wrapped_grpc_credentials*)zend_object_store_get_object( + cred1_obj TSRMLS_CC); + wrapped_grpc_credentials *cred2 = + (wrapped_grpc_credentials*)zend_object_store_get_object( + cred2_obj TSRMLS_CC); + grpc_credentials *creds = grpc_composite_credentials_create(cred1->wrapped, + cred2->wrapped); + zval *creds_object = grpc_php_wrap_credentials(creds); + RETURN_DESTROY_ZVAL(creds_object); +} + +/** + * Create Google Compute Engine credentials + * @return Credentials The new GCE credentials object + */ +PHP_METHOD(Credentials, createGce) { + grpc_credentials *creds = grpc_compute_engine_credentials_create(); + zval *creds_object = grpc_php_wrap_credentials(creds); + RETURN_DESTROY_ZVAL(creds_object); +} + +/** + * Create fake credentials. Only to be used for testing. + * @return Credentials The new fake credentials object + */ +PHP_METHOD(Credentials, createFake) { + grpc_credentials *creds = grpc_fake_transport_security_credentials_create(); + zval *creds_object = grpc_php_wrap_credentials(creds); + RETURN_DESTROY_ZVAL(creds_object); +} + +static zend_function_entry credentials_methods[] = { + PHP_ME(Credentials, createDefault, NULL, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC) + PHP_ME(Credentials, createSsl, NULL, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC) + PHP_ME(Credentials, createComposite, NULL, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC) + PHP_ME(Credentials, createGce, NULL, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC) + PHP_ME(Credentials, createFake, NULL, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC) + PHP_FE_END +}; + +void grpc_init_credentials(TSRMLS_D){ + zend_class_entry ce; + INIT_CLASS_ENTRY(ce, "Grpc\\Credentials", credentials_methods); + ce.create_object = create_wrapped_grpc_credentials; + grpc_ce_credentials = zend_register_internal_class(&ce TSRMLS_CC); +} diff --git a/src/php/ext/grpc/credentials.h b/src/php/ext/grpc/credentials.h new file mode 100755 index 0000000000000000000000000000000000000000..ba2aa898e724464bd4b805f7145a8cec27d7a615 --- /dev/null +++ b/src/php/ext/grpc/credentials.h @@ -0,0 +1,30 @@ +#ifndef NET_GRPC_PHP_GRPC_CREDENTIALS_H_ +#define NET_GRPC_PHP_GRPC_CREDENTIALS_H_ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "php.h" +#include "php_ini.h" +#include "ext/standard/info.h" +#include "php_grpc.h" + +#include "grpc/grpc.h" +#include "grpc/grpc_security.h" + +/* Class entry for the Credentials PHP class */ +zend_class_entry *grpc_ce_credentials; + +/* Wrapper struct for grpc_credentials that can be associated with a PHP + * object */ +typedef struct wrapped_grpc_credentials { + zend_object std; + + grpc_credentials *wrapped; +} wrapped_grpc_credentials; + +/* Initializes the Credentials PHP class */ +void grpc_init_credentials(TSRMLS_D); + +#endif /* NET_GRPC_PHP_GRPC_CREDENTIALS_H_ */ diff --git a/src/php/ext/grpc/event.c b/src/php/ext/grpc/event.c new file mode 100755 index 0000000000000000000000000000000000000000..8ec29c915c9485d8b1e7b3abed3a156bfad00b3c --- /dev/null +++ b/src/php/ext/grpc/event.c @@ -0,0 +1,191 @@ +#include "event.h" + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "php.h" +#include "php_ini.h" +#include "ext/standard/info.h" +#include "php_grpc.h" + +#include <stdbool.h> + +#include "grpc/grpc.h" + +#include "byte_buffer.h" +#include "call.h" +#include "timeval.h" + +/* Frees and finishes a wrapped instance of grpc_event */ +void free_wrapped_grpc_event(void *object TSRMLS_DC){ + wrapped_grpc_event *event = (wrapped_grpc_event*)object; + if(event->wrapped != NULL){ + grpc_event_finish(event->wrapped); + } + efree(event); +} + +/* Initializes an instance of wrapped_grpc_channel to be associated with an + * object of a class specified by class_type */ +zend_object_value create_wrapped_grpc_event( + zend_class_entry *class_type TSRMLS_DC){ + zend_object_value retval; + wrapped_grpc_event *intern; + intern = (wrapped_grpc_event*)emalloc(sizeof(wrapped_grpc_event)); + memset(intern, 0, sizeof(wrapped_grpc_event)); + 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_event, + NULL TSRMLS_CC); + retval.handlers = zend_get_std_object_handlers(); + return retval; +} + +zval *grpc_php_wrap_event(grpc_event *wrapped){ + zval *event_object; + MAKE_STD_ZVAL(event_object); + + object_init_ex(event_object, grpc_ce_event); + wrapped_grpc_event *event = (wrapped_grpc_event*)zend_object_store_get_object( + event_object TSRMLS_CC); + event->wrapped = wrapped; + return event_object; +} + +/** + * Get the type of the event + * @return long Integer representing the type + */ +PHP_METHOD(Event, get_type){ + wrapped_grpc_event *event = (wrapped_grpc_event*)zend_object_store_get_object( + getThis() TSRMLS_CC); + RETURN_LONG((long)(event->wrapped->type)); +} + +/** + * Get the tag of the event + * @return long The event's tag + */ +PHP_METHOD(Event, get_tag){ + wrapped_grpc_event *event = (wrapped_grpc_event*)zend_object_store_get_object( + getThis() TSRMLS_CC); + RETURN_LONG((long)(event->wrapped->tag)); +} + +/** + * Get the call associated with the event + * @return Call The call + */ +PHP_METHOD(Event, get_call){ + wrapped_grpc_event *event = (wrapped_grpc_event*)zend_object_store_get_object( + getThis() TSRMLS_CC); + zval *call_obj = grpc_php_wrap_call(event->wrapped->call); + RETURN_DESTROY_ZVAL(call_obj); +} + +/** + * Get the data associated with the event + * @return object The data, with type depending on the type field + */ +PHP_METHOD(Event, get_data){ + zval *retval; + wrapped_grpc_event *wrapped_event = + (wrapped_grpc_event*)zend_object_store_get_object( + getThis() TSRMLS_CC); + grpc_event *event = wrapped_event->wrapped; + char *detail_string; + size_t detail_len; + char *method_string; + size_t method_len; + char *host_string; + size_t host_len; + char *read_string; + size_t read_len; + + switch(event->type){ + case GRPC_QUEUE_SHUTDOWN: RETURN_NULL(); break; + case GRPC_READ: + if(event->data.read == NULL){ + RETURN_NULL(); + } else { + byte_buffer_to_string(event->data.read, &read_string, &read_len); + RETURN_STRINGL(read_string, read_len, true); + } + break; + case GRPC_INVOKE_ACCEPTED: + RETURN_LONG((long)event->data.invoke_accepted); break; + case GRPC_WRITE_ACCEPTED: + RETURN_LONG((long)event->data.write_accepted); break; + case GRPC_FINISH_ACCEPTED: + RETURN_LONG((long)event->data.finish_accepted); break; + case GRPC_CLIENT_METADATA_READ: + retval = grpc_call_create_metadata_array( + event->data.client_metadata_read.count, + event->data.client_metadata_read.elements); + break; + case GRPC_FINISHED: + MAKE_STD_ZVAL(retval); + object_init(retval); + add_property_long(retval, "code", event->data.finished.code); + if(event->data.finished.details == NULL){ + add_property_null(retval, "details"); + } else { + detail_len = strlen(event->data.finished.details); + detail_string = ecalloc(detail_len+1, sizeof(char)); + memcpy(detail_string, event->data.finished.details, detail_len); + add_property_string(retval, + "details", + detail_string, + true); + } + break; + case GRPC_SERVER_RPC_NEW: + MAKE_STD_ZVAL(retval); + object_init(retval); + method_len = strlen(event->data.server_rpc_new.method); + method_string = ecalloc(method_len+1, sizeof(char)); + memcpy(method_string, event->data.server_rpc_new.method, method_len); + add_property_string(retval, + "method", + method_string, + false); + host_len = strlen(event->data.server_rpc_new.host); + host_string = ecalloc(host_len+1, sizeof(char)); + memcpy(host_string, event->data.server_rpc_new.host, host_len); + add_property_string(retval, + "host", + host_string, + false); + add_property_zval(retval, + "absolute_timeout", + grpc_php_wrap_timeval( + event->data.server_rpc_new.deadline)); + add_property_zval(retval, + "metadata", + grpc_call_create_metadata_array( + event->data.server_rpc_new.metadata_count, + event->data.server_rpc_new.metadata_elements)); + break; + default: RETURN_NULL(); break; + } + RETURN_DESTROY_ZVAL(retval); +} + +static zend_function_entry event_methods[] = { + PHP_ME(Event, get_call, NULL, ZEND_ACC_PUBLIC) + PHP_ME(Event, get_data, NULL, ZEND_ACC_PUBLIC) + PHP_ME(Event, get_tag, NULL, ZEND_ACC_PUBLIC) + PHP_ME(Event, get_type, NULL, ZEND_ACC_PUBLIC) + PHP_FE_END +}; + +void grpc_init_event(TSRMLS_D){ + zend_class_entry ce; + INIT_CLASS_ENTRY(ce, "Grpc\\Event", event_methods); + ce.create_object = create_wrapped_grpc_event; + grpc_ce_event = zend_register_internal_class(&ce TSRMLS_CC); +} diff --git a/src/php/ext/grpc/event.h b/src/php/ext/grpc/event.h new file mode 100755 index 0000000000000000000000000000000000000000..9dc164e24911d0d9dab6da762b15d55ab2ce433b --- /dev/null +++ b/src/php/ext/grpc/event.h @@ -0,0 +1,31 @@ +#ifndef NET_GRPC_PHP_GRPC_EVENT_H_ +#define NET_GRPC_PHP_GRPC_EVENT_H_ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "php.h" +#include "php_ini.h" +#include "ext/standard/info.h" +#include "php_grpc.h" + +#include "grpc/grpc.h" + +/* Class entry for the PHP Event class */ +zend_class_entry *grpc_ce_event; + +/* Struct wrapping grpc_event that can be associated with a PHP object */ +typedef struct wrapped_grpc_event { + zend_object std; + + grpc_event *wrapped; +} wrapped_grpc_event; + +/* Initialize the Event class */ +void grpc_init_event(TSRMLS_D); + +/* Create a new Event object that wraps an existing grpc_event struct */ +zval *grpc_php_wrap_event(grpc_event *wrapped); + +#endif /* NET_GRPC_PHP_GRPC_COMPLETION_CHANNEL_H */ diff --git a/src/php/ext/grpc/php_grpc.c b/src/php/ext/grpc/php_grpc.c new file mode 100755 index 0000000000000000000000000000000000000000..c49b84542c6f555270a17faa474b0573f5cedff0 --- /dev/null +++ b/src/php/ext/grpc/php_grpc.c @@ -0,0 +1,247 @@ +#include "call.h" +#include "channel.h" +#include "server.h" +#include "completion_queue.h" +#include "event.h" +#include "timeval.h" +#include "credentials.h" +#include "server_credentials.h" + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "php.h" +#include "php_ini.h" +#include "ext/standard/info.h" +#include "php_grpc.h" + +//ZEND_DECLARE_MODULE_GLOBALS(grpc) + +/* {{{ grpc_functions[] + * + * Every user visible function must have an entry in grpc_functions[]. + */ +const zend_function_entry grpc_functions[] = { + PHP_FE_END /* Must be the last line in grpc_functions[] */ +}; +/* }}} */ + +/* {{{ grpc_module_entry + */ +zend_module_entry grpc_module_entry = { +#if ZEND_MODULE_API_NO >= 20010901 + STANDARD_MODULE_HEADER, +#endif + "grpc", + grpc_functions, + PHP_MINIT(grpc), + PHP_MSHUTDOWN(grpc), + NULL, + NULL, + PHP_MINFO(grpc), +#if ZEND_MODULE_API_NO >= 20010901 + PHP_GRPC_VERSION, +#endif + STANDARD_MODULE_PROPERTIES +}; +/* }}} */ + +#ifdef COMPILE_DL_GRPC +ZEND_GET_MODULE(grpc) +#endif + +/* {{{ PHP_INI + */ +/* Remove comments and fill if you need to have entries in php.ini +PHP_INI_BEGIN() + STD_PHP_INI_ENTRY("grpc.global_value", "42", PHP_INI_ALL, OnUpdateLong, global_value, zend_grpc_globals, grpc_globals) + STD_PHP_INI_ENTRY("grpc.global_string", "foobar", PHP_INI_ALL, OnUpdateString, global_string, zend_grpc_globals, grpc_globals) +PHP_INI_END() +*/ +/* }}} */ + +/* {{{ php_grpc_init_globals + */ +/* Uncomment this function if you have INI entries +static void php_grpc_init_globals(zend_grpc_globals *grpc_globals) +{ + grpc_globals->global_value = 0; + grpc_globals->global_string = NULL; +} +*/ +/* }}} */ + +/* {{{ PHP_MINIT_FUNCTION + */ +PHP_MINIT_FUNCTION(grpc) +{ + /* If you have INI entries, uncomment these lines + REGISTER_INI_ENTRIES(); + */ + /* Register call error constants */ + grpc_init(); + REGISTER_LONG_CONSTANT("Grpc\\CALL_OK", GRPC_CALL_OK, CONST_CS); + REGISTER_LONG_CONSTANT("Grpc\\CALL_ERROR", GRPC_CALL_ERROR, CONST_CS); + REGISTER_LONG_CONSTANT("Grpc\\CALL_ERROR_NOT_ON_SERVER", + GRPC_CALL_ERROR_NOT_ON_SERVER, + CONST_CS); + REGISTER_LONG_CONSTANT("Grpc\\CALL_ERROR_NOT_ON_CLIENT", + GRPC_CALL_ERROR_NOT_ON_CLIENT, + CONST_CS); + REGISTER_LONG_CONSTANT("Grpc\\CALL_ERROR_ALREADY_INVOKED", + GRPC_CALL_ERROR_ALREADY_INVOKED, + CONST_CS); + REGISTER_LONG_CONSTANT("Grpc\\CALL_ERROR_NOT_INVOKED", + GRPC_CALL_ERROR_NOT_INVOKED, + CONST_CS); + REGISTER_LONG_CONSTANT("Grpc\\CALL_ERROR_ALREADY_FINISHED", + GRPC_CALL_ERROR_ALREADY_FINISHED, + CONST_CS); + REGISTER_LONG_CONSTANT("Grpc\\CALL_ERROR_TOO_MANY_OPERATIONS", + GRPC_CALL_ERROR_TOO_MANY_OPERATIONS, + CONST_CS); + REGISTER_LONG_CONSTANT("Grpc\\CALL_ERROR_INVALID_FLAGS", + GRPC_CALL_ERROR_INVALID_FLAGS, + CONST_CS); + + /* Register op error constants */ + REGISTER_LONG_CONSTANT("Grpc\\OP_OK", GRPC_OP_OK, CONST_CS); + REGISTER_LONG_CONSTANT("Grpc\\OP_ERROR", GRPC_OP_ERROR, CONST_CS); + + /* Register flag constants */ + REGISTER_LONG_CONSTANT("Grpc\\WRITE_BUFFER_HINT", + GRPC_WRITE_BUFFER_HINT, + CONST_CS); + REGISTER_LONG_CONSTANT("Grpc\\WRITE_NO_COMPRESS", + GRPC_WRITE_NO_COMPRESS, + CONST_CS); + + /* Register completion type constants */ + REGISTER_LONG_CONSTANT("Grpc\\QUEUE_SHUTDOWN", + GRPC_QUEUE_SHUTDOWN, + CONST_CS); + REGISTER_LONG_CONSTANT("Grpc\\READ", GRPC_READ, CONST_CS); + REGISTER_LONG_CONSTANT("Grpc\\INVOKE_ACCEPTED", + GRPC_INVOKE_ACCEPTED, + CONST_CS); + REGISTER_LONG_CONSTANT("Grpc\\WRITE_ACCEPTED", + GRPC_WRITE_ACCEPTED, + CONST_CS); + REGISTER_LONG_CONSTANT("Grpc\\FINISH_ACCEPTED", + GRPC_FINISH_ACCEPTED, + CONST_CS); + REGISTER_LONG_CONSTANT("Grpc\\CLIENT_METADATA_READ", + GRPC_CLIENT_METADATA_READ, + CONST_CS); + REGISTER_LONG_CONSTANT("Grpc\\FINISHED", GRPC_FINISHED, CONST_CS); + REGISTER_LONG_CONSTANT("Grpc\\SERVER_RPC_NEW", + GRPC_SERVER_RPC_NEW, + CONST_CS); + + /* Register status constants */ + REGISTER_LONG_CONSTANT("Grpc\\STATUS_OK", + GRPC_STATUS_OK, + CONST_CS); + REGISTER_LONG_CONSTANT("Grpc\\STATUS_CANCELLED", + GRPC_STATUS_CANCELLED, + CONST_CS); + REGISTER_LONG_CONSTANT("Grpc\\STATUS_UNKNOWN", + GRPC_STATUS_UNKNOWN, + CONST_CS); + REGISTER_LONG_CONSTANT("Grpc\\STATUS_INVALID_ARGUMENT", + GRPC_STATUS_INVALID_ARGUMENT, + CONST_CS); + REGISTER_LONG_CONSTANT("Grpc\\STATUS_DEADLINE_EXCEEDED", + GRPC_STATUS_DEADLINE_EXCEEDED, + CONST_CS); + REGISTER_LONG_CONSTANT("Grpc\\STATUS_NOT_FOUND", + GRPC_STATUS_NOT_FOUND, + CONST_CS); + REGISTER_LONG_CONSTANT("Grpc\\STATUS_ALREADY_EXISTS", + GRPC_STATUS_ALREADY_EXISTS, + CONST_CS); + REGISTER_LONG_CONSTANT("Grpc\\STATUS_PERMISSION_DENIED", + GRPC_STATUS_PERMISSION_DENIED, + CONST_CS); + REGISTER_LONG_CONSTANT("Grpc\\STATUS_UNAUTHENTICATED", + GRPC_STATUS_UNAUTHENTICATED, + CONST_CS); + REGISTER_LONG_CONSTANT("Grpc\\STATUS_RESOURCE_EXHAUSTED", + GRPC_STATUS_RESOURCE_EXHAUSTED, + CONST_CS); + REGISTER_LONG_CONSTANT("Grpc\\STATUS_FAILED_PRECONDITION", + GRPC_STATUS_FAILED_PRECONDITION, + CONST_CS); + REGISTER_LONG_CONSTANT("Grpc\\STATUS_ABORTED", + GRPC_STATUS_ABORTED, + CONST_CS); + REGISTER_LONG_CONSTANT("Grpc\\STATUS_OUT_OF_RANGE", + GRPC_STATUS_OUT_OF_RANGE, + CONST_CS); + REGISTER_LONG_CONSTANT("Grpc\\STATUS_UNIMPLEMENTED", + GRPC_STATUS_UNIMPLEMENTED, + CONST_CS); + REGISTER_LONG_CONSTANT("Grpc\\STATUS_INTERNAL", + GRPC_STATUS_INTERNAL, + CONST_CS); + REGISTER_LONG_CONSTANT("Grpc\\STATUS_UNAVAILABLE", + GRPC_STATUS_UNAVAILABLE, + CONST_CS); + REGISTER_LONG_CONSTANT("Grpc\\STATUS_DATA_LOSS", + GRPC_STATUS_DATA_LOSS, + CONST_CS); + + grpc_init_call(TSRMLS_C); + grpc_init_channel(TSRMLS_C); + grpc_init_server(TSRMLS_C); + grpc_init_completion_queue(TSRMLS_C); + grpc_init_event(TSRMLS_C); + grpc_init_timeval(TSRMLS_C); + grpc_init_credentials(TSRMLS_C); + grpc_init_server_credentials(TSRMLS_C); + return SUCCESS; +} +/* }}} */ + +/* {{{ PHP_MSHUTDOWN_FUNCTION + */ +PHP_MSHUTDOWN_FUNCTION(grpc) +{ + /* uncomment this line if you have INI entries + UNREGISTER_INI_ENTRIES(); + */ + grpc_shutdown_timeval(TSRMLS_C); + grpc_shutdown(); + return SUCCESS; +} +/* }}} */ + +/* {{{ PHP_MINFO_FUNCTION + */ +PHP_MINFO_FUNCTION(grpc) +{ + php_info_print_table_start(); + php_info_print_table_header(2, "grpc support", "enabled"); + php_info_print_table_end(); + + /* Remove comments if you have entries in php.ini + DISPLAY_INI_ENTRIES(); + */ +} +/* }}} */ +/* The previous line is meant for vim and emacs, so it can correctly fold and + unfold functions in source code. See the corresponding marks just before + function definition, where the functions purpose is also documented. Please + follow this convention for the convenience of others editing your code. +*/ + + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: noet sw=4 ts=4 fdm=marker + * vim<600: noet sw=4 ts=4 + */ diff --git a/src/php/ext/grpc/php_grpc.h b/src/php/ext/grpc/php_grpc.h new file mode 100755 index 0000000000000000000000000000000000000000..777e0c43687738c5c694ed0613785cedf18d2bd0 --- /dev/null +++ b/src/php/ext/grpc/php_grpc.h @@ -0,0 +1,66 @@ + +#ifndef PHP_GRPC_H +#define PHP_GRPC_H + +#include <stdbool.h> + +extern zend_module_entry grpc_module_entry; +#define phpext_grpc_ptr &grpc_module_entry + +#define PHP_GRPC_VERSION "0.1.0" /* Replace with version number for your extension */ + +#ifdef PHP_WIN32 +# define PHP_GRPC_API __declspec(dllexport) +#elif defined(__GNUC__) && __GNUC__ >= 4 +# define PHP_GRPC_API __attribute__ ((visibility("default"))) +#else +# define PHP_GRPC_API +#endif + +#ifdef ZTS +#include "TSRM.h" +#endif + +#include "php.h" + +#include "grpc/grpc.h" + +#define RETURN_DESTROY_ZVAL(val) \ + RETURN_ZVAL( \ + val, \ + false /* Don't execute copy constructor */, \ + true /* Dealloc original before returning */) + +/* These are all function declarations */ +/* Code that runs at module initialization */ +PHP_MINIT_FUNCTION(grpc); +/* Code that runs at module shutdown */ +PHP_MSHUTDOWN_FUNCTION(grpc); +/* Displays information about the module */ +PHP_MINFO_FUNCTION(grpc); + +/* + Declare any global variables you may need between the BEGIN + and END macros here: + +ZEND_BEGIN_MODULE_GLOBALS(grpc) +ZEND_END_MODULE_GLOBALS(grpc) +*/ + +/* In every utility function you add that needs to use variables + in php_grpc_globals, call TSRMLS_FETCH(); after declaring other + variables used by that function, or better yet, pass in TSRMLS_CC + after the last function argument and declare your utility function + with TSRMLS_DC after the last declared argument. Always refer to + the globals in your function as GRPC_G(variable). You are + encouraged to rename these macros something shorter, see + examples in any other php module directory. +*/ + +#ifdef ZTS +#define GRPC_G(v) TSRMG(grpc_globals_id, zend_grpc_globals *, v) +#else +#define GRPC_G(v) (grpc_globals.v) +#endif + +#endif /* PHP_GRPC_H */ diff --git a/src/php/ext/grpc/server.c b/src/php/ext/grpc/server.c new file mode 100755 index 0000000000000000000000000000000000000000..7e98713bd5b12751246dafda4e633710675e1c47 --- /dev/null +++ b/src/php/ext/grpc/server.c @@ -0,0 +1,202 @@ +#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 <stdbool.h> + +#include "grpc/grpc.h" +#include "grpc/support/log.h" +#include "grpc/grpc_security.h" + +#include "server.h" +#include "completion_queue.h" +#include "channel.h" +#include "server_credentials.h" + +/* Frees and destroys an instance of wrapped_grpc_server */ +void free_wrapped_grpc_server(void *object TSRMLS_DC){ + wrapped_grpc_server *server = (wrapped_grpc_server*)object; + if(server->wrapped != NULL){ + grpc_server_shutdown(server->wrapped); + grpc_server_destroy(server->wrapped); + } + efree(server); +} + +/* 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_server( + zend_class_entry *class_type TSRMLS_DC){ + zend_object_value retval; + wrapped_grpc_server *intern; + + intern = (wrapped_grpc_server*)emalloc(sizeof(wrapped_grpc_server)); + memset(intern, 0, sizeof(wrapped_grpc_server)); + + 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_server, + NULL TSRMLS_CC); + retval.handlers = zend_get_std_object_handlers(); + return retval; +} + +/** + * Constructs a new instance of the Server class + * @param CompletionQueue $queue The completion queue to use with the server + * @param array $args The arguments to pass to the server (optional) + */ +PHP_METHOD(Server, __construct){ + wrapped_grpc_server *server = + (wrapped_grpc_server*)zend_object_store_get_object(getThis() TSRMLS_CC); + zval *queue_obj; + zval *args_array = NULL; + grpc_channel_args args; + HashTable *array_hash; + zval **creds_obj = NULL; + wrapped_grpc_server_credentials *creds = NULL; + /* "O|a" == 1 Object, 1 optional array */ + if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, + "O|a", + &queue_obj, grpc_ce_completion_queue, + &args_array) == FAILURE){ + zend_throw_exception(spl_ce_InvalidArgumentException, + "Server expects a CompletionQueue and an array", + 1 TSRMLS_CC); + return; + } + add_property_zval(getThis(), "completion_queue", queue_obj); + wrapped_grpc_completion_queue *queue = + (wrapped_grpc_completion_queue*)zend_object_store_get_object( + queue_obj TSRMLS_CC); + if (args_array == NULL) { + server->wrapped = grpc_server_create(queue->wrapped, NULL); + } else { + array_hash = Z_ARRVAL_P(args_array); + if(zend_hash_find(array_hash, + "credentials", + sizeof("credentials"), + (void**)&creds_obj) == SUCCESS) { + if(zend_get_class_entry(*creds_obj TSRMLS_CC) != + grpc_ce_server_credentials) { + zend_throw_exception(spl_ce_InvalidArgumentException, + "credentials must be a ServerCredentials object", + 1 TSRMLS_CC); + return; + } + creds = (wrapped_grpc_server_credentials*)zend_object_store_get_object( + *creds_obj TSRMLS_CC); + zend_hash_del(array_hash, "credentials", sizeof("credentials")); + } + php_grpc_read_args_array(args_array, &args); + if (creds == NULL) { + server->wrapped = grpc_server_create(queue->wrapped, &args); + } else { + gpr_log(GPR_DEBUG, "Initialized secure server"); + server->wrapped = grpc_secure_server_create(creds->wrapped, + queue->wrapped, + &args); + } + efree(args.args); + } +} + +/** + * Request a call on a server. Creates a single GRPC_SERVER_RPC_NEW event. + * @param long $tag_new The tag to associate with the new request + * @param long $tag_cancel The tag to use if the call is cancelled + * @return Void + */ +PHP_METHOD(Server, request_call){ + wrapped_grpc_server *server = + (wrapped_grpc_server*)zend_object_store_get_object(getThis() TSRMLS_CC); + long tag_new; + /* "l" == 1 long */ + if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, + "l", + &tag_new) == FAILURE){ + zend_throw_exception(spl_ce_InvalidArgumentException, + "request_call expects a long", + 1 TSRMLS_CC); + return; + } + grpc_server_request_call(server->wrapped, (void*)tag_new); +} + +/** + * Add a http2 over tcp listener. + * @param string $addr The address to add + * @return true on success, false on failure + */ +PHP_METHOD(Server, add_http2_port){ + wrapped_grpc_server *server = + (wrapped_grpc_server*)zend_object_store_get_object(getThis() TSRMLS_CC); + const char *addr; + int addr_len; + /* "s" == 1 string */ + if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, + "s", + &addr, &addr_len) == FAILURE){ + zend_throw_exception(spl_ce_InvalidArgumentException, + "add_http2_port expects a string", + 1 TSRMLS_CC); + return; + } + RETURN_BOOL(grpc_server_add_http2_port(server->wrapped, addr)); +} + +PHP_METHOD(Server, add_secure_http2_port){ + wrapped_grpc_server *server = + (wrapped_grpc_server*)zend_object_store_get_object(getThis() TSRMLS_CC); + const char *addr; + int addr_len; + /* "s" == 1 string */ + if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, + "s", + &addr, &addr_len) == FAILURE){ + zend_throw_exception(spl_ce_InvalidArgumentException, + "add_http2_port expects a string", + 1 TSRMLS_CC); + return; + } + RETURN_BOOL(grpc_server_add_secure_http2_port(server->wrapped, addr)); +} + +/** + * Start a server - tells all listeners to start listening + * @return Void + */ +PHP_METHOD(Server, start){ + wrapped_grpc_server *server = + (wrapped_grpc_server*)zend_object_store_get_object(getThis() TSRMLS_CC); + grpc_server_start(server->wrapped); +} + +static zend_function_entry server_methods[] = { + PHP_ME(Server, __construct, NULL, ZEND_ACC_PUBLIC|ZEND_ACC_CTOR) + PHP_ME(Server, request_call, NULL, ZEND_ACC_PUBLIC) + PHP_ME(Server, add_http2_port, NULL, ZEND_ACC_PUBLIC) + PHP_ME(Server, add_secure_http2_port, NULL, ZEND_ACC_PUBLIC) + PHP_ME(Server, start, NULL, ZEND_ACC_PUBLIC) + PHP_FE_END +}; + +void grpc_init_server(TSRMLS_D){ + zend_class_entry ce; + INIT_CLASS_ENTRY(ce, "Grpc\\Server", server_methods); + ce.create_object = create_wrapped_grpc_server; + grpc_ce_server = zend_register_internal_class(&ce TSRMLS_CC); +} diff --git a/src/php/ext/grpc/server.h b/src/php/ext/grpc/server.h new file mode 100755 index 0000000000000000000000000000000000000000..61ed82538ca87c6bfc3b056412e3fdf080451ac4 --- /dev/null +++ b/src/php/ext/grpc/server.h @@ -0,0 +1,28 @@ +#ifndef NET_GRPC_PHP_GRPC_SERVER_H_ +#define NET_GRPC_PHP_GRPC_SERVER_H_ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "php.h" +#include "php_ini.h" +#include "ext/standard/info.h" +#include "php_grpc.h" + +#include "grpc/grpc.h" + +/* Class entry for the Server PHP class */ +zend_class_entry *grpc_ce_server; + +/* Wrapper struct for grpc_server that can be associated with a PHP object */ +typedef struct wrapped_grpc_server { + zend_object std; + + grpc_server *wrapped; +} wrapped_grpc_server; + +/* Initializes the Server class */ +void grpc_init_server(TSRMLS_D); + +#endif /* NET_GRPC_PHP_GRPC_SERVER_H_ */ diff --git a/src/php/ext/grpc/server_credentials.c b/src/php/ext/grpc/server_credentials.c new file mode 100755 index 0000000000000000000000000000000000000000..b07790b4be9afdc25927967bac7c1e042132ae78 --- /dev/null +++ b/src/php/ext/grpc/server_credentials.c @@ -0,0 +1,117 @@ +#include "server_credentials.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 "grpc/grpc.h" +#include "grpc/grpc_security.h" + +/* Frees and destroys an instace of wrapped_grpc_server_credentials */ +void free_wrapped_grpc_server_credentials(void *object TSRMLS_DC){ + wrapped_grpc_server_credentials *creds = + (wrapped_grpc_server_credentials*)object; + if(creds->wrapped != NULL) { + grpc_server_credentials_release(creds->wrapped); + } + efree(creds); +} + +/* Initializes an instace of wrapped_grpc_server_credentials to be associated + * with an object of a class specified by class_type */ +zend_object_value create_wrapped_grpc_server_credentials( + zend_class_entry *class_type TSRMLS_DC){ + zend_object_value retval; + wrapped_grpc_server_credentials *intern; + + intern = (wrapped_grpc_server_credentials*)emalloc(sizeof( + wrapped_grpc_server_credentials)); + memset(intern, 0, sizeof(wrapped_grpc_server_credentials)); + + 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_server_credentials, + NULL TSRMLS_CC); + retval.handlers = zend_get_std_object_handlers(); + return retval; +} + +zval *grpc_php_wrap_server_credentials(grpc_server_credentials *wrapped){ + zval *server_credentials_object; + MAKE_STD_ZVAL(server_credentials_object); + object_init_ex(server_credentials_object, grpc_ce_server_credentials); + wrapped_grpc_server_credentials *server_credentials = + (wrapped_grpc_server_credentials*)zend_object_store_get_object( + server_credentials_object TSRMLS_CC); + server_credentials->wrapped = wrapped; + return server_credentials_object; +} + +/** + * Create SSL credentials. + * @param string pem_root_certs PEM encoding of the server root certificates + * @param string pem_private_key PEM encoding of the client's private key + * @param string pem_cert_chain PEM encoding of the client's certificate chain + * @return Credentials The new SSL credentials object + */ +PHP_METHOD(ServerCredentials, createSsl){ + char *pem_root_certs = 0; + char *pem_private_key; + char *pem_cert_chain; + + int root_certs_length = 0, private_key_length, cert_chain_length; + + /* "s!ss" == 1 nullable string, 2 strings */ + if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, + "s!ss", + &pem_root_certs, &root_certs_length, + &pem_private_key, &private_key_length, + &pem_cert_chain, &cert_chain_length) == FAILURE) { + zend_throw_exception(spl_ce_InvalidArgumentException, + "createSsl expects 3 strings", + 1 TSRMLS_CC); + return; + } + grpc_server_credentials *creds = grpc_ssl_server_credentials_create( + (unsigned char*)pem_root_certs, (size_t)root_certs_length, + (unsigned char*)pem_private_key, (size_t)private_key_length, + (unsigned char*)pem_cert_chain, (size_t)cert_chain_length); + zval *creds_object = grpc_php_wrap_server_credentials(creds); + RETURN_DESTROY_ZVAL(creds_object); +} + +/** + * Create fake credentials. Only to be used for testing. + * @return ServerCredentials The new fake credentials object + */ +PHP_METHOD(ServerCredentials, createFake){ + grpc_server_credentials *creds = + grpc_fake_transport_security_server_credentials_create(); + zval *creds_object = grpc_php_wrap_server_credentials(creds); + RETURN_DESTROY_ZVAL(creds_object); +} + +static zend_function_entry server_credentials_methods[] = { + PHP_ME(ServerCredentials, createSsl, NULL, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC) + PHP_ME(ServerCredentials, createFake, NULL, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC) + PHP_FE_END +}; + +void grpc_init_server_credentials(TSRMLS_D){ + zend_class_entry ce; + INIT_CLASS_ENTRY(ce, "Grpc\\ServerCredentials", server_credentials_methods); + ce.create_object = create_wrapped_grpc_server_credentials; + grpc_ce_server_credentials = zend_register_internal_class(&ce TSRMLS_CC); +} diff --git a/src/php/ext/grpc/server_credentials.h b/src/php/ext/grpc/server_credentials.h new file mode 100755 index 0000000000000000000000000000000000000000..0a5c78590228c6cc6b121afcf04f8f182094e971 --- /dev/null +++ b/src/php/ext/grpc/server_credentials.h @@ -0,0 +1,30 @@ +#ifndef NET_GRPC_PHP_GRPC_SERVER_CREDENTIALS_H_ +#define NET_GRPC_PHP_GRPC_SERVER_CREDENTIALS_H_ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "php.h" +#include "php_ini.h" +#include "ext/standard/info.h" +#include "php_grpc.h" + +#include "grpc/grpc.h" +#include "grpc/grpc_security.h" + +/* Class entry for the Server_Credentials PHP class */ +zend_class_entry *grpc_ce_server_credentials; + +/* Wrapper struct for grpc_server_credentials that can be associated with a PHP + * object */ +typedef struct wrapped_grpc_server_credentials { + zend_object std; + + grpc_server_credentials *wrapped; +} wrapped_grpc_server_credentials; + +/* Initializes the Server_Credentials PHP class */ +void grpc_init_server_credentials(TSRMLS_D); + +#endif /* NET_GRPC_PHP_GRPC_SERVER_CREDENTIALS_H_ */ diff --git a/src/php/ext/grpc/timeval.c b/src/php/ext/grpc/timeval.c new file mode 100755 index 0000000000000000000000000000000000000000..7b7e0e6443b84b976fe0c6a943ece30558f8fa8c --- /dev/null +++ b/src/php/ext/grpc/timeval.c @@ -0,0 +1,255 @@ +#include "timeval.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 <stdbool.h> + +#include "grpc/grpc.h" +#include "grpc/support/time.h" + +/* Frees and destroys an instance of wrapped_grpc_call */ +void free_wrapped_grpc_timeval(void *object TSRMLS_DC){ + efree(object); +} + +/* Initializes an instance of wrapped_grpc_timeval to be associated with an + * object of a class specified by class_type */ +zend_object_value create_wrapped_grpc_timeval( + zend_class_entry *class_type TSRMLS_DC){ + zend_object_value retval; + wrapped_grpc_timeval *intern; + intern = (wrapped_grpc_timeval*)emalloc(sizeof(wrapped_grpc_timeval)); + memset(intern, 0, sizeof(wrapped_grpc_timeval)); + 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_timeval, + NULL TSRMLS_CC); + retval.handlers = zend_get_std_object_handlers(); + return retval; +} + +zval *grpc_php_wrap_timeval(gpr_timespec wrapped){ + zval *timeval_object; + MAKE_STD_ZVAL(timeval_object); + object_init_ex(timeval_object, grpc_ce_timeval); + wrapped_grpc_timeval *timeval = + (wrapped_grpc_timeval*)zend_object_store_get_object( + timeval_object TSRMLS_CC); + memcpy(&timeval->wrapped, &wrapped, sizeof(gpr_timespec)); + return timeval_object; +} + +/** + * Constructs a new instance of the Timeval class + * @param long $usec The number of microseconds in the interval + */ +PHP_METHOD(Timeval, __construct){ + wrapped_grpc_timeval *timeval = + (wrapped_grpc_timeval*)zend_object_store_get_object(getThis() TSRMLS_CC); + long microseconds; + /* "l" == 1 long */ + if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, + "l", + µseconds) == FAILURE){ + zend_throw_exception(spl_ce_InvalidArgumentException, + "Timeval expects a long", + 1 TSRMLS_CC); + return; + } + gpr_timespec time = gpr_time_from_micros(microseconds); + memcpy(&timeval->wrapped, &time, sizeof(gpr_timespec)); +} + +/** + * Adds another Timeval to this one and returns the sum. Calculations saturate + * at infinities. + * @param Timeval $other The other Timeval object to add + * @return Timeval A new Timeval object containing the sum + */ +PHP_METHOD(Timeval, add){ + zval *other_obj; + /* "O" == 1 Object */ + if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, + "O", + &other_obj, grpc_ce_timeval) == FAILURE){ + zend_throw_exception(spl_ce_InvalidArgumentException, + "add expects a Timeval", + 1 TSRMLS_CC); + return; + } + wrapped_grpc_timeval *self = + (wrapped_grpc_timeval*)zend_object_store_get_object(getThis() TSRMLS_CC); + wrapped_grpc_timeval *other = + (wrapped_grpc_timeval*)zend_object_store_get_object(other_obj TSRMLS_CC); + zval *sum = grpc_php_wrap_timeval(gpr_time_add(self->wrapped, + other->wrapped)); + RETURN_DESTROY_ZVAL(sum); +} + +/** + * Subtracts another Timeval from this one and returns the difference. + * Calculations saturate at infinities. + * @param Timeval $other The other Timeval object to subtract + * @param Timeval A new Timeval object containing the sum + */ +PHP_METHOD(Timeval, subtract){ + zval *other_obj; + /* "O" == 1 Object */ + if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, + "O", + &other_obj, grpc_ce_timeval) == FAILURE){ + zend_throw_exception(spl_ce_InvalidArgumentException, + "subtract expects a Timeval", + 1 TSRMLS_CC); + return; + } + wrapped_grpc_timeval *self = + (wrapped_grpc_timeval*)zend_object_store_get_object(getThis() TSRMLS_CC); + wrapped_grpc_timeval *other = + (wrapped_grpc_timeval*)zend_object_store_get_object(other_obj TSRMLS_CC); + zval *diff = grpc_php_wrap_timeval(gpr_time_sub(self->wrapped, + other->wrapped)); + RETURN_DESTROY_ZVAL(diff); +} + +/** + * Return negative, 0, or positive according to whether a < b, a == b, or a > b + * respectively. + * @param Timeval $a The first time to compare + * @param Timeval $b The second time to compare + * @return long + */ +PHP_METHOD(Timeval, compare){ + zval *a_obj, *b_obj; + /* "OO" == 2 Objects */ + if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, + "OO", + &a_obj, grpc_ce_timeval, + &b_obj, grpc_ce_timeval) == FAILURE){ + zend_throw_exception(spl_ce_InvalidArgumentException, + "compare expects two Timevals", + 1 TSRMLS_CC); + return; + } + wrapped_grpc_timeval *a = + (wrapped_grpc_timeval*)zend_object_store_get_object(a_obj TSRMLS_CC); + wrapped_grpc_timeval *b = + (wrapped_grpc_timeval*)zend_object_store_get_object(b_obj TSRMLS_CC); + long result = gpr_time_cmp(a->wrapped, b->wrapped); + RETURN_LONG(result); +} + +/** + * Checks whether the two times are within $threshold of each other + * @param Timeval $a The first time to compare + * @param Timeval $b The second time to compare + * @param Timeval $threshold The threshold to check against + * @return bool True if $a and $b are within $threshold, False otherwise + */ +PHP_METHOD(Timeval, similar){ + zval *a_obj, *b_obj, *thresh_obj; + /* "OOO" == 3 Objects */ + if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, + "OOO", + &a_obj, grpc_ce_timeval, + &b_obj, grpc_ce_timeval, + &thresh_obj, grpc_ce_timeval) == FAILURE){ + zend_throw_exception(spl_ce_InvalidArgumentException, + "compare expects three Timevals", + 1 TSRMLS_CC); + return; + } + wrapped_grpc_timeval *a = + (wrapped_grpc_timeval*)zend_object_store_get_object(a_obj TSRMLS_CC); + wrapped_grpc_timeval *b = + (wrapped_grpc_timeval*)zend_object_store_get_object(b_obj TSRMLS_CC); + wrapped_grpc_timeval *thresh = + (wrapped_grpc_timeval*)zend_object_store_get_object(thresh_obj TSRMLS_CC); + int result = gpr_time_similar(a->wrapped, b->wrapped, thresh->wrapped); + RETURN_BOOL(result); +} + +/** + * Returns the current time as a timeval object + * @return Timeval The current time + */ +PHP_METHOD(Timeval, now){ + zval *now = grpc_php_wrap_timeval(gpr_now()); + RETURN_DESTROY_ZVAL(now); +} + +/** + * Returns the zero time interval as a timeval object + * @return Timeval Zero length time interval + */ +PHP_METHOD(Timeval, zero){ + zval *grpc_php_timeval_zero = grpc_php_wrap_timeval(gpr_time_0); + RETURN_ZVAL(grpc_php_timeval_zero, + false, /* Copy original before returning? */ + true /* Destroy original before returning */); +} + +/** + * Returns the infinite future time value as a timeval object + * @return Timeval Infinite future time value + */ +PHP_METHOD(Timeval, inf_future){ + zval *grpc_php_timeval_inf_future = grpc_php_wrap_timeval(gpr_inf_future); + RETURN_DESTROY_ZVAL(grpc_php_timeval_inf_future); +} + +/** + * Returns the infinite past time value as a timeval object + * @return Timeval Infinite past time value + */ +PHP_METHOD(Timeval, inf_past){ + zval *grpc_php_timeval_inf_past = grpc_php_wrap_timeval(gpr_inf_past); + RETURN_DESTROY_ZVAL(grpc_php_timeval_inf_past); +} + +/** + * Sleep until this time, interpreted as an absolute timeout + * @return void + */ +PHP_METHOD(Timeval, sleep_until){ + wrapped_grpc_timeval *this = + (wrapped_grpc_timeval*)zend_object_store_get_object(getThis() TSRMLS_CC); + gpr_sleep_until(this->wrapped); +} + +static zend_function_entry timeval_methods[] = { + PHP_ME(Timeval, __construct, NULL, ZEND_ACC_PUBLIC|ZEND_ACC_CTOR) + PHP_ME(Timeval, add, NULL, ZEND_ACC_PUBLIC) + PHP_ME(Timeval, compare, NULL, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC) + PHP_ME(Timeval, inf_future, NULL, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC) + PHP_ME(Timeval, inf_past, NULL, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC) + PHP_ME(Timeval, now, NULL, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC) + PHP_ME(Timeval, similar, NULL, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC) + PHP_ME(Timeval, sleep_until, NULL, ZEND_ACC_PUBLIC) + PHP_ME(Timeval, subtract, NULL, ZEND_ACC_PUBLIC) + PHP_ME(Timeval, zero, NULL, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC) + PHP_FE_END +}; + +void grpc_init_timeval(TSRMLS_D){ + zend_class_entry ce; + INIT_CLASS_ENTRY(ce, "Grpc\\Timeval", timeval_methods); + ce.create_object = create_wrapped_grpc_timeval; + grpc_ce_timeval = zend_register_internal_class(&ce TSRMLS_CC); +} + +void grpc_shutdown_timeval(TSRMLS_D){ +} diff --git a/src/php/ext/grpc/timeval.h b/src/php/ext/grpc/timeval.h new file mode 100755 index 0000000000000000000000000000000000000000..cfdb0c713f2e8c365f89436b64453eb211594308 --- /dev/null +++ b/src/php/ext/grpc/timeval.h @@ -0,0 +1,35 @@ +#ifndef NET_GRPC_PHP_GRPC_TIMEVAL_H_ +#define NET_GRPC_PHP_GRPC_TIMEVAL_H_ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "php.h" +#include "php_ini.h" +#include "ext/standard/info.h" +#include "php_grpc.h" + +#include "grpc/grpc.h" +#include "grpc/support/time.h" + +/* Class entry for the Timeval PHP Class */ +zend_class_entry *grpc_ce_timeval; + +/* Wrapper struct for timeval that can be associated with a PHP object */ +typedef struct wrapped_grpc_timeval { + zend_object std; + + gpr_timespec wrapped; +} wrapped_grpc_timeval; + +/* Initialize the Timeval PHP class */ +void grpc_init_timeval(TSRMLS_D); + +/* Shutdown the Timeval PHP class */ +void grpc_shutdown_timeval(TSRMLS_D); + +/* Creates a Timeval object that wraps the given timeval struct */ +zval *grpc_php_wrap_timeval(gpr_timespec wrapped); + +#endif /* NET_GRPC_PHP_GRPC_TIMEVAL_H_ */ diff --git a/src/php/lib/Grpc/ActiveCall.php b/src/php/lib/Grpc/ActiveCall.php new file mode 100755 index 0000000000000000000000000000000000000000..10a37cdb1689d2a494cf99de1306f156762fc9b3 --- /dev/null +++ b/src/php/lib/Grpc/ActiveCall.php @@ -0,0 +1,98 @@ +<?php +namespace Grpc; + +/** + * Represents an active call that allows sending and recieving binary data + */ +class ActiveCall { + private $completion_queue; + private $call; + private $flags; + private $metadata; + + /** + * Create a new active call. + * @param Channel $channel The channel to communicate on + * @param string $method The method to call on the remote server + * @param array $metadata Metadata to send with the call, if applicable + * @param long $flags Write flags to use with this call + */ + public function __construct(Channel $channel, + $method, + $metadata = array(), + $flags = 0) { + $this->completion_queue = new CompletionQueue(); + $this->call = new Call($channel, $method, Timeval::inf_future()); + $this->call->add_metadata($metadata, 0); + $this->flags = $flags; + + // Invoke the call. + $this->call->start_invoke($this->completion_queue, + INVOKE_ACCEPTED, + CLIENT_METADATA_READ, + FINISHED, 0); + $this->completion_queue->pluck(INVOKE_ACCEPTED, + Timeval::inf_future()); + $metadata_event = $this->completion_queue->pluck(CLIENT_METADATA_READ, + Timeval::inf_future()); + $this->metadata = $metadata_event->get_data(); + } + + /** + * @return The metadata sent by the server. + */ + public function getMetadata() { + return $this->metadata; + } + + /** + * Cancels the call + */ + public function cancel() { + $this->call->cancel(); + } + + /** + * Read a single message from the server. + * @return The next message from the server, or null if there is none. + */ + public function read() { + $this->call->start_read(READ); + $read_event = $this->completion_queue->pluck(READ, Timeval::inf_future()); + return $read_event->get_data(); + } + + /** + * Write a single message to the server. This cannot be called after + * writesDone is called. + * @param ByteBuffer $data The data to write + */ + public function write($data) { + if($this->call->start_write($data, + WRITE_ACCEPTED, + $this->flags) != OP_OK) { + // TODO(mlumish): more useful error + throw new \Exception("Cannot call write after writesDone"); + } + $this->completion_queue->pluck(WRITE_ACCEPTED, Timeval::inf_future()); + } + + /** + * Indicate that no more writes will be sent. + */ + public function writesDone() { + $this->call->writes_done(FINISH_ACCEPTED); + $this->completion_queue->pluck(FINISH_ACCEPTED, Timeval::inf_future()); + } + + /** + * Wait for the server to send the status, and return it. + * @return object The status object, with integer $code and string $details + * members + */ + public function getStatus() { + $status_event = $this->completion_queue->pluck(FINISHED, + Timeval::inf_future()); + return $status_event->get_data(); + } +} \ No newline at end of file diff --git a/src/php/lib/Grpc/BaseStub.php b/src/php/lib/Grpc/BaseStub.php new file mode 100755 index 0000000000000000000000000000000000000000..7aa0c4ac4d4361b58217552ec17a2fc7718f0b0e --- /dev/null +++ b/src/php/lib/Grpc/BaseStub.php @@ -0,0 +1,106 @@ +<?php + +namespace Grpc; + +/** + * Base class for generated client stubs. Stub methods are expected to call + * _simpleRequest or _streamRequest and return the result. + */ +class BaseStub { + + private $channel; + + public function __construct($hostname) { + $this->channel = new Channel($hostname, []); + } + + /** + * Close the communication channel associated with this stub + */ + public function close() { + $channel->close(); + } + + /* This class is intended to be subclassed by generated code, so all functions + begin with "_" to avoid name collisions. */ + + /** + * Call a remote method that takes a single argument and has a single output + * + * @param string $method The name of the method to call + * @param $argument The argument to the method + * @param callable $deserialize A function that deserializes the response + * @param array $metadata A metadata map to send to the server + * @return SimpleSurfaceActiveCall The active call object + */ + protected function _simpleRequest($method, + $argument, + callable $deserialize, + $metadata = array()) { + return new SimpleSurfaceActiveCall($this->channel, + $method, + $deserialize, + $argument, + $metadata); + } + + /** + * Call a remote method that takes a stream of arguments and has a single + * output + * + * @param string $method The name of the method to call + * @param $arguments An array or Traversable of arguments to stream to the + * server + * @param callable $deserialize A function that deserializes the response + * @param array $metadata A metadata map to send to the server + * @return ClientStreamingSurfaceActiveCall The active call object + */ + protected function _clientStreamRequest($method, + $arguments, + callable $deserialize, + $metadata = array()) { + return new ClientStreamingSurfaceActiveCall($this->channel, + $method, + $deserialize, + $arguments, + $metadata); + } + + /** + * Call a remote method that takes a single argument and returns a stream of + * responses + * + * @param string $method The name of the method to call + * @param $argument The argument to the method + * @param callable $deserialize A function that deserializes the responses + * @param array $metadata A metadata map to send to the server + * @return ServerStreamingSurfaceActiveCall The active call object + */ + protected function _serverStreamRequest($method, + $argument, + callable $deserialize, + $metadata = array()) { + return new ServerStreamingSurfaceActiveCall($this->channel, + $method, + $deserialize, + $argument, + $metadata); + } + + /** + * Call a remote method with messages streaming in both directions + * + * @param string $method The name of the method to call + * @param callable $deserialize A function that deserializes the responses + * @param array $metadata A metadata map to send to the server + * @return BidiStreamingSurfaceActiveCall The active call object + */ + protected function _bidiRequest($method, + callable $deserialize, + $metadata = array()) { + return new BidiStreamingSurfaceActiveCall($this->channel, + $method, + $deserialize, + $metadata); + } +} diff --git a/src/php/lib/Grpc/SurfaceActiveCall.php b/src/php/lib/Grpc/SurfaceActiveCall.php new file mode 100755 index 0000000000000000000000000000000000000000..5f943033576792a40b4ea6e9e523ba116b5870f7 --- /dev/null +++ b/src/php/lib/Grpc/SurfaceActiveCall.php @@ -0,0 +1,211 @@ +<?php +namespace Grpc; + +/** + * Represents an active call that allows sending and recieving messages. + * Subclasses restrict how data can be sent and recieved. + */ +abstract class AbstractSurfaceActiveCall { + private $active_call; + private $deserialize; + + /** + * Create a new surface active call. + * @param Channel $channel The channel to communicate on + * @param string $method The method to call on the remote server + * @param callable $deserialize The function to deserialize a value + * @param array $metadata Metadata to send with the call, if applicable + * @param long $flags Write flags to use with this call + */ + public function __construct(Channel $channel, + $method, + callable $deserialize, + $metadata = array(), + $flags = 0) { + $this->active_call = new ActiveCall($channel, $method, $metadata, $flags); + $this->deserialize = $deserialize; + } + + /** + * @return The metadata sent by the server + */ + public function getMetadata() { + return $this->metadata(); + } + + /** + * Cancels the call + */ + public function cancel() { + $this->active_call->cancel(); + } + + protected function _read() { + $response = $this->active_call->read(); + if ($response == null) { + return null; + } + return call_user_func($this->deserialize, $response); + } + + protected function _write($value) { + return $this->active_call->write($value->serialize()); + } + + protected function _writesDone() { + $this->active_call->writesDone(); + } + + protected function _getStatus() { + return $this->active_call->getStatus(); + } +} + +/** + * Represents an active call that sends a single message and then gets a single + * response. + */ +class SimpleSurfaceActiveCall extends AbstractSurfaceActiveCall { + /** + * Create a new simple (single request/single response) active call. + * @param Channel $channel The channel to communicate on + * @param string $method The method to call on the remote server + * @param callable $deserialize The function to deserialize a value + * @param $arg The argument to send + * @param array $metadata Metadata to send with the call, if applicable + */ + public function __construct(Channel $channel, + $method, + callable $deserialize, + $arg, + $metadata = array()) { + parent::__construct($channel, $method, $deserialize, $metadata, + \Grpc\WRITE_BUFFER_HINT); + $this->_write($arg); + $this->_writesDone(); + } + + /** + * Wait for the server to respond with data and a status + * @return [response data, status] + */ + public function wait() { + $response = $this->_read(); + $status = $this->_getStatus(); + return array($response, $status); + } +} + +/** + * Represents an active call that sends a stream of messages and then gets a + * single response. + */ +class ClientStreamingSurfaceActiveCall extends AbstractSurfaceActiveCall { + /** + * Create a new simple (single request/single response) active call. + * @param Channel $channel The channel to communicate on + * @param string $method The method to call on the remote server + * @param callable $deserialize The function to deserialize a value + * @param Traversable $arg_iter The iterator of arguments to send + * @param array $metadata Metadata to send with the call, if applicable + */ + public function __construct(Channel $channel, + $method, + callable $deserialize, + $arg_iter, + $metadata = array()) { + parent::__construct($channel, $method, $deserialize, $metadata, 0); + foreach($arg_iter as $arg) { + $this->_write($arg); + } + $this->_writesDone(); + } + + /** + * Wait for the server to respond with data and a status + * @return [response data, status] + */ + public function wait() { + $response = $this->_read(); + $status = $this->_getStatus(); + return array($response, $status); + } +} + +/** + * Represents an active call that sends a single message and then gets a stream + * of reponses + */ +class ServerStreamingSurfaceActiveCall extends AbstractSurfaceActiveCall { + /** + * Create a new simple (single request/single response) active call. + * @param Channel $channel The channel to communicate on + * @param string $method The method to call on the remote server + * @param callable $deserialize The function to deserialize a value + * @param $arg The argument to send + * @param array $metadata Metadata to send with the call, if applicable + */ + public function __construct(Channel $channel, + $method, + callable $deserialize, + $arg, + $metadata = array()) { + parent::__construct($channel, $method, $deserialize, $metadata, + \Grpc\WRITE_BUFFER_HINT); + $this->_write($arg); + $this->_writesDone(); + } + + /** + * @return An iterator of response values + */ + public function responses() { + while(($response = $this->_read()) != null) { + yield $response; + } + } + + public function getStatus() { + return $this->_getStatus(); + } +} + +/** + * Represents an active call that allows for sending and recieving messages in + * streams in any order. + */ +class BidiStreamingSurfaceActiveCall extends AbstractSurfaceActiveCall { + + /** + * Reads the next value from the server. + * @return The next value from the server, or null if there is none + */ + public function read() { + return $this->_read(); + } + + /** + * Writes a single message to the server. This cannot be called after + * writesDone is called. + * @param $value The message to send + */ + public function write($value) { + $this->_write($value); + } + + /** + * Indicate that no more writes will be sent + */ + public function writesDone() { + $this->_writesDone(); + } + + /** + * Wait for the server to send the status, and return it. + * @return object The status object, with integer $code and string $details + * members + */ + public function getStatus() { + return $this->_getStatus(); + } +} \ No newline at end of file diff --git a/src/php/tests/generated_code/GeneratedCodeTest.php b/src/php/tests/generated_code/GeneratedCodeTest.php new file mode 100755 index 0000000000000000000000000000000000000000..d8d726e21ad805a21fd79d2aec90dba2786776fd --- /dev/null +++ b/src/php/tests/generated_code/GeneratedCodeTest.php @@ -0,0 +1,70 @@ +<?php +require __DIR__ . '/../../lib/Grpc/ActiveCall.php'; +require __DIR__ . '/../../lib/Grpc/SurfaceActiveCall.php'; +require __DIR__ . '/../../lib/Grpc/BaseStub.php'; +require 'DrSlump/Protobuf.php'; +\DrSlump\Protobuf::autoload(); +require 'math.php'; +class GeneratedCodeTest extends PHPUnit_Framework_TestCase { + /* These tests require that a server exporting the math service must be + * running on $GRPC_TEST_HOST */ + protected static $client; + protected static $timeout; + public static function setUpBeforeClass() { + self::$client = new math\MathClient(getenv('GRPC_TEST_HOST')); + } + + public function testSimpleRequest() { + $div_arg = new math\DivArgs(); + $div_arg->setDividend(7); + $div_arg->setDivisor(4); + list($response, $status) = self::$client->Div($div_arg)->wait(); + $this->assertEquals(1, $response->getQuotient()); + $this->assertEquals(3, $response->getRemainder()); + $this->assertEquals(\Grpc\STATUS_OK, $status->code); + } + + public function testServerStreaming() { + $fib_arg = new math\FibArgs(); + $fib_arg->setLimit(7); + $call = self::$client->Fib($fib_arg); + $result_array = iterator_to_array($call->responses()); + $extract_num = function($num){ + return $num->getNum(); + }; + $values = array_map($extract_num, $result_array); + $this->assertEquals([1, 1, 2, 3, 5, 8, 13], $values); + $status = $call->getStatus(); + $this->assertEquals(\Grpc\STATUS_OK, $status->code); + } + + public function testClientStreaming() { + $num_iter = function() { + for ($i = 0; $i < 7; $i++) { + $num = new math\Num(); + $num->setNum($i); + yield $num; + } + }; + $call = self::$client->Sum($num_iter()); + list($response, $status) = $call->wait(); + $this->assertEquals(21, $response->getNum()); + $this->assertEquals(\Grpc\STATUS_OK, $status->code); + } + + public function testBidiStreaming() { + $call = self::$client->DivMany(); + for ($i = 0; $i < 7; $i++) { + $div_arg = new math\DivArgs(); + $div_arg->setDividend(2 * $i + 1); + $div_arg->setDivisor(2); + $call->write($div_arg); + $response = $call->read(); + $this->assertEquals($i, $response->getQuotient()); + $this->assertEquals(1, $response->getRemainder()); + } + $call->writesDone(); + $status = $call->getStatus(); + $this->assertEquals(\Grpc\STATUS_OK, $status->code); + } +} \ No newline at end of file diff --git a/src/php/tests/generated_code/math.php b/src/php/tests/generated_code/math.php new file mode 100755 index 0000000000000000000000000000000000000000..d50f94e11c76166cd3d94e3ec39bbe9f648c9c54 --- /dev/null +++ b/src/php/tests/generated_code/math.php @@ -0,0 +1,479 @@ +<?php +// DO NOT EDIT! Generated by Protobuf-PHP protoc plugin 1.0 +// Source: math.proto +// Date: 2014-11-14 00:00:41 + +namespace math { + + class DivArgs extends \DrSlump\Protobuf\Message { + + /** @var int */ + public $dividend = null; + + /** @var int */ + public $divisor = null; + + + /** @var \Closure[] */ + protected static $__extensions = array(); + + public static function descriptor() + { + $descriptor = new \DrSlump\Protobuf\Descriptor(__CLASS__, 'math.DivArgs'); + + // REQUIRED INT64 dividend = 1 + $f = new \DrSlump\Protobuf\Field(); + $f->number = 1; + $f->name = "dividend"; + $f->type = \DrSlump\Protobuf::TYPE_INT64; + $f->rule = \DrSlump\Protobuf::RULE_REQUIRED; + $descriptor->addField($f); + + // REQUIRED INT64 divisor = 2 + $f = new \DrSlump\Protobuf\Field(); + $f->number = 2; + $f->name = "divisor"; + $f->type = \DrSlump\Protobuf::TYPE_INT64; + $f->rule = \DrSlump\Protobuf::RULE_REQUIRED; + $descriptor->addField($f); + + foreach (self::$__extensions as $cb) { + $descriptor->addField($cb(), true); + } + + return $descriptor; + } + + /** + * Check if <dividend> has a value + * + * @return boolean + */ + public function hasDividend(){ + return $this->_has(1); + } + + /** + * Clear <dividend> value + * + * @return \math\DivArgs + */ + public function clearDividend(){ + return $this->_clear(1); + } + + /** + * Get <dividend> value + * + * @return int + */ + public function getDividend(){ + return $this->_get(1); + } + + /** + * Set <dividend> value + * + * @param int $value + * @return \math\DivArgs + */ + public function setDividend( $value){ + return $this->_set(1, $value); + } + + /** + * Check if <divisor> has a value + * + * @return boolean + */ + public function hasDivisor(){ + return $this->_has(2); + } + + /** + * Clear <divisor> value + * + * @return \math\DivArgs + */ + public function clearDivisor(){ + return $this->_clear(2); + } + + /** + * Get <divisor> value + * + * @return int + */ + public function getDivisor(){ + return $this->_get(2); + } + + /** + * Set <divisor> value + * + * @param int $value + * @return \math\DivArgs + */ + public function setDivisor( $value){ + return $this->_set(2, $value); + } + } +} + +namespace math { + + class DivReply extends \DrSlump\Protobuf\Message { + + /** @var int */ + public $quotient = null; + + /** @var int */ + public $remainder = null; + + + /** @var \Closure[] */ + protected static $__extensions = array(); + + public static function descriptor() + { + $descriptor = new \DrSlump\Protobuf\Descriptor(__CLASS__, 'math.DivReply'); + + // REQUIRED INT64 quotient = 1 + $f = new \DrSlump\Protobuf\Field(); + $f->number = 1; + $f->name = "quotient"; + $f->type = \DrSlump\Protobuf::TYPE_INT64; + $f->rule = \DrSlump\Protobuf::RULE_REQUIRED; + $descriptor->addField($f); + + // REQUIRED INT64 remainder = 2 + $f = new \DrSlump\Protobuf\Field(); + $f->number = 2; + $f->name = "remainder"; + $f->type = \DrSlump\Protobuf::TYPE_INT64; + $f->rule = \DrSlump\Protobuf::RULE_REQUIRED; + $descriptor->addField($f); + + foreach (self::$__extensions as $cb) { + $descriptor->addField($cb(), true); + } + + return $descriptor; + } + + /** + * Check if <quotient> has a value + * + * @return boolean + */ + public function hasQuotient(){ + return $this->_has(1); + } + + /** + * Clear <quotient> value + * + * @return \math\DivReply + */ + public function clearQuotient(){ + return $this->_clear(1); + } + + /** + * Get <quotient> value + * + * @return int + */ + public function getQuotient(){ + return $this->_get(1); + } + + /** + * Set <quotient> value + * + * @param int $value + * @return \math\DivReply + */ + public function setQuotient( $value){ + return $this->_set(1, $value); + } + + /** + * Check if <remainder> has a value + * + * @return boolean + */ + public function hasRemainder(){ + return $this->_has(2); + } + + /** + * Clear <remainder> value + * + * @return \math\DivReply + */ + public function clearRemainder(){ + return $this->_clear(2); + } + + /** + * Get <remainder> value + * + * @return int + */ + public function getRemainder(){ + return $this->_get(2); + } + + /** + * Set <remainder> value + * + * @param int $value + * @return \math\DivReply + */ + public function setRemainder( $value){ + return $this->_set(2, $value); + } + } +} + +namespace math { + + class FibArgs extends \DrSlump\Protobuf\Message { + + /** @var int */ + public $limit = null; + + + /** @var \Closure[] */ + protected static $__extensions = array(); + + public static function descriptor() + { + $descriptor = new \DrSlump\Protobuf\Descriptor(__CLASS__, 'math.FibArgs'); + + // OPTIONAL INT64 limit = 1 + $f = new \DrSlump\Protobuf\Field(); + $f->number = 1; + $f->name = "limit"; + $f->type = \DrSlump\Protobuf::TYPE_INT64; + $f->rule = \DrSlump\Protobuf::RULE_OPTIONAL; + $descriptor->addField($f); + + foreach (self::$__extensions as $cb) { + $descriptor->addField($cb(), true); + } + + return $descriptor; + } + + /** + * Check if <limit> has a value + * + * @return boolean + */ + public function hasLimit(){ + return $this->_has(1); + } + + /** + * Clear <limit> value + * + * @return \math\FibArgs + */ + public function clearLimit(){ + return $this->_clear(1); + } + + /** + * Get <limit> value + * + * @return int + */ + public function getLimit(){ + return $this->_get(1); + } + + /** + * Set <limit> value + * + * @param int $value + * @return \math\FibArgs + */ + public function setLimit( $value){ + return $this->_set(1, $value); + } + } +} + +namespace math { + + class Num extends \DrSlump\Protobuf\Message { + + /** @var int */ + public $num = null; + + + /** @var \Closure[] */ + protected static $__extensions = array(); + + public static function descriptor() + { + $descriptor = new \DrSlump\Protobuf\Descriptor(__CLASS__, 'math.Num'); + + // REQUIRED INT64 num = 1 + $f = new \DrSlump\Protobuf\Field(); + $f->number = 1; + $f->name = "num"; + $f->type = \DrSlump\Protobuf::TYPE_INT64; + $f->rule = \DrSlump\Protobuf::RULE_REQUIRED; + $descriptor->addField($f); + + foreach (self::$__extensions as $cb) { + $descriptor->addField($cb(), true); + } + + return $descriptor; + } + + /** + * Check if <num> has a value + * + * @return boolean + */ + public function hasNum(){ + return $this->_has(1); + } + + /** + * Clear <num> value + * + * @return \math\Num + */ + public function clearNum(){ + return $this->_clear(1); + } + + /** + * Get <num> value + * + * @return int + */ + public function getNum(){ + return $this->_get(1); + } + + /** + * Set <num> value + * + * @param int $value + * @return \math\Num + */ + public function setNum( $value){ + return $this->_set(1, $value); + } + } +} + +namespace math { + + class FibReply extends \DrSlump\Protobuf\Message { + + /** @var int */ + public $count = null; + + + /** @var \Closure[] */ + protected static $__extensions = array(); + + public static function descriptor() + { + $descriptor = new \DrSlump\Protobuf\Descriptor(__CLASS__, 'math.FibReply'); + + // REQUIRED INT64 count = 1 + $f = new \DrSlump\Protobuf\Field(); + $f->number = 1; + $f->name = "count"; + $f->type = \DrSlump\Protobuf::TYPE_INT64; + $f->rule = \DrSlump\Protobuf::RULE_REQUIRED; + $descriptor->addField($f); + + foreach (self::$__extensions as $cb) { + $descriptor->addField($cb(), true); + } + + return $descriptor; + } + + /** + * Check if <count> has a value + * + * @return boolean + */ + public function hasCount(){ + return $this->_has(1); + } + + /** + * Clear <count> value + * + * @return \math\FibReply + */ + public function clearCount(){ + return $this->_clear(1); + } + + /** + * Get <count> value + * + * @return int + */ + public function getCount(){ + return $this->_get(1); + } + + /** + * Set <count> value + * + * @param int $value + * @return \math\FibReply + */ + public function setCount( $value){ + return $this->_set(1, $value); + } + } +} + +namespace math { + + class MathClient extends \Grpc\BaseStub { + /** + * @param math\DivArgs $input + * @return math\DivReply + */ + public function Div(\math\DivArgs $argument, $metadata = array()) { + return $this->_simpleRequest('/Math/Div', $argument, '\math\DivReply::deserialize', $metadata); + } + /** + * @param math\DivArgs $input + * @return math\DivReply + */ + public function DivMany($metadata = array()) { + return $this->_bidiRequest('/Math/DivMany', '\math\DivReply::deserialize', $metadata); + } + /** + * @param math\FibArgs $input + * @return math\Num + */ + public function Fib($argument, $metadata = array()) { + return $this->_serverStreamRequest('/Math/Fib', $argument, '\math\Num::deserialize', $metadata); + } + /** + * @param math\Num $input + * @return math\Num + */ + public function Sum($arguments, $metadata = array()) { + return $this->_clientStreamRequest('/Math/Sum', $arguments, '\math\Num::deserialize', $metadata); + } + } +} diff --git a/src/php/tests/interop/empty.php b/src/php/tests/interop/empty.php new file mode 100755 index 0000000000000000000000000000000000000000..0107f2530b99fb15d6b95501128e60c38d138b57 --- /dev/null +++ b/src/php/tests/interop/empty.php @@ -0,0 +1,26 @@ +<?php +// DO NOT EDIT! Generated by Protobuf-PHP protoc plugin 1.0 +// Source: net/proto2/proto/empty.proto +// Date: 2014-12-03 22:02:20 + +namespace proto2 { + + class EmptyMessage extends \DrSlump\Protobuf\Message { + + + /** @var \Closure[] */ + protected static $__extensions = array(); + + public static function descriptor() + { + $descriptor = new \DrSlump\Protobuf\Descriptor(__CLASS__, 'proto2.EmptyMessage'); + + foreach (self::$__extensions as $cb) { + $descriptor->addField($cb(), true); + } + + return $descriptor; + } + } +} + diff --git a/src/php/tests/interop/interop_client.php b/src/php/tests/interop/interop_client.php new file mode 100755 index 0000000000000000000000000000000000000000..9810c86272852a7d3c7819d42990a5f8a142e390 --- /dev/null +++ b/src/php/tests/interop/interop_client.php @@ -0,0 +1,191 @@ +<?php +require __DIR__ . '/../../lib/Grpc/ActiveCall.php'; +require __DIR__ . '/../../lib/Grpc/SurfaceActiveCall.php'; +require __DIR__ . '/../../lib/Grpc/BaseStub.php'; +require 'DrSlump/Protobuf.php'; +\DrSlump\Protobuf::autoload(); +require 'empty.php'; +require 'message_set.php'; +require 'messages.php'; +require 'test.php'; +/** + * Assertion function that always exits with an error code if the assertion is + * falsy + * @param $value Assertion value. Should be true. + * @param $error_message Message to display if the assertion is false + */ +function hardAssert($value, $error_message) { + if(!$value) { + echo $error_message . "\n"; + exit(1); + } +} + +/** + * Run the empty_unary test. + * Currently not tested against any server as of 2014-12-04 + * @param $stub Stub object that has service methods + */ +function emptyUnary($stub) { + list($result, $status) = $stub->EmptyCall(new proto2\EmptyMessage())->wait(); + hardAssert($status->code == Grpc\STATUS_OK, 'Call did not complete successfully'); + hardAssert($result != null, 'Call completed with a null response'); +} + +/** + * Run the large_unary test. + * Passes when run against the C++ server as of 2014-12-04 + * Not tested against any other server as of 2014-12-04 + * @param $stub Stub object that has service methods + */ +function largeUnary($stub) { + $request_len = 271828; + $response_len = 314159; + + $request = new grpc\testing\SimpleRequest(); + $request->setResponseType(grpc\testing\PayloadType::COMPRESSABLE); + $request->setResponseSize($response_len); + $payload = new grpc\testing\Payload(); + $payload->setType(grpc\testing\PayloadType::COMPRESSABLE); + $payload->setBody(str_repeat("\0", $request_len)); + $request->setPayload($payload); + + list($result, $status) = $stub->UnaryCall($request)->wait(); + hardAssert($status->code == Grpc\STATUS_OK, 'Call did not complete successfully'); + hardAssert($result != null, 'Call returned a null response'); + $payload = $result->getPayload(); + hardAssert($payload->getType() == grpc\testing\PayloadType::COMPRESSABLE, + 'Payload had the wrong type'); + hardAssert(strlen($payload->getBody()) == $response_len, + 'Payload had the wrong length'); + hardAssert($payload->getBody() == str_repeat("\0", $response_len), + 'Payload had the wrong content'); +} + +/** + * Run the client_streaming test. + * Not tested against any server as of 2014-12-04. + * @param $stub Stub object that has service methods + */ +function clientStreaming($stub) { + $request_lengths = array(27182, 8, 1828, 45904); + + $requests = array_map( + function($length) { + $request = new grpc\testing\StreamingInputCallRequest(); + $payload = new grpc\testing\Payload(); + $payload->setBody(str_repeat("\0", $length)); + $request->setPayload($payload); + return $request; + }, $request_lengths); + + list($result, $status) = $stub->StreamingInputCall($requests)->wait(); + hardAssert($status->code == Grpc\STATUS_OK, 'Call did not complete successfully'); + hardAssert($result->getAggregatedPayloadSize() == 74922, + 'aggregated_payload_size was incorrect'); +} + +/** + * Run the server_streaming test. + * Not tested against any server as of 2014-12-04. + * @param $stub Stub object that has service methods. + */ +function serverStreaming($stub) { + $sizes = array(31415, 9, 2653, 58979); + + $request = new grpc\testing\StreamingOutputCallRequest(); + $request->setResponseType(grpc\testing\PayloadType::COMPRESSABLE); + foreach($sizes as $size) { + $response_parameters = new grpc\testing\ResponseParameters(); + $response_parameters->setSize($size); + $request->addResponseParameters($response_parameters); + } + + $call = $stub->StreamingOutputCall($request); + hardAssert($call->getStatus()->code == Grpc\STATUS_OK, + 'Call did not complete successfully'); + $i = 0; + foreach($call->responses() as $value) { + hardAssert($i < 4, 'Too many responses'); + $payload = $value->getPayload(); + hardAssert($payload->getType() == grpc\testing\PayloadType::COMPRESSABLE, + 'Payload ' . $i . ' had the wrong type'); + hardAssert(strlen($payload->getBody()) == $sizes[$i], + 'Response ' . $i . ' had the wrong length'); + } +} + +/** + * Run the ping_pong test. + * Not tested against any server as of 2014-12-04. + * @param $stub Stub object that has service methods. + */ +function pingPong($stub) { + $request_lengths = array(27182, 8, 1828, 45904); + $response_lengths = array(31415, 9, 2653, 58979); + + $call = $stub->FullDuplexCall(); + for($i = 0; $i < 4; $i++) { + $request = new grpc\testing\StreamingOutputCallRequest(); + $request->setResponseType(grpc\testing\PayloadType::COMPRESSABLE); + $response_parameters = new grpc\testing\ResponseParameters(); + $response_parameters->setSize($response_lengths[$i]); + $request->addResponseParameters($response_parameters); + $payload = new grpc\testing\Payload(); + $payload->setBody(str_repeat("\0", $request_lengths[$i])); + $request->setPayload($payload); + + $call->write($request); + $response = $call->read(); + + hardAssert($response != null, 'Server returned too few responses'); + $payload = $response->getPayload(); + hardAssert($payload->getType() == grpc\testing\PayloadType::COMPRESSABLE, + 'Payload ' . $i . ' had the wrong type'); + hardAssert(strlen($payload->getBody()) == $response_lengths[$i], + 'Payload ' . $i . ' had the wrong length'); + } + $call->writesDone(); + hardAssert($call->read() == null, 'Server returned too many responses'); + hardAssert($call->getStatus()->code == Grpc\STATUS_OK, + 'Call did not complete successfully'); +} + +$args = getopt('', array('server_host:', 'server_port:', 'test_case:')); +if (!array_key_exists('server_host', $args) || + !array_key_exists('server_port', $args) || + !array_key_exists('test_case', $args)) { + throw new Exception('Missing argument'); +} + +$server_address = $args['server_host'] . ':' . $args['server_port']; + +$credentials = Grpc\Credentials::createSsl( + file_get_contents(dirname(__FILE__) . '/../data/ca.pem')); +$stub = new grpc\testing\TestServiceClient( + $server_address, + [ + 'grpc.ssl_target_name_override' => 'foo.test.google.com', + 'credentials' => $credentials + ]); + +echo "Connecting to $server_address\n"; +echo "Running test case $args[test_case]\n"; + +switch($args['test_case']) { + case 'empty_unary': + emptyUnary($stub); + break; + case 'large_unary': + largeUnary($stub); + break; + case 'client_streaming': + clientStreaming($stub); + break; + case 'server_streaming': + serverStreaming($stub); + break; + case 'ping_pong': + pingPong($stub); + break; +} \ No newline at end of file diff --git a/src/php/tests/interop/message_set.php b/src/php/tests/interop/message_set.php new file mode 100755 index 0000000000000000000000000000000000000000..c35c6d74b2437e58e2130f88b950c61677bd59a1 --- /dev/null +++ b/src/php/tests/interop/message_set.php @@ -0,0 +1,26 @@ +<?php +// DO NOT EDIT! Generated by Protobuf-PHP protoc plugin 1.0 +// Source: net/proto2/bridge/proto/message_set.proto +// Date: 2014-12-03 22:02:20 + +namespace proto2\bridge { + + class MessageSet extends \DrSlump\Protobuf\Message { + + + /** @var \Closure[] */ + protected static $__extensions = array(); + + public static function descriptor() + { + $descriptor = new \DrSlump\Protobuf\Descriptor(__CLASS__, 'proto2.bridge.MessageSet'); + + foreach (self::$__extensions as $cb) { + $descriptor->addField($cb(), true); + } + + return $descriptor; + } + } +} + diff --git a/src/php/tests/interop/messages.php b/src/php/tests/interop/messages.php new file mode 100755 index 0000000000000000000000000000000000000000..beaec7c0d875d0e001a11cea5ce1e79c25e5c860 --- /dev/null +++ b/src/php/tests/interop/messages.php @@ -0,0 +1,1011 @@ +<?php +// DO NOT EDIT! Generated by Protobuf-PHP protoc plugin 1.0 +// Source: third_party/stubby/testing/proto/messages.proto +// Date: 2014-12-03 22:02:20 + +namespace grpc\testing { + + class PayloadType extends \DrSlump\Protobuf\Enum { + const COMPRESSABLE = 0; + const UNCOMPRESSABLE = 1; + const RANDOM = 2; + } +} +namespace grpc\testing { + + class Payload extends \DrSlump\Protobuf\Message { + + /** @var int - \grpc\testing\PayloadType */ + public $type = null; + + /** @var string */ + public $body = null; + + + /** @var \Closure[] */ + protected static $__extensions = array(); + + public static function descriptor() + { + $descriptor = new \DrSlump\Protobuf\Descriptor(__CLASS__, 'grpc.testing.Payload'); + + // OPTIONAL ENUM type = 1 + $f = new \DrSlump\Protobuf\Field(); + $f->number = 1; + $f->name = "type"; + $f->type = \DrSlump\Protobuf::TYPE_ENUM; + $f->rule = \DrSlump\Protobuf::RULE_OPTIONAL; + $f->reference = '\grpc\testing\PayloadType'; + $descriptor->addField($f); + + // OPTIONAL BYTES body = 2 + $f = new \DrSlump\Protobuf\Field(); + $f->number = 2; + $f->name = "body"; + $f->type = \DrSlump\Protobuf::TYPE_BYTES; + $f->rule = \DrSlump\Protobuf::RULE_OPTIONAL; + $descriptor->addField($f); + + foreach (self::$__extensions as $cb) { + $descriptor->addField($cb(), true); + } + + return $descriptor; + } + + /** + * Check if <type> has a value + * + * @return boolean + */ + public function hasType(){ + return $this->_has(1); + } + + /** + * Clear <type> value + * + * @return \grpc\testing\Payload + */ + public function clearType(){ + return $this->_clear(1); + } + + /** + * Get <type> value + * + * @return int - \grpc\testing\PayloadType + */ + public function getType(){ + return $this->_get(1); + } + + /** + * Set <type> value + * + * @param int - \grpc\testing\PayloadType $value + * @return \grpc\testing\Payload + */ + public function setType( $value){ + return $this->_set(1, $value); + } + + /** + * Check if <body> has a value + * + * @return boolean + */ + public function hasBody(){ + return $this->_has(2); + } + + /** + * Clear <body> value + * + * @return \grpc\testing\Payload + */ + public function clearBody(){ + return $this->_clear(2); + } + + /** + * Get <body> value + * + * @return string + */ + public function getBody(){ + return $this->_get(2); + } + + /** + * Set <body> value + * + * @param string $value + * @return \grpc\testing\Payload + */ + public function setBody( $value){ + return $this->_set(2, $value); + } + } +} + +namespace grpc\testing { + + class SimpleRequest extends \DrSlump\Protobuf\Message { + + /** @var int - \grpc\testing\PayloadType */ + public $response_type = null; + + /** @var int */ + public $response_size = null; + + /** @var \grpc\testing\Payload */ + public $payload = null; + + + /** @var \Closure[] */ + protected static $__extensions = array(); + + public static function descriptor() + { + $descriptor = new \DrSlump\Protobuf\Descriptor(__CLASS__, 'grpc.testing.SimpleRequest'); + + // OPTIONAL ENUM response_type = 1 + $f = new \DrSlump\Protobuf\Field(); + $f->number = 1; + $f->name = "response_type"; + $f->type = \DrSlump\Protobuf::TYPE_ENUM; + $f->rule = \DrSlump\Protobuf::RULE_OPTIONAL; + $f->reference = '\grpc\testing\PayloadType'; + $descriptor->addField($f); + + // OPTIONAL INT32 response_size = 2 + $f = new \DrSlump\Protobuf\Field(); + $f->number = 2; + $f->name = "response_size"; + $f->type = \DrSlump\Protobuf::TYPE_INT32; + $f->rule = \DrSlump\Protobuf::RULE_OPTIONAL; + $descriptor->addField($f); + + // OPTIONAL MESSAGE payload = 3 + $f = new \DrSlump\Protobuf\Field(); + $f->number = 3; + $f->name = "payload"; + $f->type = \DrSlump\Protobuf::TYPE_MESSAGE; + $f->rule = \DrSlump\Protobuf::RULE_OPTIONAL; + $f->reference = '\grpc\testing\Payload'; + $descriptor->addField($f); + + foreach (self::$__extensions as $cb) { + $descriptor->addField($cb(), true); + } + + return $descriptor; + } + + /** + * Check if <response_type> has a value + * + * @return boolean + */ + public function hasResponseType(){ + return $this->_has(1); + } + + /** + * Clear <response_type> value + * + * @return \grpc\testing\SimpleRequest + */ + public function clearResponseType(){ + return $this->_clear(1); + } + + /** + * Get <response_type> value + * + * @return int - \grpc\testing\PayloadType + */ + public function getResponseType(){ + return $this->_get(1); + } + + /** + * Set <response_type> value + * + * @param int - \grpc\testing\PayloadType $value + * @return \grpc\testing\SimpleRequest + */ + public function setResponseType( $value){ + return $this->_set(1, $value); + } + + /** + * Check if <response_size> has a value + * + * @return boolean + */ + public function hasResponseSize(){ + return $this->_has(2); + } + + /** + * Clear <response_size> value + * + * @return \grpc\testing\SimpleRequest + */ + public function clearResponseSize(){ + return $this->_clear(2); + } + + /** + * Get <response_size> value + * + * @return int + */ + public function getResponseSize(){ + return $this->_get(2); + } + + /** + * Set <response_size> value + * + * @param int $value + * @return \grpc\testing\SimpleRequest + */ + public function setResponseSize( $value){ + return $this->_set(2, $value); + } + + /** + * Check if <payload> has a value + * + * @return boolean + */ + public function hasPayload(){ + return $this->_has(3); + } + + /** + * Clear <payload> value + * + * @return \grpc\testing\SimpleRequest + */ + public function clearPayload(){ + return $this->_clear(3); + } + + /** + * Get <payload> value + * + * @return \grpc\testing\Payload + */ + public function getPayload(){ + return $this->_get(3); + } + + /** + * Set <payload> value + * + * @param \grpc\testing\Payload $value + * @return \grpc\testing\SimpleRequest + */ + public function setPayload(\grpc\testing\Payload $value){ + return $this->_set(3, $value); + } + } +} + +namespace grpc\testing { + + class SimpleResponse extends \DrSlump\Protobuf\Message { + + /** @var \grpc\testing\Payload */ + public $payload = null; + + /** @var int */ + public $effective_gaia_user_id = null; + + + /** @var \Closure[] */ + protected static $__extensions = array(); + + public static function descriptor() + { + $descriptor = new \DrSlump\Protobuf\Descriptor(__CLASS__, 'grpc.testing.SimpleResponse'); + + // OPTIONAL MESSAGE payload = 1 + $f = new \DrSlump\Protobuf\Field(); + $f->number = 1; + $f->name = "payload"; + $f->type = \DrSlump\Protobuf::TYPE_MESSAGE; + $f->rule = \DrSlump\Protobuf::RULE_OPTIONAL; + $f->reference = '\grpc\testing\Payload'; + $descriptor->addField($f); + + // OPTIONAL INT64 effective_gaia_user_id = 2 + $f = new \DrSlump\Protobuf\Field(); + $f->number = 2; + $f->name = "effective_gaia_user_id"; + $f->type = \DrSlump\Protobuf::TYPE_INT64; + $f->rule = \DrSlump\Protobuf::RULE_OPTIONAL; + $descriptor->addField($f); + + foreach (self::$__extensions as $cb) { + $descriptor->addField($cb(), true); + } + + return $descriptor; + } + + /** + * Check if <payload> has a value + * + * @return boolean + */ + public function hasPayload(){ + return $this->_has(1); + } + + /** + * Clear <payload> value + * + * @return \grpc\testing\SimpleResponse + */ + public function clearPayload(){ + return $this->_clear(1); + } + + /** + * Get <payload> value + * + * @return \grpc\testing\Payload + */ + public function getPayload(){ + return $this->_get(1); + } + + /** + * Set <payload> value + * + * @param \grpc\testing\Payload $value + * @return \grpc\testing\SimpleResponse + */ + public function setPayload(\grpc\testing\Payload $value){ + return $this->_set(1, $value); + } + + /** + * Check if <effective_gaia_user_id> has a value + * + * @return boolean + */ + public function hasEffectiveGaiaUserId(){ + return $this->_has(2); + } + + /** + * Clear <effective_gaia_user_id> value + * + * @return \grpc\testing\SimpleResponse + */ + public function clearEffectiveGaiaUserId(){ + return $this->_clear(2); + } + + /** + * Get <effective_gaia_user_id> value + * + * @return int + */ + public function getEffectiveGaiaUserId(){ + return $this->_get(2); + } + + /** + * Set <effective_gaia_user_id> value + * + * @param int $value + * @return \grpc\testing\SimpleResponse + */ + public function setEffectiveGaiaUserId( $value){ + return $this->_set(2, $value); + } + } +} + +namespace grpc\testing { + + class SimpleContext extends \DrSlump\Protobuf\Message { + + /** @var string */ + public $value = null; + + + /** @var \Closure[] */ + protected static $__extensions = array(); + + public static function descriptor() + { + $descriptor = new \DrSlump\Protobuf\Descriptor(__CLASS__, 'grpc.testing.SimpleContext'); + + // OPTIONAL STRING value = 1 + $f = new \DrSlump\Protobuf\Field(); + $f->number = 1; + $f->name = "value"; + $f->type = \DrSlump\Protobuf::TYPE_STRING; + $f->rule = \DrSlump\Protobuf::RULE_OPTIONAL; + $descriptor->addField($f); + + foreach (self::$__extensions as $cb) { + $descriptor->addField($cb(), true); + } + + return $descriptor; + } + + /** + * Check if <value> has a value + * + * @return boolean + */ + public function hasValue(){ + return $this->_has(1); + } + + /** + * Clear <value> value + * + * @return \grpc\testing\SimpleContext + */ + public function clearValue(){ + return $this->_clear(1); + } + + /** + * Get <value> value + * + * @return string + */ + public function getValue(){ + return $this->_get(1); + } + + /** + * Set <value> value + * + * @param string $value + * @return \grpc\testing\SimpleContext + */ + public function setValue( $value){ + return $this->_set(1, $value); + } + } +} + +namespace grpc\testing { + + class StreamingInputCallRequest extends \DrSlump\Protobuf\Message { + + /** @var \grpc\testing\Payload */ + public $payload = null; + + + /** @var \Closure[] */ + protected static $__extensions = array(); + + public static function descriptor() + { + $descriptor = new \DrSlump\Protobuf\Descriptor(__CLASS__, 'grpc.testing.StreamingInputCallRequest'); + + // OPTIONAL MESSAGE payload = 1 + $f = new \DrSlump\Protobuf\Field(); + $f->number = 1; + $f->name = "payload"; + $f->type = \DrSlump\Protobuf::TYPE_MESSAGE; + $f->rule = \DrSlump\Protobuf::RULE_OPTIONAL; + $f->reference = '\grpc\testing\Payload'; + $descriptor->addField($f); + + foreach (self::$__extensions as $cb) { + $descriptor->addField($cb(), true); + } + + return $descriptor; + } + + /** + * Check if <payload> has a value + * + * @return boolean + */ + public function hasPayload(){ + return $this->_has(1); + } + + /** + * Clear <payload> value + * + * @return \grpc\testing\StreamingInputCallRequest + */ + public function clearPayload(){ + return $this->_clear(1); + } + + /** + * Get <payload> value + * + * @return \grpc\testing\Payload + */ + public function getPayload(){ + return $this->_get(1); + } + + /** + * Set <payload> value + * + * @param \grpc\testing\Payload $value + * @return \grpc\testing\StreamingInputCallRequest + */ + public function setPayload(\grpc\testing\Payload $value){ + return $this->_set(1, $value); + } + } +} + +namespace grpc\testing { + + class StreamingInputCallResponse extends \DrSlump\Protobuf\Message { + + /** @var int */ + public $aggregated_payload_size = null; + + + /** @var \Closure[] */ + protected static $__extensions = array(); + + public static function descriptor() + { + $descriptor = new \DrSlump\Protobuf\Descriptor(__CLASS__, 'grpc.testing.StreamingInputCallResponse'); + + // OPTIONAL INT32 aggregated_payload_size = 1 + $f = new \DrSlump\Protobuf\Field(); + $f->number = 1; + $f->name = "aggregated_payload_size"; + $f->type = \DrSlump\Protobuf::TYPE_INT32; + $f->rule = \DrSlump\Protobuf::RULE_OPTIONAL; + $descriptor->addField($f); + + foreach (self::$__extensions as $cb) { + $descriptor->addField($cb(), true); + } + + return $descriptor; + } + + /** + * Check if <aggregated_payload_size> has a value + * + * @return boolean + */ + public function hasAggregatedPayloadSize(){ + return $this->_has(1); + } + + /** + * Clear <aggregated_payload_size> value + * + * @return \grpc\testing\StreamingInputCallResponse + */ + public function clearAggregatedPayloadSize(){ + return $this->_clear(1); + } + + /** + * Get <aggregated_payload_size> value + * + * @return int + */ + public function getAggregatedPayloadSize(){ + return $this->_get(1); + } + + /** + * Set <aggregated_payload_size> value + * + * @param int $value + * @return \grpc\testing\StreamingInputCallResponse + */ + public function setAggregatedPayloadSize( $value){ + return $this->_set(1, $value); + } + } +} + +namespace grpc\testing { + + class ResponseParameters extends \DrSlump\Protobuf\Message { + + /** @var int */ + public $size = null; + + /** @var int */ + public $interval_us = null; + + + /** @var \Closure[] */ + protected static $__extensions = array(); + + public static function descriptor() + { + $descriptor = new \DrSlump\Protobuf\Descriptor(__CLASS__, 'grpc.testing.ResponseParameters'); + + // OPTIONAL INT32 size = 1 + $f = new \DrSlump\Protobuf\Field(); + $f->number = 1; + $f->name = "size"; + $f->type = \DrSlump\Protobuf::TYPE_INT32; + $f->rule = \DrSlump\Protobuf::RULE_OPTIONAL; + $descriptor->addField($f); + + // OPTIONAL INT32 interval_us = 2 + $f = new \DrSlump\Protobuf\Field(); + $f->number = 2; + $f->name = "interval_us"; + $f->type = \DrSlump\Protobuf::TYPE_INT32; + $f->rule = \DrSlump\Protobuf::RULE_OPTIONAL; + $descriptor->addField($f); + + foreach (self::$__extensions as $cb) { + $descriptor->addField($cb(), true); + } + + return $descriptor; + } + + /** + * Check if <size> has a value + * + * @return boolean + */ + public function hasSize(){ + return $this->_has(1); + } + + /** + * Clear <size> value + * + * @return \grpc\testing\ResponseParameters + */ + public function clearSize(){ + return $this->_clear(1); + } + + /** + * Get <size> value + * + * @return int + */ + public function getSize(){ + return $this->_get(1); + } + + /** + * Set <size> value + * + * @param int $value + * @return \grpc\testing\ResponseParameters + */ + public function setSize( $value){ + return $this->_set(1, $value); + } + + /** + * Check if <interval_us> has a value + * + * @return boolean + */ + public function hasIntervalUs(){ + return $this->_has(2); + } + + /** + * Clear <interval_us> value + * + * @return \grpc\testing\ResponseParameters + */ + public function clearIntervalUs(){ + return $this->_clear(2); + } + + /** + * Get <interval_us> value + * + * @return int + */ + public function getIntervalUs(){ + return $this->_get(2); + } + + /** + * Set <interval_us> value + * + * @param int $value + * @return \grpc\testing\ResponseParameters + */ + public function setIntervalUs( $value){ + return $this->_set(2, $value); + } + } +} + +namespace grpc\testing { + + class StreamingOutputCallRequest extends \DrSlump\Protobuf\Message { + + /** @var int - \grpc\testing\PayloadType */ + public $response_type = null; + + /** @var \grpc\testing\ResponseParameters[] */ + public $response_parameters = array(); + + /** @var \grpc\testing\Payload */ + public $payload = null; + + + /** @var \Closure[] */ + protected static $__extensions = array(); + + public static function descriptor() + { + $descriptor = new \DrSlump\Protobuf\Descriptor(__CLASS__, 'grpc.testing.StreamingOutputCallRequest'); + + // OPTIONAL ENUM response_type = 1 + $f = new \DrSlump\Protobuf\Field(); + $f->number = 1; + $f->name = "response_type"; + $f->type = \DrSlump\Protobuf::TYPE_ENUM; + $f->rule = \DrSlump\Protobuf::RULE_OPTIONAL; + $f->reference = '\grpc\testing\PayloadType'; + $descriptor->addField($f); + + // REPEATED MESSAGE response_parameters = 2 + $f = new \DrSlump\Protobuf\Field(); + $f->number = 2; + $f->name = "response_parameters"; + $f->type = \DrSlump\Protobuf::TYPE_MESSAGE; + $f->rule = \DrSlump\Protobuf::RULE_REPEATED; + $f->reference = '\grpc\testing\ResponseParameters'; + $descriptor->addField($f); + + // OPTIONAL MESSAGE payload = 3 + $f = new \DrSlump\Protobuf\Field(); + $f->number = 3; + $f->name = "payload"; + $f->type = \DrSlump\Protobuf::TYPE_MESSAGE; + $f->rule = \DrSlump\Protobuf::RULE_OPTIONAL; + $f->reference = '\grpc\testing\Payload'; + $descriptor->addField($f); + + foreach (self::$__extensions as $cb) { + $descriptor->addField($cb(), true); + } + + return $descriptor; + } + + /** + * Check if <response_type> has a value + * + * @return boolean + */ + public function hasResponseType(){ + return $this->_has(1); + } + + /** + * Clear <response_type> value + * + * @return \grpc\testing\StreamingOutputCallRequest + */ + public function clearResponseType(){ + return $this->_clear(1); + } + + /** + * Get <response_type> value + * + * @return int - \grpc\testing\PayloadType + */ + public function getResponseType(){ + return $this->_get(1); + } + + /** + * Set <response_type> value + * + * @param int - \grpc\testing\PayloadType $value + * @return \grpc\testing\StreamingOutputCallRequest + */ + public function setResponseType( $value){ + return $this->_set(1, $value); + } + + /** + * Check if <response_parameters> has a value + * + * @return boolean + */ + public function hasResponseParameters(){ + return $this->_has(2); + } + + /** + * Clear <response_parameters> value + * + * @return \grpc\testing\StreamingOutputCallRequest + */ + public function clearResponseParameters(){ + return $this->_clear(2); + } + + /** + * Get <response_parameters> value + * + * @param int $idx + * @return \grpc\testing\ResponseParameters + */ + public function getResponseParameters($idx = NULL){ + return $this->_get(2, $idx); + } + + /** + * Set <response_parameters> value + * + * @param \grpc\testing\ResponseParameters $value + * @return \grpc\testing\StreamingOutputCallRequest + */ + public function setResponseParameters(\grpc\testing\ResponseParameters $value, $idx = NULL){ + return $this->_set(2, $value, $idx); + } + + /** + * Get all elements of <response_parameters> + * + * @return \grpc\testing\ResponseParameters[] + */ + public function getResponseParametersList(){ + return $this->_get(2); + } + + /** + * Add a new element to <response_parameters> + * + * @param \grpc\testing\ResponseParameters $value + * @return \grpc\testing\StreamingOutputCallRequest + */ + public function addResponseParameters(\grpc\testing\ResponseParameters $value){ + return $this->_add(2, $value); + } + + /** + * Check if <payload> has a value + * + * @return boolean + */ + public function hasPayload(){ + return $this->_has(3); + } + + /** + * Clear <payload> value + * + * @return \grpc\testing\StreamingOutputCallRequest + */ + public function clearPayload(){ + return $this->_clear(3); + } + + /** + * Get <payload> value + * + * @return \grpc\testing\Payload + */ + public function getPayload(){ + return $this->_get(3); + } + + /** + * Set <payload> value + * + * @param \grpc\testing\Payload $value + * @return \grpc\testing\StreamingOutputCallRequest + */ + public function setPayload(\grpc\testing\Payload $value){ + return $this->_set(3, $value); + } + } +} + +namespace grpc\testing { + + class StreamingOutputCallResponse extends \DrSlump\Protobuf\Message { + + /** @var \grpc\testing\Payload */ + public $payload = null; + + + /** @var \Closure[] */ + protected static $__extensions = array(); + + public static function descriptor() + { + $descriptor = new \DrSlump\Protobuf\Descriptor(__CLASS__, 'grpc.testing.StreamingOutputCallResponse'); + + // OPTIONAL MESSAGE payload = 1 + $f = new \DrSlump\Protobuf\Field(); + $f->number = 1; + $f->name = "payload"; + $f->type = \DrSlump\Protobuf::TYPE_MESSAGE; + $f->rule = \DrSlump\Protobuf::RULE_OPTIONAL; + $f->reference = '\grpc\testing\Payload'; + $descriptor->addField($f); + + foreach (self::$__extensions as $cb) { + $descriptor->addField($cb(), true); + } + + return $descriptor; + } + + /** + * Check if <payload> has a value + * + * @return boolean + */ + public function hasPayload(){ + return $this->_has(1); + } + + /** + * Clear <payload> value + * + * @return \grpc\testing\StreamingOutputCallResponse + */ + public function clearPayload(){ + return $this->_clear(1); + } + + /** + * Get <payload> value + * + * @return \grpc\testing\Payload + */ + public function getPayload(){ + return $this->_get(1); + } + + /** + * Set <payload> value + * + * @param \grpc\testing\Payload $value + * @return \grpc\testing\StreamingOutputCallResponse + */ + public function setPayload(\grpc\testing\Payload $value){ + return $this->_set(1, $value); + } + } +} + +namespace { + \proto2\bridge\MessageSet::extension(function(){ + // OPTIONAL MESSAGE grpc\testing\SimpleContext\message_set_extension = 71139615 + $f = new \DrSlump\Protobuf\Field(); + $f->number = 71139615; + $f->name = "grpc\testing\SimpleContext\message_set_extension"; + $f->type = \DrSlump\Protobuf::TYPE_MESSAGE; + $f->rule = \DrSlump\Protobuf::RULE_OPTIONAL; + $f->reference = '\grpc\testing\SimpleContext'; + return $f; + }); +} \ No newline at end of file diff --git a/src/php/tests/interop/test.php b/src/php/tests/interop/test.php new file mode 100755 index 0000000000000000000000000000000000000000..fe6d0fb6c45d2c3dbe1ce7b0f5bd7d3fa527f03b --- /dev/null +++ b/src/php/tests/interop/test.php @@ -0,0 +1,52 @@ +<?php +// DO NOT EDIT! Generated by Protobuf-PHP protoc plugin 1.0 +// Source: third_party/stubby/testing/proto/test.proto +// Date: 2014-12-03 22:02:20 + +namespace grpc\testing { + + class TestServiceClient extends \Grpc\BaseStub { + /** + * @param proto2\EmptyMessage $input + * @return proto2\EmptyMessage + */ + public function EmptyCall(\proto2\EmptyMessage $argument, $metadata = array()) { + return $this->_simpleRequest('/TestService/EmptyCall', $argument, '\proto2\EmptyMessage::deserialize', $metadata); + } + /** + * @param grpc\testing\SimpleRequest $input + * @return grpc\testing\SimpleResponse + */ + public function UnaryCall(\grpc\testing\SimpleRequest $argument, $metadata = array()) { + return $this->_simpleRequest('/TestService/UnaryCall', $argument, '\grpc\testing\SimpleResponse::deserialize', $metadata); + } + /** + * @param grpc\testing\StreamingOutputCallRequest $input + * @return grpc\testing\StreamingOutputCallResponse + */ + public function StreamingOutputCall($argument, $metadata = array()) { + return $this->_serverStreamRequest('/TestService/StreamingOutputCall', $argument, '\grpc\testing\StreamingOutputCallResponse::deserialize', $metadata); + } + /** + * @param grpc\testing\StreamingInputCallRequest $input + * @return grpc\testing\StreamingInputCallResponse + */ + public function StreamingInputCall($arguments, $metadata = array()) { + return $this->_clientStreamRequest('/TestService/StreamingInputCall', $arguments, '\grpc\testing\StreamingInputCallResponse::deserialize', $metadata); + } + /** + * @param grpc\testing\StreamingOutputCallRequest $input + * @return grpc\testing\StreamingOutputCallResponse + */ + public function FullDuplexCall($metadata = array()) { + return $this->_bidiRequest('/TestService/FullDuplexCall', '\grpc\testing\StreamingOutputCallResponse::deserialize', $metadata); + } + /** + * @param grpc\testing\StreamingOutputCallRequest $input + * @return grpc\testing\StreamingOutputCallResponse + */ + public function HalfDuplexCall($metadata = array()) { + return $this->_bidiRequest('/TestService/HalfDuplexCall', '\grpc\testing\StreamingOutputCallResponse::deserialize', $metadata); + } + } +} diff --git a/src/php/tests/unit_tests/CallTest.php b/src/php/tests/unit_tests/CallTest.php new file mode 100755 index 0000000000000000000000000000000000000000..150b8c38609706b674b6f932fa085d6fc4bb0882 --- /dev/null +++ b/src/php/tests/unit_tests/CallTest.php @@ -0,0 +1,48 @@ +<?php +class CallTest extends PHPUnit_Framework_TestCase{ + static $server; + + public static function setUpBeforeClass() { + $cq = new Grpc\CompletionQueue(); + self::$server = new Grpc\Server($cq, []); + self::$server->add_http2_port('localhost:9001'); + } + + public function setUp() { + $this->channel = new Grpc\Channel('localhost:9001', []); + $this->call = new Grpc\Call($this->channel, + '/foo', + Grpc\Timeval::inf_future()); + } + + /* These test methods with assertTrue(true) at the end just check that the + method calls completed without errors. PHPUnit warns for tests with no + asserts, and this avoids that warning without changing the meaning of the + tests */ + + public function testAddEmptyMetadata() { + $this->call->add_metadata([], 0); + /* Dummy assert: Checks that the previous call completed without error */ + $this->assertTrue(true); + } + + public function testAddSingleMetadata() { + $this->call->add_metadata(['key' => 'value'], 0); + /* Dummy assert: Checks that the previous call completed without error */ + $this->assertTrue(true); + } + + public function testAddMultiValueMetadata() { + $this->call->add_metadata(['key' => ['value1', 'value2']], 0); + /* Dummy assert: Checks that the previous call completed without error */ + $this->assertTrue(true); + } + + public function testAddSingleAndMultiValueMetadata() { + $this->call->add_metadata( + ['key1' => 'value1', + 'key2' => ['value2', 'value3']], 0); + /* Dummy assert: Checks that the previous call completed without error */ + $this->assertTrue(true); + } +} diff --git a/src/php/tests/unit_tests/CompletionQueueTest.php b/src/php/tests/unit_tests/CompletionQueueTest.php new file mode 100755 index 0000000000000000000000000000000000000000..f88cd6300275150bd68c11a7c1c19fc11aa531a7 --- /dev/null +++ b/src/php/tests/unit_tests/CompletionQueueTest.php @@ -0,0 +1,14 @@ +<?php +class CompletionQueueTest extends PHPUnit_Framework_TestCase{ + public function testNextReturnsNullWithNoCall() { + $cq = new Grpc\CompletionQueue(); + $event = $cq->next(Grpc\Timeval::zero()); + $this->assertNull($event); + } + + public function testPluckReturnsNullWithNoCall() { + $cq = new Grpc\CompletionQueue(); + $event = $cq->pluck(0, Grpc\Timeval::zero()); + $this->assertNull($event); + } +} \ No newline at end of file diff --git a/src/php/tests/unit_tests/EndToEndTest.php b/src/php/tests/unit_tests/EndToEndTest.php new file mode 100755 index 0000000000000000000000000000000000000000..a6e4a89ac02397fe468c4f7555bcc9a3efe4f242 --- /dev/null +++ b/src/php/tests/unit_tests/EndToEndTest.php @@ -0,0 +1,185 @@ +<?php +class EndToEndTest extends PHPUnit_Framework_TestCase{ + public function setUp() { + $this->client_queue = new Grpc\CompletionQueue(); + $this->server_queue = new Grpc\CompletionQueue(); + $this->server = new Grpc\Server($this->server_queue, []); + $this->server->add_http2_port('localhost:9000'); + $this->channel = new Grpc\Channel('localhost:9000', []); + } + + public function tearDown() { + unset($this->channel); + unset($this->server); + unset($this->client_queue); + unset($this->server_queue); + } + + public function testSimpleRequestBody() { + $deadline = Grpc\Timeval::inf_future(); + $status_text = 'xyz'; + $call = new Grpc\Call($this->channel, + 'dummy_method', + $deadline); + $tag = 1; + $this->assertEquals(Grpc\CALL_OK, + $call->start_invoke($this->client_queue, + $tag, + $tag, + $tag)); + + $server_tag = 2; + + // the client invocation was accepted + $event = $this->client_queue->next($deadline); + $this->assertNotNull($event); + $this->assertEquals(Grpc\INVOKE_ACCEPTED, $event->get_type()); + + $this->assertEquals(Grpc\CALL_OK, $call->writes_done($tag)); + $event = $this->client_queue->next($deadline); + $this->assertNotNull($event); + $this->assertEquals(Grpc\FINISH_ACCEPTED, $event->get_type()); + $this->assertEquals(Grpc\OP_OK, $event->get_data()); + + // check that a server rpc new was received + $this->server->start(); + $this->assertEquals(Grpc\CALL_OK, $this->server->request_call($server_tag)); + $event = $this->server_queue->next($deadline); + $this->assertNotNull($event); + $this->assertEquals(Grpc\SERVER_RPC_NEW, $event->get_type()); + $server_call = $event->get_call(); + $this->assertNotNull($server_call); + $this->assertEquals(Grpc\CALL_OK, + $server_call->accept($this->server_queue, $server_tag)); + + // the server sends the status + $this->assertEquals(Grpc\CALL_OK, + $server_call->start_write_status(Grpc\STATUS_OK, + $status_text, + $server_tag)); + $event = $this->server_queue->next($deadline); + $this->assertNotNull($event); + $this->assertEquals(Grpc\FINISH_ACCEPTED, $event->get_type()); + $this->assertEquals(Grpc\OP_OK, $event->get_data()); + + // the client gets CLIENT_METADATA_READ + $event = $this->client_queue->next($deadline); + $this->assertNotNull($event); + $this->assertEquals(Grpc\CLIENT_METADATA_READ, $event->get_type()); + + // the client gets FINISHED + $event = $this->client_queue->next($deadline); + $this->assertNotNull($event); + $this->assertEquals(Grpc\FINISHED, $event->get_type()); + $status = $event->get_data(); + $this->assertEquals(Grpc\STATUS_OK, $status->code); + $this->assertEquals($status_text, $status->details); + + // and the server gets FINISHED + $event = $this->server_queue->next($deadline); + $this->assertNotNull($event); + $this->assertEquals(Grpc\FINISHED, $event->get_type()); + $status = $event->get_data(); + + unset($call); + unset($server_call); + } + + public function testClientServerFullRequestResponse() { + $deadline = Grpc\Timeval::inf_future(); + $req_text = 'client_server_full_request_response'; + $reply_text = 'reply:client_server_full_request_response'; + $status_text = 'status:client_server_full_response_text'; + + $call = new Grpc\Call($this->channel, + 'dummy_method', + $deadline); + $tag = 1; + $this->assertEquals(Grpc\CALL_OK, + $call->start_invoke($this->client_queue, + $tag, + $tag, + $tag)); + + $server_tag = 2; + + // the client invocation was accepted + $event = $this->client_queue->next($deadline); + $this->assertNotNull($event); + $this->assertEquals(Grpc\INVOKE_ACCEPTED, $event->get_type()); + + // the client writes + $this->assertEquals(Grpc\CALL_OK, $call->start_write($req_text, $tag)); + $event = $this->client_queue->next($deadline); + $this->assertNotNull($event); + $this->assertEquals(Grpc\WRITE_ACCEPTED, $event->get_type()); + + // check that a server rpc new was received + $this->server->start(); + $this->assertEquals(Grpc\CALL_OK, $this->server->request_call($server_tag)); + $event = $this->server_queue->next($deadline); + $this->assertNotNull($event); + $this->assertEquals(Grpc\SERVER_RPC_NEW, $event->get_type()); + $server_call = $event->get_call(); + $this->assertNotNull($server_call); + $this->assertEquals(Grpc\CALL_OK, + $server_call->accept($this->server_queue, $server_tag)); + + // start the server read + $this->assertEquals(Grpc\CALL_OK, $server_call->start_read($server_tag)); + $event = $this->server_queue->next($deadline); + $this->assertNotNull($event); + $this->assertEquals(Grpc\READ, $event->get_type()); + $this->assertEquals($req_text, $event->get_data()); + + // the server replies + $this->assertEquals(Grpc\CALL_OK, + $server_call->start_write($reply_text, $server_tag)); + $event = $this->server_queue->next($deadline); + $this->assertNotNull($event); + $this->assertEquals(Grpc\WRITE_ACCEPTED, $event->get_type()); + + // the client reads the metadata + $event = $this->client_queue->next($deadline); + $this->assertNotNull($event); + $this->assertEquals(Grpc\CLIENT_METADATA_READ, $event->get_type()); + + // the client reads the reply + $this->assertEquals(Grpc\CALL_OK, $call->start_read($tag)); + $event = $this->client_queue->next($deadline); + $this->assertNotNull($event); + $this->assertEquals(Grpc\READ, $event->get_type()); + $this->assertEquals($reply_text, $event->get_data()); + + // the client sends writes done + $this->assertEquals(Grpc\CALL_OK, $call->writes_done($tag)); + $event = $this->client_queue->next($deadline); + $this->assertEquals(Grpc\FINISH_ACCEPTED, $event->get_type()); + $this->assertEquals(Grpc\OP_OK, $event->get_data()); + + // the server sends the status + $this->assertEquals(Grpc\CALL_OK, + $server_call->start_write_status(GRPC\STATUS_OK, + $status_text, + $server_tag)); + $event = $this->server_queue->next($deadline); + $this->assertEquals(Grpc\FINISH_ACCEPTED, $event->get_type()); + $this->assertEquals(Grpc\OP_OK, $event->get_data()); + + // the client gets FINISHED + $event = $this->client_queue->next($deadline); + $this->assertNotNull($event); + $this->assertEquals(Grpc\FINISHED, $event->get_type()); + $status = $event->get_data(); + $this->assertEquals(Grpc\STATUS_OK, $status->code); + $this->assertEquals($status_text, $status->details); + + // and the server gets FINISHED + $event = $this->server_queue->next($deadline); + $this->assertNotNull($event); + $this->assertEquals(Grpc\FINISHED, $event->get_type()); + + unset($call); + unset($server_call); + } +} \ No newline at end of file diff --git a/src/php/tests/unit_tests/SecureEndToEndTest.php b/src/php/tests/unit_tests/SecureEndToEndTest.php new file mode 100755 index 0000000000000000000000000000000000000000..d645c03e4c9fe35b44d77e5218340a1b90d05d96 --- /dev/null +++ b/src/php/tests/unit_tests/SecureEndToEndTest.php @@ -0,0 +1,196 @@ +<?php +class SecureEndToEndTest extends PHPUnit_Framework_TestCase{ + public function setUp() { + $this->client_queue = new Grpc\CompletionQueue(); + $this->server_queue = new Grpc\CompletionQueue(); + $credentials = Grpc\Credentials::createSsl( + file_get_contents(dirname(__FILE__) . '/../data/ca.pem')); + $server_credentials = Grpc\ServerCredentials::createSsl( + null, + file_get_contents(dirname(__FILE__) . '/../data/server1.key'), + file_get_contents(dirname(__FILE__) . '/../data/server1.pem')); + $this->server = new Grpc\Server($this->server_queue, + ['credentials' => $server_credentials]); + $this->server->add_secure_http2_port('localhost:9000'); + $this->channel = new Grpc\Channel( + 'localhost:9000', + [ + 'grpc.ssl_target_name_override' => 'foo.test.google.com', + 'credentials' => $credentials + ]); + } + + public function tearDown() { + unset($this->channel); + unset($this->server); + unset($this->client_queue); + unset($this->server_queue); + } + + public function testSimpleRequestBody() { + $this->server->start(); + $deadline = Grpc\Timeval::inf_future(); + $status_text = 'xyz'; + $call = new Grpc\Call($this->channel, + 'dummy_method', + $deadline); + $tag = 1; + $this->assertEquals(Grpc\CALL_OK, + $call->start_invoke($this->client_queue, + $tag, + $tag, + $tag)); + $server_tag = 2; + + // the client invocation was accepted + $event = $this->client_queue->next($deadline); + $this->assertNotNull($event); + $this->assertEquals(Grpc\INVOKE_ACCEPTED, $event->get_type()); + + $this->assertEquals(Grpc\CALL_OK, $call->writes_done($tag)); + $event = $this->client_queue->next($deadline); + $this->assertNotNull($event); + $this->assertEquals(Grpc\FINISH_ACCEPTED, $event->get_type()); + $this->assertEquals(Grpc\OP_OK, $event->get_data()); + + // check that a server rpc new was received + $this->assertEquals(Grpc\CALL_OK, $this->server->request_call($server_tag)); + $event = $this->server_queue->next($deadline); + $this->assertNotNull($event); + $this->assertEquals(Grpc\SERVER_RPC_NEW, $event->get_type()); + $server_call = $event->get_call(); + $this->assertNotNull($server_call); + $this->assertEquals(Grpc\CALL_OK, + $server_call->accept($this->server_queue, $server_tag)); + + // the server sends the status + $this->assertEquals(Grpc\CALL_OK, + $server_call->start_write_status(Grpc\STATUS_OK, + $status_text, + $server_tag)); + $event = $this->server_queue->next($deadline); + $this->assertNotNull($event); + $this->assertEquals(Grpc\FINISH_ACCEPTED, $event->get_type()); + $this->assertEquals(Grpc\OP_OK, $event->get_data()); + + // the client gets CLIENT_METADATA_READ + $event = $this->client_queue->next($deadline); + $this->assertNotNull($event); + $this->assertEquals(Grpc\CLIENT_METADATA_READ, $event->get_type()); + + // the client gets FINISHED + $event = $this->client_queue->next($deadline); + $this->assertNotNull($event); + $this->assertEquals(Grpc\FINISHED, $event->get_type()); + $status = $event->get_data(); + $this->assertEquals(Grpc\STATUS_OK, $status->code); + $this->assertEquals($status_text, $status->details); + + // and the server gets FINISHED + $event = $this->server_queue->next($deadline); + $this->assertNotNull($event); + $this->assertEquals(Grpc\FINISHED, $event->get_type()); + $status = $event->get_data(); + + unset($call); + unset($server_call); + } + + public function testClientServerFullRequestResponse() { + $this->server->start(); + $deadline = Grpc\Timeval::inf_future(); + $req_text = 'client_server_full_request_response'; + $reply_text = 'reply:client_server_full_request_response'; + $status_text = 'status:client_server_full_response_text'; + + $call = new Grpc\Call($this->channel, + 'dummy_method', + $deadline); + $tag = 1; + $this->assertEquals(Grpc\CALL_OK, + $call->start_invoke($this->client_queue, + $tag, + $tag, + $tag)); + + $server_tag = 2; + + // the client invocation was accepted + $event = $this->client_queue->next($deadline); + $this->assertNotNull($event); + $this->assertEquals(Grpc\INVOKE_ACCEPTED, $event->get_type()); + + // the client writes + $this->assertEquals(Grpc\CALL_OK, $call->start_write($req_text, $tag)); + $event = $this->client_queue->next($deadline); + $this->assertNotNull($event); + $this->assertEquals(Grpc\WRITE_ACCEPTED, $event->get_type()); + + // check that a server rpc new was received + $this->assertEquals(Grpc\CALL_OK, $this->server->request_call($server_tag)); + $event = $this->server_queue->next($deadline); + $this->assertNotNull($event); + $this->assertEquals(Grpc\SERVER_RPC_NEW, $event->get_type()); + $server_call = $event->get_call(); + $this->assertNotNull($server_call); + $this->assertEquals(Grpc\CALL_OK, + $server_call->accept($this->server_queue, $server_tag)); + + // start the server read + $this->assertEquals(Grpc\CALL_OK, $server_call->start_read($server_tag)); + $event = $this->server_queue->next($deadline); + $this->assertNotNull($event); + $this->assertEquals(Grpc\READ, $event->get_type()); + $this->assertEquals($req_text, $event->get_data()); + + // the server replies + $this->assertEquals(Grpc\CALL_OK, + $server_call->start_write($reply_text, $server_tag)); + $event = $this->server_queue->next($deadline); + $this->assertNotNull($event); + $this->assertEquals(Grpc\WRITE_ACCEPTED, $event->get_type()); + + // the client reads the metadata + $event = $this->client_queue->next($deadline); + $this->assertNotNull($event); + $this->assertEquals(Grpc\CLIENT_METADATA_READ, $event->get_type()); + + // the client reads the reply + $this->assertEquals(Grpc\CALL_OK, $call->start_read($tag)); + $event = $this->client_queue->next($deadline); + $this->assertNotNull($event); + $this->assertEquals(Grpc\READ, $event->get_type()); + $this->assertEquals($reply_text, $event->get_data()); + + // the client sends writes done + $this->assertEquals(Grpc\CALL_OK, $call->writes_done($tag)); + $event = $this->client_queue->next($deadline); + $this->assertEquals(Grpc\FINISH_ACCEPTED, $event->get_type()); + $this->assertEquals(Grpc\OP_OK, $event->get_data()); + + // the server sends the status + $this->assertEquals(Grpc\CALL_OK, + $server_call->start_write_status(GRPC\STATUS_OK, + $status_text, + $server_tag)); + $event = $this->server_queue->next($deadline); + $this->assertEquals(Grpc\FINISH_ACCEPTED, $event->get_type()); + $this->assertEquals(Grpc\OP_OK, $event->get_data()); + + // the client gets FINISHED + $event = $this->client_queue->next($deadline); + $this->assertNotNull($event); + $this->assertEquals(Grpc\FINISHED, $event->get_type()); + $status = $event->get_data(); + $this->assertEquals(Grpc\STATUS_OK, $status->code); + $this->assertEquals($status_text, $status->details); + + // and the server gets FINISHED + $event = $this->server_queue->next($deadline); + $this->assertNotNull($event); + $this->assertEquals(Grpc\FINISHED, $event->get_type()); + + unset($call); + unset($server_call); + } +} \ No newline at end of file diff --git a/src/php/tests/unit_tests/TimevalTest.php b/src/php/tests/unit_tests/TimevalTest.php new file mode 100755 index 0000000000000000000000000000000000000000..6af9fba0431cedd560dbc0473481ef9d6bc9142d --- /dev/null +++ b/src/php/tests/unit_tests/TimevalTest.php @@ -0,0 +1,32 @@ +<?php +class TimevalTest extends PHPUnit_Framework_TestCase{ + public function testCompareSame() { + $zero = Grpc\Timeval::zero(); + $this->assertEquals(0, Grpc\Timeval::compare($zero, $zero)); + } + + public function testPastIsLessThanZero() { + $zero = Grpc\Timeval::zero(); + $past = Grpc\Timeval::inf_past(); + $this->assertLessThan(0, Grpc\Timeval::compare($past, $zero)); + $this->assertGreaterThan(0, Grpc\Timeval::compare($zero, $past)); + } + + public function testFutureIsGreaterThanZero() { + $zero = Grpc\Timeval::zero(); + $future = Grpc\Timeval::inf_future(); + $this->assertLessThan(0, Grpc\Timeval::compare($zero, $future)); + $this->assertGreaterThan(0, Grpc\Timeval::compare($future, $zero)); + } + + /** + * @depends testFutureIsGreaterThanZero + */ + public function testNowIsBetweenZeroAndFuture() { + $zero = Grpc\Timeval::zero(); + $future = Grpc\Timeval::inf_future(); + $now = Grpc\Timeval::now(); + $this->assertLessThan(0, Grpc\Timeval::compare($zero, $now)); + $this->assertLessThan(0, Grpc\Timeval::compare($now, $future)); + } +} \ No newline at end of file