Merge remote-tracking branch 'upstream/master' into filter_call_init_failure

pull/7024/head
Mark D. Roth 9 years ago
commit 898d84d309
  1. 53
      doc/c-style-guide.md
  2. BIN
      doc/images/load_balancing_design.png
  3. 75
      doc/load-balancing.md
  4. 1
      doc/statuscodes.md
  5. 24
      gRPC-Core.podspec
  6. 2
      include/grpc++/impl/codegen/async_unary_call.h
  7. 4
      include/grpc++/impl/codegen/config_protobuf.h
  8. 17
      include/grpc/impl/codegen/compression_types.h
  9. 38
      include/grpc/impl/codegen/grpc_types.h
  10. 2
      src/core/ext/client_config/channel_connectivity.c
  11. 30
      src/core/lib/iomgr/ev_poll_posix.c
  12. 8
      src/objective-c/GRPCClient/GRPCCall.m
  13. 394
      src/objective-c/tests/CoreCronetEnd2EndTests/CoreCronetEnd2EndTests.m
  14. 24
      src/objective-c/tests/CoreCronetEnd2EndTests/Info.plist
  15. 12
      src/objective-c/tests/Podfile
  16. 168
      src/objective-c/tests/Tests.xcodeproj/project.pbxproj
  17. 99
      src/objective-c/tests/Tests.xcodeproj/xcshareddata/xcschemes/CoreCronetEnd2EndTests.xcscheme
  18. 2
      src/php/composer.json
  19. 692
      src/php/ext/grpc/call.c
  20. 36
      src/php/ext/grpc/call.h
  21. 103
      src/php/ext/grpc/call_credentials.c
  22. 23
      src/php/ext/grpc/call_credentials.h
  23. 194
      src/php/ext/grpc/channel.c
  24. 23
      src/php/ext/grpc/channel.h
  25. 92
      src/php/ext/grpc/channel_credentials.c
  26. 23
      src/php/ext/grpc/channel_credentials.h
  27. 54
      src/php/ext/grpc/php_grpc.c
  28. 4
      src/php/ext/grpc/php_grpc.h
  29. 136
      src/php/ext/grpc/server.c
  30. 20
      src/php/ext/grpc/server.h
  31. 67
      src/php/ext/grpc/server_credentials.c
  32. 20
      src/php/ext/grpc/server_credentials.h
  33. 144
      src/php/ext/grpc/timeval.c
  34. 23
      src/php/ext/grpc/timeval.h
  35. 4
      src/php/lib/Grpc/BaseStub.php
  36. 1
      src/php/lib/Grpc/BidiStreamingCall.php
  37. 1
      src/php/lib/Grpc/ClientStreamingCall.php
  38. 1
      src/php/lib/Grpc/ServerStreamingCall.php
  39. 1
      src/php/lib/Grpc/UnaryCall.php
  40. 3
      src/php/tests/unit_tests/CallCredentialsTest.php
  41. 49
      src/php/tests/unit_tests/CallTest.php
  42. 25
      src/php/tests/unit_tests/ChannelCredentialsTest.php
  43. 107
      src/php/tests/unit_tests/ChannelTest.php
  44. 3
      src/php/tests/unit_tests/EndToEndTest.php
  45. 0
      src/php/tests/unit_tests/SecureEndToEndTest.php
  46. 85
      src/php/tests/unit_tests/ServerTest.php
  47. 53
      src/php/tests/unit_tests/TimevalTest.php
  48. 6
      src/ruby/bin/math_services.rb
  49. 8
      src/ruby/ext/grpc/rb_call.c
  50. 464
      src/ruby/ext/grpc/rb_compression_options.c
  51. 44
      src/ruby/ext/grpc/rb_compression_options.h
  52. 4
      src/ruby/ext/grpc/rb_grpc.c
  53. 2
      src/ruby/ext/grpc/rb_server.c
  54. 18
      src/ruby/pb/src/proto/grpc/testing/messages.rb
  55. 152
      src/ruby/pb/test/client.rb
  56. 15
      src/ruby/pb/test/proto/empty.rb
  57. 80
      src/ruby/pb/test/proto/messages.rb
  58. 14
      src/ruby/pb/test/proto/test.rb
  59. 64
      src/ruby/pb/test/proto/test_services.rb
  60. 6
      src/ruby/pb/test/server.rb
  61. 18
      src/ruby/qps/src/proto/grpc/testing/messages.rb
  62. 164
      src/ruby/spec/compression_options_spec.rb
  63. 24
      templates/gRPC-Core.podspec.template
  64. 2
      tools/gcp/stress_test/stress_test_utils.py
  65. 28
      tools/profiling/latency_profile/profile_analyzer.py
  66. 21
      tools/run_tests/perf_html_report.template
  67. 2
      tools/run_tests/performance/bq_upload_result.py
  68. 10
      tools/run_tests/performance/scenario_result_schema.json
  69. 37
      tools/run_tests/report_utils.py
  70. 2
      tools/run_tests/run_interop_tests.py
  71. 11
      tools/run_tests/run_performance_tests.py

@ -9,16 +9,17 @@ Here we document style rules for C usage in the gRPC Core library.
General
-------
- Layout rules are defined by clang-format, and all code should be passed through
clang-format. A (docker-based) script to do so is included in
[tools/distrib/clang\_format\_code.sh] (../tools/distrib/clang_format_code.sh).
- Layout rules are defined by clang-format, and all code should be passed
through clang-format. A (docker-based) script to do so is included in
[tools/distrib/clang\_format\_code.sh](../tools/distrib/clang_format_code.sh).
Header Files
------------
- Public header files (those in the include/grpc tree) should compile as pedantic C89
- Public header files should be includable from C++ programs. That is, they should
include the following:
- Public header files (those in the include/grpc tree) should compile as
pedantic C89.
- Public header files should be includable from C++ programs. That is, they
should include the following:
```c
#ifdef __cplusplus
extern "C" {
@ -34,24 +35,34 @@ Header Files
- All header files should have a #define guard to prevent multiple inclusion.
To guarantee uniqueness they should be based on the file's path.
For public headers: include/grpc/grpc.h --> GRPC_GRPC_H
For public headers: `include/grpc/grpc.h``GRPC_GRPC_H`
For private headers:
`src/core/channel/channel_stack.h`
`GRPC_INTERNAL_CORE_CHANNEL_CHANNEL_STACK_H`
Variable Initialization
-----------------------
When declaring a (non-static) pointer variable, always initialize it to `NULL`.
Even in the case of static pointer variables, it's recommended to explicitly
initialize them to `NULL`.
For private headers:
src/core/channel/channel_stack.h --> GRPC_INTERNAL_CORE_CHANNEL_CHANNEL_STACK_H
C99 Features
------------
- Variable sized arrays are not allowed
- Do not use the 'inline' keyword
- Flexible array members are allowed (https://en.wikipedia.org/wiki/Flexible_array_member)
- Variable sized arrays are not allowed.
- Do not use the 'inline' keyword.
- Flexible array members are allowed
(https://en.wikipedia.org/wiki/Flexible_array_member).
Comments
--------
Within public header files, only `/* */` comments are allowed.
Within implementation files and private headers, either single line `//`
Within implementation files and private headers, either single line `//`
or multi line `/* */` comments are allowed. Only one comment style per file is
allowed however (i.e. if single line comments are used anywhere within a file,
ALL comments within that file must be single line comments).
@ -59,7 +70,15 @@ ALL comments within that file must be single line comments).
Symbol Names
------------
- Non-static functions must be prefixed by grpc_
- static functions must not be prefixed by grpc_
- enumeration values and #define names are uppercased, all others are lowercased
- Multiple word identifiers use underscore as a delimiter (NEVER camel casing)
- Non-static functions must be prefixed by `grpc_`
- Static functions must *not* be prefixed by `grpc_`
- Enumeration values and `#define` names must be uppercase. All other values
must be lowercase.
- Multiple word identifiers use underscore as a delimiter, *never* camel
case. E.g. `variable_name`.
Functions
----------
- The use of [`atexit()`](http://man7.org/linux/man-pages/man3/atexit.3.html) is
in forbidden in libgrpc.

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

@ -4,7 +4,7 @@ Load Balancing in gRPC
# Objective
To design a load balancing API between a gRPC client and a Load Balancer to
instruct the client how to send load to multiple backend servers.
instruct the client how to send load to multiple backend servers.
# Background
@ -19,7 +19,7 @@ have temporary copies of the RPC request and response. This model also increases
latency to the RPCs.
The proxy model was deemed inefficient when considering request heavy services
like storage.
like storage.
### Balancing-aware Client
@ -28,7 +28,7 @@ example, the client could contain many load balancing policies (Round Robin,
Random, etc) used to select servers from a list. In this model, a list of
servers would be either statically configured in the client, provided by the
name resolution system, an external load balancer, etc. In any case, the client
is responsible for choosing the preferred server from the list.
is responsible for choosing the preferred server from the list.
One of the drawbacks of this approach is writing and maintaining the load
balancing policies in multiple languages and/or versions of the clients. These
@ -53,14 +53,69 @@ unavailability or health issues. The load balancer will make any necessary
complex decisions and inform the client. The load balancer may communicate with
the backend servers to collect load and health information.
## Requirements
#### Simple API and client
The gRPC client load balancing code must be simple and portable. The client
should only contain simple algorithms (ie Round Robin) for server selection. For
complex algorithms, the client should rely on a load balancer to provide load
balancing configuration and the list of servers to which the client should send
requests. The balancer will update the server list as needed to balance the load
as well as handle server unavailability or health issues. The load balancer will
make any necessary complex decisions and inform the client. The load balancer
may communicate with the backend servers to collect load and health information.
#### Security
The load balancer may be separate from the actual server backends and a
compromise of the load balancer should only lead to a compromise of the
loadbalancing functionality. In other words, a compromised load balancer should
not be able to cause a client to trust a (potentially malicious) backend server
any more than in a comparable situation without loadbalancing.
# Proposed Architecture
The gRPC load balancing approach follows the third approach, by having an
external load balancer which provides simple clients with a list of servers.
The gRPC load balancing implements the external load balancing server approach:
an external load balancer provides simple clients with an up-to-date list of
servers.
![image](images/load_balancing_design.png)
1. On startup, the gRPC client issues a name resolution request for the service.
The name will resolve to one or more IP addresses to gRPC servers, a hint on
whether the IP address(es) point to a load balancer or not, and also return a
client config.
2. The gRPC client connects to a gRPC Server.
1. If the name resolution has hinted that the endpoint is a load balancer,
the client's gRPC LB policy will attempt to open a stream to the load
balancer service. The server may respond in only one of the following
ways.
1. `status::UNIMPLEMENTED`. There is no loadbalancing in use. The client
call will fail.
2. "I am a Load Balancer and here is the server list." (Goto Step 4.)
3. "Please contact Load Balancer X" (See Step 3.) The client will close
this connection and cancel the stream.
4. If the server fails to respond, the client will wait for some timeout
and then re-resolve the name (process to Step 1 above).
2. If the name resolution has not hinted that the endpoint is a load
balancer, the client connects directly to the service it wants to talk to.
3. The gRPC client's gRPC LB policy opens a separate connection to the Load
Balancer. If this fails, it will go back to step 1 and try another address.
1. During channel initialization to the Load Balancer, the client will
attempt to open a stream to the Load Balancer service.
2. The Load Balancer will return a server list to the gRPC client. If the
server list is empty, the call will wait until a non-empty one is
received. Optional: The Load Balancer will also open channels to the gRPC
servers if load reporting is needed.
4. The gRPC client will send RPCs to the gRPC servers contained in the server
list from the Load Balancer.
5. Optional: The gRPC servers may periodically report load to the Load Balancer.
## Client
When establishing a gRPC stream to the balancer, the client will send an initial
When establishing a gRPC _stream_ to the balancer, the client will send an initial
request to the load balancer (via a regular gRPC message). The load balancer
will respond with client config (including, for example, settings for flow
control, RPC deadlines, etc.) or a redirect to another load balancer. If the
@ -87,11 +142,3 @@ balancer in order to compute the next list of servers.
The gRPC Server is responsible for answering RPC requests and providing
responses to the client. The server will also report load to the load balancer
if a reporting stream was opened for this purpose.
### Security
The load balancer may be separate from the actual server backends and a
compromise of the load balancer should only lead to a compromise of the
loadbalancing functionality. In other words, a compromised load balancer should
not be able to cause a client to trust a (potentially malicious) backend server
any more than in a comparable situation without loadbalancing.

@ -18,6 +18,7 @@ Only a subset of the pre-defined status codes are generated by the gRPC librarie
| Could not decompress, but compression algorithm supported (Server -> Client) | INTERNAL | Client |
| Compression mechanism used by client not supported at server | UNIMPLEMENTED | Server |
| Server temporarily out of resources (e.g., Flow-control resource limits reached) | RESOURCE_EXHAUSTED | Server|
| Client does not have enough memory to hold the server response | RESOURCE_EXHAUSTED | Client |
| Flow-control protocol violation | INTERNAL | Both |
| Error parsing returned status | UNKNOWN | Client |
| Incorrect Auth metadata ( Credentials failed to get metadata, Incompatible credentials set on channel and call, Invalid host set in `:authority` metadata, etc.) | UNAUTHENTICATED | Both |

@ -101,6 +101,8 @@ Pod::Spec.new do |s|
'ALWAYS_SEARCH_USER_PATHS' => 'NO',
}
s.default_subspecs = 'Interface', 'Implementation'
# Like many other C libraries, gRPC-Core has its public headers under `include/<libname>/` and its
# sources and private headers in other directories outside `include/`. Cocoapods' linter doesn't
# allow any header to be listed outside the `header_mappings_dir` (even though doing so works in
@ -759,4 +761,26 @@ Pod::Spec.new do |s|
'src/core/ext/census/mlog.h',
'src/core/ext/census/rpc_metric_id.h'
end
s.subspec 'Cronet-Interface' do |ss|
ss.header_mappings_dir = 'include/grpc'
ss.source_files = 'include/grpc/grpc_cronet.h'
end
s.subspec 'Cronet-Tests' do |ss|
ss.header_mappings_dir = '.'
ss.source_files = 'src/core/ext/transport/cronet/client/secure/cronet_channel_create.c',
'src/core/ext/transport/cronet/transport/cronet_transport.c',
'test/core/end2end/cq_verifier.{c,h}',
'test/core/end2end/end2end_tests.{c,h}',
'test/core/end2end/tests/*.{c,h}',
'test/core/end2end/data/*.{c,h}',
'test/core/util/test_config.{c,h}',
'test/core/util/port.h',
'test/core/util/port_posix.c',
'test/core/util/port_server_client.{c,h}'
ss.dependency 'CronetFramework'
end
end

@ -65,7 +65,7 @@ class ClientAsyncResponseReader GRPC_FINAL
const W& request)
: context_(context),
call_(channel->CreateCall(method, context, cq)),
collection_(new CallOpSetCollection) {
collection_(std::make_shared<CallOpSetCollection>()) {
collection_->init_buf_.SetCollection(collection_);
collection_->init_buf_.SendInitialMetadata(
context->send_initial_metadata_, context->initial_metadata_flags());

@ -49,7 +49,7 @@
#include <google/protobuf/descriptor.pb.h>
#define GRPC_CUSTOM_DESCRIPTOR ::google::protobuf::Descriptor
#define GRPC_CUSTOM_DESCRIPTORPOOL ::google::protobuf::DescriptorPool
#define GPRC_CUSTOM_FIELDDESCRIPTOR ::google::protobuf::FieldDescriptor
#define GRPC_CUSTOM_FIELDDESCRIPTOR ::google::protobuf::FieldDescriptor
#define GRPC_CUSTOM_FILEDESCRIPTOR ::google::protobuf::FileDescriptor
#define GRPC_CUSTOM_FILEDESCRIPTORPROTO ::google::protobuf::FileDescriptorProto
#define GRPC_CUSTOM_METHODDESCRIPTOR ::google::protobuf::MethodDescriptor
@ -75,7 +75,7 @@ typedef GRPC_CUSTOM_PROTOBUF_INT64 int64;
typedef GRPC_CUSTOM_DESCRIPTOR Descriptor;
typedef GRPC_CUSTOM_DESCRIPTORPOOL DescriptorPool;
typedef GPRC_CUSTOM_FIELDDESCRIPTOR FieldDescriptor;
typedef GRPC_CUSTOM_FIELDDESCRIPTOR FieldDescriptor;
typedef GRPC_CUSTOM_FILEDESCRIPTOR FileDescriptor;
typedef GRPC_CUSTOM_FILEDESCRIPTORPROTO FileDescriptorProto;
typedef GRPC_CUSTOM_METHODDESCRIPTOR MethodDescriptor;

@ -46,12 +46,27 @@ extern "C" {
#define GRPC_COMPRESSION_REQUEST_ALGORITHM_MD_KEY \
"grpc-internal-encoding-request"
/** To be used in channel arguments */
/** To be used in channel arguments.
*
* \addtogroup grpc_arg_keys
* \{ */
/** Default compression algorithm for the channel.
* Its value is an int from the \a grpc_compression_algorithm enum. */
#define GRPC_COMPRESSION_CHANNEL_DEFAULT_ALGORITHM \
"grpc.default_compression_algorithm"
/** Default compression level for the channel.
* Its value is an int from the \a grpc_compression_level enum. */
#define GRPC_COMPRESSION_CHANNEL_DEFAULT_LEVEL "grpc.default_compression_level"
/** Compression algorithms supported by the channel.
* Its value is a bitset (an int). Bits correspond to algorithms in \a
* grpc_compression_algorithm. For example, its LSB corresponds to
* GRPC_COMPRESS_NONE, the next bit to GRPC_COMPRESS_DEFLATE, etc.
* Unset bits disable support for the algorithm. By default all algorithms are
* supported. It's not possible to disable GRPC_COMPRESS_NONE (the attempt will
* be ignored). */
#define GRPC_COMPRESSION_CHANNEL_ENABLED_ALGORITHMS_BITSET \
"grpc.compression_enabled_algorithms_bitset"
/** \} */
/* The various compression algorithms supported by gRPC */
typedef enum {

@ -106,58 +106,66 @@ typedef struct {
by grpc_arg; keys are strings to allow easy backwards-compatible extension
by arbitrary parties.
All evaluation is performed at channel creation time (i.e. the values in
this structure need only live through the creation invocation). */
this structure need only live through the creation invocation).
See the description of the \ref grpc_arg_keys "available args" for more
details. */
typedef struct {
size_t num_args;
grpc_arg *args;
} grpc_channel_args;
/* Channel argument keys: */
/** Enable census for tracing and stats collection */
/** \defgroup grpc_arg_keys
* Channel argument keys.
* \{
*/
/** If non-zero, enable census for tracing and stats collection. */
#define GRPC_ARG_ENABLE_CENSUS "grpc.census"
/** Enable load reporting */
/** If non-zero, enable load reporting. */
#define GRPC_ARG_ENABLE_LOAD_REPORTING "grpc.loadreporting"
/** Maximum number of concurrent incoming streams to allow on a http2
connection */
connection. Int valued. */
#define GRPC_ARG_MAX_CONCURRENT_STREAMS "grpc.max_concurrent_streams"
/** Maximum message length that the channel can receive */
/** Maximum message length that the channel can receive. Int valued, bytes. */
#define GRPC_ARG_MAX_MESSAGE_LENGTH "grpc.max_message_length"
/** Initial sequence number for http2 transports */
/** Initial sequence number for http2 transports. Int valued. */
#define GRPC_ARG_HTTP2_INITIAL_SEQUENCE_NUMBER \
"grpc.http2.initial_sequence_number"
/** Amount to read ahead on individual streams. Defaults to 64kb, larger
values can help throughput on high-latency connections.
NOTE: at some point we'd like to auto-tune this, and this parameter
will become a no-op. */
will become a no-op. Int valued, bytes. */
#define GRPC_ARG_HTTP2_STREAM_LOOKAHEAD_BYTES "grpc.http2.lookahead_bytes"
/** How much memory to use for hpack decoding */
/** How much memory to use for hpack decoding. Int valued, bytes. */
#define GRPC_ARG_HTTP2_HPACK_TABLE_SIZE_DECODER \
"grpc.http2.hpack_table_size.decoder"
/** How much memory to use for hpack encoding */
/** How much memory to use for hpack encoding. Int valued, bytes. */
#define GRPC_ARG_HTTP2_HPACK_TABLE_SIZE_ENCODER \
"grpc.http2.hpack_table_size.encoder"
/** Default authority to pass if none specified on call construction */
/** Default authority to pass if none specified on call construction. A string.
* */
#define GRPC_ARG_DEFAULT_AUTHORITY "grpc.default_authority"
/** Primary user agent: goes at the start of the user-agent metadata
sent on each request */
sent on each request. A string. */
#define GRPC_ARG_PRIMARY_USER_AGENT_STRING "grpc.primary_user_agent"
/** Secondary user agent: goes at the end of the user-agent metadata
sent on each request */
sent on each request. A string. */
#define GRPC_ARG_SECONDARY_USER_AGENT_STRING "grpc.secondary_user_agent"
/** The maximum time between subsequent connection attempts, in ms */
#define GRPC_ARG_MAX_RECONNECT_BACKOFF_MS "grpc.max_reconnect_backoff_ms"
/* The caller of the secure_channel_create functions may override the target
name used for SSL host name checking using this channel argument which is of
type GRPC_ARG_STRING. This *should* be used for testing only.
type \a GRPC_ARG_STRING. This *should* be used for testing only.
If this argument is not specified, the name used for SSL host name checking
will be the target parameter (assuming that the secure channel is an SSL
channel). If this parameter is specified and the underlying is not an SSL
channel, it will just be ignored. */
#define GRPC_SSL_TARGET_NAME_OVERRIDE_ARG "grpc.ssl_target_name_override"
/* Maximum metadata size */
/* Maximum metadata size, in bytes. */
#define GRPC_ARG_MAX_METADATA_SIZE "grpc.max_metadata_size"
/** If non-zero, allow the use of SO_REUSEPORT if it's available (default 1) */
#define GRPC_ARG_ALLOW_REUSEPORT "grpc.so_reuseport"
/** \} */
/** Result of a grpc call. If the caller satisfies the prerequisites of a
particular operation, the grpc_call_error returned will be GRPC_CALL_OK.

@ -59,7 +59,7 @@ grpc_connectivity_state grpc_channel_check_connectivity_state(
}
gpr_log(GPR_ERROR,
"grpc_channel_check_connectivity_state called on something that is "
"not a (u)client channel, but '%s'",
"not a client channel, but '%s'",
client_channel_elem->filter->name);
grpc_exec_ctx_finish(&exec_ctx);
return GRPC_CHANNEL_SHUTDOWN;

@ -844,6 +844,11 @@ static grpc_error *pollset_work(grpc_exec_ctx *exec_ctx, grpc_pollset *pollset,
*worker_hdl = &worker;
grpc_error *error = GRPC_ERROR_NONE;
/* Avoid malloc for small number of elements. */
enum { inline_elements = 96 };
struct pollfd pollfd_space[inline_elements];
struct grpc_fd_watcher watcher_space[inline_elements];
/* pollset->mu already held */
int added_worker = 0;
int locked = 1;
@ -899,15 +904,23 @@ static grpc_error *pollset_work(grpc_exec_ctx *exec_ctx, grpc_pollset *pollset,
int r;
size_t i, fd_count;
nfds_t pfd_count;
/* TODO(ctiller): inline some elements to avoid an allocation */
grpc_fd_watcher *watchers;
struct pollfd *pfds;
timeout = poll_deadline_to_millis_timeout(deadline, now);
/* TODO(ctiller): perform just one malloc here if we exceed the inline
* case */
pfds = gpr_malloc(sizeof(*pfds) * (pollset->fd_count + 2));
watchers = gpr_malloc(sizeof(*watchers) * (pollset->fd_count + 2));
if (pollset->fd_count + 2 <= inline_elements) {
pfds = pollfd_space;
watchers = watcher_space;
} else {
/* Allocate one buffer to hold both pfds and watchers arrays */
const size_t pfd_size = sizeof(*pfds) * (pollset->fd_count + 2);
const size_t watch_size = sizeof(*watchers) * (pollset->fd_count + 2);
void *buf = gpr_malloc(pfd_size + watch_size);
pfds = buf;
watchers = (void *)((char *)buf + pfd_size);
}
fd_count = 0;
pfd_count = 2;
pfds[0].fd = GRPC_WAKEUP_FD_GET_READ_FD(&grpc_global_wakeup_fd);
@ -974,8 +987,11 @@ static grpc_error *pollset_work(grpc_exec_ctx *exec_ctx, grpc_pollset *pollset,
}
}
gpr_free(pfds);
gpr_free(watchers);
if (pfds != pollfd_space) {
/* pfds and watchers are in the same memory block pointed to by pfds */
gpr_free(pfds);
}
GPR_TIMER_END("maybe_work_and_unlock", 0);
locked = 0;
} else {

@ -208,13 +208,9 @@ NSString * const kGRPCTrailersKey = @"io.grpc.TrailersKey";
// don't want to throw, because the app shouldn't crash for a behavior
// that's on the hands of any server to have. Instead we finish and ask
// the server to cancel.
//
// TODO(jcanizales): No canonical code is appropriate for this situation
// (because it's just a client problem). Use another domain and an
// appropriately-documented code.
[weakSelf finishWithError:[NSError errorWithDomain:kGRPCErrorDomain
code:GRPCErrorCodeInternal
userInfo:nil]];
code:GRPCErrorCodeResourceExhausted
userInfo:@{NSLocalizedDescriptionKey: @"Client does not have enough memory to hold the server response."}]];
[weakSelf cancelCall];
return;
}

@ -0,0 +1,394 @@
/*
*
* Copyright 2015, Google Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
/*
* This test file is derived from fixture h2_ssl.c in core end2end test
* (test/core/end2end/fixture/h2_ssl.c). The structure of the fixture is
* preserved as much as possible
*
* This fixture creates a server full stack using chttp2 and a client
* full stack using Cronet. End-to-end tests are run against this
* configuration
*
*/
#import <XCTest/XCTest.h>
#include "test/core/end2end/end2end_tests.h"
#include <stdio.h>
#include <string.h>
#include <grpc/support/alloc.h>
#include <grpc/support/host_port.h>
#include <grpc/support/log.h>
#include "src/core/lib/channel/channel_args.h"
#include "src/core/lib/security/credentials/credentials.h"
#include "src/core/lib/support/env.h"
#include "src/core/lib/support/string.h"
#include "src/core/lib/support/tmpfile.h"
#include "test/core/end2end/data/ssl_test_data.h"
#include "test/core/util/port.h"
#include "test/core/util/test_config.h"
#include <grpc/grpc_cronet.h>
#import <Cronet/Cronet.h>
typedef struct fullstack_secure_fixture_data {
char *localaddr;
} fullstack_secure_fixture_data;
static grpc_end2end_test_fixture chttp2_create_fixture_secure_fullstack(
grpc_channel_args *client_args, grpc_channel_args *server_args) {
grpc_end2end_test_fixture f;
int port = grpc_pick_unused_port_or_die();
fullstack_secure_fixture_data *ffd =
gpr_malloc(sizeof(fullstack_secure_fixture_data));
memset(&f, 0, sizeof(f));
gpr_join_host_port(&ffd->localaddr, "localhost", port);
f.fixture_data = ffd;
f.cq = grpc_completion_queue_create(NULL);
return f;
}
static void process_auth_failure(void *state, grpc_auth_context *ctx,
const grpc_metadata *md, size_t md_count,
grpc_process_auth_metadata_done_cb cb,
void *user_data) {
GPR_ASSERT(state == NULL);
cb(user_data, NULL, 0, NULL, 0, GRPC_STATUS_UNAUTHENTICATED, NULL);
}
static void cronet_init_client_secure_fullstack(
grpc_end2end_test_fixture *f, grpc_channel_args *client_args,
cronet_engine *cronetEngine) {
fullstack_secure_fixture_data *ffd = f->fixture_data;
f->client =
grpc_cronet_secure_channel_create(cronetEngine, ffd->localaddr, client_args, NULL);
GPR_ASSERT(f->client != NULL);
}
static void chttp2_init_server_secure_fullstack(
grpc_end2end_test_fixture *f, grpc_channel_args *server_args,
grpc_server_credentials *server_creds) {
fullstack_secure_fixture_data *ffd = f->fixture_data;
if (f->server) {
grpc_server_destroy(f->server);
}
f->server = grpc_server_create(server_args, NULL);
grpc_server_register_completion_queue(f->server, f->cq, NULL);
GPR_ASSERT(grpc_server_add_secure_http2_port(f->server, ffd->localaddr,
server_creds));
grpc_server_credentials_release(server_creds);
grpc_server_start(f->server);
}
static void chttp2_tear_down_secure_fullstack(grpc_end2end_test_fixture *f) {
fullstack_secure_fixture_data *ffd = f->fixture_data;
gpr_free(ffd->localaddr);
gpr_free(ffd);
}
static void cronet_init_client_simple_ssl_secure_fullstack(
grpc_end2end_test_fixture *f, grpc_channel_args *client_args) {
grpc_arg ssl_name_override = {GRPC_ARG_STRING,
GRPC_SSL_TARGET_NAME_OVERRIDE_ARG,
{"foo.test.google.fr"}};
grpc_channel_args *new_client_args =
grpc_channel_args_copy_and_add(client_args, &ssl_name_override, 1);
[Cronet setHttp2Enabled:YES];
[Cronet start];
cronet_engine *cronetEngine = [Cronet getGlobalEngine];
cronet_init_client_secure_fullstack(f, new_client_args, cronetEngine);
grpc_channel_args_destroy(new_client_args);
}
static int fail_server_auth_check(grpc_channel_args *server_args) {
size_t i;
if (server_args == NULL) return 0;
for (i = 0; i < server_args->num_args; i++) {
if (strcmp(server_args->args[i].key, FAIL_AUTH_CHECK_SERVER_ARG_NAME) ==
0) {
return 1;
}
}
return 0;
}
static void chttp2_init_server_simple_ssl_secure_fullstack(
grpc_end2end_test_fixture *f, grpc_channel_args *server_args) {
grpc_ssl_pem_key_cert_pair pem_cert_key_pair = {test_server1_key,
test_server1_cert};
grpc_server_credentials *ssl_creds =
grpc_ssl_server_credentials_create(NULL, &pem_cert_key_pair, 1, 0, NULL);
if (fail_server_auth_check(server_args)) {
grpc_auth_metadata_processor processor = {process_auth_failure, NULL, NULL};
grpc_server_credentials_set_auth_metadata_processor(ssl_creds, processor);
}
chttp2_init_server_secure_fullstack(f, server_args, ssl_creds);
}
/* All test configurations */
static grpc_end2end_test_config configs[] = {
{"chttp2/simple_ssl_fullstack",
FEATURE_MASK_SUPPORTS_DELAYED_CONNECTION |
FEATURE_MASK_SUPPORTS_PER_CALL_CREDENTIALS,
chttp2_create_fixture_secure_fullstack,
cronet_init_client_simple_ssl_secure_fullstack,
chttp2_init_server_simple_ssl_secure_fullstack,
chttp2_tear_down_secure_fullstack},
};
static char *roots_filename;
@interface CoreCronetEnd2EndTests : XCTestCase
@end
@implementation CoreCronetEnd2EndTests
// The setUp() function is run before the test cases run and only run once
+ (void)setUp {
[super setUp];
FILE *roots_file;
size_t roots_size = strlen(test_root_cert);
char *argv[] = {"CoreCronetEnd2EndTests"};
grpc_test_init(1, argv);
grpc_end2end_tests_pre_init();
/* Set the SSL roots env var. */
roots_file = gpr_tmpfile("chttp2_simple_ssl_fullstack_test", &roots_filename);
GPR_ASSERT(roots_filename != NULL);
GPR_ASSERT(roots_file != NULL);
GPR_ASSERT(fwrite(test_root_cert, 1, roots_size, roots_file) == roots_size);
fclose(roots_file);
gpr_setenv(GRPC_DEFAULT_SSL_ROOTS_FILE_PATH_ENV_VAR, roots_filename);
grpc_init();
}
// The tearDown() function is run after all test cases finish running
+ (void)tearDown {
grpc_shutdown();
/* Cleanup. */
remove(roots_filename);
gpr_free(roots_filename);
[super tearDown];
}
- (void)testIndividualCase:(char*)test_case {
char *argv[] = {"h2_ssl", test_case};
for (int i = 0; i < sizeof(configs) / sizeof(*configs); i++) {
grpc_end2end_tests(sizeof(argv) / sizeof(argv[0]), argv, configs[i]);
}
}
// TODO(mxyan): Use NSStringFromSelector(_cmd) to acquire test name from the
// test case method name, so that bodies of test cases can stay identical
- (void)testBadHostname {
[self testIndividualCase:"bad_hostname"];
}
- (void)testBinaryMetadata {
[self testIndividualCase:"binary_metadata"];
}
- (void)testCallCreds {
[self testIndividualCase:"call_creds"];
}
- (void)testCancelAfterAccept {
[self testIndividualCase:"cancel_after_accept"];
}
- (void)testCancelAfterClientDone {
[self testIndividualCase:"cancel_after_client_done"];
}
- (void)testCancelAfterInvoke {
[self testIndividualCase:"cancel_after_invoke"];
}
- (void)testCancelBeforeInvoke {
[self testIndividualCase:"cancel_before_invoke"];
}
- (void)testCancelInAVacuum {
[self testIndividualCase:"cancel_in_a_vacuum"];
}
- (void)testCancelWithStatus {
[self testIndividualCase:"cancel_with_status"];
}
- (void)testCompressedPayload {
[self testIndividualCase:"compressed_payload"];
}
- (void)testConnectivity {
[self testIndividualCase:"connectivity"];
}
- (void)testDefaultHost {
[self testIndividualCase:"default_host"];
}
- (void)testDisappearingServer {
[self testIndividualCase:"disappearing_server"];
}
- (void)testEmptyBatch {
[self testIndividualCase:"empty_batch"];
}
- (void)testFilterCausesClose {
[self testIndividualCase:"filter_causes_close"];
}
- (void)testGracefulServerShutdown {
[self testIndividualCase:"graceful_server_shutdown"];
}
- (void)testHighInitialSeqno {
[self testIndividualCase:"high_initial_seqno"];
}
- (void)testHpackSize {
[self testIndividualCase:"hpack_size"];
}
- (void)testIdempotentRequest {
[self testIndividualCase:"idempotent_request"];
}
- (void)testInvokeLargeRequest {
[self testIndividualCase:"invoke_large_request"];
}
- (void)testLargeMetadata {
[self testIndividualCase:"large_metadata"];
}
- (void)testMaxConcurrentStreams {
[self testIndividualCase:"max_concurrent_streams"];
}
- (void)testMaxMessageLength {
[self testIndividualCase:"max_message_length"];
}
- (void)testNegativeDeadline {
[self testIndividualCase:"negative_deadline"];
}
- (void)testNetworkStatusChange {
[self testIndividualCase:"network_status_change"];
}
- (void)testNoOp {
[self testIndividualCase:"no_op"];
}
- (void)testPayload {
[self testIndividualCase:"payload"];
}
- (void)testPing {
[self testIndividualCase:"ping"];
}
- (void)testPingPongStreaming {
[self testIndividualCase:"ping_pong_streaming"];
}
- (void)testRegisteredCall {
[self testIndividualCase:"registered_call"];
}
- (void)testRequestWithFlags {
[self testIndividualCase:"request_with_flags"];
}
- (void)testRequestWithPayload {
[self testIndividualCase:"request_with_payload"];
}
- (void)testServerFinishesRequest {
[self testIndividualCase:"server_finishes_request"];
}
- (void)testShutdownFinishesCalls {
[self testIndividualCase:"shutdown_finishes_calls"];
}
- (void)testShutdownFinishesTags {
[self testIndividualCase:"shutdown_finishes_tags"];
}
- (void)testSimpleDelayedRequest {
[self testIndividualCase:"simple_delayed_request"];
}
- (void)testSimpleMetadata {
[self testIndividualCase:"simple_metadata"];
}
- (void)testSimpleRequest {
[self testIndividualCase:"simple_request"];
}
- (void)testStreamingErrorResponse {
[self testIndividualCase:"streaming_error_response"];
}
- (void)testTrailingMetadata {
[self testIndividualCase:"trailing_metadata"];
}
@end

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>gRPC.$(PRODUCT_NAME:rfc1034identifier)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>BNDL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1</string>
</dict>
</plist>

@ -22,17 +22,23 @@ GRPC_LOCAL_SRC = '../../..'
pod '!ProtoCompiler-gRPCPlugin', :path => "#{GRPC_LOCAL_SRC}/src/objective-c"
pod 'BoringSSL', :podspec => "#{GRPC_LOCAL_SRC}/src/objective-c", :inhibit_warnings => true
pod 'CronetFramework', :podspec => "#{GRPC_LOCAL_SRC}/src/objective-c"
pod 'gRPC', :path => GRPC_LOCAL_SRC
pod 'gRPC-Core', :path => GRPC_LOCAL_SRC
pod 'gRPC-RxLibrary', :path => GRPC_LOCAL_SRC
pod 'gRPC-ProtoRPC', :path => GRPC_LOCAL_SRC
pod 'RemoteTest', :path => "RemoteTestClient"
end
end
target 'CoreCronetEnd2EndTests' do
pod 'BoringSSL', :podspec => "#{GRPC_LOCAL_SRC}/src/objective-c", :inhibit_warnings => true
pod 'CronetFramework', :podspec => "#{GRPC_LOCAL_SRC}/src/objective-c"
pod 'gRPC-Core', :path => GRPC_LOCAL_SRC
pod 'gRPC-Core/Cronet-Interface', :path => GRPC_LOCAL_SRC
pod 'gRPC-Core/Cronet-Tests', :path => GRPC_LOCAL_SRC
end
# gRPC-Core.podspec needs to be modified to be successfully used for local development. A Podfile's
# pre_install hook lets us do that. The block passed to it runs after the podspecs are downloaded
# and before they are installed in the user project.
@ -69,7 +75,7 @@ post_install do |installer|
target.build_configurations.each do |config|
config.build_settings['GCC_TREAT_WARNINGS_AS_ERRORS'] = 'YES'
end
if target.name == 'gRPC-Core'
if target.name == 'gRPC-Core' or target.name == 'gRPC-Core.default-Cronet-Interface-Cronet-Tests'
target.build_configurations.each do |config|
# TODO(zyc): Remove this setting after the issue is resolved
# GPR_UNREACHABLE_CODE causes "Control may reach end of non-void

@ -12,6 +12,9 @@
20DFDF829DD993A4A00D5662 /* libPods-RxLibraryUnitTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = A58BE6DF1C62D1739EBB2C78 /* libPods-RxLibraryUnitTests.a */; };
333E8FC01C8285B7C547D799 /* libPods-InteropTestsLocalCleartext.a in Frameworks */ = {isa = PBXBuildFile; fileRef = FD346DB2C23F676C4842F3FF /* libPods-InteropTestsLocalCleartext.a */; };
3D7C85F6AA68C4A205E3BA16 /* libPods-Tests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 20DFF2F3C97EF098FE5A3171 /* libPods-Tests.a */; };
5E8A5DA71D3840B4000F8BC4 /* CoreCronetEnd2EndTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 5E8A5DA61D3840B4000F8BC4 /* CoreCronetEnd2EndTests.m */; };
5E8A5DA91D3840B4000F8BC4 /* libTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 635697C71B14FC11007A7283 /* libTests.a */; };
60D2A57ED559F34428C2EEC5 /* libPods-CoreCronetEnd2EndTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = FBD98AC417B9882D32B19F28 /* libPods-CoreCronetEnd2EndTests.a */; };
6312AE4E1B1BF49B00341DEE /* GRPCClientTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 6312AE4D1B1BF49B00341DEE /* GRPCClientTests.m */; };
63423F4A1B150A5F006CF63C /* libTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 635697C71B14FC11007A7283 /* libTests.a */; };
635697CD1B14FC11007A7283 /* Tests.m in Sources */ = {isa = PBXBuildFile; fileRef = 635697CC1B14FC11007A7283 /* Tests.m */; };
@ -38,6 +41,13 @@
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
5E8A5DAA1D3840B4000F8BC4 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 635697BF1B14FC11007A7283 /* Project object */;
proxyType = 1;
remoteGlobalIDString = 635697C61B14FC11007A7283;
remoteInfo = Tests;
};
63423F4B1B150A5F006CF63C /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 635697BF1B14FC11007A7283 /* Project object */;
@ -91,12 +101,16 @@
060EF32D7EC0DF67ED617507 /* Pods-Tests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Tests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Tests/Pods-Tests.debug.xcconfig"; sourceTree = "<group>"; };
07D10A965323BEA7FE59A74B /* Pods-RxLibraryUnitTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RxLibraryUnitTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-RxLibraryUnitTests/Pods-RxLibraryUnitTests.debug.xcconfig"; sourceTree = "<group>"; };
0A4F89D9C90E9C30990218F0 /* Pods.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = Pods.release.xcconfig; path = "Pods/Target Support Files/Pods/Pods.release.xcconfig"; sourceTree = "<group>"; };
0D2284C3DF7E57F0ED504E39 /* Pods-CoreCronetEnd2EndTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-CoreCronetEnd2EndTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-CoreCronetEnd2EndTests/Pods-CoreCronetEnd2EndTests.debug.xcconfig"; sourceTree = "<group>"; };
20DFF2F3C97EF098FE5A3171 /* libPods-Tests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Tests.a"; sourceTree = BUILT_PRODUCTS_DIR; };
35F2B6BF3BAE8F0DC4AFD76E /* libPods.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libPods.a; sourceTree = BUILT_PRODUCTS_DIR; };
3B0861FC805389C52DB260D4 /* Pods-RxLibraryUnitTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RxLibraryUnitTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-RxLibraryUnitTests/Pods-RxLibraryUnitTests.release.xcconfig"; sourceTree = "<group>"; };
4AD97096D13D7416DC91A72A /* Pods-CoreCronetEnd2EndTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-CoreCronetEnd2EndTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-CoreCronetEnd2EndTests/Pods-CoreCronetEnd2EndTests.release.xcconfig"; sourceTree = "<group>"; };
51A275E86C141416ED63FF76 /* Pods-InteropTestsLocalCleartext.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-InteropTestsLocalCleartext.release.xcconfig"; path = "Pods/Target Support Files/Pods-InteropTestsLocalCleartext/Pods-InteropTestsLocalCleartext.release.xcconfig"; sourceTree = "<group>"; };
553BBBED24E4162D1F769D65 /* Pods-InteropTestsLocalSSL.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-InteropTestsLocalSSL.debug.xcconfig"; path = "Pods/Target Support Files/Pods-InteropTestsLocalSSL/Pods-InteropTestsLocalSSL.debug.xcconfig"; sourceTree = "<group>"; };
5761E98978DDDF136A58CB7E /* Pods-AllTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AllTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-AllTests/Pods-AllTests.release.xcconfig"; sourceTree = "<group>"; };
5E8A5DA41D3840B4000F8BC4 /* CoreCronetEnd2EndTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = CoreCronetEnd2EndTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
5E8A5DA61D3840B4000F8BC4 /* CoreCronetEnd2EndTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CoreCronetEnd2EndTests.m; sourceTree = "<group>"; };
6312AE4D1B1BF49B00341DEE /* GRPCClientTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GRPCClientTests.m; sourceTree = "<group>"; };
63423F441B150A5F006CF63C /* AllTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = AllTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
63423F501B151B77006CF63C /* RxLibraryUnitTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RxLibraryUnitTests.m; sourceTree = "<group>"; };
@ -123,11 +137,21 @@
E1486220285AF123EB124008 /* Pods-InteropTestsLocalCleartext.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-InteropTestsLocalCleartext.debug.xcconfig"; path = "Pods/Target Support Files/Pods-InteropTestsLocalCleartext/Pods-InteropTestsLocalCleartext.debug.xcconfig"; sourceTree = "<group>"; };
E4275A759BDBDF143B9B438F /* Pods-InteropTestsRemote.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-InteropTestsRemote.release.xcconfig"; path = "Pods/Target Support Files/Pods-InteropTestsRemote/Pods-InteropTestsRemote.release.xcconfig"; sourceTree = "<group>"; };
E6733B838B28453434B556E2 /* Pods-Tests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Tests.release.xcconfig"; path = "Pods/Target Support Files/Pods-Tests/Pods-Tests.release.xcconfig"; sourceTree = "<group>"; };
FBD98AC417B9882D32B19F28 /* libPods-CoreCronetEnd2EndTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-CoreCronetEnd2EndTests.a"; sourceTree = BUILT_PRODUCTS_DIR; };
FD346DB2C23F676C4842F3FF /* libPods-InteropTestsLocalCleartext.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-InteropTestsLocalCleartext.a"; sourceTree = BUILT_PRODUCTS_DIR; };
FF7B5489BCFE40111D768DD0 /* Pods.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = Pods.debug.xcconfig; path = "Pods/Target Support Files/Pods/Pods.debug.xcconfig"; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
5E8A5DA11D3840B4000F8BC4 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
5E8A5DA91D3840B4000F8BC4 /* libTests.a in Frameworks */,
60D2A57ED559F34428C2EEC5 /* libPods-CoreCronetEnd2EndTests.a in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
63423F411B150A5F006CF63C /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
@ -194,6 +218,7 @@
DBE059B4AC7A51919467EEC0 /* libPods-InteropTestsRemote.a */,
A58BE6DF1C62D1739EBB2C78 /* libPods-RxLibraryUnitTests.a */,
20DFF2F3C97EF098FE5A3171 /* libPods-Tests.a */,
FBD98AC417B9882D32B19F28 /* libPods-CoreCronetEnd2EndTests.a */,
);
name = Frameworks;
sourceTree = "<group>";
@ -215,15 +240,26 @@
3B0861FC805389C52DB260D4 /* Pods-RxLibraryUnitTests.release.xcconfig */,
060EF32D7EC0DF67ED617507 /* Pods-Tests.debug.xcconfig */,
E6733B838B28453434B556E2 /* Pods-Tests.release.xcconfig */,
0D2284C3DF7E57F0ED504E39 /* Pods-CoreCronetEnd2EndTests.debug.xcconfig */,
4AD97096D13D7416DC91A72A /* Pods-CoreCronetEnd2EndTests.release.xcconfig */,
);
name = Pods;
sourceTree = "<group>";
};
5E8A5DA51D3840B4000F8BC4 /* CoreCronetEnd2EndTests */ = {
isa = PBXGroup;
children = (
5E8A5DA61D3840B4000F8BC4 /* CoreCronetEnd2EndTests.m */,
);
path = CoreCronetEnd2EndTests;
sourceTree = "<group>";
};
635697BE1B14FC11007A7283 = {
isa = PBXGroup;
children = (
635697C91B14FC11007A7283 /* Tests */,
63E240CF1B6C63DC005F3B0E /* TestCertificates.bundle */,
5E8A5DA51D3840B4000F8BC4 /* CoreCronetEnd2EndTests */,
635697C81B14FC11007A7283 /* Products */,
51E4650F34F854F41FF053B3 /* Pods */,
136D535E19727099B941D7B1 /* Frameworks */,
@ -239,6 +275,7 @@
63DC84231BE15267000708E8 /* InteropTestsRemote.xctest */,
63DC84341BE15294000708E8 /* InteropTestsLocalSSL.xctest */,
63DC84431BE152B5000708E8 /* InteropTestsLocalCleartext.xctest */,
5E8A5DA41D3840B4000F8BC4 /* CoreCronetEnd2EndTests.xctest */,
);
name = Products;
sourceTree = "<group>";
@ -270,6 +307,27 @@
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
5E8A5DA31D3840B4000F8BC4 /* CoreCronetEnd2EndTests */ = {
isa = PBXNativeTarget;
buildConfigurationList = 5E8A5DAE1D3840B4000F8BC4 /* Build configuration list for PBXNativeTarget "CoreCronetEnd2EndTests" */;
buildPhases = (
F58F17E425446B15028B9F74 /* [CP] Check Pods Manifest.lock */,
5E8A5DA01D3840B4000F8BC4 /* Sources */,
5E8A5DA11D3840B4000F8BC4 /* Frameworks */,
5E8A5DA21D3840B4000F8BC4 /* Resources */,
E63468C760D0724F18861822 /* [CP] Embed Pods Frameworks */,
6DFE9E77CAB5760196D79E0F /* [CP] Copy Pods Resources */,
);
buildRules = (
);
dependencies = (
5E8A5DAB1D3840B4000F8BC4 /* PBXTargetDependency */,
);
name = CoreCronetEnd2EndTests;
productName = CoreCronetEnd2EndTests;
productReference = 5E8A5DA41D3840B4000F8BC4 /* CoreCronetEnd2EndTests.xctest */;
productType = "com.apple.product-type.bundle.unit-test";
};
63423F431B150A5F006CF63C /* AllTests */ = {
isa = PBXNativeTarget;
buildConfigurationList = 63423F4D1B150A5F006CF63C /* Build configuration list for PBXNativeTarget "AllTests" */;
@ -403,6 +461,9 @@
LastUpgradeCheck = 0630;
ORGANIZATIONNAME = gRPC;
TargetAttributes = {
5E8A5DA31D3840B4000F8BC4 = {
CreatedOnToolsVersion = 7.3.1;
};
63423F431B150A5F006CF63C = {
CreatedOnToolsVersion = 6.3.1;
};
@ -441,11 +502,19 @@
63DC84221BE15267000708E8 /* InteropTestsRemote */,
63DC84331BE15294000708E8 /* InteropTestsLocalSSL */,
63DC84421BE152B5000708E8 /* InteropTestsLocalCleartext */,
5E8A5DA31D3840B4000F8BC4 /* CoreCronetEnd2EndTests */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
5E8A5DA21D3840B4000F8BC4 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
63423F421B150A5F006CF63C /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
@ -561,6 +630,21 @@
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-InteropTestsLocalSSL/Pods-InteropTestsLocalSSL-resources.sh\"\n";
showEnvVarsInLog = 0;
};
6DFE9E77CAB5760196D79E0F /* [CP] Copy Pods Resources */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "[CP] Copy Pods Resources";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-CoreCronetEnd2EndTests/Pods-CoreCronetEnd2EndTests-resources.sh\"\n";
showEnvVarsInLog = 0;
};
7418AC7B3844B29E48D24FC7 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
@ -741,9 +825,47 @@
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-RxLibraryUnitTests/Pods-RxLibraryUnitTests-resources.sh\"\n";
showEnvVarsInLog = 0;
};
E63468C760D0724F18861822 /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "[CP] Embed Pods Frameworks";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-CoreCronetEnd2EndTests/Pods-CoreCronetEnd2EndTests-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
F58F17E425446B15028B9F74 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "[CP] Check Pods Manifest.lock";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n";
showEnvVarsInLog = 0;
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
5E8A5DA01D3840B4000F8BC4 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
5E8A5DA71D3840B4000F8BC4 /* CoreCronetEnd2EndTests.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
63423F401B150A5F006CF63C /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
@ -804,6 +926,11 @@
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
5E8A5DAB1D3840B4000F8BC4 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 635697C61B14FC11007A7283 /* Tests */;
targetProxy = 5E8A5DAA1D3840B4000F8BC4 /* PBXContainerItemProxy */;
};
63423F4C1B150A5F006CF63C /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 635697C61B14FC11007A7283 /* Tests */;
@ -832,6 +959,38 @@
/* End PBXTargetDependency section */
/* Begin XCBuildConfiguration section */
5E8A5DAC1D3840B4000F8BC4 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 0D2284C3DF7E57F0ED504E39 /* Pods-CoreCronetEnd2EndTests.debug.xcconfig */;
buildSettings = {
CLANG_ANALYZER_NONNULL = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_TESTABILITY = YES;
INFOPLIST_FILE = CoreCronetEnd2EndTests/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 9.3;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = io.grpc.CoreCronetEnd2EndTests;
PRODUCT_NAME = "$(TARGET_NAME)";
USER_HEADER_SEARCH_PATHS = "$(inherited) \"${PODS_ROOT}/../../../..\"";
};
name = Debug;
};
5E8A5DAD1D3840B4000F8BC4 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 4AD97096D13D7416DC91A72A /* Pods-CoreCronetEnd2EndTests.release.xcconfig */;
buildSettings = {
CLANG_ANALYZER_NONNULL = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
INFOPLIST_FILE = CoreCronetEnd2EndTests/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 9.3;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = io.grpc.CoreCronetEnd2EndTests;
PRODUCT_NAME = "$(TARGET_NAME)";
USER_HEADER_SEARCH_PATHS = "$(inherited) \"${PODS_ROOT}/../../../..\"";
};
name = Release;
};
63423F4E1B150A5F006CF63C /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = B94C27C06733CF98CE1B2757 /* Pods-AllTests.debug.xcconfig */;
@ -1071,6 +1230,15 @@
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
5E8A5DAE1D3840B4000F8BC4 /* Build configuration list for PBXNativeTarget "CoreCronetEnd2EndTests" */ = {
isa = XCConfigurationList;
buildConfigurations = (
5E8A5DAC1D3840B4000F8BC4 /* Debug */,
5E8A5DAD1D3840B4000F8BC4 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
63423F4D1B150A5F006CF63C /* Build configuration list for PBXNativeTarget "AllTests" */ = {
isa = XCConfigurationList;
buildConfigurations = (

@ -0,0 +1,99 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0730"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "NO"
buildForArchiving = "NO"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "5E8A5DA31D3840B4000F8BC4"
BuildableName = "CoreCronetEnd2EndTests.xctest"
BlueprintName = "CoreCronetEnd2EndTests"
ReferencedContainer = "container:Tests.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "5E8A5DA31D3840B4000F8BC4"
BuildableName = "CoreCronetEnd2EndTests.xctest"
BlueprintName = "CoreCronetEnd2EndTests"
ReferencedContainer = "container:Tests.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "5E8A5DA31D3840B4000F8BC4"
BuildableName = "CoreCronetEnd2EndTests.xctest"
BlueprintName = "CoreCronetEnd2EndTests"
ReferencedContainer = "container:Tests.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "5E8A5DA31D3840B4000F8BC4"
BuildableName = "CoreCronetEnd2EndTests.xctest"
BlueprintName = "CoreCronetEnd2EndTests"
ReferencedContainer = "container:Tests.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "5E8A5DA31D3840B4000F8BC4"
BuildableName = "CoreCronetEnd2EndTests.xctest"
BlueprintName = "CoreCronetEnd2EndTests"
ReferencedContainer = "container:Tests.xcodeproj">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

@ -9,7 +9,7 @@
"require": {
"php": ">=5.5.0",
"stanley-cheung/protobuf-php": "dev-master",
"google/auth": "v0.7"
"google/auth": "v0.9"
},
"autoload": {
"psr-4": {

@ -59,12 +59,15 @@
zend_class_entry *grpc_ce_call;
#if PHP_MAJOR_VERSION < 7
/* 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->owned && call->wrapped != NULL) {
grpc_call_destroy(call->wrapped);
}
zend_object_std_dtor(&call->std TSRMLS_CC);
efree(call);
}
@ -203,6 +206,131 @@ bool create_metadata_array(zval *array, grpc_metadata_array *metadata) {
return true;
}
#else
static zend_object_handlers call_ce_handlers;
/* Frees and destroys an instance of wrapped_grpc_call */
static void free_wrapped_grpc_call(zend_object *object) {
wrapped_grpc_call *call = wrapped_grpc_call_from_obj(object);
if (call->owned && call->wrapped != NULL) {
grpc_call_destroy(call->wrapped);
}
zend_object_std_dtor(&call->std);
}
/* Initializes an instance of wrapped_grpc_call to be associated with an
* object of a class specified by class_type */
zend_object *create_wrapped_grpc_call(zend_class_entry *class_type) {
wrapped_grpc_call *intern;
intern = ecalloc(1, sizeof(wrapped_grpc_call) +
zend_object_properties_size(class_type));
zend_object_std_init(&intern->std, class_type);
object_properties_init(&intern->std, class_type);
intern->std.handlers = &call_ce_handlers;
return &intern->std;
}
/* Wraps a grpc_call struct in a PHP object. Owned indicates whether the
struct should be destroyed at the end of the object's lifecycle */
void grpc_php_wrap_call(grpc_call *wrapped, bool owned, zval *call_object) {
object_init_ex(call_object, grpc_ce_call);
wrapped_grpc_call *call = Z_WRAPPED_GRPC_CALL_P(call_object);
call->wrapped = wrapped;
call->owned = owned;
}
/* Creates and returns a PHP array object with the data in a
* grpc_metadata_array. Returns NULL on failure */
void grpc_parse_metadata_array(grpc_metadata_array *metadata_array,
zval *array) {
int count = metadata_array->count;
grpc_metadata *elements = metadata_array->metadata;
int i;
zval *data;
HashTable *array_hash;
zval inner_array;
char *str_key;
char *str_val;
size_t key_len;
array_init(array);
array_hash = HASH_OF(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 ((data = zend_hash_str_find(array_hash, str_key, key_len)) != NULL) {
if (Z_TYPE_P(data) != IS_ARRAY) {
zend_throw_exception(zend_exception_get_default(),
"Metadata hash somehow contains wrong types.",
1);
efree(str_key);
efree(str_val);
return;
}
add_next_index_stringl(data, str_val, elem->value_length);
} else {
array_init(&inner_array);
add_next_index_stringl(&inner_array, str_val, elem->value_length);
add_assoc_zval(array, str_key, &inner_array);
}
}
}
/* Populates a grpc_metadata_array with the data in a PHP array object.
Returns true on success and false on failure */
bool create_metadata_array(zval *array, grpc_metadata_array *metadata) {
zval *inner_array;
zval *value;
HashTable *array_hash;
HashTable *inner_array_hash;
zend_string *key;
if (Z_TYPE_P(array) != IS_ARRAY) {
return false;
}
grpc_metadata_array_init(metadata);
array_hash = HASH_OF(array);
ZEND_HASH_FOREACH_STR_KEY_VAL(array_hash, key, inner_array) {
if (key == NULL) {
return false;
}
if (Z_TYPE_P(inner_array) != IS_ARRAY) {
return false;
}
inner_array_hash = HASH_OF(inner_array);
metadata->capacity += zend_hash_num_elements(inner_array_hash);
}
ZEND_HASH_FOREACH_END();
metadata->metadata = gpr_malloc(metadata->capacity * sizeof(grpc_metadata));
ZEND_HASH_FOREACH_STR_KEY_VAL(array_hash, key, inner_array) {
if (key == NULL) {
return false;
}
inner_array_hash = HASH_OF(inner_array);
ZEND_HASH_FOREACH_VAL(inner_array_hash, value) {
if (Z_TYPE_P(value) != IS_STRING) {
return false;
}
metadata->metadata[metadata->count].key = ZSTR_VAL(key);
metadata->metadata[metadata->count].value = Z_STRVAL_P(value);
metadata->metadata[metadata->count].value_length = Z_STRLEN_P(value);
metadata->count += 1;
} ZEND_HASH_FOREACH_END();
} ZEND_HASH_FOREACH_END();
return true;
}
#endif
/**
* Constructs a new instance of the Call class.
* @param Channel $channel The channel to associate the call with. Must not be
@ -211,30 +339,38 @@ bool create_metadata_array(zval *array, grpc_metadata_array *metadata) {
* @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;
char *host_override = NULL;
#if PHP_MAJOR_VERSION < 7
int method_len;
int host_override_len = 0;
wrapped_grpc_call *call =
(wrapped_grpc_call *)zend_object_store_get_object(getThis() TSRMLS_CC);
#else
size_t method_len;
size_t host_override_len = 0;
wrapped_grpc_call *call = Z_WRAPPED_GRPC_CALL_P(getThis());
#endif
/* "OsO|s" == 1 Object, 1 string, 1 Object, 1 optional string */
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "OsO|s",
&channel_obj, grpc_ce_channel,
&method, &method_len,
&deadline_obj, grpc_ce_timeval,
&host_override, &host_override_len)
== FAILURE) {
zend_throw_exception(
spl_ce_InvalidArgumentException,
"Call expects a Channel, a String, a Timeval and an optional String",
1 TSRMLS_CC);
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "OsO|s", &channel_obj,
grpc_ce_channel, &method, &method_len,
&deadline_obj, grpc_ce_timeval, &host_override,
&host_override_len) == FAILURE) {
zend_throw_exception(spl_ce_InvalidArgumentException,
"Call expects a Channel, a String, a Timeval and "
"an optional String", 1 TSRMLS_CC);
return;
}
#if PHP_MAJOR_VERSION < 7
wrapped_grpc_channel *channel =
(wrapped_grpc_channel *)zend_object_store_get_object(
channel_obj TSRMLS_CC);
#else
wrapped_grpc_channel *channel = Z_WRAPPED_GRPC_CHANNEL_P(channel_obj);
#endif
if (channel->wrapped == NULL) {
zend_throw_exception(spl_ce_InvalidArgumentException,
"Call cannot be constructed from a closed Channel",
@ -242,12 +378,17 @@ PHP_METHOD(Call, __construct) {
return;
}
add_property_zval(getThis(), "channel", channel_obj);
#if PHP_MAJOR_VERSION < 7
wrapped_grpc_timeval *deadline =
(wrapped_grpc_timeval *)zend_object_store_get_object(
deadline_obj TSRMLS_CC);
call->wrapped = grpc_channel_create_call(
channel->wrapped, NULL, GRPC_PROPAGATE_DEFAULTS, completion_queue, method,
host_override, deadline->wrapped, NULL);
#else
wrapped_grpc_timeval *deadline = Z_WRAPPED_GRPC_TIMEVAL_P(deadline_obj);
#endif
call->wrapped =
grpc_channel_create_call(channel->wrapped, NULL, GRPC_PROPAGATE_DEFAULTS,
completion_queue, method, host_override,
deadline->wrapped, NULL);
call->owned = true;
}
@ -257,22 +398,40 @@ PHP_METHOD(Call, __construct) {
* @return object Object with results of all actions
*/
PHP_METHOD(Call, startBatch) {
#if PHP_MAJOR_VERSION < 7
wrapped_grpc_call *call =
(wrapped_grpc_call *)zend_object_store_get_object(getThis() TSRMLS_CC);
grpc_op ops[8];
size_t op_num = 0;
zval *array;
zval **value;
zval **inner_value;
HashTable *array_hash;
HashPosition array_pointer;
HashTable *status_hash;
HashTable *message_hash;
zval **message_value;
zval **message_flags;
char *key;
uint key_len;
ulong index;
zval *result;
zval *recv_status;
MAKE_STD_ZVAL(result);
object_init(result);
#else
wrapped_grpc_call *call = Z_WRAPPED_GRPC_CALL_P(getThis());
zval *value;
zval *inner_value;
zval *message_value;
zval *message_flags;
zend_string *key;
zend_ulong index;
zval recv_status;
object_init(return_value);
#endif
grpc_op ops[8];
size_t op_num = 0;
zval *array;
HashTable *array_hash;
HashTable *status_hash;
HashTable *message_hash;
grpc_metadata_array metadata;
grpc_metadata_array trailing_metadata;
grpc_metadata_array recv_metadata;
@ -283,17 +442,16 @@ PHP_METHOD(Call, startBatch) {
grpc_byte_buffer *message;
int cancelled;
grpc_call_error error;
zval *result;
char *message_str;
size_t message_len;
zval *recv_status;
grpc_metadata_array_init(&metadata);
grpc_metadata_array_init(&trailing_metadata);
grpc_metadata_array_init(&recv_metadata);
grpc_metadata_array_init(&recv_trailing_metadata);
MAKE_STD_ZVAL(result);
object_init(result);
memset(ops, 0, sizeof(ops));
/* "a" == 1 array */
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "a", &array) ==
FAILURE) {
@ -301,6 +459,9 @@ PHP_METHOD(Call, startBatch) {
"start_batch expects an array", 1 TSRMLS_CC);
goto cleanup;
}
#if PHP_MAJOR_VERSION < 7
array_hash = Z_ARRVAL_P(array);
for (zend_hash_internal_pointer_reset_ex(array_hash, &array_pointer);
zend_hash_get_current_data_ex(array_hash, (void**)&value,
@ -313,124 +474,250 @@ PHP_METHOD(Call, startBatch) {
goto cleanup;
}
switch(index) {
case GRPC_OP_SEND_INITIAL_METADATA:
if (!create_metadata_array(*value, &metadata)) {
case GRPC_OP_SEND_INITIAL_METADATA:
if (!create_metadata_array(*value, &metadata)) {
zend_throw_exception(spl_ce_InvalidArgumentException,
"Bad metadata value given", 1 TSRMLS_CC);
goto cleanup;
}
ops[op_num].data.send_initial_metadata.count =
metadata.count;
ops[op_num].data.send_initial_metadata.metadata =
metadata.metadata;
break;
case GRPC_OP_SEND_MESSAGE:
if (Z_TYPE_PP(value) != IS_ARRAY) {
zend_throw_exception(spl_ce_InvalidArgumentException,
"Expected an array for send message",
1 TSRMLS_CC);
goto cleanup;
}
message_hash = Z_ARRVAL_PP(value);
if (zend_hash_find(message_hash, "flags", sizeof("flags"),
(void **)&message_flags) == SUCCESS) {
if (Z_TYPE_PP(message_flags) != IS_LONG) {
zend_throw_exception(spl_ce_InvalidArgumentException,
"Bad metadata value given", 1 TSRMLS_CC);
goto cleanup;
"Expected an int for message flags",
1 TSRMLS_CC);
}
ops[op_num].data.send_initial_metadata.count =
metadata.count;
ops[op_num].data.send_initial_metadata.metadata =
metadata.metadata;
break;
case GRPC_OP_SEND_MESSAGE:
if (Z_TYPE_PP(value) != IS_ARRAY) {
ops[op_num].flags = Z_LVAL_PP(message_flags) & GRPC_WRITE_USED_MASK;
}
if (zend_hash_find(message_hash, "message", sizeof("message"),
(void **)&message_value) != SUCCESS ||
Z_TYPE_PP(message_value) != IS_STRING) {
zend_throw_exception(spl_ce_InvalidArgumentException,
"Expected a string for send message",
1 TSRMLS_CC);
goto cleanup;
}
ops[op_num].data.send_message =
string_to_byte_buffer(Z_STRVAL_PP(message_value),
Z_STRLEN_PP(message_value));
break;
case GRPC_OP_SEND_CLOSE_FROM_CLIENT:
break;
case GRPC_OP_SEND_STATUS_FROM_SERVER:
status_hash = Z_ARRVAL_PP(value);
if (zend_hash_find(status_hash, "metadata", sizeof("metadata"),
(void **)&inner_value) == SUCCESS) {
if (!create_metadata_array(*inner_value, &trailing_metadata)) {
zend_throw_exception(spl_ce_InvalidArgumentException,
"Expected an array for send message",
"Bad trailing metadata value given",
1 TSRMLS_CC);
goto cleanup;
}
message_hash = Z_ARRVAL_PP(value);
if (zend_hash_find(message_hash, "flags", sizeof("flags"),
(void **)&message_flags) == SUCCESS) {
if (Z_TYPE_PP(message_flags) != IS_LONG) {
zend_throw_exception(spl_ce_InvalidArgumentException,
"Expected an int for message flags",
1 TSRMLS_CC);
}
ops[op_num].flags = Z_LVAL_PP(message_flags) & GRPC_WRITE_USED_MASK;
ops[op_num].data.send_status_from_server.trailing_metadata =
trailing_metadata.metadata;
ops[op_num].data.send_status_from_server.trailing_metadata_count =
trailing_metadata.count;
}
if (zend_hash_find(status_hash, "code", sizeof("code"),
(void**)&inner_value) == SUCCESS) {
if (Z_TYPE_PP(inner_value) != IS_LONG) {
zend_throw_exception(spl_ce_InvalidArgumentException,
"Status code must be an integer",
1 TSRMLS_CC);
goto cleanup;
}
if (zend_hash_find(message_hash, "message", sizeof("message"),
(void **)&message_value) != SUCCESS ||
Z_TYPE_PP(message_value) != IS_STRING) {
ops[op_num].data.send_status_from_server.status =
Z_LVAL_PP(inner_value);
} else {
zend_throw_exception(spl_ce_InvalidArgumentException,
"Integer status code is required",
1 TSRMLS_CC);
goto cleanup;
}
if (zend_hash_find(status_hash, "details", sizeof("details"),
(void**)&inner_value) == SUCCESS) {
if (Z_TYPE_PP(inner_value) != IS_STRING) {
zend_throw_exception(spl_ce_InvalidArgumentException,
"Expected a string for send message",
"Status details must be a string",
1 TSRMLS_CC);
goto cleanup;
}
ops[op_num].data.send_message =
string_to_byte_buffer(Z_STRVAL_PP(message_value),
Z_STRLEN_PP(message_value));
break;
case GRPC_OP_SEND_CLOSE_FROM_CLIENT:
break;
case GRPC_OP_SEND_STATUS_FROM_SERVER:
status_hash = Z_ARRVAL_PP(value);
if (zend_hash_find(status_hash, "metadata", sizeof("metadata"),
(void **)&inner_value) == SUCCESS) {
if (!create_metadata_array(*inner_value, &trailing_metadata)) {
zend_throw_exception(spl_ce_InvalidArgumentException,
"Bad trailing metadata value given",
1 TSRMLS_CC);
goto cleanup;
}
ops[op_num].data.send_status_from_server.trailing_metadata =
trailing_metadata.metadata;
ops[op_num].data.send_status_from_server.trailing_metadata_count =
trailing_metadata.count;
ops[op_num].data.send_status_from_server.status_details =
Z_STRVAL_PP(inner_value);
} else {
zend_throw_exception(spl_ce_InvalidArgumentException,
"String status details is required",
1 TSRMLS_CC);
goto cleanup;
}
break;
case GRPC_OP_RECV_INITIAL_METADATA:
ops[op_num].data.recv_initial_metadata = &recv_metadata;
break;
case GRPC_OP_RECV_MESSAGE:
ops[op_num].data.recv_message = &message;
break;
case GRPC_OP_RECV_STATUS_ON_CLIENT:
ops[op_num].data.recv_status_on_client.trailing_metadata =
&recv_trailing_metadata;
ops[op_num].data.recv_status_on_client.status = &status;
ops[op_num].data.recv_status_on_client.status_details =
&status_details;
ops[op_num].data.recv_status_on_client.status_details_capacity =
&status_details_capacity;
break;
case GRPC_OP_RECV_CLOSE_ON_SERVER:
ops[op_num].data.recv_close_on_server.cancelled = &cancelled;
break;
default:
zend_throw_exception(spl_ce_InvalidArgumentException,
"Unrecognized key in batch", 1 TSRMLS_CC);
goto cleanup;
}
ops[op_num].op = (grpc_op_type)index;
ops[op_num].flags = 0;
ops[op_num].reserved = NULL;
op_num++;
}
#else
array_hash = HASH_OF(array);
ZEND_HASH_FOREACH_KEY_VAL(array_hash, index, key, value) {
if (key) {
zend_throw_exception(spl_ce_InvalidArgumentException,
"batch keys must be integers", 1);
goto cleanup;
}
switch(index) {
case GRPC_OP_SEND_INITIAL_METADATA:
if (!create_metadata_array(value, &metadata)) {
zend_throw_exception(spl_ce_InvalidArgumentException,
"Bad metadata value given", 1);
goto cleanup;
}
ops[op_num].data.send_initial_metadata.count = metadata.count;
ops[op_num].data.send_initial_metadata.metadata = metadata.metadata;
break;
case GRPC_OP_SEND_MESSAGE:
if (Z_TYPE_P(value) != IS_ARRAY) {
zend_throw_exception(spl_ce_InvalidArgumentException,
"Expected an array for send message", 1);
goto cleanup;
}
message_hash = HASH_OF(value);
if ((message_flags =
zend_hash_str_find(message_hash, "flags",
sizeof("flags") - 1)) != NULL) {
if (Z_TYPE_P(message_flags) != IS_LONG) {
zend_throw_exception(spl_ce_InvalidArgumentException,
"Expected an int for message flags", 1);
}
ops[op_num].flags = Z_LVAL_P(message_flags) & GRPC_WRITE_USED_MASK;
}
if ((message_value = zend_hash_str_find(message_hash, "message",
sizeof("message") - 1))
== NULL || Z_TYPE_P(message_value) != IS_STRING) {
zend_throw_exception(spl_ce_InvalidArgumentException,
"Expected a string for send message", 1);
goto cleanup;
}
ops[op_num].data.send_message =
string_to_byte_buffer(Z_STRVAL_P(message_value),
Z_STRLEN_P(message_value));
break;
case GRPC_OP_SEND_CLOSE_FROM_CLIENT:
break;
case GRPC_OP_SEND_STATUS_FROM_SERVER:
status_hash = HASH_OF(value);
if ((inner_value = zend_hash_str_find(status_hash, "metadata",
sizeof("metadata") - 1))
!= NULL) {
if (!create_metadata_array(inner_value, &trailing_metadata)) {
zend_throw_exception(spl_ce_InvalidArgumentException,
"Bad trailing metadata value given", 1);
goto cleanup;
}
if (zend_hash_find(status_hash, "code", sizeof("code"),
(void**)&inner_value) == SUCCESS) {
if (Z_TYPE_PP(inner_value) != IS_LONG) {
zend_throw_exception(spl_ce_InvalidArgumentException,
"Status code must be an integer",
1 TSRMLS_CC);
goto cleanup;
}
ops[op_num].data.send_status_from_server.status =
Z_LVAL_PP(inner_value);
} else {
ops[op_num].data.send_status_from_server.trailing_metadata =
trailing_metadata.metadata;
ops[op_num].data.send_status_from_server.trailing_metadata_count =
trailing_metadata.count;
}
if ((inner_value = zend_hash_str_find(status_hash, "code",
sizeof("code") - 1)) != NULL) {
if (Z_TYPE_P(inner_value) != IS_LONG) {
zend_throw_exception(spl_ce_InvalidArgumentException,
"Integer status code is required",
1 TSRMLS_CC);
"Status code must be an integer", 1);
goto cleanup;
}
if (zend_hash_find(status_hash, "details", sizeof("details"),
(void**)&inner_value) == SUCCESS) {
if (Z_TYPE_PP(inner_value) != IS_STRING) {
zend_throw_exception(spl_ce_InvalidArgumentException,
"Status details must be a string",
1 TSRMLS_CC);
goto cleanup;
}
ops[op_num].data.send_status_from_server.status_details =
Z_STRVAL_PP(inner_value);
} else {
ops[op_num].data.send_status_from_server.status =
Z_LVAL_P(inner_value);
} else {
zend_throw_exception(spl_ce_InvalidArgumentException,
"Integer status code is required", 1);
goto cleanup;
}
if ((inner_value = zend_hash_str_find(status_hash, "details",
sizeof("details") - 1)) != NULL) {
if (Z_TYPE_P(inner_value) != IS_STRING) {
zend_throw_exception(spl_ce_InvalidArgumentException,
"String status details is required",
1 TSRMLS_CC);
"Status details must be a string", 1);
goto cleanup;
}
break;
case GRPC_OP_RECV_INITIAL_METADATA:
ops[op_num].data.recv_initial_metadata = &recv_metadata;
break;
case GRPC_OP_RECV_MESSAGE:
ops[op_num].data.recv_message = &message;
break;
case GRPC_OP_RECV_STATUS_ON_CLIENT:
ops[op_num].data.recv_status_on_client.trailing_metadata =
&recv_trailing_metadata;
ops[op_num].data.recv_status_on_client.status = &status;
ops[op_num].data.recv_status_on_client.status_details =
&status_details;
ops[op_num].data.recv_status_on_client.status_details_capacity =
&status_details_capacity;
break;
case GRPC_OP_RECV_CLOSE_ON_SERVER:
ops[op_num].data.recv_close_on_server.cancelled = &cancelled;
break;
default:
ops[op_num].data.send_status_from_server.status_details =
Z_STRVAL_P(inner_value);
} else {
zend_throw_exception(spl_ce_InvalidArgumentException,
"Unrecognized key in batch", 1 TSRMLS_CC);
"String status details is required", 1);
goto cleanup;
}
break;
case GRPC_OP_RECV_INITIAL_METADATA:
ops[op_num].data.recv_initial_metadata = &recv_metadata;
break;
case GRPC_OP_RECV_MESSAGE:
ops[op_num].data.recv_message = &message;
break;
case GRPC_OP_RECV_STATUS_ON_CLIENT:
ops[op_num].data.recv_status_on_client.trailing_metadata =
&recv_trailing_metadata;
ops[op_num].data.recv_status_on_client.status = &status;
ops[op_num].data.recv_status_on_client.status_details =
&status_details;
ops[op_num].data.recv_status_on_client.status_details_capacity =
&status_details_capacity;
break;
case GRPC_OP_RECV_CLOSE_ON_SERVER:
ops[op_num].data.recv_close_on_server.cancelled = &cancelled;
break;
default:
zend_throw_exception(spl_ce_InvalidArgumentException,
"Unrecognized key in batch", 1);
goto cleanup;
}
ops[op_num].op = (grpc_op_type)index;
ops[op_num].flags = 0;
ops[op_num].reserved = NULL;
op_num++;
}
ZEND_HASH_FOREACH_END();
#endif
error = grpc_call_start_batch(call->wrapped, ops, op_num, call->wrapped,
NULL);
if (error != GRPC_CALL_OK) {
@ -441,52 +728,98 @@ PHP_METHOD(Call, startBatch) {
}
grpc_completion_queue_pluck(completion_queue, call->wrapped,
gpr_inf_future(GPR_CLOCK_REALTIME), NULL);
#if PHP_MAJOR_VERSION < 7
for (int i = 0; i < op_num; i++) {
switch(ops[i].op) {
case GRPC_OP_SEND_INITIAL_METADATA:
add_property_bool(result, "send_metadata", true);
break;
case GRPC_OP_SEND_MESSAGE:
add_property_bool(result, "send_message", true);
break;
case GRPC_OP_SEND_CLOSE_FROM_CLIENT:
add_property_bool(result, "send_close", true);
break;
case GRPC_OP_SEND_STATUS_FROM_SERVER:
add_property_bool(result, "send_status", true);
break;
case GRPC_OP_RECV_INITIAL_METADATA:
array = grpc_parse_metadata_array(&recv_metadata TSRMLS_CC);
add_property_zval(result, "metadata", array);
Z_DELREF_P(array);
break;
case GRPC_OP_RECV_MESSAGE:
byte_buffer_to_string(message, &message_str, &message_len);
if (message_str == NULL) {
add_property_null(result, "message");
} else {
add_property_stringl(result, "message", message_str, message_len,
false);
}
break;
case GRPC_OP_RECV_STATUS_ON_CLIENT:
MAKE_STD_ZVAL(recv_status);
object_init(recv_status);
array = grpc_parse_metadata_array(&recv_trailing_metadata TSRMLS_CC);
add_property_zval(recv_status, "metadata", array);
Z_DELREF_P(array);
add_property_long(recv_status, "code", status);
add_property_string(recv_status, "details", status_details, true);
add_property_zval(result, "status", recv_status);
Z_DELREF_P(recv_status);
break;
case GRPC_OP_RECV_CLOSE_ON_SERVER:
add_property_bool(result, "cancelled", cancelled);
break;
default:
break;
case GRPC_OP_SEND_INITIAL_METADATA:
add_property_bool(result, "send_metadata", true);
break;
case GRPC_OP_SEND_MESSAGE:
add_property_bool(result, "send_message", true);
break;
case GRPC_OP_SEND_CLOSE_FROM_CLIENT:
add_property_bool(result, "send_close", true);
break;
case GRPC_OP_SEND_STATUS_FROM_SERVER:
add_property_bool(result, "send_status", true);
break;
case GRPC_OP_RECV_INITIAL_METADATA:
array = grpc_parse_metadata_array(&recv_metadata TSRMLS_CC);
add_property_zval(result, "metadata", array);
Z_DELREF_P(array);
break;
case GRPC_OP_RECV_MESSAGE:
byte_buffer_to_string(message, &message_str, &message_len);
if (message_str == NULL) {
add_property_null(result, "message");
} else {
add_property_stringl(result, "message", message_str, message_len,
false);
}
break;
case GRPC_OP_RECV_STATUS_ON_CLIENT:
MAKE_STD_ZVAL(recv_status);
object_init(recv_status);
array = grpc_parse_metadata_array(&recv_trailing_metadata TSRMLS_CC);
add_property_zval(recv_status, "metadata", array);
Z_DELREF_P(array);
add_property_long(recv_status, "code", status);
add_property_string(recv_status, "details", status_details, true);
add_property_zval(result, "status", recv_status);
Z_DELREF_P(recv_status);
break;
case GRPC_OP_RECV_CLOSE_ON_SERVER:
add_property_bool(result, "cancelled", cancelled);
break;
default:
break;
}
}
#else
for (int i = 0; i < op_num; i++) {
switch(ops[i].op) {
case GRPC_OP_SEND_INITIAL_METADATA:
add_property_bool(return_value, "send_metadata", true);
break;
case GRPC_OP_SEND_MESSAGE:
add_property_bool(return_value, "send_message", true);
break;
case GRPC_OP_SEND_CLOSE_FROM_CLIENT:
add_property_bool(return_value, "send_close", true);
break;
case GRPC_OP_SEND_STATUS_FROM_SERVER:
add_property_bool(return_value, "send_status", true);
break;
case GRPC_OP_RECV_INITIAL_METADATA:
grpc_parse_metadata_array(&recv_metadata, array);
add_property_zval(return_value, "metadata", array);
break;
case GRPC_OP_RECV_MESSAGE:
byte_buffer_to_string(message, &message_str, &message_len);
if (message_str == NULL) {
add_property_null(return_value, "message");
} else {
add_property_stringl(return_value, "message", message_str,
message_len);
}
break;
case GRPC_OP_RECV_STATUS_ON_CLIENT:
object_init(&recv_status);
grpc_parse_metadata_array(&recv_trailing_metadata, array);
add_property_zval(&recv_status, "metadata", array);
add_property_long(&recv_status, "code", status);
add_property_string(&recv_status, "details", status_details);
add_property_zval(return_value, "status", &recv_status);
break;
case GRPC_OP_RECV_CLOSE_ON_SERVER:
add_property_bool(return_value, "cancelled", cancelled);
break;
default:
break;
}
}
#endif
cleanup:
grpc_metadata_array_destroy(&metadata);
grpc_metadata_array_destroy(&trailing_metadata);
@ -503,7 +836,11 @@ cleanup:
grpc_byte_buffer_destroy(message);
}
}
#if PHP_MAJOR_VERSION < 7
RETURN_DESTROY_ZVAL(result);
#else
RETURN_DESTROY_ZVAL(return_value);
#endif
}
/**
@ -511,9 +848,14 @@ cleanup:
* @return string The URI of the endpoint
*/
PHP_METHOD(Call, getPeer) {
#if PHP_MAJOR_VERSION < 7
wrapped_grpc_call *call =
(wrapped_grpc_call *)zend_object_store_get_object(getThis() TSRMLS_CC);
RETURN_STRING(grpc_call_get_peer(call->wrapped), 1);
#else
wrapped_grpc_call *call = Z_WRAPPED_GRPC_CALL_P(getThis());
RETURN_STRING(grpc_call_get_peer(call->wrapped));
#endif
}
/**
@ -521,8 +863,12 @@ PHP_METHOD(Call, getPeer) {
* has not already ended with another status.
*/
PHP_METHOD(Call, cancel) {
#if PHP_MAJOR_VERSION < 7
wrapped_grpc_call *call =
(wrapped_grpc_call *)zend_object_store_get_object(getThis() TSRMLS_CC);
#else
wrapped_grpc_call *call = Z_WRAPPED_GRPC_CALL_P(getThis());
#endif
grpc_call_cancel(call->wrapped, NULL);
}
@ -543,12 +889,17 @@ PHP_METHOD(Call, setCredentials) {
return;
}
#if PHP_MAJOR_VERSION < 7
wrapped_grpc_call_credentials *creds =
(wrapped_grpc_call_credentials *)zend_object_store_get_object(
creds_obj TSRMLS_CC);
wrapped_grpc_call *call =
(wrapped_grpc_call *)zend_object_store_get_object(getThis() TSRMLS_CC);
#else
wrapped_grpc_call_credentials *creds =
Z_WRAPPED_GRPC_CALL_CREDS_P(creds_obj);
wrapped_grpc_call *call = Z_WRAPPED_GRPC_CALL_P(getThis());
#endif
grpc_call_error error = GRPC_CALL_ERROR;
error = grpc_call_set_credentials(call->wrapped, creds->wrapped);
@ -556,16 +907,23 @@ PHP_METHOD(Call, setCredentials) {
}
static zend_function_entry call_methods[] = {
PHP_ME(Call, __construct, NULL, ZEND_ACC_PUBLIC | ZEND_ACC_CTOR)
PHP_ME(Call, startBatch, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Call, getPeer, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Call, cancel, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Call, setCredentials, NULL, ZEND_ACC_PUBLIC)
PHP_FE_END};
PHP_ME(Call, __construct, NULL, ZEND_ACC_PUBLIC | ZEND_ACC_CTOR)
PHP_ME(Call, startBatch, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Call, getPeer, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Call, cancel, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Call, setCredentials, 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);
#if PHP_MAJOR_VERSION >= 7
memcpy(&call_ce_handlers, zend_get_std_object_handlers(),
sizeof(zend_object_handlers));
call_ce_handlers.offset = XtOffsetOf(wrapped_grpc_call, std);
call_ce_handlers.free_obj = free_wrapped_grpc_call;
#endif
}

@ -48,17 +48,15 @@
/* Class entry for the Call PHP class */
extern zend_class_entry *grpc_ce_call;
#if PHP_MAJOR_VERSION < 7
/* Wrapper struct for grpc_call that can be associated with a PHP object */
typedef struct wrapped_grpc_call {
zend_object std;
bool owned;
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, bool owned TSRMLS_DC);
@ -66,6 +64,36 @@ zval *grpc_php_wrap_call(grpc_call *wrapped, bool owned TSRMLS_DC);
* call metadata */
zval *grpc_parse_metadata_array(grpc_metadata_array *metadata_array TSRMLS_DC);
#else
/* Wrapper struct for grpc_call that can be associated with a PHP object */
typedef struct wrapped_grpc_call {
bool owned;
grpc_call *wrapped;
zend_object std;
} wrapped_grpc_call;
static inline wrapped_grpc_call
*wrapped_grpc_call_from_obj(zend_object *obj) {
return (wrapped_grpc_call*)((char*)(obj) -
XtOffsetOf(wrapped_grpc_call, std));
}
#define Z_WRAPPED_GRPC_CALL_P(zv) wrapped_grpc_call_from_obj(Z_OBJ_P((zv)))
/* Creates a Call object that wraps the given grpc_call struct */
void grpc_php_wrap_call(grpc_call *wrapped, bool owned, zval *call_object);
/* Creates and returns a PHP associative array of metadata from a C array of
* call metadata */
void grpc_parse_metadata_array(grpc_metadata_array *metadata_array,
zval *array);
#endif /* PHP_MAJOR_VERSION */
/* Initializes the Call PHP class */
void grpc_init_call(TSRMLS_D);
/* Populates a grpc_metadata_array with the data in a PHP array object.
Returns true on success and false on failure */
bool create_metadata_array(zval *array, grpc_metadata_array *metadata);

@ -53,6 +53,8 @@
zend_class_entry *grpc_ce_call_credentials;
#if PHP_MAJOR_VERSION < 7
/* Frees and destroys an instance of wrapped_grpc_call_credentials */
void free_wrapped_grpc_call_credentials(void *object TSRMLS_DC) {
wrapped_grpc_call_credentials *creds =
@ -60,6 +62,7 @@ void free_wrapped_grpc_call_credentials(void *object TSRMLS_DC) {
if (creds->wrapped != NULL) {
grpc_call_credentials_release(creds->wrapped);
}
zend_object_std_dtor(&creds->std TSRMLS_CC);
efree(creds);
}
@ -94,6 +97,43 @@ zval *grpc_php_wrap_call_credentials(grpc_call_credentials *wrapped TSRMLS_DC) {
return credentials_object;
}
#else
static zend_object_handlers call_credentials_ce_handlers;
/* Frees and destroys an instance of wrapped_grpc_call_credentials */
static void free_wrapped_grpc_call_credentials(zend_object *object) {
wrapped_grpc_call_credentials *creds =
wrapped_grpc_call_creds_from_obj(object);
if (creds->wrapped != NULL) {
grpc_call_credentials_release(creds->wrapped);
}
zend_object_std_dtor(&creds->std);
}
/* Initializes an instance of wrapped_grpc_call_credentials to be
* associated with an object of a class specified by class_type */
zend_object *create_wrapped_grpc_call_credentials(zend_class_entry
*class_type) {
wrapped_grpc_call_credentials *intern;
intern = ecalloc(1, sizeof(wrapped_grpc_call_credentials) +
zend_object_properties_size(class_type));
zend_object_std_init(&intern->std, class_type);
object_properties_init(&intern->std, class_type);
intern->std.handlers = &call_credentials_ce_handlers;
return &intern->std;
}
void grpc_php_wrap_call_credentials(grpc_call_credentials *wrapped,
zval *credentials_object) {
object_init_ex(credentials_object, grpc_ce_call_credentials);
wrapped_grpc_call_credentials *credentials =
Z_WRAPPED_GRPC_CALL_CREDS_P(credentials_object);
credentials->wrapped = wrapped;
}
#endif
/**
* Create composite credentials from two existing credentials.
* @param CallCredentials cred1 The first credential
@ -113,6 +153,7 @@ PHP_METHOD(CallCredentials, createComposite) {
1 TSRMLS_CC);
return;
}
#if PHP_MAJOR_VERSION < 7
wrapped_grpc_call_credentials *cred1 =
(wrapped_grpc_call_credentials *)zend_object_store_get_object(
cred1_obj TSRMLS_CC);
@ -124,6 +165,17 @@ PHP_METHOD(CallCredentials, createComposite) {
NULL);
zval *creds_object = grpc_php_wrap_call_credentials(creds TSRMLS_CC);
RETURN_DESTROY_ZVAL(creds_object);
#else
wrapped_grpc_call_credentials *cred1 =
Z_WRAPPED_GRPC_CALL_CREDS_P(cred1_obj);
wrapped_grpc_call_credentials *cred2 =
Z_WRAPPED_GRPC_CALL_CREDS_P(cred2_obj);
grpc_call_credentials *creds =
grpc_composite_call_credentials_create(cred1->wrapped,
cred2->wrapped, NULL);
grpc_php_wrap_call_credentials(creds, return_value);
RETURN_DESTROY_ZVAL(return_value);
#endif
}
/**
@ -141,13 +193,10 @@ PHP_METHOD(CallCredentials, createFromPlugin) {
memset(fci_cache, 0, sizeof(zend_fcall_info_cache));
/* "f" == 1 function */
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "f", fci,
fci_cache,
fci->params,
fci->param_count) == FAILURE) {
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "f*", fci, fci_cache,
fci->params, fci->param_count) == FAILURE) {
zend_throw_exception(spl_ce_InvalidArgumentException,
"createFromPlugin expects 1 callback",
1 TSRMLS_CC);
"createFromPlugin expects 1 callback", 1 TSRMLS_CC);
return;
}
@ -165,10 +214,15 @@ PHP_METHOD(CallCredentials, createFromPlugin) {
plugin.state = (void *)state;
plugin.type = "";
grpc_call_credentials *creds = grpc_metadata_credentials_create_from_plugin(
plugin, NULL);
grpc_call_credentials *creds =
grpc_metadata_credentials_create_from_plugin(plugin, NULL);
#if PHP_MAJOR_VERSION < 7
zval *creds_object = grpc_php_wrap_call_credentials(creds TSRMLS_CC);
RETURN_DESTROY_ZVAL(creds_object);
#else
grpc_php_wrap_call_credentials(creds, return_value);
RETURN_DESTROY_ZVAL(return_value);
#endif
}
/* Callback function for plugin creds API */
@ -181,6 +235,7 @@ void plugin_get_metadata(void *ptr, grpc_auth_metadata_context context,
/* prepare to call the user callback function with info from the
* grpc_auth_metadata_context */
#if PHP_MAJOR_VERSION < 7
zval **params[1];
zval *arg;
zval *retval;
@ -192,21 +247,41 @@ void plugin_get_metadata(void *ptr, grpc_auth_metadata_context context,
state->fci->param_count = 1;
state->fci->params = params;
state->fci->retval_ptr_ptr = &retval;
#else
zval arg;
zval retval;
object_init(&arg);
add_property_string(&arg, "service_url", context.service_url);
add_property_string(&arg, "method_name", context.method_name);
state->fci->param_count = 1;
state->fci->params = &arg;
state->fci->retval = &retval;
#endif
/* call the user callback function */
zend_call_function(state->fci, state->fci_cache TSRMLS_CC);
#if PHP_MAJOR_VERSION < 7
if (Z_TYPE_P(retval) != IS_ARRAY) {
#else
if (Z_TYPE_P(&retval) != IS_ARRAY) {
#endif
zend_throw_exception(spl_ce_InvalidArgumentException,
"plugin callback must return metadata array",
1 TSRMLS_CC);
return;
}
grpc_metadata_array metadata;
#if PHP_MAJOR_VERSION < 7
if (!create_metadata_array(retval, &metadata)) {
#else
if (!create_metadata_array(&retval, &metadata)) {
#endif
zend_throw_exception(spl_ce_InvalidArgumentException,
"invalid metadata", 1 TSRMLS_CC);
grpc_metadata_array_destroy(&metadata);
return;
}
/* TODO: handle error */
@ -229,11 +304,21 @@ static zend_function_entry call_credentials_methods[] = {
ZEND_ACC_PUBLIC | ZEND_ACC_STATIC)
PHP_ME(CallCredentials, createFromPlugin, NULL,
ZEND_ACC_PUBLIC | ZEND_ACC_STATIC)
PHP_FE_END};
PHP_FE_END
};
void grpc_init_call_credentials(TSRMLS_D) {
zend_class_entry ce;
INIT_CLASS_ENTRY(ce, "Grpc\\CallCredentials", call_credentials_methods);
ce.create_object = create_wrapped_grpc_call_credentials;
grpc_ce_call_credentials = zend_register_internal_class(&ce TSRMLS_CC);
#if PHP_MAJOR_VERSION >= 7
memcpy(&call_credentials_ce_handlers,
zend_get_std_object_handlers(),
sizeof(zend_object_handlers));
call_credentials_ce_handlers.offset =
XtOffsetOf(wrapped_grpc_call_credentials, std);
call_credentials_ce_handlers.free_obj =
free_wrapped_grpc_call_credentials;
#endif
}

@ -49,14 +49,37 @@
/* Class entry for the CallCredentials PHP class */
extern zend_class_entry *grpc_ce_call_credentials;
#if PHP_MAJOR_VERSION < 7
/* Wrapper struct for grpc_call_credentials that can be associated
* with a PHP object */
typedef struct wrapped_grpc_call_credentials {
zend_object std;
grpc_call_credentials *wrapped;
} wrapped_grpc_call_credentials;
#else
/* Wrapper struct for grpc_call_credentials that can be associated
* with a PHP object */
typedef struct wrapped_grpc_call_credentials {
grpc_call_credentials *wrapped;
zend_object std;
} wrapped_grpc_call_credentials;
static inline wrapped_grpc_call_credentials
*wrapped_grpc_call_creds_from_obj(zend_object *obj) {
return
(wrapped_grpc_call_credentials*)((char*)(obj) -
XtOffsetOf(wrapped_grpc_call_credentials,
std));
}
#define Z_WRAPPED_GRPC_CALL_CREDS_P(zv) \
wrapped_grpc_call_creds_from_obj(Z_OBJ_P((zv)))
#endif /* PHP_MAJOR_VERSION */
/* Struct to hold callback function for plugin creds API */
typedef struct plugin_state {
zend_fcall_info *fci;

@ -57,12 +57,15 @@
zend_class_entry *grpc_ce_channel;
#if PHP_MAJOR_VERSION < 7
/* 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);
}
zend_object_std_dtor(&channel->std TSRMLS_CC);
efree(channel);
}
@ -83,7 +86,8 @@ zend_object_value create_wrapped_grpc_channel(zend_class_entry *class_type
return retval;
}
void php_grpc_read_args_array(zval *args_array, grpc_channel_args *args TSRMLS_DC) {
void php_grpc_read_args_array(zval *args_array,
grpc_channel_args *args TSRMLS_DC) {
HashTable *array_hash;
HashPosition array_pointer;
int args_index;
@ -107,23 +111,88 @@ void php_grpc_read_args_array(zval *args_array, grpc_channel_args *args TSRMLS_D
}
args->args[args_index].key = key;
switch (Z_TYPE_P(*data)) {
case IS_LONG:
args->args[args_index].value.integer = (int)Z_LVAL_P(*data);
args->args[args_index].type = GRPC_ARG_INTEGER;
break;
case IS_STRING:
args->args[args_index].value.string = Z_STRVAL_P(*data);
args->args[args_index].type = GRPC_ARG_STRING;
break;
default:
zend_throw_exception(spl_ce_InvalidArgumentException,
"args values must be int or string", 1 TSRMLS_CC);
return;
case IS_LONG:
args->args[args_index].value.integer = (int)Z_LVAL_P(*data);
args->args[args_index].type = GRPC_ARG_INTEGER;
break;
case IS_STRING:
args->args[args_index].value.string = Z_STRVAL_P(*data);
args->args[args_index].type = GRPC_ARG_STRING;
break;
default:
zend_throw_exception(spl_ce_InvalidArgumentException,
"args values must be int or string", 1 TSRMLS_CC);
return;
}
args_index++;
}
}
#else
static zend_object_handlers channel_ce_handlers;
/* Frees and destroys an instance of wrapped_grpc_channel */
static void free_wrapped_grpc_channel(zend_object *object) {
wrapped_grpc_channel *channel = wrapped_grpc_channel_from_obj(object);
if (channel->wrapped != NULL) {
grpc_channel_destroy(channel->wrapped);
}
zend_object_std_dtor(&channel->std);
}
/* Initializes an instance of wrapped_grpc_channel to be associated with an
* object of a class specified by class_type */
zend_object *create_wrapped_grpc_channel(zend_class_entry *class_type) {
wrapped_grpc_channel *intern;
intern = ecalloc(1, sizeof(wrapped_grpc_channel) +
zend_object_properties_size(class_type));
zend_object_std_init(&intern->std, class_type);
object_properties_init(&intern->std, class_type);
intern->std.handlers = &channel_ce_handlers;
return &intern->std;
}
void php_grpc_read_args_array(zval *args_array, grpc_channel_args *args) {
HashTable *array_hash;
int args_index;
zval *data;
zend_string *key;
array_hash = HASH_OF(args_array);
if (!array_hash) {
zend_throw_exception(spl_ce_InvalidArgumentException,
"array_hash is NULL", 1);
return;
}
args->num_args = zend_hash_num_elements(array_hash);
args->args = ecalloc(args->num_args, sizeof(grpc_arg));
args_index = 0;
ZEND_HASH_FOREACH_STR_KEY_VAL(array_hash, key, data) {
if (key == NULL) {
zend_throw_exception(spl_ce_InvalidArgumentException,
"args keys must be strings", 1);
}
args->args[args_index].key = ZSTR_VAL(key);
switch (Z_TYPE_P(data)) {
case IS_LONG:
args->args[args_index].value.integer = (int)Z_LVAL_P(data);
args->args[args_index].type = GRPC_ARG_INTEGER;
break;
case IS_STRING:
args->args[args_index].value.string = Z_STRVAL_P(data);
args->args[args_index].type = GRPC_ARG_STRING;
break;
default:
zend_throw_exception(spl_ce_InvalidArgumentException,
"args values must be int or string", 1);
return;
}
args_index++;
} ZEND_HASH_FOREACH_END();
}
#endif
/**
* Construct an instance of the Channel class. If the $args array contains a
* "credentials" key mapping to a ChannelCredentials object, a secure channel
@ -132,16 +201,23 @@ void php_grpc_read_args_array(zval *args_array, grpc_channel_args *args TSRMLS_D
* @param array $args The arguments to pass to the Channel (optional)
*/
PHP_METHOD(Channel, __construct) {
#if PHP_MAJOR_VERSION < 7
wrapped_grpc_channel *channel =
(wrapped_grpc_channel *)zend_object_store_get_object(
getThis() TSRMLS_CC);
char *target;
zval **creds_obj = NULL;
int target_length;
#else
wrapped_grpc_channel *channel = Z_WRAPPED_GRPC_CHANNEL_P(getThis());
zval *creds_obj = NULL;
size_t target_length;
#endif
char *target;
zval *args_array = NULL;
grpc_channel_args args;
HashTable *array_hash;
zval **creds_obj = NULL;
wrapped_grpc_channel_credentials *creds = NULL;
/* "sa" == 1 string, 1 array */
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sa", &target,
&target_length, &args_array) == FAILURE) {
@ -149,6 +225,7 @@ PHP_METHOD(Channel, __construct) {
"Channel expects a string and an array", 1 TSRMLS_CC);
return;
}
#if PHP_MAJOR_VERSION < 7
array_hash = Z_ARRVAL_P(args_array);
if (zend_hash_find(array_hash, "credentials", sizeof("credentials"),
(void **)&creds_obj) == SUCCESS) {
@ -167,6 +244,24 @@ PHP_METHOD(Channel, __construct) {
zend_hash_del(array_hash, "credentials", 12);
}
}
#else
array_hash = HASH_OF(args_array);
if ((creds_obj = zend_hash_str_find(array_hash, "credentials",
sizeof("credentials") - 1)) != NULL) {
if (Z_TYPE_P(creds_obj) == IS_NULL) {
creds = NULL;
zend_hash_str_del(array_hash, "credentials", sizeof("credentials") - 1);
} else if (Z_OBJ_P(creds_obj)->ce != grpc_ce_channel_credentials) {
zend_throw_exception(spl_ce_InvalidArgumentException,
"credentials must be a ChannelCredentials object",
1);
return;
} else {
creds = Z_WRAPPED_GRPC_CHANNEL_CREDS_P(creds_obj);
zend_hash_str_del(array_hash, "credentials", sizeof("credentials") - 1);
}
}
#endif
php_grpc_read_args_array(args_array, &args TSRMLS_CC);
if (creds == NULL) {
channel->wrapped = grpc_insecure_channel_create(target, &args, NULL);
@ -182,9 +277,14 @@ PHP_METHOD(Channel, __construct) {
* @return string The URI of the endpoint
*/
PHP_METHOD(Channel, getTarget) {
#if PHP_MAJOR_VERSION < 7
wrapped_grpc_channel *channel =
(wrapped_grpc_channel *)zend_object_store_get_object(getThis() TSRMLS_CC);
(wrapped_grpc_channel *)zend_object_store_get_object(getThis() TSRMLS_CC);
RETURN_STRING(grpc_channel_get_target(channel->wrapped), 1);
#else
wrapped_grpc_channel *channel = Z_WRAPPED_GRPC_CHANNEL_P(getThis());
RETURN_STRING(grpc_channel_get_target(channel->wrapped));
#endif
}
/**
@ -193,12 +293,17 @@ PHP_METHOD(Channel, getTarget) {
* @return long The grpc connectivity state
*/
PHP_METHOD(Channel, getConnectivityState) {
#if PHP_MAJOR_VERSION < 7
wrapped_grpc_channel *channel =
(wrapped_grpc_channel *)zend_object_store_get_object(getThis() TSRMLS_CC);
bool try_to_connect;
#else
wrapped_grpc_channel *channel = Z_WRAPPED_GRPC_CHANNEL_P(getThis());
#endif
bool try_to_connect = false;
/* "|b" == 1 optional bool */
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|b", &try_to_connect) ==
FAILURE) {
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|b", &try_to_connect)
== FAILURE) {
zend_throw_exception(spl_ce_InvalidArgumentException,
"getConnectivityState expects a bool", 1 TSRMLS_CC);
return;
@ -215,10 +320,16 @@ PHP_METHOD(Channel, getConnectivityState) {
* before deadline
*/
PHP_METHOD(Channel, watchConnectivityState) {
#if PHP_MAJOR_VERSION < 7
long last_state;
wrapped_grpc_channel *channel =
(wrapped_grpc_channel *)zend_object_store_get_object(getThis() TSRMLS_CC);
long last_state;
#else
zend_long last_state;
wrapped_grpc_channel *channel = Z_WRAPPED_GRPC_CHANNEL_P(getThis());
#endif
zval *deadline_obj;
/* "lO" == 1 long 1 object */
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "lO",
&last_state, &deadline_obj, grpc_ce_timeval) == FAILURE) {
@ -228,15 +339,20 @@ PHP_METHOD(Channel, watchConnectivityState) {
return;
}
#if PHP_MAJOR_VERSION < 7
wrapped_grpc_timeval *deadline =
(wrapped_grpc_timeval *)zend_object_store_get_object(
deadline_obj TSRMLS_CC);
grpc_channel_watch_connectivity_state(
channel->wrapped, (grpc_connectivity_state)last_state,
deadline->wrapped, completion_queue, NULL);
grpc_event event = grpc_completion_queue_pluck(
completion_queue, NULL,
gpr_inf_future(GPR_CLOCK_REALTIME), NULL);
#else
wrapped_grpc_timeval *deadline = Z_WRAPPED_GRPC_TIMEVAL_P(deadline_obj);
#endif
grpc_channel_watch_connectivity_state(channel->wrapped,
(grpc_connectivity_state)last_state,
deadline->wrapped, completion_queue,
NULL);
grpc_event event =
grpc_completion_queue_pluck(completion_queue, NULL,
gpr_inf_future(GPR_CLOCK_REALTIME), NULL);
RETURN_BOOL(event.success);
}
@ -244,8 +360,12 @@ PHP_METHOD(Channel, watchConnectivityState) {
* Close the channel
*/
PHP_METHOD(Channel, close) {
#if PHP_MAJOR_VERSION < 7
wrapped_grpc_channel *channel =
(wrapped_grpc_channel *)zend_object_store_get_object(getThis() TSRMLS_CC);
(wrapped_grpc_channel *)zend_object_store_get_object(getThis() TSRMLS_CC);
#else
wrapped_grpc_channel *channel = Z_WRAPPED_GRPC_CHANNEL_P(getThis());
#endif
if (channel->wrapped != NULL) {
grpc_channel_destroy(channel->wrapped);
channel->wrapped = NULL;
@ -253,16 +373,24 @@ PHP_METHOD(Channel, close) {
}
static zend_function_entry channel_methods[] = {
PHP_ME(Channel, __construct, NULL, ZEND_ACC_PUBLIC | ZEND_ACC_CTOR)
PHP_ME(Channel, getTarget, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Channel, getConnectivityState, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Channel, watchConnectivityState, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Channel, close, NULL, ZEND_ACC_PUBLIC)
PHP_FE_END};
PHP_ME(Channel, __construct, NULL, ZEND_ACC_PUBLIC | ZEND_ACC_CTOR)
PHP_ME(Channel, getTarget, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Channel, getConnectivityState, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Channel, watchConnectivityState, NULL, ZEND_ACC_PUBLIC)
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);
#if PHP_MAJOR_VERSION >= 7
memcpy(&channel_ce_handlers, zend_get_std_object_handlers(),
sizeof(zend_object_handlers));
channel_ce_handlers.offset =
XtOffsetOf(wrapped_grpc_channel, std);
channel_ce_handlers.free_obj = free_wrapped_grpc_channel;
#endif
}

@ -48,17 +48,38 @@
/* Class entry for the PHP Channel class */
extern zend_class_entry *grpc_ce_channel;
#if PHP_MAJOR_VERSION < 7
/* Wrapper struct for grpc_channel that can be associated with a PHP object */
typedef struct wrapped_grpc_channel {
zend_object std;
grpc_channel *wrapped;
} wrapped_grpc_channel;
#else
/* Wrapper struct for grpc_channel that can be associated with a PHP object */
typedef struct wrapped_grpc_channel {
grpc_channel *wrapped;
zend_object std;
} wrapped_grpc_channel;
static inline wrapped_grpc_channel
*wrapped_grpc_channel_from_obj(zend_object *obj) {
return (wrapped_grpc_channel*)((char*)(obj) -
XtOffsetOf(wrapped_grpc_channel, std));
}
#define Z_WRAPPED_GRPC_CHANNEL_P(zv) \
wrapped_grpc_channel_from_obj(Z_OBJ_P((zv)))
#endif /* PHP_MAJOR_VERSION */
/* 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 TSRMLS_DC);
void php_grpc_read_args_array(zval *args_array, grpc_channel_args *args
TSRMLS_DC);
#endif /* NET_GRPC_PHP_GRPC_CHANNEL_H_ */

@ -52,7 +52,6 @@
#include <grpc/grpc_security.h>
zend_class_entry *grpc_ce_channel_credentials;
static char *default_pem_root_certs = NULL;
static grpc_ssl_roots_override_result get_ssl_roots_override(
@ -64,6 +63,8 @@ static grpc_ssl_roots_override_result get_ssl_roots_override(
return GRPC_SSL_ROOTS_OVERRIDE_OK;
}
#if PHP_MAJOR_VERSION < 7
/* Frees and destroys an instance of wrapped_grpc_channel_credentials */
void free_wrapped_grpc_channel_credentials(void *object TSRMLS_DC) {
wrapped_grpc_channel_credentials *creds =
@ -71,6 +72,7 @@ void free_wrapped_grpc_channel_credentials(void *object TSRMLS_DC) {
if (creds->wrapped != NULL) {
grpc_channel_credentials_release(creds->wrapped);
}
zend_object_std_dtor(&creds->std TSRMLS_CC);
efree(creds);
}
@ -94,7 +96,8 @@ zend_object_value create_wrapped_grpc_channel_credentials(
return retval;
}
zval *grpc_php_wrap_channel_credentials(grpc_channel_credentials *wrapped TSRMLS_DC) {
zval *grpc_php_wrap_channel_credentials(grpc_channel_credentials
*wrapped TSRMLS_DC) {
zval *credentials_object;
MAKE_STD_ZVAL(credentials_object);
object_init_ex(credentials_object, grpc_ce_channel_credentials);
@ -105,6 +108,43 @@ zval *grpc_php_wrap_channel_credentials(grpc_channel_credentials *wrapped TSRMLS
return credentials_object;
}
#else
static zend_object_handlers channel_credentials_ce_handlers;
/* Frees and destroys an instance of wrapped_grpc_channel_credentials */
static void free_wrapped_grpc_channel_credentials(zend_object *object) {
wrapped_grpc_channel_credentials *creds =
wrapped_grpc_channel_creds_from_obj(object);
if (creds->wrapped != NULL) {
grpc_channel_credentials_release(creds->wrapped);
}
zend_object_std_dtor(&creds->std);
}
/* Initializes an instance of wrapped_grpc_channel_credentials to be
* associated with an object of a class specified by class_type */
zend_object *create_wrapped_grpc_channel_credentials(zend_class_entry
*class_type) {
wrapped_grpc_channel_credentials *intern;
intern = ecalloc(1, sizeof(wrapped_grpc_channel_credentials) +
zend_object_properties_size(class_type));
zend_object_std_init(&intern->std, class_type);
object_properties_init(&intern->std, class_type);
intern->std.handlers = &channel_credentials_ce_handlers;
return &intern->std;
}
void grpc_php_wrap_channel_credentials(grpc_channel_credentials *wrapped,
zval *credentials_object) {
object_init_ex(credentials_object, grpc_ce_channel_credentials);
wrapped_grpc_channel_credentials *credentials =
Z_WRAPPED_GRPC_CHANNEL_CREDS_P(credentials_object);
credentials->wrapped = wrapped;
}
#endif
/**
* Set default roots pem.
* @param string pem_roots PEM encoding of the server root certificates
@ -112,7 +152,13 @@ zval *grpc_php_wrap_channel_credentials(grpc_channel_credentials *wrapped TSRMLS
*/
PHP_METHOD(ChannelCredentials, setDefaultRootsPem) {
char *pem_roots;
#if PHP_MAJOR_VERSION < 7
int pem_roots_length;
#else
size_t pem_roots_length;
#endif
/* "s" == 1 string */
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &pem_roots,
&pem_roots_length) == FAILURE) {
zend_throw_exception(spl_ce_InvalidArgumentException,
@ -129,8 +175,13 @@ PHP_METHOD(ChannelCredentials, setDefaultRootsPem) {
*/
PHP_METHOD(ChannelCredentials, createDefault) {
grpc_channel_credentials *creds = grpc_google_default_credentials_create();
#if PHP_MAJOR_VERSION < 7
zval *creds_object = grpc_php_wrap_channel_credentials(creds TSRMLS_CC);
RETURN_DESTROY_ZVAL(creds_object);
#else
grpc_php_wrap_channel_credentials(creds, return_value);
RETURN_DESTROY_ZVAL(return_value);
#endif
}
/**
@ -146,11 +197,15 @@ PHP_METHOD(ChannelCredentials, createSsl) {
char *pem_root_certs = NULL;
grpc_ssl_pem_key_cert_pair pem_key_cert_pair;
#if PHP_MAJOR_VERSION < 7
int root_certs_length = 0, private_key_length = 0, cert_chain_length = 0;
#else
size_t root_certs_length = 0, private_key_length = 0, cert_chain_length = 0;
#endif
pem_key_cert_pair.private_key = pem_key_cert_pair.cert_chain = NULL;
/* "|s!s!s! == 3 optional nullable strings */
/* "|s!s!s!" == 3 optional nullable strings */
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|s!s!s!",
&pem_root_certs, &root_certs_length,
&pem_key_cert_pair.private_key,
@ -164,8 +219,13 @@ PHP_METHOD(ChannelCredentials, createSsl) {
grpc_channel_credentials *creds = grpc_ssl_credentials_create(
pem_root_certs,
pem_key_cert_pair.private_key == NULL ? NULL : &pem_key_cert_pair, NULL);
#if PHP_MAJOR_VERSION < 7
zval *creds_object = grpc_php_wrap_channel_credentials(creds TSRMLS_CC);
RETURN_DESTROY_ZVAL(creds_object);
#else
grpc_php_wrap_channel_credentials(creds, return_value);
RETURN_DESTROY_ZVAL(return_value);
#endif
}
/**
@ -178,7 +238,7 @@ PHP_METHOD(ChannelCredentials, createComposite) {
zval *cred1_obj;
zval *cred2_obj;
/* "OO" == 3 Objects */
/* "OO" == 2 Objects */
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "OO", &cred1_obj,
grpc_ce_channel_credentials, &cred2_obj,
grpc_ce_call_credentials) == FAILURE) {
@ -186,6 +246,7 @@ PHP_METHOD(ChannelCredentials, createComposite) {
"createComposite expects 2 Credentials", 1 TSRMLS_CC);
return;
}
#if PHP_MAJOR_VERSION < 7
wrapped_grpc_channel_credentials *cred1 =
(wrapped_grpc_channel_credentials *)zend_object_store_get_object(
cred1_obj TSRMLS_CC);
@ -197,6 +258,17 @@ PHP_METHOD(ChannelCredentials, createComposite) {
NULL);
zval *creds_object = grpc_php_wrap_channel_credentials(creds TSRMLS_CC);
RETURN_DESTROY_ZVAL(creds_object);
#else
wrapped_grpc_channel_credentials *cred1 =
Z_WRAPPED_GRPC_CHANNEL_CREDS_P(cred1_obj);
wrapped_grpc_call_credentials *cred2 =
Z_WRAPPED_GRPC_CALL_CREDS_P(cred2_obj);
grpc_channel_credentials *creds =
grpc_composite_channel_credentials_create(cred1->wrapped,
cred2->wrapped, NULL);
grpc_php_wrap_channel_credentials(creds, return_value);
RETURN_DESTROY_ZVAL(return_value);
#endif
}
/**
@ -218,7 +290,8 @@ static zend_function_entry channel_credentials_methods[] = {
ZEND_ACC_PUBLIC | ZEND_ACC_STATIC)
PHP_ME(ChannelCredentials, createInsecure, NULL,
ZEND_ACC_PUBLIC | ZEND_ACC_STATIC)
PHP_FE_END};
PHP_FE_END
};
void grpc_init_channel_credentials(TSRMLS_D) {
zend_class_entry ce;
@ -227,4 +300,13 @@ void grpc_init_channel_credentials(TSRMLS_D) {
grpc_set_ssl_roots_override_callback(get_ssl_roots_override);
ce.create_object = create_wrapped_grpc_channel_credentials;
grpc_ce_channel_credentials = zend_register_internal_class(&ce TSRMLS_CC);
#if PHP_MAJOR_VERSION >= 7
memcpy(&channel_credentials_ce_handlers,
zend_get_std_object_handlers(),
sizeof(zend_object_handlers));
channel_credentials_ce_handlers.offset =
XtOffsetOf(wrapped_grpc_channel_credentials, std);
channel_credentials_ce_handlers.free_obj =
free_wrapped_grpc_channel_credentials;
#endif
}

@ -49,14 +49,37 @@
/* Class entry for the ChannelCredentials PHP class */
extern zend_class_entry *grpc_ce_channel_credentials;
#if PHP_MAJOR_VERSION < 7
/* Wrapper struct for grpc_channel_credentials that can be associated
* with a PHP object */
typedef struct wrapped_grpc_channel_credentials {
zend_object std;
grpc_channel_credentials *wrapped;
} wrapped_grpc_channel_credentials;
#else
/* Wrapper struct for grpc_channel_credentials that can be associated
* with a PHP object */
typedef struct wrapped_grpc_channel_credentials {
grpc_channel_credentials *wrapped;
zend_object std;
} wrapped_grpc_channel_credentials;
static inline wrapped_grpc_channel_credentials
*wrapped_grpc_channel_creds_from_obj(zend_object *obj) {
return
(wrapped_grpc_channel_credentials *)
((char*)(obj) -
XtOffsetOf(wrapped_grpc_channel_credentials, std));
}
#define Z_WRAPPED_GRPC_CHANNEL_CREDS_P(zv) \
wrapped_grpc_channel_creds_from_obj(Z_OBJ_P((zv)))
#endif /* PHP_MAJOR_VERSION */
/* Initializes the ChannelCredentials PHP class */
void grpc_init_channel_credentials(TSRMLS_D);

@ -64,15 +64,19 @@ const zend_function_entry grpc_functions[] = {
*/
zend_module_entry grpc_module_entry = {
#if ZEND_MODULE_API_NO >= 20010901
STANDARD_MODULE_HEADER,
STANDARD_MODULE_HEADER,
#endif
"grpc", grpc_functions, PHP_MINIT(grpc),
PHP_MSHUTDOWN(grpc), NULL, NULL,
PHP_MINFO(grpc),
"grpc",
grpc_functions,
PHP_MINIT(grpc),
PHP_MSHUTDOWN(grpc),
NULL,
NULL,
PHP_MINFO(grpc),
#if ZEND_MODULE_API_NO >= 20010901
PHP_GRPC_VERSION,
PHP_GRPC_VERSION,
#endif
STANDARD_MODULE_PROPERTIES};
STANDARD_MODULE_PROPERTIES};
/* }}} */
#ifdef COMPILE_DL_GRPC
@ -82,23 +86,24 @@ ZEND_GET_MODULE(grpc)
/* {{{ 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_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;
}
static void php_grpc_init_globals(zend_grpc_globals *grpc_globals)
{
grpc_globals->global_value = 0;
grpc_globals->global_string = NULL;
}
*/
/* }}} */
@ -106,7 +111,7 @@ static void php_grpc_init_globals(zend_grpc_globals *grpc_globals)
*/
PHP_MINIT_FUNCTION(grpc) {
/* If you have INI entries, uncomment these lines
REGISTER_INI_ENTRIES();
REGISTER_INI_ENTRIES();
*/
/* Register call error constants */
grpc_init();
@ -246,7 +251,7 @@ PHP_MINIT_FUNCTION(grpc) {
*/
PHP_MSHUTDOWN_FUNCTION(grpc) {
/* uncomment this line if you have INI entries
UNREGISTER_INI_ENTRIES();
UNREGISTER_INI_ENTRIES();
*/
// WARNING: This function IS being called by PHP when the extension
// is unloaded but the logs were somehow suppressed.
@ -265,7 +270,7 @@ PHP_MINFO_FUNCTION(grpc) {
php_info_print_table_end();
/* Remove comments if you have entries in php.ini
DISPLAY_INI_ENTRIES();
DISPLAY_INI_ENTRIES();
*/
}
/* }}} */
@ -274,12 +279,3 @@ PHP_MINFO_FUNCTION(grpc) {
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
*/

@ -72,8 +72,8 @@ PHP_MSHUTDOWN_FUNCTION(grpc);
PHP_MINFO_FUNCTION(grpc);
/*
Declare any global variables you may need between the BEGIN
and END macros here:
Declare any global variables you may need between the BEGIN
and END macros here:
ZEND_BEGIN_MODULE_GLOBALS(grpc)
ZEND_END_MODULE_GLOBALS(grpc)

@ -58,6 +58,8 @@
zend_class_entry *grpc_ce_server;
#if PHP_MAJOR_VERSION < 7
/* 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;
@ -68,6 +70,7 @@ void free_wrapped_grpc_server(void *object TSRMLS_DC) {
gpr_inf_future(GPR_CLOCK_REALTIME), NULL);
grpc_server_destroy(server->wrapped);
}
zend_object_std_dtor(&server->std TSRMLS_CC);
efree(server);
}
@ -90,15 +93,51 @@ zend_object_value create_wrapped_grpc_server(zend_class_entry *class_type
return retval;
}
#else
static zend_object_handlers server_ce_handlers;
/* Frees and destroys an instance of wrapped_grpc_server */
static void free_wrapped_grpc_server(zend_object *object) {
wrapped_grpc_server *server = wrapped_grpc_server_from_obj(object);
if (server->wrapped != NULL) {
grpc_server_shutdown_and_notify(server->wrapped, completion_queue, NULL);
grpc_server_cancel_all_calls(server->wrapped);
grpc_completion_queue_pluck(completion_queue, NULL,
gpr_inf_future(GPR_CLOCK_REALTIME), NULL);
grpc_server_destroy(server->wrapped);
}
zend_object_std_dtor(&server->std);
}
/* Initializes an instance of wrapped_grpc_call to be associated with an object
* of a class specified by class_type */
zend_object *create_wrapped_grpc_server(zend_class_entry *class_type) {
wrapped_grpc_server *intern;
intern = ecalloc(1, sizeof(wrapped_grpc_server) +
zend_object_properties_size(class_type));
zend_object_std_init(&intern->std, class_type);
object_properties_init(&intern->std, class_type);
intern->std.handlers = &server_ce_handlers;
return &intern->std;
}
#endif
/**
* Constructs a new instance of the Server class
* @param array $args The arguments to pass to the server (optional)
*/
PHP_METHOD(Server, __construct) {
#if PHP_MAJOR_VERSION < 7
wrapped_grpc_server *server =
(wrapped_grpc_server *)zend_object_store_get_object(getThis() TSRMLS_CC);
#else
wrapped_grpc_server *server = Z_WRAPPED_GRPC_SERVER_P(getThis());
#endif
zval *args_array = NULL;
grpc_channel_args args;
/* "|a" == 1 optional array */
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|a", &args_array) ==
FAILURE) {
@ -110,6 +149,8 @@ PHP_METHOD(Server, __construct) {
if (args_array == NULL) {
server->wrapped = grpc_server_create(NULL, NULL);
} else {
//TODO(thinkerou): deal it if key of array is long, crash now on php7
// and update unit test case
php_grpc_read_args_array(args_array, &args TSRMLS_CC);
server->wrapped = grpc_server_create(&args, NULL);
efree(args.args);
@ -126,15 +167,22 @@ PHP_METHOD(Server, __construct) {
*/
PHP_METHOD(Server, requestCall) {
grpc_call_error error_code;
wrapped_grpc_server *server =
(wrapped_grpc_server *)zend_object_store_get_object(getThis() TSRMLS_CC);
grpc_call *call;
grpc_call_details details;
grpc_metadata_array metadata;
zval *result;
grpc_event event;
#if PHP_MAJOR_VERSION < 7
wrapped_grpc_server *server =
(wrapped_grpc_server *)zend_object_store_get_object(getThis() TSRMLS_CC);
zval *result;
MAKE_STD_ZVAL(result);
object_init(result);
#else
wrapped_grpc_server *server = Z_WRAPPED_GRPC_SERVER_P(getThis());
object_init(return_value);
#endif
grpc_call_details_init(&details);
grpc_metadata_array_init(&metadata);
error_code =
@ -146,23 +194,48 @@ PHP_METHOD(Server, requestCall) {
goto cleanup;
}
event = grpc_completion_queue_pluck(completion_queue, NULL,
gpr_inf_future(GPR_CLOCK_REALTIME), NULL);
gpr_inf_future(GPR_CLOCK_REALTIME),
NULL);
if (!event.success) {
zend_throw_exception(spl_ce_LogicException,
"Failed to request a call for some reason",
1 TSRMLS_CC);
goto cleanup;
}
#if PHP_MAJOR_VERSION < 7
add_property_zval(result, "call", grpc_php_wrap_call(call, true TSRMLS_CC));
add_property_string(result, "method", details.method, true);
add_property_string(result, "host", details.host, true);
add_property_zval(result, "absolute_deadline",
grpc_php_wrap_timeval(details.deadline TSRMLS_CC));
add_property_zval(result, "metadata", grpc_parse_metadata_array(&metadata TSRMLS_CC));
add_property_zval(result, "metadata", grpc_parse_metadata_array(&metadata
TSRMLS_CC));
cleanup:
grpc_call_details_destroy(&details);
grpc_metadata_array_destroy(&metadata);
RETURN_DESTROY_ZVAL(result);
#else
zval zv_call;
zval zv_timeval;
zval zv_md;
grpc_php_wrap_call(call, true, &zv_call);
grpc_php_wrap_timeval(details.deadline, &zv_timeval);
grpc_parse_metadata_array(&metadata, &zv_md);
add_property_zval(return_value, "call", &zv_call);
add_property_string(return_value, "method", details.method);
add_property_string(return_value, "host", details.host);
add_property_zval(return_value, "absolute_deadline", &zv_timeval);
add_property_zval(return_value, "metadata", &zv_md);
cleanup:
grpc_call_details_destroy(&details);
grpc_metadata_array_destroy(&metadata);
RETURN_DESTROY_ZVAL(return_value);
#endif
}
/**
@ -171,13 +244,19 @@ cleanup:
* @return true on success, false on failure
*/
PHP_METHOD(Server, addHttp2Port) {
wrapped_grpc_server *server =
(wrapped_grpc_server *)zend_object_store_get_object(getThis() TSRMLS_CC);
const char *addr;
#if PHP_MAJOR_VERSION < 7
int addr_len;
wrapped_grpc_server *server =
(wrapped_grpc_server *)zend_object_store_get_object(getThis() TSRMLS_CC);
#else
size_t addr_len;
wrapped_grpc_server *server = Z_WRAPPED_GRPC_SERVER_P(getThis());
#endif
/* "s" == 1 string */
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &addr, &addr_len) ==
FAILURE) {
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;
@ -186,11 +265,17 @@ PHP_METHOD(Server, addHttp2Port) {
}
PHP_METHOD(Server, addSecureHttp2Port) {
wrapped_grpc_server *server =
(wrapped_grpc_server *)zend_object_store_get_object(getThis() TSRMLS_CC);
const char *addr;
int addr_len;
zval *creds_obj;
#if PHP_MAJOR_VERSION < 7
int addr_len;
wrapped_grpc_server *server =
(wrapped_grpc_server *)zend_object_store_get_object(getThis() TSRMLS_CC);
#else
size_t addr_len;
wrapped_grpc_server *server = Z_WRAPPED_GRPC_SERVER_P(getThis());
#endif
/* "sO" == 1 string, 1 object */
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sO", &addr, &addr_len,
&creds_obj, grpc_ce_server_credentials) ==
@ -200,9 +285,14 @@ PHP_METHOD(Server, addSecureHttp2Port) {
"add_http2_port expects a string and a ServerCredentials", 1 TSRMLS_CC);
return;
}
#if PHP_MAJOR_VERSION < 7
wrapped_grpc_server_credentials *creds =
(wrapped_grpc_server_credentials *)zend_object_store_get_object(
creds_obj TSRMLS_CC);
#else
wrapped_grpc_server_credentials *creds =
Z_WRAPPED_GRPC_SERVER_CREDS_P(creds_obj);
#endif
RETURN_LONG(grpc_server_add_secure_http2_port(server->wrapped, addr,
creds->wrapped));
}
@ -212,21 +302,33 @@ PHP_METHOD(Server, addSecureHttp2Port) {
* @return Void
*/
PHP_METHOD(Server, start) {
#if PHP_MAJOR_VERSION < 7
wrapped_grpc_server *server =
(wrapped_grpc_server *)zend_object_store_get_object(getThis() TSRMLS_CC);
#else
wrapped_grpc_server *server = Z_WRAPPED_GRPC_SERVER_P(getThis());
#endif
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, requestCall, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Server, addHttp2Port, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Server, addSecureHttp2Port, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Server, start, NULL, ZEND_ACC_PUBLIC) PHP_FE_END};
PHP_ME(Server, __construct, NULL, ZEND_ACC_PUBLIC | ZEND_ACC_CTOR)
PHP_ME(Server, requestCall, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Server, addHttp2Port, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Server, addSecureHttp2Port, 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);
#if PHP_MAJOR_VERSION >= 7
memcpy(&server_ce_handlers, zend_get_std_object_handlers(),
sizeof(zend_object_handlers));
server_ce_handlers.offset = XtOffsetOf(wrapped_grpc_server, std);
server_ce_handlers.free_obj = free_wrapped_grpc_server;
#endif
}

@ -48,13 +48,33 @@
/* Class entry for the Server PHP class */
extern zend_class_entry *grpc_ce_server;
#if PHP_MAJOR_VERSION < 7
/* 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;
#else
/* Wrapper struct for grpc_server that can be associated with a PHP object */
typedef struct wrapped_grpc_server {
grpc_server *wrapped;
zend_object std;
} wrapped_grpc_server;
static inline wrapped_grpc_server
*wrapped_grpc_server_from_obj(zend_object *obj) {
return (wrapped_grpc_server*)((char*)(obj) -
XtOffsetOf(wrapped_grpc_server, std));
}
#define Z_WRAPPED_GRPC_SERVER_P(zv) \
wrapped_grpc_server_from_obj(Z_OBJ_P((zv)))
#endif /* PHP_MAJOR_VERSION */
/* Initializes the Server class */
void grpc_init_server(TSRMLS_D);

@ -51,6 +51,8 @@
zend_class_entry *grpc_ce_server_credentials;
#if PHP_MAJOR_VERSION < 7
/* 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 =
@ -58,6 +60,7 @@ void free_wrapped_grpc_server_credentials(void *object TSRMLS_DC) {
if (creds->wrapped != NULL) {
grpc_server_credentials_release(creds->wrapped);
}
zend_object_std_dtor(&creds->std TSRMLS_CC);
efree(creds);
}
@ -81,7 +84,8 @@ zend_object_value create_wrapped_grpc_server_credentials(
return retval;
}
zval *grpc_php_wrap_server_credentials(grpc_server_credentials *wrapped TSRMLS_DC) {
zval *grpc_php_wrap_server_credentials(grpc_server_credentials
*wrapped TSRMLS_DC) {
zval *server_credentials_object;
MAKE_STD_ZVAL(server_credentials_object);
object_init_ex(server_credentials_object, grpc_ce_server_credentials);
@ -92,6 +96,43 @@ zval *grpc_php_wrap_server_credentials(grpc_server_credentials *wrapped TSRMLS_D
return server_credentials_object;
}
#else
static zend_object_handlers server_credentials_ce_handlers;
/* Frees and destroys an instace of wrapped_grpc_server_credentials */
static void free_wrapped_grpc_server_credentials(zend_object *object) {
wrapped_grpc_server_credentials *creds =
wrapped_grpc_server_creds_from_obj(object);
if (creds->wrapped != NULL) {
grpc_server_credentials_release(creds->wrapped);
}
zend_object_std_dtor(&creds->std);
}
/* Initializes an instace of wrapped_grpc_server_credentials to be associated
* with an object of a class specified by class_type */
zend_object *create_wrapped_grpc_server_credentials(zend_class_entry
*class_type) {
wrapped_grpc_server_credentials *intern;
intern = ecalloc(1, sizeof(wrapped_grpc_server_credentials) +
zend_object_properties_size(class_type));
zend_object_std_init(&intern->std, class_type);
object_properties_init(&intern->std, class_type);
intern->std.handlers = &server_credentials_ce_handlers;
return &intern->std;
}
void grpc_php_wrap_server_credentials(grpc_server_credentials *wrapped,
zval *server_credentials_object) {
object_init_ex(server_credentials_object, grpc_ce_server_credentials);
wrapped_grpc_server_credentials *server_credentials =
Z_WRAPPED_GRPC_SERVER_CREDS_P(server_credentials_object);
server_credentials->wrapped = wrapped;
}
#endif
/**
* Create SSL credentials.
* @param string pem_root_certs PEM encoding of the server root certificates
@ -103,7 +144,11 @@ PHP_METHOD(ServerCredentials, createSsl) {
char *pem_root_certs = 0;
grpc_ssl_pem_key_cert_pair pem_key_cert_pair;
#if PHP_MAJOR_VERSION < 7
int root_certs_length = 0, private_key_length, cert_chain_length;
#else
size_t root_certs_length = 0, private_key_length, cert_chain_length;
#endif
/* "s!ss" == 1 nullable string, 2 strings */
/* TODO: support multiple key cert pairs. */
@ -120,17 +165,33 @@ PHP_METHOD(ServerCredentials, createSsl) {
grpc_server_credentials *creds = grpc_ssl_server_credentials_create_ex(
pem_root_certs, &pem_key_cert_pair, 1,
GRPC_SSL_DONT_REQUEST_CLIENT_CERTIFICATE, NULL);
#if PHP_MAJOR_VERSION < 7
zval *creds_object = grpc_php_wrap_server_credentials(creds TSRMLS_CC);
RETURN_DESTROY_ZVAL(creds_object);
#else
grpc_php_wrap_server_credentials(creds, return_value);
RETURN_DESTROY_ZVAL(return_value);
#endif
}
static zend_function_entry server_credentials_methods[] = {
PHP_ME(ServerCredentials, createSsl, NULL,
ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) PHP_FE_END};
PHP_ME(ServerCredentials, createSsl, 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);
#if PHP_MAJOR_VERSION >= 7
memcpy(&server_credentials_ce_handlers,
zend_get_std_object_handlers(),
sizeof(zend_object_handlers));
server_credentials_ce_handlers.offset =
XtOffsetOf(wrapped_grpc_server_credentials, std);
server_credentials_ce_handlers.free_obj =
free_wrapped_grpc_server_credentials;
#endif
}

@ -49,14 +49,34 @@
/* Class entry for the Server_Credentials PHP class */
extern zend_class_entry *grpc_ce_server_credentials;
#if PHP_MAJOR_VERSION < 7
/* 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;
#else
typedef struct wrapped_grpc_server_credentials {
grpc_server_credentials *wrapped;
zend_object std;
} wrapped_grpc_server_credentials;
static inline wrapped_grpc_server_credentials
*wrapped_grpc_server_creds_from_obj(zend_object *obj) {
return (wrapped_grpc_server_credentials*)
((char*)(obj) -
XtOffsetOf(wrapped_grpc_server_credentials, std));
}
#define Z_WRAPPED_GRPC_SERVER_CREDS_P(zv) \
wrapped_grpc_server_creds_from_obj(Z_OBJ_P((zv)))
#endif /* PHP_MAJOR_VERSION */
/* Initializes the Server_Credentials PHP class */
void grpc_init_server_credentials(TSRMLS_D);

@ -52,8 +52,14 @@
zend_class_entry *grpc_ce_timeval;
#if PHP_MAJOR_VERSION < 7
/* Frees and destroys an instance of wrapped_grpc_call */
void free_wrapped_grpc_timeval(void *object TSRMLS_DC) { efree(object); }
void free_wrapped_grpc_timeval(void *object TSRMLS_DC) {
wrapped_grpc_timeval *timeval = (wrapped_grpc_timeval *)object;
zend_object_std_dtor(&timeval->std TSRMLS_CC);
efree(timeval);
}
/* Initializes an instance of wrapped_grpc_timeval to be associated with an
* object of a class specified by class_type */
@ -83,14 +89,50 @@ zval *grpc_php_wrap_timeval(gpr_timespec wrapped TSRMLS_DC) {
return timeval_object;
}
#else
static zend_object_handlers timeval_ce_handlers;
/* Frees and destroys an instance of wrapped_grpc_call */
static void free_wrapped_grpc_timeval(zend_object *object) {
wrapped_grpc_timeval *timeval = wrapped_grpc_timeval_from_obj(object);
zend_object_std_dtor(&timeval->std);
}
/* Initializes an instance of wrapped_grpc_timeval to be associated with an
* object of a class specified by class_type */
zend_object *create_wrapped_grpc_timeval(zend_class_entry *class_type) {
wrapped_grpc_timeval *intern;
intern = ecalloc(1, sizeof(wrapped_grpc_timeval) +
zend_object_properties_size(class_type));
zend_object_std_init(&intern->std, class_type);
object_properties_init(&intern->std, class_type);
intern->std.handlers = &timeval_ce_handlers;
return &intern->std;
}
void grpc_php_wrap_timeval(gpr_timespec wrapped, zval *timeval_object) {
object_init_ex(timeval_object, grpc_ce_timeval);
wrapped_grpc_timeval *timeval = Z_WRAPPED_GRPC_TIMEVAL_P(timeval_object);
memcpy(&timeval->wrapped, &wrapped, sizeof(gpr_timespec));
}
#endif
/**
* Constructs a new instance of the Timeval class
* @param long $usec The number of microseconds in the interval
*/
PHP_METHOD(Timeval, __construct) {
#if PHP_MAJOR_VERSION < 7
wrapped_grpc_timeval *timeval =
(wrapped_grpc_timeval *)zend_object_store_get_object(getThis() TSRMLS_CC);
long microseconds;
#else
wrapped_grpc_timeval *timeval = Z_WRAPPED_GRPC_TIMEVAL_P(getThis());
zend_long microseconds;
#endif
/* "l" == 1 long */
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l", &microseconds) ==
FAILURE) {
@ -110,6 +152,7 @@ PHP_METHOD(Timeval, __construct) {
*/
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) {
@ -117,13 +160,23 @@ PHP_METHOD(Timeval, add) {
"add expects a Timeval", 1 TSRMLS_CC);
return;
}
#if PHP_MAJOR_VERSION < 7
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) TSRMLS_CC);
grpc_php_wrap_timeval(gpr_time_add(self->wrapped, other->wrapped)
TSRMLS_CC);
RETURN_DESTROY_ZVAL(sum);
#else
wrapped_grpc_timeval *self = Z_WRAPPED_GRPC_TIMEVAL_P(getThis());
wrapped_grpc_timeval *other = Z_WRAPPED_GRPC_TIMEVAL_P(other_obj);
grpc_php_wrap_timeval(gpr_time_add(self->wrapped, other->wrapped),
return_value);
RETURN_DESTROY_ZVAL(return_value);
#endif
}
/**
@ -134,6 +187,7 @@ PHP_METHOD(Timeval, add) {
*/
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) {
@ -141,13 +195,22 @@ PHP_METHOD(Timeval, subtract) {
"subtract expects a Timeval", 1 TSRMLS_CC);
return;
}
#if PHP_MAJOR_VERSION < 7
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) TSRMLS_CC);
grpc_php_wrap_timeval(gpr_time_sub(self->wrapped, other->wrapped)
TSRMLS_CC);
RETURN_DESTROY_ZVAL(diff);
#else
wrapped_grpc_timeval *self = Z_WRAPPED_GRPC_TIMEVAL_P(getThis());
wrapped_grpc_timeval *other = Z_WRAPPED_GRPC_TIMEVAL_P(other_obj);
grpc_php_wrap_timeval(gpr_time_sub(self->wrapped, other->wrapped),
return_value);
RETURN_DESTROY_ZVAL(return_value);
#endif
}
/**
@ -158,7 +221,9 @@ PHP_METHOD(Timeval, subtract) {
* @return long
*/
PHP_METHOD(Timeval, compare) {
zval *a_obj, *b_obj;
zval *a_obj;
zval *b_obj;
/* "OO" == 2 Objects */
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "OO", &a_obj,
grpc_ce_timeval, &b_obj,
@ -167,10 +232,15 @@ PHP_METHOD(Timeval, compare) {
"compare expects two Timevals", 1 TSRMLS_CC);
return;
}
#if PHP_MAJOR_VERSION < 7
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);
#else
wrapped_grpc_timeval *a = Z_WRAPPED_GRPC_TIMEVAL_P(a_obj);
wrapped_grpc_timeval *b = Z_WRAPPED_GRPC_TIMEVAL_P(b_obj);
#endif
long result = gpr_time_cmp(a->wrapped, b->wrapped);
RETURN_LONG(result);
}
@ -183,7 +253,10 @@ PHP_METHOD(Timeval, compare) {
* @return bool True if $a and $b are within $threshold, False otherwise
*/
PHP_METHOD(Timeval, similar) {
zval *a_obj, *b_obj, *thresh_obj;
zval *a_obj;
zval *b_obj;
zval *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,
@ -192,6 +265,7 @@ PHP_METHOD(Timeval, similar) {
"compare expects three Timevals", 1 TSRMLS_CC);
return;
}
#if PHP_MAJOR_VERSION < 7
wrapped_grpc_timeval *a =
(wrapped_grpc_timeval *)zend_object_store_get_object(a_obj TSRMLS_CC);
wrapped_grpc_timeval *b =
@ -199,6 +273,11 @@ PHP_METHOD(Timeval, similar) {
wrapped_grpc_timeval *thresh =
(wrapped_grpc_timeval *)zend_object_store_get_object(
thresh_obj TSRMLS_CC);
#else
wrapped_grpc_timeval *a = Z_WRAPPED_GRPC_TIMEVAL_P(a_obj);
wrapped_grpc_timeval *b = Z_WRAPPED_GRPC_TIMEVAL_P(b_obj);
wrapped_grpc_timeval *thresh = Z_WRAPPED_GRPC_TIMEVAL_P(thresh_obj);
#endif
int result = gpr_time_similar(a->wrapped, b->wrapped, thresh->wrapped);
RETURN_BOOL(result);
}
@ -208,8 +287,13 @@ PHP_METHOD(Timeval, similar) {
* @return Timeval The current time
*/
PHP_METHOD(Timeval, now) {
#if PHP_MAJOR_VERSION < 7
zval *now = grpc_php_wrap_timeval(gpr_now(GPR_CLOCK_REALTIME) TSRMLS_CC);
RETURN_DESTROY_ZVAL(now);
#else
grpc_php_wrap_timeval(gpr_now(GPR_CLOCK_REALTIME), return_value);
RETURN_DESTROY_ZVAL(return_value);
#endif
}
/**
@ -217,11 +301,18 @@ PHP_METHOD(Timeval, now) {
* @return Timeval Zero length time interval
*/
PHP_METHOD(Timeval, zero) {
#if PHP_MAJOR_VERSION < 7
zval *grpc_php_timeval_zero =
grpc_php_wrap_timeval(gpr_time_0(GPR_CLOCK_REALTIME) TSRMLS_CC);
RETURN_ZVAL(grpc_php_timeval_zero,
false, /* Copy original before returning? */
true /* Destroy original before returning */);
#else
grpc_php_wrap_timeval(gpr_time_0(GPR_CLOCK_REALTIME), return_value);
RETURN_ZVAL(return_value,
false, /* Copy original before returning? */
true /* Destroy original before returning */);
#endif
}
/**
@ -229,9 +320,14 @@ PHP_METHOD(Timeval, zero) {
* @return Timeval Infinite future time value
*/
PHP_METHOD(Timeval, infFuture) {
#if PHP_MAJOR_VERSION < 7
zval *grpc_php_timeval_inf_future =
grpc_php_wrap_timeval(gpr_inf_future(GPR_CLOCK_REALTIME) TSRMLS_CC);
RETURN_DESTROY_ZVAL(grpc_php_timeval_inf_future);
#else
grpc_php_wrap_timeval(gpr_inf_future(GPR_CLOCK_REALTIME), return_value);
RETURN_DESTROY_ZVAL(return_value);
#endif
}
/**
@ -239,9 +335,14 @@ PHP_METHOD(Timeval, infFuture) {
* @return Timeval Infinite past time value
*/
PHP_METHOD(Timeval, infPast) {
#if PHP_MAJOR_VERSION < 7
zval *grpc_php_timeval_inf_past =
grpc_php_wrap_timeval(gpr_inf_past(GPR_CLOCK_REALTIME) TSRMLS_CC);
RETURN_DESTROY_ZVAL(grpc_php_timeval_inf_past);
#else
grpc_php_wrap_timeval(gpr_inf_past(GPR_CLOCK_REALTIME), return_value);
RETURN_DESTROY_ZVAL(return_value);
#endif
}
/**
@ -249,28 +350,41 @@ PHP_METHOD(Timeval, infPast) {
* @return void
*/
PHP_METHOD(Timeval, sleepUntil) {
#if PHP_MAJOR_VERSION < 7
wrapped_grpc_timeval *this =
(wrapped_grpc_timeval *)zend_object_store_get_object(getThis() TSRMLS_CC);
#else
wrapped_grpc_timeval *this = Z_WRAPPED_GRPC_TIMEVAL_P(getThis());
#endif
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, infFuture, NULL, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC)
PHP_ME(Timeval, infPast, 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, sleepUntil, 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};
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, infFuture, NULL, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC)
PHP_ME(Timeval, infPast, 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, sleepUntil, 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);
#if PHP_MAJOR_VERSION >= 7
memcpy(&timeval_ce_handlers, zend_get_std_object_handlers(),
sizeof(zend_object_handlers));
timeval_ce_handlers.offset =
XtOffsetOf(wrapped_grpc_timeval, std);
timeval_ce_handlers.free_obj = free_wrapped_grpc_timeval;
#endif
}
void grpc_shutdown_timeval(TSRMLS_D) {}

@ -50,12 +50,31 @@
extern zend_class_entry *grpc_ce_timeval;
/* Wrapper struct for timeval that can be associated with a PHP object */
#if PHP_MAJOR_VERSION < 7
typedef struct wrapped_grpc_timeval {
zend_object std;
gpr_timespec wrapped;
} wrapped_grpc_timeval;
#else
typedef struct wrapped_grpc_timeval {
gpr_timespec wrapped;
zend_object std;
} wrapped_grpc_timeval;
static inline wrapped_grpc_timeval
*wrapped_grpc_timeval_from_obj(zend_object *obj) {
return (wrapped_grpc_timeval*)((char*)(obj) -
XtOffsetOf(wrapped_grpc_timeval, std));
}
#define Z_WRAPPED_GRPC_TIMEVAL_P(zv) \
wrapped_grpc_timeval_from_obj(Z_OBJ_P((zv)))
#endif /* PHP_MAJOR_VERSION */
/* Initialize the Timeval PHP class */
void grpc_init_timeval(TSRMLS_D);
@ -63,6 +82,10 @@ void grpc_init_timeval(TSRMLS_D);
void grpc_shutdown_timeval(TSRMLS_D);
/* Creates a Timeval object that wraps the given timeval struct */
#if PHP_MAJOR_VERSION < 7
zval *grpc_php_wrap_timeval(gpr_timespec wrapped TSRMLS_DC);
#else
void grpc_php_wrap_timeval(gpr_timespec wrapped, zval *timeval_object);
#endif /* PHP_MAJOR_VERSION */
#endif /* NET_GRPC_PHP_GRPC_TIMEVAL_H_ */

@ -84,8 +84,8 @@ class BaseStub
}
if ($channel) {
if (!is_a($channel, 'Channel')) {
throw new \Exception("The channel argument is not a".
"Channel object");
throw new \Exception('The channel argument is not a'.
'Channel object');
}
$this->channel = $channel;
} else {

@ -113,6 +113,7 @@ class BidiStreamingCall extends AbstractCall
]);
$this->trailing_metadata = $status_event->status->metadata;
return $status_event->status;
}
}

@ -88,6 +88,7 @@ class ClientStreamingCall extends AbstractCall
$status = $event->status;
$this->trailing_metadata = $status->metadata;
return [$this->deserializeResponse($event->message), $status];
}
}

@ -92,6 +92,7 @@ class ServerStreamingCall extends AbstractCall
]);
$this->trailing_metadata = $status_event->status->metadata;
return $status_event->status;
}
}

@ -77,6 +77,7 @@ class UnaryCall extends AbstractCall
$status = $event->status;
$this->trailing_metadata = $status->metadata;
return [$this->deserializeResponse($event->message), $status];
}
}

@ -148,7 +148,8 @@ class CallCredentialsTest extends PHPUnit_Framework_TestCase
$this->call_credentials,
$call_credentials2
);
$this->assertSame('Grpc\CallCredentials', get_class($call_credentials3));
$this->assertSame('Grpc\CallCredentials',
get_class($call_credentials3));
}
/**

@ -50,6 +50,18 @@ class CallTest extends PHPUnit_Framework_TestCase
Grpc\Timeval::infFuture());
}
public function tearDown()
{
unset($this->call);
unset($this->channel);
}
public function testConstructor()
{
$this->assertSame('Grpc\Call', get_class($this->call));
$this->assertObjectHasAttribute('channel', $this->call);
}
public function testAddEmptyMetadata()
{
$batch = [
@ -81,7 +93,8 @@ class CallTest extends PHPUnit_Framework_TestCase
{
$batch = [
Grpc\OP_SEND_INITIAL_METADATA => ['key1' => ['value1'],
'key2' => ['value2', 'value3'], ],
'key2' => ['value2',
'value3'], ],
];
$result = $this->call->startBatch($batch);
$this->assertTrue($result->send_metadata);
@ -118,4 +131,38 @@ class CallTest extends PHPUnit_Framework_TestCase
];
$result = $this->call->startBatch($batch);
}
/**
* @expectedException InvalidArgumentException
*/
public function testInvalidConstuctor()
{
$this->call = new Grpc\Call();
$this->assertNull($this->call);
}
/**
* @expectedException InvalidArgumentException
*/
public function testInvalidConstuctor2()
{
$this->call = new Grpc\Call('hi', 'hi', 'hi');
$this->assertNull($this->call);
}
/**
* @expectedException InvalidArgumentException
*/
public function testInvalidSetCredentials()
{
$this->call->setCredentials('hi');
}
/**
* @expectedException InvalidArgumentException
*/
public function testInvalidSetCredentials2()
{
$this->call->setCredentials([]);
}
}

@ -42,10 +42,23 @@ class ChanellCredentialsTest extends PHPUnit_Framework_TestCase
{
}
public function testCreateDefault()
public function testCreateSslWith3Null()
{
$channel_credentials = Grpc\ChannelCredentials::createDefault();
$this->assertSame('Grpc\ChannelCredentials', get_class($channel_credentials));
$channel_credentials = Grpc\ChannelCredentials::createSsl(null, null,
null);
$this->assertNotNull($channel_credentials);
}
public function testCreateSslWith3NullString()
{
$channel_credentials = Grpc\ChannelCredentials::createSsl('', '', '');
$this->assertNotNull($channel_credentials);
}
public function testCreateInsecure()
{
$channel_credentials = Grpc\ChannelCredentials::createInsecure();
$this->assertNull($channel_credentials);
}
/**
@ -64,10 +77,4 @@ class ChanellCredentialsTest extends PHPUnit_Framework_TestCase
$channel_credentials = Grpc\ChannelCredentials::createComposite(
'something', 'something');
}
public function testCreateInsecure()
{
$channel_credentials = Grpc\ChannelCredentials::createInsecure();
$this->assertNull($channel_credentials);
}
}

@ -40,6 +40,7 @@ class ChannelTest extends PHPUnit_Framework_TestCase
public function tearDown()
{
unset($this->channel);
}
public function testInsecureCredentials()
@ -53,6 +54,82 @@ class ChannelTest extends PHPUnit_Framework_TestCase
$this->assertSame('Grpc\Channel', get_class($this->channel));
}
public function testGetConnectivityState()
{
$this->channel = new Grpc\Channel('localhost:0',
['credentials' => Grpc\ChannelCredentials::createInsecure()]);
$state = $this->channel->getConnectivityState();
$this->assertEquals(0, $state);
}
public function testGetConnectivityStateWithInt()
{
$this->channel = new Grpc\Channel('localhost:0',
['credentials' => Grpc\ChannelCredentials::createInsecure()]);
$state = $this->channel->getConnectivityState(123);
$this->assertEquals(0, $state);
}
public function testGetConnectivityStateWithString()
{
$this->channel = new Grpc\Channel('localhost:0',
['credentials' => Grpc\ChannelCredentials::createInsecure()]);
$state = $this->channel->getConnectivityState('hello');
$this->assertEquals(0, $state);
}
public function testGetConnectivityStateWithBool()
{
$this->channel = new Grpc\Channel('localhost:0',
['credentials' => Grpc\ChannelCredentials::createInsecure()]);
$state = $this->channel->getConnectivityState(true);
$this->assertEquals(0, $state);
}
public function testGetTarget()
{
$this->channel = new Grpc\Channel('localhost:8888',
['credentials' => Grpc\ChannelCredentials::createInsecure()]);
$target = $this->channel->getTarget();
$this->assertTrue(is_string($target));
}
public function testWatchConnectivityState()
{
$this->channel = new Grpc\Channel('localhost:0',
['credentials' => Grpc\ChannelCredentials::createInsecure()]);
$time = new Grpc\Timeval(1000);
$state = $this->channel->watchConnectivityState(123, $time);
$this->assertTrue($state);
unset($time);
}
public function testClose()
{
$this->channel = new Grpc\Channel('localhost:0',
['credentials' => Grpc\ChannelCredentials::createInsecure()]);
$this->assertNotNull($this->channel);
$this->channel->close();
}
/**
* @expectedException InvalidArgumentException
*/
public function testInvalidConstructorWithNull()
{
$this->channel = new Grpc\Channel();
$this->assertNull($this->channel);
}
/**
* @expectedException InvalidArgumentException
*/
public function testInvalidConstructorWith()
{
$this->channel = new Grpc\Channel('localhost', 'invalid');
$this->assertNull($this->channel);
}
/**
* @expectedException InvalidArgumentException
*/
@ -78,4 +155,34 @@ class ChannelTest extends PHPUnit_Framework_TestCase
]
);
}
/**
* @expectedException InvalidArgumentException
*/
public function testInvalidGetConnectivityStateWithArray()
{
$this->channel = new Grpc\Channel('localhost:0',
['credentials' => Grpc\ChannelCredentials::createInsecure()]);
$this->channel->getConnectivityState([]);
}
/**
* @expectedException InvalidArgumentException
*/
public function testInvalidWatchConnectivityState()
{
$this->channel = new Grpc\Channel('localhost:0',
['credentials' => Grpc\ChannelCredentials::createInsecure()]);
$this->channel->watchConnectivityState([]);
}
/**
* @expectedException InvalidArgumentException
*/
public function testInvalidWatchConnectivityState2()
{
$this->channel = new Grpc\Channel('localhost:0',
['credentials' => Grpc\ChannelCredentials::createInsecure()]);
$this->channel->watchConnectivityState(1, 'hi');
}
}

@ -521,7 +521,8 @@ class EndToEndTest extends PHPUnit_Framework_TestCase
public function testGetConnectivityState()
{
$this->assertTrue($this->channel->getConnectivityState() == Grpc\CHANNEL_IDLE);
$this->assertTrue($this->channel->getConnectivityState() ==
Grpc\CHANNEL_IDLE);
}
public function testWatchConnectivityStateFailed()

@ -36,10 +36,70 @@ class ServerTest extends PHPUnit_Framework_TestCase
{
public function setUp()
{
$this->server = null;
}
public function tearDown()
{
unset($this->server);
}
public function testConstructorWithNull()
{
$this->server = new Grpc\Server();
$this->assertNotNull($this->server);
}
public function testConstructorWithNullArray()
{
$this->server = new Grpc\Server([]);
$this->assertNotNull($this->server);
}
public function testConstructorWithArray()
{
// key of array must be string
$this->server = new Grpc\Server(['ip' => '127.0.0.1',
'port' => '8080', ]);
$this->assertNotNull($this->server);
}
public function testRequestCall()
{
$this->server = new Grpc\Server();
$port = $this->server->addHttp2Port('0.0.0.0:8888');
$this->server->start();
$channel = new Grpc\Channel('localhost:8888',
['credentials' => Grpc\ChannelCredentials::createInsecure()]);
$deadline = Grpc\Timeval::infFuture();
$call = new Grpc\Call($channel, 'dummy_method', $deadline);
$event = $call->startBatch([Grpc\OP_SEND_INITIAL_METADATA => [],
Grpc\OP_SEND_CLOSE_FROM_CLIENT => true,
]);
$c = $this->server->requestCall();
$this->assertObjectHasAttribute('call', $c);
$this->assertObjectHasAttribute('method', $c);
$this->assertSame('dummy_method', $c->method);
$this->assertObjectHasAttribute('host', $c);
$this->assertTrue(is_string($c->host));
$this->assertObjectHasAttribute('absolute_deadline', $c);
$this->assertObjectHasAttribute('metadata', $c);
unset($call);
unset($channel);
}
private function createSslObj()
{
$server_credentials = Grpc\ServerCredentials::createSsl(
null,
file_get_contents(dirname(__FILE__).'/../data/server1.key'),
file_get_contents(dirname(__FILE__).'/../data/server1.pem'));
return $server_credentials;
}
/**
@ -47,7 +107,8 @@ class ServerTest extends PHPUnit_Framework_TestCase
*/
public function testInvalidConstructor()
{
$server = new Grpc\Server('invalid_host');
$this->server = new Grpc\Server('invalid_host');
$this->assertNull($this->server);
}
/**
@ -56,7 +117,7 @@ class ServerTest extends PHPUnit_Framework_TestCase
public function testInvalidAddHttp2Port()
{
$this->server = new Grpc\Server([]);
$this->port = $this->server->addHttp2Port(['0.0.0.0:0']);
$port = $this->server->addHttp2Port(['0.0.0.0:0']);
}
/**
@ -65,6 +126,24 @@ class ServerTest extends PHPUnit_Framework_TestCase
public function testInvalidAddSecureHttp2Port()
{
$this->server = new Grpc\Server([]);
$this->port = $this->server->addSecureHttp2Port(['0.0.0.0:0']);
$port = $this->server->addSecureHttp2Port(['0.0.0.0:0']);
}
/**
* @expectedException InvalidArgumentException
*/
public function testInvalidAddSecureHttp2Port2()
{
$this->server = new Grpc\Server();
$port = $this->server->addSecureHttp2Port('0.0.0.0:0');
}
/**
* @expectedException InvalidArgumentException
*/
public function testInvalidAddSecureHttp2Port3()
{
$this->server = new Grpc\Server();
$port = $this->server->addSecureHttp2Port('0.0.0.0:0', 'invalid');
}
}

@ -33,6 +33,57 @@
*/
class TimevalTest extends PHPUnit_Framework_TestCase
{
public function setUp()
{
}
public function tearDown()
{
unset($this->time);
}
public function testConstructorWithInt()
{
$this->time = new Grpc\Timeval(1234);
$this->assertNotNull($this->time);
$this->assertSame('Grpc\Timeval', get_class($this->time));
}
public function testConstructorWithNegative()
{
$this->time = new Grpc\Timeval(-123);
$this->assertNotNull($this->time);
$this->assertSame('Grpc\Timeval', get_class($this->time));
}
public function testConstructorWithZero()
{
$this->time = new Grpc\Timeval(0);
$this->assertNotNull($this->time);
$this->assertSame('Grpc\Timeval', get_class($this->time));
}
public function testConstructorWithOct()
{
$this->time = new Grpc\Timeval(0123);
$this->assertNotNull($this->time);
$this->assertSame('Grpc\Timeval', get_class($this->time));
}
public function testConstructorWithHex()
{
$this->time = new Grpc\Timeval(0x1A);
$this->assertNotNull($this->time);
$this->assertSame('Grpc\Timeval', get_class($this->time));
}
public function testConstructorWithFloat()
{
$this->time = new Grpc\Timeval(123.456);
$this->assertNotNull($this->time);
$this->assertSame('Grpc\Timeval', get_class($this->time));
}
public function testCompareSame()
{
$zero = Grpc\Timeval::zero();
@ -70,6 +121,7 @@ class TimevalTest extends PHPUnit_Framework_TestCase
public function testNowAndAdd()
{
$now = Grpc\Timeval::now();
$this->assertNotNull($now);
$delta = new Grpc\Timeval(1000);
$deadline = $now->add($delta);
$this->assertGreaterThan(0, Grpc\Timeval::compare($deadline, $now));
@ -154,5 +206,6 @@ class TimevalTest extends PHPUnit_Framework_TestCase
public function testSimilarInvalidParam()
{
$a = Grpc\Timeval::similar(1000, 1100, 1200);
$this->assertNull($delta);
}
}

@ -44,15 +44,15 @@ module Math
self.unmarshal_class_method = :decode
self.service_name = 'math.Math'
# Div divides args.dividend by args.divisor and returns the quotient and
# remainder.
# Div divides DivArgs.dividend by DivArgs.divisor and returns the quotient
# and remainder.
rpc :Div, DivArgs, DivReply
# DivMany accepts an arbitrary number of division args from the client stream
# and sends back the results in the reply stream. The stream continues until
# the client closes its end; the server does the same after sending all the
# replies. The stream ends immediately if either end aborts.
rpc :DivMany, stream(DivArgs), stream(DivReply)
# Fib generates numbers in the Fibonacci sequence. If args.limit > 0, Fib
# Fib generates numbers in the Fibonacci sequence. If FibArgs.limit > 0, Fib
# generates up to limit numbers; otherwise it continues until the call is
# canceled. Unlike Fib above, Fib has no final FibReply.
rpc :Fib, FibArgs, stream(Num)

@ -38,6 +38,7 @@
#include <grpc/grpc.h>
#include <grpc/support/alloc.h>
#include <grpc/impl/codegen/compression_types.h>
#include "rb_byte_buffer.h"
#include "rb_call_credentials.h"
@ -910,6 +911,12 @@ static void Init_grpc_op_codes() {
UINT2NUM(GRPC_OP_RECV_CLOSE_ON_SERVER));
}
static void Init_grpc_metadata_keys() {
VALUE grpc_rb_mMetadataKeys = rb_define_module_under(grpc_rb_mGrpcCore, "MetadataKeys");
rb_define_const(grpc_rb_mMetadataKeys, "COMPRESSION_REQUEST_ALGORITHM",
rb_str_new2(GRPC_COMPRESSION_REQUEST_ALGORITHM_MD_KEY));
}
void Init_grpc_call() {
/* CallError inherits from Exception to signal that it is non-recoverable */
grpc_rb_eCallError =
@ -972,6 +979,7 @@ void Init_grpc_call() {
Init_grpc_error_codes();
Init_grpc_op_codes();
Init_grpc_write_flags();
Init_grpc_metadata_keys();
}
/* Gets the call from the ruby object */

@ -0,0 +1,464 @@
/*
*
* Copyright 2015, Google Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
#include <ruby/ruby.h>
#include "rb_compression_options.h"
#include "rb_grpc_imports.generated.h"
#include <grpc/compression.h>
#include <grpc/grpc.h>
#include <grpc/impl/codegen/alloc.h>
#include <grpc/impl/codegen/compression_types.h>
#include <grpc/impl/codegen/grpc_types.h>
#include <string.h>
#include "rb_grpc.h"
static VALUE grpc_rb_cCompressionOptions = Qnil;
/* Ruby Ids for the names of valid compression levels. */
static VALUE id_compress_level_none = Qnil;
static VALUE id_compress_level_low = Qnil;
static VALUE id_compress_level_medium = Qnil;
static VALUE id_compress_level_high = Qnil;
/* grpc_rb_compression_options wraps a grpc_compression_options.
* It can be used to get the channel argument key-values for specific
* compression settings. */
/* Note that ruby objects of this type don't carry any state in other
* Ruby objects and don't have a mark for GC. */
typedef struct grpc_rb_compression_options {
/* The actual compression options that's being wrapped */
grpc_compression_options *wrapped;
} grpc_rb_compression_options;
/* Destroys the compression options instances and free the
* wrapped grpc compression options. */
static void grpc_rb_compression_options_free(void *p) {
grpc_rb_compression_options *wrapper = NULL;
if (p == NULL) {
return;
};
wrapper = (grpc_rb_compression_options *)p;
if (wrapper->wrapped != NULL) {
gpr_free(wrapper->wrapped);
wrapper->wrapped = NULL;
}
xfree(p);
}
/* Ruby recognized data type for the CompressionOptions class. */
static rb_data_type_t grpc_rb_compression_options_data_type = {
"grpc_compression_options",
{NULL,
grpc_rb_compression_options_free,
GRPC_RB_MEMSIZE_UNAVAILABLE,
{NULL, NULL}},
NULL,
NULL,
#ifdef RUBY_TYPED_FREE_IMMEDIATELY
RUBY_TYPED_FREE_IMMEDIATELY
#endif
};
/* Allocates CompressionOptions instances.
Allocate the wrapped grpc compression options and
initialize it here too. */
static VALUE grpc_rb_compression_options_alloc(VALUE cls) {
grpc_rb_compression_options *wrapper =
gpr_malloc(sizeof(grpc_rb_compression_options));
wrapper->wrapped = NULL;
wrapper->wrapped = gpr_malloc(sizeof(grpc_compression_options));
grpc_compression_options_init(wrapper->wrapped);
return TypedData_Wrap_Struct(cls, &grpc_rb_compression_options_data_type,
wrapper);
}
/* Disables a compression algorithm, given the GRPC core internal number of a
* compression algorithm. */
VALUE grpc_rb_compression_options_disable_compression_algorithm_internal(
VALUE self, VALUE algorithm_to_disable) {
grpc_compression_algorithm compression_algorithm = 0;
grpc_rb_compression_options *wrapper = NULL;
TypedData_Get_Struct(self, grpc_rb_compression_options,
&grpc_rb_compression_options_data_type, wrapper);
compression_algorithm =
(grpc_compression_algorithm)NUM2INT(algorithm_to_disable);
grpc_compression_options_disable_algorithm(wrapper->wrapped,
compression_algorithm);
return Qnil;
}
/* Gets the compression internal enum value of a compression level given its
* name. */
grpc_compression_level grpc_rb_compression_options_level_name_to_value_internal(
VALUE level_name) {
Check_Type(level_name, T_SYMBOL);
/* Check the compression level of the name passed in, and see which macro
* from the GRPC core header files match. */
if (id_compress_level_none == SYM2ID(level_name)) {
return GRPC_COMPRESS_LEVEL_NONE;
} else if (id_compress_level_low == SYM2ID(level_name)) {
return GRPC_COMPRESS_LEVEL_LOW;
} else if (id_compress_level_medium == SYM2ID(level_name)) {
return GRPC_COMPRESS_LEVEL_MED;
} else if (id_compress_level_high == SYM2ID(level_name)) {
return GRPC_COMPRESS_LEVEL_HIGH;
}
rb_raise(rb_eArgError,
"Unrecognized compression level name."
"Valid compression level names are none, low, medium, and high.");
/* Dummy return statement. */
return GRPC_COMPRESS_LEVEL_NONE;
}
/* Sets the default compression level, given the name of a compression level.
* Throws an error if no algorithm matched. */
void grpc_rb_compression_options_set_default_level(
grpc_compression_options *options, VALUE new_level_name) {
options->default_level.level =
grpc_rb_compression_options_level_name_to_value_internal(new_level_name);
options->default_level.is_set = 1;
}
/* Gets the internal value of a compression algorithm suitable as the value
* in a GRPC core channel arguments hash.
* algorithm_value is an out parameter.
* Raises an error if the name of the algorithm passed in is invalid. */
void grpc_rb_compression_options_algorithm_name_to_value_internal(
grpc_compression_algorithm *algorithm_value, VALUE algorithm_name) {
char *name_str = NULL;
long name_len = 0;
VALUE algorithm_name_as_string = Qnil;
Check_Type(algorithm_name, T_SYMBOL);
/* Convert the algorithm symbol to a ruby string, so that we can get the
* correct C string out of it. */
algorithm_name_as_string = rb_funcall(algorithm_name, rb_intern("to_s"), 0);
name_str = RSTRING_PTR(algorithm_name_as_string);
name_len = RSTRING_LEN(algorithm_name_as_string);
/* Raise an error if the name isn't recognized as a compression algorithm by
* the algorithm parse function
* in GRPC core. */
if (!grpc_compression_algorithm_parse(name_str, name_len, algorithm_value)) {
rb_raise(rb_eNameError, "Invalid compression algorithm name: %s",
StringValueCStr(algorithm_name_as_string));
}
}
/* Indicates whether a given algorithm is enabled on this instance, given the
* readable algorithm name. */
VALUE grpc_rb_compression_options_is_algorithm_enabled(VALUE self,
VALUE algorithm_name) {
grpc_rb_compression_options *wrapper = NULL;
grpc_compression_algorithm internal_algorithm_value;
TypedData_Get_Struct(self, grpc_rb_compression_options,
&grpc_rb_compression_options_data_type, wrapper);
grpc_rb_compression_options_algorithm_name_to_value_internal(
&internal_algorithm_value, algorithm_name);
if (grpc_compression_options_is_algorithm_enabled(wrapper->wrapped,
internal_algorithm_value)) {
return Qtrue;
}
return Qfalse;
}
/* Sets the default algorithm to the name of the algorithm passed in.
* Raises an error if the name is not a valid compression algorithm name. */
void grpc_rb_compression_options_set_default_algorithm(
grpc_compression_options *options, VALUE algorithm_name) {
grpc_rb_compression_options_algorithm_name_to_value_internal(
&options->default_algorithm.algorithm, algorithm_name);
options->default_algorithm.is_set = 1;
}
/* Disables an algorithm on the current instance, given the name of an
* algorithm.
* Fails if the algorithm name is invalid. */
void grpc_rb_compression_options_disable_algorithm(
grpc_compression_options *compression_options, VALUE algorithm_name) {
grpc_compression_algorithm internal_algorithm_value;
grpc_rb_compression_options_algorithm_name_to_value_internal(
&internal_algorithm_value, algorithm_name);
grpc_compression_options_disable_algorithm(compression_options,
internal_algorithm_value);
}
/* Provides a ruby hash of GRPC core channel argument key-values that
* correspond to the compression settings on this instance. */
VALUE grpc_rb_compression_options_to_hash(VALUE self) {
grpc_rb_compression_options *wrapper = NULL;
grpc_compression_options *compression_options = NULL;
VALUE channel_arg_hash = rb_hash_new();
VALUE key = Qnil;
VALUE value = Qnil;
TypedData_Get_Struct(self, grpc_rb_compression_options,
&grpc_rb_compression_options_data_type, wrapper);
compression_options = wrapper->wrapped;
/* Add key-value pairs to the new Ruby hash. It can be used
* as GRPC core channel arguments. */
if (compression_options->default_level.is_set) {
key = rb_str_new2(GRPC_COMPRESSION_CHANNEL_DEFAULT_LEVEL);
value = INT2NUM((int)compression_options->default_level.level);
rb_hash_aset(channel_arg_hash, key, value);
}
if (compression_options->default_algorithm.is_set) {
key = rb_str_new2(GRPC_COMPRESSION_CHANNEL_DEFAULT_ALGORITHM);
value = INT2NUM((int)compression_options->default_algorithm.algorithm);
rb_hash_aset(channel_arg_hash, key, value);
}
key = rb_str_new2(GRPC_COMPRESSION_CHANNEL_ENABLED_ALGORITHMS_BITSET);
value = INT2NUM((int)compression_options->enabled_algorithms_bitset);
rb_hash_aset(channel_arg_hash, key, value);
return channel_arg_hash;
}
/* Converts an internal enum level value to a readable level name.
* Fails if the level value is invalid. */
VALUE grpc_rb_compression_options_level_value_to_name_internal(
grpc_compression_level compression_value) {
switch (compression_value) {
case GRPC_COMPRESS_LEVEL_NONE:
return ID2SYM(id_compress_level_none);
case GRPC_COMPRESS_LEVEL_LOW:
return ID2SYM(id_compress_level_low);
case GRPC_COMPRESS_LEVEL_MED:
return ID2SYM(id_compress_level_medium);
case GRPC_COMPRESS_LEVEL_HIGH:
return ID2SYM(id_compress_level_high);
default:
rb_raise(
rb_eArgError,
"Failed to convert compression level value to name for value: %d",
(int)compression_value);
}
}
/* Converts an algorithm internal enum value to a readable name.
* Fails if the enum value is invalid. */
VALUE grpc_rb_compression_options_algorithm_value_to_name_internal(
grpc_compression_algorithm internal_value) {
char *algorithm_name = NULL;
if (!grpc_compression_algorithm_name(internal_value, &algorithm_name)) {
rb_raise(rb_eArgError, "Failed to convert algorithm value to name");
}
return ID2SYM(rb_intern(algorithm_name));
}
/* Gets the readable name of the default algorithm if one has been set.
* Returns nil if no algorithm has been set. */
VALUE grpc_rb_compression_options_get_default_algorithm(VALUE self) {
grpc_compression_algorithm internal_value;
grpc_rb_compression_options *wrapper = NULL;
TypedData_Get_Struct(self, grpc_rb_compression_options,
&grpc_rb_compression_options_data_type, wrapper);
if (wrapper->wrapped->default_algorithm.is_set) {
internal_value = wrapper->wrapped->default_algorithm.algorithm;
return grpc_rb_compression_options_algorithm_value_to_name_internal(
internal_value);
}
return Qnil;
}
/* Gets the internal value of the default compression level that is to be passed
* to the GRPC core as a channel argument value.
* A nil return value means that it hasn't been set. */
VALUE grpc_rb_compression_options_get_default_level(VALUE self) {
grpc_compression_level internal_value;
grpc_rb_compression_options *wrapper = NULL;
TypedData_Get_Struct(self, grpc_rb_compression_options,
&grpc_rb_compression_options_data_type, wrapper);
if (wrapper->wrapped->default_level.is_set) {
internal_value = wrapper->wrapped->default_level.level;
return grpc_rb_compression_options_level_value_to_name_internal(
internal_value);
}
return Qnil;
}
/* Gets a list of the disabled algorithms as readable names.
* Returns an empty list if no algorithms have been disabled. */
VALUE grpc_rb_compression_options_get_disabled_algorithms(VALUE self) {
VALUE disabled_algorithms = rb_ary_new();
grpc_compression_algorithm internal_value;
grpc_rb_compression_options *wrapper = NULL;
TypedData_Get_Struct(self, grpc_rb_compression_options,
&grpc_rb_compression_options_data_type, wrapper);
for (internal_value = GRPC_COMPRESS_NONE;
internal_value < GRPC_COMPRESS_ALGORITHMS_COUNT; internal_value++) {
if (!grpc_compression_options_is_algorithm_enabled(wrapper->wrapped,
internal_value)) {
rb_ary_push(disabled_algorithms,
grpc_rb_compression_options_algorithm_value_to_name_internal(
internal_value));
}
}
return disabled_algorithms;
}
/* Initializes the compression options wrapper.
* Takes an optional hash parameter.
*
* Example call-seq:
* options = CompressionOptions.new(
* default_level: :none,
* disabled_algorithms: [:gzip]
* )
* channel_arg hash = Hash.new[...]
* channel_arg_hash_with_compression_options = channel_arg_hash.merge(options)
*/
VALUE grpc_rb_compression_options_init(int argc, VALUE *argv, VALUE self) {
grpc_rb_compression_options *wrapper = NULL;
VALUE default_algorithm = Qnil;
VALUE default_level = Qnil;
VALUE disabled_algorithms = Qnil;
VALUE algorithm_name = Qnil;
VALUE hash_arg = Qnil;
rb_scan_args(argc, argv, "01", &hash_arg);
/* Check if the hash parameter was passed, or if invalid arguments were
* passed. */
if (hash_arg == Qnil) {
return self;
} else if (TYPE(hash_arg) != T_HASH || argc > 1) {
rb_raise(rb_eArgError,
"Invalid arguments. Expecting optional hash parameter");
}
TypedData_Get_Struct(self, grpc_rb_compression_options,
&grpc_rb_compression_options_data_type, wrapper);
/* Set the default algorithm if one was chosen. */
default_algorithm =
rb_hash_aref(hash_arg, ID2SYM(rb_intern("default_algorithm")));
if (default_algorithm != Qnil) {
grpc_rb_compression_options_set_default_algorithm(wrapper->wrapped,
default_algorithm);
}
/* Set the default level if one was chosen. */
default_level = rb_hash_aref(hash_arg, ID2SYM(rb_intern("default_level")));
if (default_level != Qnil) {
grpc_rb_compression_options_set_default_level(wrapper->wrapped,
default_level);
}
/* Set the disabled algorithms if any were chosen. */
disabled_algorithms =
rb_hash_aref(hash_arg, ID2SYM(rb_intern("disabled_algorithms")));
if (disabled_algorithms != Qnil) {
Check_Type(disabled_algorithms, T_ARRAY);
for (int i = 0; i < RARRAY_LEN(disabled_algorithms); i++) {
algorithm_name = rb_ary_entry(disabled_algorithms, i);
grpc_rb_compression_options_disable_algorithm(wrapper->wrapped,
algorithm_name);
}
}
return self;
}
void Init_grpc_compression_options() {
grpc_rb_cCompressionOptions = rb_define_class_under(
grpc_rb_mGrpcCore, "CompressionOptions", rb_cObject);
/* Allocates an object managed by the ruby runtime. */
rb_define_alloc_func(grpc_rb_cCompressionOptions,
grpc_rb_compression_options_alloc);
/* Initializes the ruby wrapper. #new method takes an optional hash argument.
*/
rb_define_method(grpc_rb_cCompressionOptions, "initialize",
grpc_rb_compression_options_init, -1);
/* Methods for getting the default algorithm, default level, and disabled
* algorithms as readable names. */
rb_define_method(grpc_rb_cCompressionOptions, "default_algorithm",
grpc_rb_compression_options_get_default_algorithm, 0);
rb_define_method(grpc_rb_cCompressionOptions, "default_level",
grpc_rb_compression_options_get_default_level, 0);
rb_define_method(grpc_rb_cCompressionOptions, "disabled_algorithms",
grpc_rb_compression_options_get_disabled_algorithms, 0);
/* Determines whether or not an algorithm is enabled, given a readable
* algorithm name.*/
rb_define_method(grpc_rb_cCompressionOptions, "algorithm_enabled?",
grpc_rb_compression_options_is_algorithm_enabled, 1);
/* Provides a hash of the compression settings suitable
* for passing to server or channel args. */
rb_define_method(grpc_rb_cCompressionOptions, "to_hash",
grpc_rb_compression_options_to_hash, 0);
rb_define_alias(grpc_rb_cCompressionOptions, "to_channel_arg_hash",
"to_hash");
/* Ruby ids for the names of the different compression levels. */
id_compress_level_none = rb_intern("none");
id_compress_level_low = rb_intern("low");
id_compress_level_medium = rb_intern("medium");
id_compress_level_high = rb_intern("high");
}

@ -0,0 +1,44 @@
/*
*
* Copyright 2015, Google Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
#ifndef GRPC_RB_COMPRESSION_OPTIONS_H_
#define GRPC_RB_COMPRESSION_OPTIONS_H_
#include <ruby/ruby.h>
#include <grpc/grpc.h>
/* Initializes the compression options ruby wrapper. */
void Init_grpc_compression_options();
#endif /* GRPC_RB_COMPRESSION_OPTIONS_H_ */

@ -49,6 +49,7 @@
#include "rb_loader.h"
#include "rb_server.h"
#include "rb_server_credentials.h"
#include "rb_compression_options.h"
static VALUE grpc_rb_cTimeVal = Qnil;
@ -220,7 +221,7 @@ static VALUE grpc_rb_time_val_to_time(VALUE self) {
time_const);
real_time = gpr_convert_clock_type(*time_const, GPR_CLOCK_REALTIME);
return rb_funcall(rb_cTime, id_at, 2, INT2NUM(real_time.tv_sec),
INT2NUM(real_time.tv_nsec));
INT2NUM(real_time.tv_nsec / 1000));
}
/* Invokes inspect on the ctime version of the time val. */
@ -332,4 +333,5 @@ void Init_grpc_c() {
Init_grpc_server_credentials();
Init_grpc_status_codes();
Init_grpc_time_consts();
Init_grpc_compression_options();
}

@ -218,7 +218,7 @@ static VALUE grpc_rb_server_request_call(VALUE self) {
grpc_rb_sNewServerRpc, rb_str_new2(st.details.method),
rb_str_new2(st.details.host),
rb_funcall(rb_cTime, id_at, 2, INT2NUM(deadline.tv_sec),
INT2NUM(deadline.tv_nsec)),
INT2NUM(deadline.tv_nsec / 1000)),
grpc_rb_md_ary_to_h(&st.md_ary), grpc_rb_wrap_call(call, call_queue),
NULL);
grpc_request_call_stack_cleanup(&st);

@ -4,6 +4,9 @@
require 'google/protobuf'
Google::Protobuf::DescriptorPool.generated_pool.build do
add_message "grpc.testing.BoolValue" do
optional :value, :bool, 1
end
add_message "grpc.testing.Payload" do
optional :type, :enum, 1, "grpc.testing.PayloadType"
optional :body, :bytes, 2
@ -18,8 +21,9 @@ Google::Protobuf::DescriptorPool.generated_pool.build do
optional :payload, :message, 3, "grpc.testing.Payload"
optional :fill_username, :bool, 4
optional :fill_oauth_scope, :bool, 5
optional :response_compression, :enum, 6, "grpc.testing.CompressionType"
optional :response_compressed, :message, 6, "grpc.testing.BoolValue"
optional :response_status, :message, 7, "grpc.testing.EchoStatus"
optional :expect_compressed, :message, 8, "grpc.testing.BoolValue"
end
add_message "grpc.testing.SimpleResponse" do
optional :payload, :message, 1, "grpc.testing.Payload"
@ -28,6 +32,7 @@ Google::Protobuf::DescriptorPool.generated_pool.build do
end
add_message "grpc.testing.StreamingInputCallRequest" do
optional :payload, :message, 1, "grpc.testing.Payload"
optional :expect_compressed, :message, 2, "grpc.testing.BoolValue"
end
add_message "grpc.testing.StreamingInputCallResponse" do
optional :aggregated_payload_size, :int32, 1
@ -35,12 +40,12 @@ Google::Protobuf::DescriptorPool.generated_pool.build do
add_message "grpc.testing.ResponseParameters" do
optional :size, :int32, 1
optional :interval_us, :int32, 2
optional :compressed, :message, 3, "grpc.testing.BoolValue"
end
add_message "grpc.testing.StreamingOutputCallRequest" do
optional :response_type, :enum, 1, "grpc.testing.PayloadType"
repeated :response_parameters, :message, 2, "grpc.testing.ResponseParameters"
optional :payload, :message, 3, "grpc.testing.Payload"
optional :response_compression, :enum, 6, "grpc.testing.CompressionType"
optional :response_status, :message, 7, "grpc.testing.EchoStatus"
end
add_message "grpc.testing.StreamingOutputCallResponse" do
@ -55,18 +60,12 @@ Google::Protobuf::DescriptorPool.generated_pool.build do
end
add_enum "grpc.testing.PayloadType" do
value :COMPRESSABLE, 0
value :UNCOMPRESSABLE, 1
value :RANDOM, 2
end
add_enum "grpc.testing.CompressionType" do
value :NONE, 0
value :GZIP, 1
value :DEFLATE, 2
end
end
module Grpc
module Testing
BoolValue = Google::Protobuf::DescriptorPool.generated_pool.lookup("grpc.testing.BoolValue").msgclass
Payload = Google::Protobuf::DescriptorPool.generated_pool.lookup("grpc.testing.Payload").msgclass
EchoStatus = Google::Protobuf::DescriptorPool.generated_pool.lookup("grpc.testing.EchoStatus").msgclass
SimpleRequest = Google::Protobuf::DescriptorPool.generated_pool.lookup("grpc.testing.SimpleRequest").msgclass
@ -79,6 +78,5 @@ module Grpc
ReconnectParams = Google::Protobuf::DescriptorPool.generated_pool.lookup("grpc.testing.ReconnectParams").msgclass
ReconnectInfo = Google::Protobuf::DescriptorPool.generated_pool.lookup("grpc.testing.ReconnectInfo").msgclass
PayloadType = Google::Protobuf::DescriptorPool.generated_pool.lookup("grpc.testing.PayloadType").enummodule
CompressionType = Google::Protobuf::DescriptorPool.generated_pool.lookup("grpc.testing.CompressionType").enummodule
end
end

@ -52,9 +52,9 @@ require_relative '../../lib/grpc'
require 'googleauth'
require 'google/protobuf'
require_relative 'proto/empty'
require_relative 'proto/messages'
require_relative 'proto/test_services'
require_relative '../src/proto/grpc/testing/empty'
require_relative '../src/proto/grpc/testing/messages'
require_relative '../src/proto/grpc/testing/test_services'
AUTH_ENV = Google::Auth::CredentialsLoader::ENV_VAR
@ -111,6 +111,18 @@ end
# creates a test stub that accesses host:port securely.
def create_stub(opts)
address = "#{opts.host}:#{opts.port}"
# Provide channel args that request compression by default
# for compression interop tests
if ['client_compressed_unary',
'client_compressed_streaming'].include?(opts.test_case)
compression_options =
GRPC::Core::CompressionOptions.new(default_algorithm: :gzip)
compression_channel_args = compression_options.to_channel_arg_hash
else
compression_channel_args = {}
end
if opts.secure
creds = ssl_creds(opts.use_test_ca)
stub_opts = {
@ -145,10 +157,15 @@ def create_stub(opts)
end
GRPC.logger.info("... connecting securely to #{address}")
stub_opts[:channel_args].merge!(compression_channel_args)
Grpc::Testing::TestService::Stub.new(address, creds, **stub_opts)
else
GRPC.logger.info("... connecting insecurely to #{address}")
Grpc::Testing::TestService::Stub.new(address, :this_channel_is_insecure)
Grpc::Testing::TestService::Stub.new(
address,
:this_channel_is_insecure,
channel_args: compression_channel_args
)
end
end
@ -216,10 +233,28 @@ class BlockingEnumerator
end
end
# Intended to be used to wrap a call_op, and to adjust
# the write flag of the call_op in between messages yielded to it.
class WriteFlagSettingStreamingInputEnumerable
attr_accessor :call_op
def initialize(requests_and_write_flags)
@requests_and_write_flags = requests_and_write_flags
end
def each
@requests_and_write_flags.each do |request_and_flag|
@call_op.write_flag = request_and_flag[:write_flag]
yield request_and_flag[:request]
end
end
end
# defines methods corresponding to each interop test case.
class NamedTests
include Grpc::Testing
include Grpc::Testing::PayloadType
include GRPC::Core::MetadataKeys
def initialize(stub, args)
@stub = stub
@ -235,6 +270,48 @@ class NamedTests
perform_large_unary
end
def client_compressed_unary
# first request used also for the probe
req_size, wanted_response_size = 271_828, 314_159
expect_compressed = BoolValue.new(value: true)
payload = Payload.new(type: :COMPRESSABLE, body: nulls(req_size))
req = SimpleRequest.new(response_type: :COMPRESSABLE,
response_size: wanted_response_size,
payload: payload,
expect_compressed: expect_compressed)
# send a probe to see if CompressedResponse is supported on the server
send_probe_for_compressed_request_support do
request_uncompressed_args = {
COMPRESSION_REQUEST_ALGORITHM => 'identity'
}
@stub.unary_call(req, metadata: request_uncompressed_args)
end
# make a call with a compressed message
resp = @stub.unary_call(req)
assert('Expected second unary call with compression to work') do
resp.payload.body.length == wanted_response_size
end
# make a call with an uncompressed message
stub_options = {
COMPRESSION_REQUEST_ALGORITHM => 'identity'
}
req = SimpleRequest.new(
response_type: :COMPRESSABLE,
response_size: wanted_response_size,
payload: payload,
expect_compressed: BoolValue.new(value: false)
)
resp = @stub.unary_call(req, metadata: stub_options)
assert('Expected second unary call with compression to work') do
resp.payload.body.length == wanted_response_size
end
end
def service_account_creds
# ignore this test if the oauth options are not set
if @args.oauth_scope.nil?
@ -309,6 +386,50 @@ class NamedTests
end
end
def client_compressed_streaming
# first request used also by the probe
first_request = StreamingInputCallRequest.new(
payload: Payload.new(type: :COMPRESSABLE, body: nulls(27_182)),
expect_compressed: BoolValue.new(value: true)
)
# send a probe to see if CompressedResponse is supported on the server
send_probe_for_compressed_request_support do
request_uncompressed_args = {
COMPRESSION_REQUEST_ALGORITHM => 'identity'
}
@stub.streaming_input_call([first_request],
metadata: request_uncompressed_args)
end
second_request = StreamingInputCallRequest.new(
payload: Payload.new(type: :COMPRESSABLE, body: nulls(45_904)),
expect_compressed: BoolValue.new(value: false)
)
# Create the requests messages and the corresponding write flags
# for each message
requests = WriteFlagSettingStreamingInputEnumerable.new([
{ request: first_request,
write_flag: 0 },
{ request: second_request,
write_flag: GRPC::Core::WriteFlags::NO_COMPRESS }
])
# Create the call_op, pass it to the requests enumerable, and
# run the call
call_op = @stub.streaming_input_call(requests,
return_op: true)
requests.call_op = call_op
resp = call_op.execute
wanted_aggregate_size = 73_086
assert("#{__callee__}: aggregate payload size is incorrect") do
wanted_aggregate_size == resp.aggregated_payload_size
end
end
def server_streaming
msg_sizes = [31_415, 9, 2653, 58_979]
response_spec = msg_sizes.map { |s| ResponseParameters.new(size: s) }
@ -415,6 +536,29 @@ class NamedTests
end
resp
end
# Send probing message for compressed request on the server, to see
# if it's implemented.
def send_probe_for_compressed_request_support(&send_probe)
bad_status_occured = false
begin
send_probe.call
rescue GRPC::BadStatus => e
if e.code == GRPC::Core::StatusCodes::INVALID_ARGUMENT
bad_status_occured = true
else
fail AssertionError, "Bad status received but code is #{e.code}"
end
rescue Exception => e
fail AssertionError, "Expected BadStatus. Received: #{e.inspect}"
end
assert('CompressedRequest probe failed') do
bad_status_occured
end
end
end
# Args is used to hold the command line info.

@ -1,15 +0,0 @@
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: test/proto/empty.proto
require 'google/protobuf'
Google::Protobuf::DescriptorPool.generated_pool.build do
add_message "grpc.testing.Empty" do
end
end
module Grpc
module Testing
Empty = Google::Protobuf::DescriptorPool.generated_pool.lookup("grpc.testing.Empty").msgclass
end
end

@ -1,80 +0,0 @@
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: test/proto/messages.proto
require 'google/protobuf'
Google::Protobuf::DescriptorPool.generated_pool.build do
add_message "grpc.testing.Payload" do
optional :type, :enum, 1, "grpc.testing.PayloadType"
optional :body, :bytes, 2
end
add_message "grpc.testing.EchoStatus" do
optional :code, :int32, 1
optional :message, :string, 2
end
add_message "grpc.testing.SimpleRequest" do
optional :response_type, :enum, 1, "grpc.testing.PayloadType"
optional :response_size, :int32, 2
optional :payload, :message, 3, "grpc.testing.Payload"
optional :fill_username, :bool, 4
optional :fill_oauth_scope, :bool, 5
optional :response_compression, :enum, 6, "grpc.testing.CompressionType"
optional :response_status, :message, 7, "grpc.testing.EchoStatus"
end
add_message "grpc.testing.SimpleResponse" do
optional :payload, :message, 1, "grpc.testing.Payload"
optional :username, :string, 2
optional :oauth_scope, :string, 3
end
add_message "grpc.testing.StreamingInputCallRequest" do
optional :payload, :message, 1, "grpc.testing.Payload"
end
add_message "grpc.testing.StreamingInputCallResponse" do
optional :aggregated_payload_size, :int32, 1
end
add_message "grpc.testing.ResponseParameters" do
optional :size, :int32, 1
optional :interval_us, :int32, 2
end
add_message "grpc.testing.StreamingOutputCallRequest" do
optional :response_type, :enum, 1, "grpc.testing.PayloadType"
repeated :response_parameters, :message, 2, "grpc.testing.ResponseParameters"
optional :payload, :message, 3, "grpc.testing.Payload"
optional :response_compression, :enum, 6, "grpc.testing.CompressionType"
optional :response_status, :message, 7, "grpc.testing.EchoStatus"
end
add_message "grpc.testing.StreamingOutputCallResponse" do
optional :payload, :message, 1, "grpc.testing.Payload"
end
add_message "grpc.testing.ReconnectInfo" do
optional :passed, :bool, 1
repeated :backoff_ms, :int32, 2
end
add_enum "grpc.testing.PayloadType" do
value :COMPRESSABLE, 0
value :UNCOMPRESSABLE, 1
value :RANDOM, 2
end
add_enum "grpc.testing.CompressionType" do
value :NONE, 0
value :GZIP, 1
value :DEFLATE, 2
end
end
module Grpc
module Testing
Payload = Google::Protobuf::DescriptorPool.generated_pool.lookup("grpc.testing.Payload").msgclass
EchoStatus = Google::Protobuf::DescriptorPool.generated_pool.lookup("grpc.testing.EchoStatus").msgclass
SimpleRequest = Google::Protobuf::DescriptorPool.generated_pool.lookup("grpc.testing.SimpleRequest").msgclass
SimpleResponse = Google::Protobuf::DescriptorPool.generated_pool.lookup("grpc.testing.SimpleResponse").msgclass
StreamingInputCallRequest = Google::Protobuf::DescriptorPool.generated_pool.lookup("grpc.testing.StreamingInputCallRequest").msgclass
StreamingInputCallResponse = Google::Protobuf::DescriptorPool.generated_pool.lookup("grpc.testing.StreamingInputCallResponse").msgclass
ResponseParameters = Google::Protobuf::DescriptorPool.generated_pool.lookup("grpc.testing.ResponseParameters").msgclass
StreamingOutputCallRequest = Google::Protobuf::DescriptorPool.generated_pool.lookup("grpc.testing.StreamingOutputCallRequest").msgclass
StreamingOutputCallResponse = Google::Protobuf::DescriptorPool.generated_pool.lookup("grpc.testing.StreamingOutputCallResponse").msgclass
ReconnectInfo = Google::Protobuf::DescriptorPool.generated_pool.lookup("grpc.testing.ReconnectInfo").msgclass
PayloadType = Google::Protobuf::DescriptorPool.generated_pool.lookup("grpc.testing.PayloadType").enummodule
CompressionType = Google::Protobuf::DescriptorPool.generated_pool.lookup("grpc.testing.CompressionType").enummodule
end
end

@ -1,14 +0,0 @@
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: test/proto/test.proto
require 'google/protobuf'
require 'test/proto/empty'
require 'test/proto/messages'
Google::Protobuf::DescriptorPool.generated_pool.build do
end
module Grpc
module Testing
end
end

@ -1,64 +0,0 @@
# Generated by the protocol buffer compiler. DO NOT EDIT!
# Source: test/proto/test.proto for package 'grpc.testing'
require 'grpc'
require 'test/proto/test'
module Grpc
module Testing
module TestService
# TODO: add proto service documentation here
class Service
include GRPC::GenericService
self.marshal_class_method = :encode
self.unmarshal_class_method = :decode
self.service_name = 'grpc.testing.TestService'
rpc :EmptyCall, Empty, Empty
rpc :UnaryCall, SimpleRequest, SimpleResponse
rpc :StreamingOutputCall, StreamingOutputCallRequest, stream(StreamingOutputCallResponse)
rpc :StreamingInputCall, stream(StreamingInputCallRequest), StreamingInputCallResponse
rpc :FullDuplexCall, stream(StreamingOutputCallRequest), stream(StreamingOutputCallResponse)
rpc :HalfDuplexCall, stream(StreamingOutputCallRequest), stream(StreamingOutputCallResponse)
end
Stub = Service.rpc_stub_class
end
module UnimplementedService
# TODO: add proto service documentation here
class Service
include GRPC::GenericService
self.marshal_class_method = :encode
self.unmarshal_class_method = :decode
self.service_name = 'grpc.testing.UnimplementedService'
rpc :UnimplementedCall, Empty, Empty
end
Stub = Service.rpc_stub_class
end
module ReconnectService
# TODO: add proto service documentation here
class Service
include GRPC::GenericService
self.marshal_class_method = :encode
self.unmarshal_class_method = :decode
self.service_name = 'grpc.testing.ReconnectService'
rpc :Start, Empty, Empty
rpc :Stop, Empty, ReconnectInfo
end
Stub = Service.rpc_stub_class
end
end
end

@ -50,9 +50,9 @@ require 'optparse'
require 'grpc'
require 'test/proto/empty'
require 'test/proto/messages'
require 'test/proto/test_services'
require_relative '../src/proto/grpc/testing/empty'
require_relative '../src/proto/grpc/testing/messages'
require_relative '../src/proto/grpc/testing/test_services'
# DebugIsTruncated extends the default Logger to truncate debug messages
class DebugIsTruncated < Logger

@ -4,6 +4,9 @@
require 'google/protobuf'
Google::Protobuf::DescriptorPool.generated_pool.build do
add_message "grpc.testing.BoolValue" do
optional :value, :bool, 1
end
add_message "grpc.testing.Payload" do
optional :type, :enum, 1, "grpc.testing.PayloadType"
optional :body, :bytes, 2
@ -18,8 +21,9 @@ Google::Protobuf::DescriptorPool.generated_pool.build do
optional :payload, :message, 3, "grpc.testing.Payload"
optional :fill_username, :bool, 4
optional :fill_oauth_scope, :bool, 5
optional :response_compression, :enum, 6, "grpc.testing.CompressionType"
optional :response_compressed, :message, 6, "grpc.testing.BoolValue"
optional :response_status, :message, 7, "grpc.testing.EchoStatus"
optional :expect_compressed, :message, 8, "grpc.testing.BoolValue"
end
add_message "grpc.testing.SimpleResponse" do
optional :payload, :message, 1, "grpc.testing.Payload"
@ -28,6 +32,7 @@ Google::Protobuf::DescriptorPool.generated_pool.build do
end
add_message "grpc.testing.StreamingInputCallRequest" do
optional :payload, :message, 1, "grpc.testing.Payload"
optional :expect_compressed, :message, 2, "grpc.testing.BoolValue"
end
add_message "grpc.testing.StreamingInputCallResponse" do
optional :aggregated_payload_size, :int32, 1
@ -35,12 +40,12 @@ Google::Protobuf::DescriptorPool.generated_pool.build do
add_message "grpc.testing.ResponseParameters" do
optional :size, :int32, 1
optional :interval_us, :int32, 2
optional :compressed, :message, 3, "grpc.testing.BoolValue"
end
add_message "grpc.testing.StreamingOutputCallRequest" do
optional :response_type, :enum, 1, "grpc.testing.PayloadType"
repeated :response_parameters, :message, 2, "grpc.testing.ResponseParameters"
optional :payload, :message, 3, "grpc.testing.Payload"
optional :response_compression, :enum, 6, "grpc.testing.CompressionType"
optional :response_status, :message, 7, "grpc.testing.EchoStatus"
end
add_message "grpc.testing.StreamingOutputCallResponse" do
@ -55,18 +60,12 @@ Google::Protobuf::DescriptorPool.generated_pool.build do
end
add_enum "grpc.testing.PayloadType" do
value :COMPRESSABLE, 0
value :UNCOMPRESSABLE, 1
value :RANDOM, 2
end
add_enum "grpc.testing.CompressionType" do
value :NONE, 0
value :GZIP, 1
value :DEFLATE, 2
end
end
module Grpc
module Testing
BoolValue = Google::Protobuf::DescriptorPool.generated_pool.lookup("grpc.testing.BoolValue").msgclass
Payload = Google::Protobuf::DescriptorPool.generated_pool.lookup("grpc.testing.Payload").msgclass
EchoStatus = Google::Protobuf::DescriptorPool.generated_pool.lookup("grpc.testing.EchoStatus").msgclass
SimpleRequest = Google::Protobuf::DescriptorPool.generated_pool.lookup("grpc.testing.SimpleRequest").msgclass
@ -79,6 +78,5 @@ module Grpc
ReconnectParams = Google::Protobuf::DescriptorPool.generated_pool.lookup("grpc.testing.ReconnectParams").msgclass
ReconnectInfo = Google::Protobuf::DescriptorPool.generated_pool.lookup("grpc.testing.ReconnectInfo").msgclass
PayloadType = Google::Protobuf::DescriptorPool.generated_pool.lookup("grpc.testing.PayloadType").enummodule
CompressionType = Google::Protobuf::DescriptorPool.generated_pool.lookup("grpc.testing.CompressionType").enummodule
end
end

@ -0,0 +1,164 @@
# Copyright 2015, Google Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following disclaimer
# in the documentation and/or other materials provided with the
# distribution.
# * Neither the name of Google Inc. nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
require 'grpc'
describe GRPC::Core::CompressionOptions do
# Note these constants should be updated
# according to what the core lib provides.
# Names of supported compression algorithms
ALGORITHMS = [:identity, :deflate, :gzip]
# Names of valid supported compression levels
COMPRESS_LEVELS = [:none, :low, :medium, :high]
it 'implements to_s' do
expect { GRPC::Core::CompressionOptions.new.to_s }.to_not raise_error
end
it '#to_channel_arg_hash gives the same result as #to_hash' do
options = GRPC::Core::CompressionOptions.new
expect(options.to_channel_arg_hash).to eq(options.to_hash)
end
# Test the normal call sequence of creating an instance
# and then obtaining the resulting channel-arg hash that
# corresponds to the compression settings of the instance
describe 'creating, reading, and converting to channel args hash' do
it 'works when no optional args were provided' do
options = GRPC::Core::CompressionOptions.new
ALGORITHMS.each do |algorithm|
expect(options.algorithm_enabled?(algorithm)).to be true
end
expect(options.disabled_algorithms).to be_empty
expect(options.default_algorithm).to be nil
expect(options.default_level).to be nil
expect(options.to_hash).to be_instance_of(Hash)
end
it 'works when disabling multiple algorithms' do
options = GRPC::Core::CompressionOptions.new(
default_algorithm: :identity,
default_level: :none,
disabled_algorithms: [:gzip, :deflate]
)
[:gzip, :deflate].each do |algorithm|
expect(options.algorithm_enabled?(algorithm)).to be false
expect(options.disabled_algorithms.include?(algorithm)).to be true
end
expect(options.default_algorithm).to be(:identity)
expect(options.default_level).to be(:none)
expect(options.to_hash).to be_instance_of(Hash)
end
it 'works when all optional args have been set' do
options = GRPC::Core::CompressionOptions.new(
default_algorithm: :gzip,
default_level: :low,
disabled_algorithms: [:deflate]
)
expect(options.algorithm_enabled?(:deflate)).to be false
expect(options.algorithm_enabled?(:gzip)).to be true
expect(options.disabled_algorithms).to eq([:deflate])
expect(options.default_algorithm).to be(:gzip)
expect(options.default_level).to be(:low)
expect(options.to_hash).to be_instance_of(Hash)
end
it 'doesnt fail when no algorithms are disabled' do
options = GRPC::Core::CompressionOptions.new(
default_algorithm: :identity,
default_level: :high
)
ALGORITHMS.each do |algorithm|
expect(options.algorithm_enabled?(algorithm)).to be(true)
end
expect(options.disabled_algorithms).to be_empty
expect(options.default_algorithm).to be(:identity)
expect(options.default_level).to be(:high)
expect(options.to_hash).to be_instance_of(Hash)
end
end
describe '#new with bad parameters' do
it 'should fail with more than one parameter' do
blk = proc { GRPC::Core::CompressionOptions.new(:gzip, :none) }
expect { blk.call }.to raise_error
end
it 'should fail with a non-hash parameter' do
blk = proc { GRPC::Core::CompressionOptions.new(:gzip) }
expect { blk.call }.to raise_error
end
end
describe '#default_algorithm' do
it 'returns nil if unset' do
options = GRPC::Core::CompressionOptions.new
expect(options.default_algorithm).to be(nil)
end
end
describe '#default_level' do
it 'returns nil if unset' do
options = GRPC::Core::CompressionOptions.new
expect(options.default_level).to be(nil)
end
end
describe '#disabled_algorithms' do
it 'returns an empty list if no algorithms were disabled' do
options = GRPC::Core::CompressionOptions.new
expect(options.disabled_algorithms).to be_empty
end
end
describe '#algorithm_enabled?' do
[:none, :any, 'gzip', Object.new, 1].each do |name|
it "should fail for parameter ${name} of class #{name.class}" do
options = GRPC::Core::CompressionOptions.new(
disabled_algorithms: [:gzip])
blk = proc do
options.algorithm_enabled?(name)
end
expect { blk.call }.to raise_error
end
end
end
end

@ -128,6 +128,8 @@
'ALWAYS_SEARCH_USER_PATHS' => 'NO',
}
s.default_subspecs = 'Interface', 'Implementation'
# Like many other C libraries, gRPC-Core has its public headers under `include/<libname>/` and its
# sources and private headers in other directories outside `include/`. Cocoapods' linter doesn't
# allow any header to be listed outside the `header_mappings_dir` (even though doing so works in
@ -154,4 +156,26 @@
ss.private_header_files = ${ruby_multiline_list(grpc_private_headers(libs), 30)}
end
s.subspec 'Cronet-Interface' do |ss|
ss.header_mappings_dir = 'include/grpc'
ss.source_files = 'include/grpc/grpc_cronet.h'
end
s.subspec 'Cronet-Tests' do |ss|
ss.header_mappings_dir = '.'
ss.source_files = 'src/core/ext/transport/cronet/client/secure/cronet_channel_create.c',
'src/core/ext/transport/cronet/transport/cronet_transport.c',
'test/core/end2end/cq_verifier.{c,h}',
'test/core/end2end/end2end_tests.{c,h}',
'test/core/end2end/tests/*.{c,h}',
'test/core/end2end/data/*.{c,h}',
'test/core/util/test_config.{c,h}',
'test/core/util/port.h',
'test/core/util/port_posix.c',
'test/core/util/port_server_client.{c,h}'
ss.dependency 'CronetFramework'
end
end

@ -121,7 +121,7 @@ class BigQueryHelper:
if not page['jobComplete']:
print('TIMEOUT ERROR: The query %s timed out. Current timeout value is'
' %d msec. Returning False (i.e assuming there are no failures)'
) % (query, timeoout_msec)
) % (query, timeout_msec)
return False
num_failures = int(page['totalRows'])

@ -43,6 +43,7 @@ TIME_FROM_SCOPE_START = object()
TIME_TO_SCOPE_END = object()
TIME_FROM_STACK_START = object()
TIME_TO_STACK_END = object()
TIME_FROM_LAST_IMPORTANT = object()
argp = argparse.ArgumentParser(description='Process output of basic_prof builds')
@ -78,10 +79,14 @@ class ScopeBuilder(object):
self.call_stack_builder.lines.append(line_item)
def finish(self, line):
assert line['tag'] == self.top_line.tag, 'expected %s, got %s; thread=%s; t0=%f t1=%f' % (self.top_line.tag, line['tag'], line['thd'], self.top_line.start_time, line['t'])
assert line['tag'] == self.top_line.tag, (
'expected %s, got %s; thread=%s; t0=%f t1=%f' %
(self.top_line.tag, line['tag'], line['thd'], self.top_line.start_time,
line['t']))
final_time_stamp = line['t']
assert self.top_line.end_time is None
self.top_line.end_time = final_time_stamp
self.top_line.important = self.top_line.important or line['imp']
assert SELF_TIME not in self.top_line.times
self.top_line.times[SELF_TIME] = final_time_stamp - self.top_line.start_time
for line in self.call_stack_builder.lines[self.first_child_pos:]:
@ -101,9 +106,14 @@ class CallStackBuilder(object):
start_time = self.lines[0].start_time
end_time = self.lines[0].end_time
self.signature = self.signature.hexdigest()
last_important = start_time
for line in self.lines:
line.times[TIME_FROM_STACK_START] = line.start_time - start_time
line.times[TIME_TO_STACK_END] = end_time - line.end_time
line.times[TIME_FROM_LAST_IMPORTANT] = line.start_time - last_important
if line.important:
last_important = line.end_time
last_important = end_time
def add(self, line):
line_type = line['type']
@ -113,7 +123,9 @@ class CallStackBuilder(object):
self.stk.append(ScopeBuilder(self, line))
return False
elif line_type == '}':
assert self.stk, 'expected non-empty stack for closing %s; thread=%s; t=%f' % (line['tag'], line['thd'], line['t'])
assert self.stk, (
'expected non-empty stack for closing %s; thread=%s; t=%f' %
(line['tag'], line['thd'], line['t']))
self.stk.pop().finish(line)
if not self.stk:
self.finish()
@ -216,9 +228,16 @@ def time_format(idx):
return ''
return ent
BANNER = {
'simple': 'Count: %(count)d',
'html': '<h1>Count: %(count)d</h1>'
}
FORMAT = [
('TAG', lambda line: '..'*line.indent + tidy_tag(line.tag)),
('LOC', lambda line: '%s:%d' % (line.filename[line.filename.rfind('/')+1:], line.fileline)),
('IMP', lambda line: '*' if line.important else ''),
('FROM_IMP', time_format(TIME_FROM_LAST_IMPORTANT)),
('FROM_STACK_START', time_format(TIME_FROM_STACK_START)),
('SELF', time_format(SELF_TIME)),
('TO_STACK_END', time_format(TIME_TO_STACK_END)),
@ -227,11 +246,6 @@ FORMAT = [
('TO_SCOPE_END', time_format(TIME_TO_SCOPE_END)),
]
BANNER = {
'simple': 'Count: %(count)d',
'html': '<h1>Count: %(count)d</h1>'
}
if args.fmt == 'html':
print '<html>'
print '<head>'

@ -0,0 +1,21 @@
<!DOCTYPE html>
<html lang="en">
<head><title>Performance Test Result</title></head>
<body>
<h2>Performance Test Result</h2>
<table style="width:50%" border="1">
<% sorted_test_cases = sorted(resultset.keys()) %>
% for test_case in sorted_test_cases:
<tr><td bgcolor="#00BFFF" style="width:30%"><b>${test_case}</b></td>
<% result = resultset[test_case] %>
<td>
% for k, v in result.iteritems():
${k}: ${v}<br>
% endfor
</td>
</tr>
% endfor
</table>
</body>
</html>

@ -118,6 +118,8 @@ def _flatten_result_inplace(scenario_result):
for stats in scenario_result['clientStats']:
stats['latencies'] = json.dumps(stats['latencies'])
scenario_result['serverCores'] = json.dumps(scenario_result['serverCores'])
scenario_result['clientSuccess'] = json.dumps(scenario_result['clientSuccess'])
scenario_result['serverSuccess'] = json.dumps(scenario_result['serverSuccess'])
def _populate_metadata_inplace(scenario_result):

@ -198,5 +198,15 @@
"mode": "NULLABLE"
}
]
},
{
"name": "clientSuccess",
"type": "STRING",
"mode": "NULLABLE"
},
{
"name": "serverSuccess",
"type": "STRING",
"mode": "NULLABLE"
}
]

@ -37,6 +37,8 @@ try:
from mako import exceptions
except (ImportError):
pass # Mako not installed but it is ok.
import glob
import json
import os
import string
import xml.etree.cElementTree as ET
@ -120,3 +122,38 @@ def render_interop_html_report(
print(exceptions.text_error_template().render())
raise
def render_perf_html_report(report_dir):
"""Generate a simple HTML report for the perf tests."""
template_file = 'tools/run_tests/perf_html_report.template'
try:
mytemplate = Template(filename=template_file, format_exceptions=True)
except NameError:
print('Mako template is not installed. Skipping HTML report generation.')
return
except IOError as e:
print('Failed to find the template %s: %s' % (template_file, e))
return
resultset = {}
for result_file in glob.glob(os.path.join(report_dir, '*.json')):
with open(result_file, 'r') as f:
scenario_result = json.loads(f.read())
test_case = scenario_result['scenario']['name']
if 'ping_pong' in test_case:
latency50 = round(scenario_result['summary']['latency50'], 2)
latency99 = round(scenario_result['summary']['latency99'], 2)
summary = {'latency50': latency50, 'latency99': latency99}
else:
summary = {'qps': round(scenario_result['summary']['qps'], 2)}
resultset[test_case] = summary
args = {'resultset': resultset}
html_file_path = os.path.join(report_dir, 'index.html')
try:
with open(html_file_path, 'w') as output_file:
mytemplate.render_context(Context(output_file, **args))
except:
print(exceptions.text_error_template().render())
raise

@ -288,7 +288,7 @@ class RubyLanguage:
return {}
def unimplemented_test_cases(self):
return _SKIP_ADVANCED + _SKIP_COMPRESSION
return _SKIP_ADVANCED + _SKIP_SERVER_COMPRESSION
def unimplemented_test_cases_server(self):
return _SKIP_ADVANCED + _SKIP_COMPRESSION

@ -40,6 +40,7 @@ import multiprocessing
import os
import pipes
import re
import report_utils
import subprocess
import sys
import tempfile
@ -54,6 +55,7 @@ os.chdir(_ROOT)
_REMOTE_HOST_USERNAME = 'jenkins'
_REPORT_DIR = 'perf_reports'
class QpsWorkerJob:
@ -103,7 +105,11 @@ def create_scenario_jobspec(scenario_json, workers, remote_host=None,
cmd += 'BQ_RESULT_TABLE="%s" ' % bq_result_table
cmd += 'tools/run_tests/performance/run_qps_driver.sh '
cmd += '--scenarios_json=%s ' % pipes.quote(json.dumps({'scenarios': [scenario_json]}))
cmd += '--scenario_result_file=scenario_result.json'
if not os.path.isdir(_REPORT_DIR):
os.makedirs(_REPORT_DIR)
report_path = os.path.join(_REPORT_DIR,
'%s-scenario_result.json' % scenario_json['name'])
cmd += '--scenario_result_file=%s' % report_path
if remote_host:
user_at_host = '%s@%s' % (_REMOTE_HOST_USERNAME, remote_host)
cmd = 'ssh %s "cd ~/performance_workspace/grpc/ && "%s' % (user_at_host, pipes.quote(cmd))
@ -436,6 +442,9 @@ try:
jobset.message('START', 'Running scenarios.', do_newline=True)
num_failures, _ = jobset.run(
scenarios, newline_on_success=True, maxjobs=1)
report_utils.render_perf_html_report(_REPORT_DIR)
if num_failures == 0:
jobset.message('SUCCESS',
'All scenarios finished successfully.',

Loading…
Cancel
Save