Merge remote-tracking branch 'refs/remotes/grpc/master'

pull/4091/head
murgatroid99 9 years ago
commit 1900ca157f
  1. 66
      .travis.yml
  2. 149
      Makefile
  3. 25
      build.yaml
  4. 91
      doc/PROTOCOL-HTTP2.md
  5. 2
      examples/README.md
  6. 10
      examples/objective-c/auth_sample/AuthTestService.podspec
  7. 9
      examples/objective-c/auth_sample/MakeRPCViewController.m
  8. 3
      examples/objective-c/auth_sample/Podfile
  9. 188
      examples/objective-c/auth_sample/README.md
  10. 6
      examples/objective-c/helloworld/README.md
  11. 2
      examples/objective-c/route_guide/Podfile
  12. 358
      examples/objective-c/route_guide/README.md
  13. 10
      examples/objective-c/route_guide/RouteGuide.podspec
  14. 16
      examples/objective-c/route_guide/RouteGuideClient.xcodeproj/project.pbxproj
  15. 59
      examples/objective-c/route_guide/ViewControllers.m
  16. 8
      gRPC.podspec
  17. 1
      include/grpc++/support/byte_buffer.h
  18. 2
      include/grpc++/support/string_ref.h
  19. 4
      include/grpc++/support/sync_stream.h
  20. 3
      include/grpc/byte_buffer.h
  21. 2
      include/grpc/support/histogram.h
  22. 5
      src/core/iomgr/pollset_posix.c
  23. 1
      src/core/iomgr/pollset_posix.h
  24. 2
      src/core/support/histogram.c
  25. 4
      src/core/support/time_win32.c
  26. 19
      src/core/surface/byte_buffer_reader.c
  27. 2
      src/cpp/util/string_ref.cc
  28. 1
      src/csharp/.gitignore
  29. 2
      src/csharp/.nuget/packages.config
  30. 14
      src/csharp/Grpc.Core.Tests/ClientServerTest.cs
  31. 1
      src/csharp/Grpc.Core.Tests/Grpc.Core.Tests.csproj
  32. 19
      src/csharp/Grpc.Core.Tests/Internal/TimespecTest.cs
  33. 99
      src/csharp/Grpc.Core.Tests/PerformanceTest.cs
  34. 7
      src/csharp/Grpc.Core/Grpc.Core.csproj
  35. 24
      src/csharp/Grpc.Core/Internal/AsyncCall.cs
  36. 12
      src/csharp/Grpc.Core/Internal/AsyncCallBase.cs
  37. 4
      src/csharp/Grpc.Core/Internal/CallSafeHandle.cs
  38. 4
      src/csharp/Grpc.Core/Internal/ChannelSafeHandle.cs
  39. 4
      src/csharp/Grpc.Core/Internal/CompletionQueueSafeHandle.cs
  40. 3
      src/csharp/Grpc.Core/Internal/Enums.cs
  41. 4
      src/csharp/Grpc.Core/Internal/MetadataArraySafeHandle.cs
  42. 13
      src/csharp/Grpc.Core/Internal/Timespec.cs
  43. 47
      src/csharp/Grpc.Core/Profiling/IProfiler.cs
  44. 87
      src/csharp/Grpc.Core/Profiling/ProfilerEntry.cs
  45. 60
      src/csharp/Grpc.Core/Profiling/ProfilerScope.cs
  46. 131
      src/csharp/Grpc.Core/Profiling/Profilers.cs
  47. 1
      src/csharp/README.md
  48. 1
      src/node/README.md
  49. 21
      src/node/src/client.js
  50. 2
      src/node/src/common.js
  51. 9
      src/node/src/metadata.js
  52. 15
      src/node/src/server.js
  53. 16
      src/objective-c/GRPCClient/GRPCCall+OAuth2.h
  54. 28
      src/objective-c/GRPCClient/GRPCCall+Tests.h
  55. 3
      src/objective-c/GRPCClient/GRPCCall+Tests.m
  56. 236
      src/objective-c/GRPCClient/GRPCCall.h
  57. 12
      src/objective-c/GRPCClient/private/GRPCChannel.h
  58. 20
      src/objective-c/GRPCClient/private/GRPCCompletionQueue.h
  59. 6
      src/objective-c/GRPCClient/private/GRPCHost.h
  60. 8
      src/objective-c/GRPCClient/private/GRPCSecureChannel.h
  61. 2
      src/objective-c/GRPCClient/private/GRPCWrappedCall.h
  62. 6
      src/objective-c/GRPCClient/private/NSError+GRPC.h
  63. 6
      src/objective-c/ProtoRPC/ProtoMethod.h
  64. 1
      src/objective-c/README.md
  65. 36
      src/objective-c/RxLibrary/GRXBufferedPipe.h
  66. 54
      src/objective-c/RxLibrary/GRXConcurrentWriteable.h
  67. 24
      src/objective-c/RxLibrary/GRXForwardingWriter.h
  68. 70
      src/objective-c/RxLibrary/GRXImmediateWriter.h
  69. 22
      src/objective-c/RxLibrary/GRXWriteable.h
  70. 44
      src/objective-c/RxLibrary/GRXWriter+Immediate.h
  71. 6
      src/objective-c/RxLibrary/GRXWriter+Transformations.h
  72. 116
      src/objective-c/RxLibrary/GRXWriter.h
  73. 22
      src/objective-c/RxLibrary/NSEnumerator+GRXUtil.h
  74. 12
      src/objective-c/RxLibrary/private/GRXNSBlockEnumerator.h
  75. 14
      src/objective-c/RxLibrary/private/GRXNSFastEnumerator.h
  76. 8
      src/objective-c/RxLibrary/private/GRXNSScalarEnumerator.h
  77. 2
      src/objective-c/RxLibrary/transformations/GRXMappingWriter.h
  78. 128
      src/objective-c/change-comments.py
  79. 31
      src/objective-c/format-all-comments.sh
  80. 36
      src/objective-c/tests/GRPCClientTests.m
  81. 16
      src/objective-c/tests/InteropTests.h
  82. 14
      src/objective-c/tests/InteropTests.m
  83. 4
      src/objective-c/tests/InteropTestsLocalCleartext.m
  84. 4
      src/objective-c/tests/InteropTestsLocalSSL.m
  85. 50
      src/objective-c/tests/InteropTestsRemote.m
  86. 164
      src/objective-c/tests/LocalClearTextTests.m
  87. 18
      src/objective-c/tests/Podfile
  88. 622
      src/objective-c/tests/Tests.xcodeproj/project.pbxproj
  89. 14
      src/objective-c/tests/Tests.xcodeproj/xcshareddata/xcschemes/AllTests.xcscheme
  90. 101
      src/objective-c/tests/Tests.xcodeproj/xcshareddata/xcschemes/InteropTestsLocalCleartext.xcscheme
  91. 95
      src/objective-c/tests/Tests.xcodeproj/xcshareddata/xcschemes/InteropTestsLocalSSL.xcscheme
  92. 95
      src/objective-c/tests/Tests.xcodeproj/xcshareddata/xcschemes/InteropTestsRemote.xcscheme
  93. 90
      src/objective-c/tests/Tests.xcodeproj/xcshareddata/xcschemes/RxLibraryUnitTests.xcscheme
  94. 40
      src/php/bin/run_php_cs_fixer.sh
  95. 34
      src/php/lib/Grpc/AbstractCall.php
  96. 143
      src/php/lib/Grpc/BaseStub.php
  97. 41
      src/php/lib/Grpc/BidiStreamingCall.php
  98. 32
      src/php/lib/Grpc/ClientStreamingCall.php
  99. 33
      src/php/lib/Grpc/ServerStreamingCall.php
  100. 25
      src/php/lib/Grpc/UnaryCall.php
  101. Some files were not shown because too many files have changed in this diff Show More

@ -1,42 +1,32 @@
language: cpp
before_install:
- sudo add-apt-repository ppa:yjwong/gflags -y
- sudo add-apt-repository ppa:h-rayflood/llvm -y
- sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 3FA7E0328081BFF6A14DA29AA6A19B38D3D831EF
- echo "deb http://download.mono-project.com/repo/debian wheezy main" | sudo tee /etc/apt/sources.list.d/mono-xamarin.list
- echo "deb http://download.mono-project.com/repo/debian wheezy-libtiff-compat main" | sudo tee -a /etc/apt/sources.list.d/mono-xamarin.list
- sudo apt-get update -qq
- sudo apt-get install -qq libgtest-dev libgflags-dev python-virtualenv python-dev python3-dev clang-3.5
- sudo pip install --upgrade virtualenv
- sudo pip install cpp-coveralls mako simplejson
- sudo apt-get install -qq mono-devel nunit
- wget www.nuget.org/NuGet.exe -O nuget.exe
language: objective-c
osx_image: xcode7.1
env:
global:
- RUBY_VERSION=2.1
- COVERALLS_PARALLEL=true
- CPPFLAGS=-I/tmp/prebuilt/include
- NUGET="mono nuget.exe"
matrix:
- CONFIG=opt TEST=sanity JOBS=1
- CONFIG=gcov TEST=c JOBS=16
- CONFIG=gcov TEST=c++ JOBS=16
- CONFIG=opt TEST=c JOBS=16
- CONFIG=opt TEST=c++ JOBS=16
- CONFIG=opt TEST=node JOBS=16
- CONFIG=opt TEST=ruby JOBS=16
- CONFIG=opt TEST=python JOBS=1
- CONFIG=opt TEST=csharp JOBS=16
- USE_GCC=4.4 CONFIG=opt TEST=build JOBS=16
script:
- rvm use $RUBY_VERSION
- gem install bundler
- ./tools/run_tests/prepare_travis.sh
- if [ ! -z "$USE_GCC" ] ; then export CC=gcc-$USE_GCC ; export CXX=g++-$USE_GCC ; fi
- ./tools/run_tests/run_tests.py -l $TEST -t -j $JOBS -c $CONFIG -s 4.0
after_success:
- if [ "$CONFIG" = "gcov" ] ; then coveralls --exclude third_party --exclude gens --exclude test --exclude tools --exclude src/compiler -b. --gcov-options '\-p' ; fi
- CONFIG=opt
- TEST=objc
- JOBS=1
before_install:
- brew install gflags
# Pod install does this too, but we don't want the output.
- pod repo update --silent
install:
- make grpc_objective_c_plugin
- pushd src/objective-c/tests
# Needs to be verbose, or otherwise OpenSSL's prepare_command makes Travis
# time out:
- pod install --verbose
- popd
before_script:
- make interop_server
- bins/$CONFIG/interop_server --port=5050 &
- bins/$CONFIG/interop_server --port=5051 --use_tls &
xcode_workspace: src/objective-c/tests/Tests.xcworkspace
xcode_scheme:
- RxLibraryUnitTests
- InteropTestsLocalSSL
- InteropTestsLocalCleartext
# TODO(jcanizales): Investigate why they time out:
# - InteropTestsRemote
xcode_sdk: iphonesimulator9.1
notifications:
email: false
webhooks:
- https://coveralls.io/webhook?repo_token=54IxAHPjJNdQJzJAhPU0MFpCtg7KvcydQ

File diff suppressed because one or more lines are too long

@ -751,7 +751,11 @@ libs:
- test/cpp/qps/timer.h
- test/cpp/util/benchmark_config.h
src:
- test/proto/qpstest.proto
- test/proto/messages.proto
- test/proto/benchmarks/control.proto
- test/proto/benchmarks/payloads.proto
- test/proto/benchmarks/services.proto
- test/proto/benchmarks/stats.proto
- test/cpp/qps/perf_db.proto
- test/cpp/qps/client_async.cc
- test/cpp/qps/client_sync.cc
@ -1937,6 +1941,7 @@ targets:
- posix
- name: qps_openloop_test
build: test
run: false
language: c++
src:
- test/cpp/qps/qps_openloop_test.cc
@ -2037,6 +2042,23 @@ targets:
- grpc
- gpr_test_util
- gpr
- name: secure_sync_unary_ping_pong_test
build: test
language: c++
src:
- test/cpp/qps/secure_sync_unary_ping_pong_test.cc
deps:
- qps
- grpc++_test_util
- grpc_test_util
- grpc++
- grpc
- gpr_test_util
- gpr
platforms:
- mac
- linux
- posix
- name: server_crash_test
build: test
language: c++
@ -2107,6 +2129,7 @@ targets:
- posix
- name: stress_test
build: test
run: false
language: c++
headers:
- test/cpp/interop/client_helper.h

@ -10,58 +10,64 @@ Production rules are using <a href="http://tools.ietf.org/html/rfc5234">ABNF syn
The following is the general sequence of message atoms in a GRPC request & response message stream
* Request → Request-Headers *Delimited-Message EOS
* Response → (Response-Headers *Delimited-Message Trailers) / Trailers-Only
* Request → Request-Headers \*Length-Prefixed-Message EOS
* Response → (Response-Headers \*Length-Prefixed-Message Trailers) / Trailers-Only
### Requests
* Request → Request-Headers *Delimited-Message EOS
* Request → Request-Headers \*Length-Prefixed-Message EOS
Request-Headers are delivered as HTTP2 headers in HEADERS + CONTINUATION frames.
* **Request-Headers** → Call-Definition *Custom-Metadata
* **Call-Definition** → Method Scheme Path TE [Authority] [Timeout] [Content-Type] [Message-Type] [Message-Encoding] [Message-Accept-Encoding] [User-Agent]
* **Method**“:method POST”
* **Scheme**“:scheme ” (“http” / “https”)
* **Path**“:path” {_path identifying method within exposed API_}
* **Authority**“:authority” {_virtual host name of authority_}
* **TE**“te” “trailers” # Used to detect incompatible proxies
* **Timeout**“grpc-timeout” TimeoutValue TimeoutUnit
* **Request-Headers** → Call-Definition \*Custom-Metadata
* **Call-Definition** → Method Scheme Path TE [Authority] [Timeout] Content-Type [Message-Type] [Message-Encoding] [Message-Accept-Encoding] [User-Agent]
* **Method**":method POST"
* **Scheme**":scheme " ("http" / "https")
* **Path**":path" {_path identifying method within exposed API_}
* **Authority**":authority" {_virtual host name of authority_}
* **TE**"te" "trailers" # Used to detect incompatible proxies
* **Timeout**"grpc-timeout" TimeoutValue TimeoutUnit
* **TimeoutValue** → {_positive integer as ASCII string of at most 8 digits_}
* **TimeoutUnit** → Hour / Minute / Second / Millisecond / Microsecond / Nanosecond
* **Hour**“H”
* **Minute**“M”
* **Second**“S”
* **Millisecond**“m”
* **Microsecond**“u”
* **Nanosecond**“n”
* **Content-Type**“content-type” “application/grpc” [(“+proto” / “+json” / {_custom_})]
* **Content-Coding**“gzip” / “deflate” / “snappy” / {_custom_}
* **Message-Encoding**“grpc-encoding” Content-Coding
* **Message-Accept-Encoding**“grpc-accept-encoding” Content-Coding *("," Content-Coding)
* **User-Agent**“user-agent” {_structured user-agent string_}
* **Message-Type**“grpc-message-type” {_type name for message schema_}
* **Hour**"H"
* **Minute**"M"
* **Second**"S"
* **Millisecond**"m"
* **Microsecond**"u"
* **Nanosecond**"n"
* **Content-Type**"content-type" "application/grpc" [("+proto" / "+json" / {_custom_})]
* **Content-Coding**"identity" / "gzip" / "deflate" / "snappy" / {_custom_}
* **Message-Encoding**"grpc-encoding" Content-Coding
* **Message-Accept-Encoding**"grpc-accept-encoding" Content-Coding \*("," Content-Coding)
* **User-Agent**"user-agent" {_structured user-agent string_}
* **Message-Type**"grpc-message-type" {_type name for message schema_}
* **Custom-Metadata** → Binary-Header / ASCII-Header
* **Binary-Header** → {Header-Name “-bin” } {_base64 encoded value_}
* **Binary-Header** → {Header-Name "-bin" } {_base64 encoded value_}
* **ASCII-Header** → Header-Name {_value_}
* **Header-Name** → 1*( %x30-39 / %x61-7A / “_” / “-”) ; 0-9 a-z
* **Header-Name** → 1\*( %x30-39 / %x61-7A / "\_" / "-") ; 0-9 a-z \_ -
HTTP2 requires that reserved headers, ones starting with “:” appear before all other headers. Additionally implementations should send **Timeout** immediately after the reserved headers and they should send the **Call-Definition** headers before sending **Custom-Metadata**.
HTTP2 requires that reserved headers, ones starting with ":" appear before all other headers. Additionally implementations should send **Timeout** immediately after the reserved headers and they should send the **Call-Definition** headers before sending **Custom-Metadata**.
If **Timeout** is omitted a server should assume an infinite timeout. Client implementations are free to send a default minimum timeout based on their deployment requirements.
**Custom-Metadata** is an arbitrary set of key-value pairs defined by the application layer. Aside from transport limits on the total length of HTTP2 HEADERS the only other constraint is that header names starting with “grpc-” are reserved for future use.
**Custom-Metadata** is an arbitrary set of key-value pairs defined by the application layer. Header names starting with "grpc-" but not listed here are reserved for future GRPC use and should not be used by applications as **Custom-Metadata**.
Note that HTTP2 does not allow arbitrary octet sequences for header values so binary header values must be encoded using Base64 as per https://tools.ietf.org/html/rfc4648#section-4. Implementations MUST accept padded and un-padded values and should emit un-padded values. Applications define binary headers by having their names end with “-bin”. Runtime libraries use this suffix to detect binary headers and properly apply base64 encoding & decoding as headers are sent and received.
Note that HTTP2 does not allow arbitrary octet sequences for header values so binary header values must be encoded using Base64 as per https://tools.ietf.org/html/rfc4648#section-4. Implementations MUST accept padded and un-padded values and should emit un-padded values. Applications define binary headers by having their names end with "-bin". Runtime libraries use this suffix to detect binary headers and properly apply base64 encoding & decoding as headers are sent and received.
The repeated sequence of **Delimited-Message** items is delivered in DATA frames
Servers may limit the size of **Request-Headers**, with a default of 8 KiB
suggested. Implementations are encouraged to compute total header size like
HTTP/2's `SETTINGS_MAX_HEADER_LIST_SIZE`: the sum of all header fields, for each
field the sum of the uncompressed field name and value lengths plus 32, with
binary values' lengths being post-Base64.
* **Delimited-Message** → Compressed-Flag Message-Length Message
The repeated sequence of **Length-Prefixed-Message** items is delivered in DATA frames
* **Length-Prefixed-Message** → Compressed-Flag Message-Length Message
* **Compressed-Flag** → 0 / 1 # encoded as 1 byte unsigned integer
* **Message-Length** → {_length of Message_} # encoded as 4 byte unsigned integer
* **Message** → *{binary octet}
* **Message**\*{binary octet}
A **Compressed-Flag** value of 1 indicates that the binary octet sequence of **Message** is compressed using the mechanism declared by the **Message-Encoding** header. A value of 0 indicates that no encoding of **Message** bytes has occurred. Compression contexts are NOT maintained over message boundaries, implementations must create a new context for each message in the stream. If the **Message-Encoding** header is omitted then the **Compressed-Flag** must be 0.
@ -69,13 +75,13 @@ For requests, **EOS** (end-of-stream) is indicated by the presence of the END_ST
###Responses
* **Response** → (Response-Headers *Delimited-Message Trailers) / Trailers-Only
* **Response-Headers** → HTTP-Status [Message-Encoding] [Message-Accept-Encoding] Content-Type *Custom-Metadata
* **Response** → (Response-Headers \*Length-Prefixed-Message Trailers) / Trailers-Only
* **Response-Headers** → HTTP-Status [Message-Encoding] [Message-Accept-Encoding] Content-Type \*Custom-Metadata
* **Trailers-Only** → HTTP-Status Content-Type Trailers
* **Trailers** → Status [Status-Message] *Custom-Metadata
* **HTTP-Status**“:status 200”
* **Status**“grpc-status” <status-code-as-ASCII-string>
* **Status-Message**“grpc-message” <descriptive text for status as ASCII string>
* **Trailers** → Status [Status-Message] \*Custom-Metadata
* **HTTP-Status**":status 200"
* **Status**"grpc-status" <status-code-as-ASCII-string>
* **Status-Message**"grpc-message" <descriptive text for status as ASCII string>
**Response-Headers** & **Trailers-Only** are each delivered in a single HTTP2 HEADERS frame block. Most responses are expected to have both headers and trailers but **Trailers-Only** is permitted for calls that produce an immediate error. Status must be sent in **Trailers** even if the status code is OK.
@ -83,6 +89,9 @@ For responses end-of-stream is indicated by the presence of the END_STREAM flag
Implementations should expect broken deployments to send non-200 HTTP status codes in responses as well as a variety of non-GRPC content-types and to omit **Status** & **Status-Message**. Implementations must synthesize a **Status** & **Status-Message** to propagate to the application layer when this occurs.
Clients may limit the size of **Response-Headers**, **Trailers**, and
**Trailers-Only**, with a default of 8 KiB each suggested.
####Example
Sample unary-call showing HTTP2 framing sequence
@ -101,7 +110,7 @@ grpc-encoding = gzip
authorization = Bearer y235.wef315yfh138vh31hv93hv8h3v
DATA (flags = END_STREAM)
<Delimited Message>
<Length-Prefixed Message>
```
**Response**
```
@ -110,7 +119,7 @@ HEADERS (flags = END_HEADERS)
grpc-encoding = gzip
DATA
<Delimited Message>
<Length-Prefixed Message>
HEADERS (flags = END_STREAM, END_HEADERS)
grpc-status = 0 # OK
@ -120,7 +129,7 @@ trace-proto-bin = jher831yy13JHy3hc
While the protocol does not require a user-agent to function it is recommended that clients provide a structured user-agent string that provides a basic description of the calling library, version & platform to facilitate issue diagnosis in heterogeneous environments. The following structure is recommended to library developers
```
User-Agent → “grpc-” Language ?(“-” Variant) “/” Version ?( “ (“ *(AdditionalProperty “;”) “)” )
User-Agent → "grpc-" Language ?("-" Variant) "/" Version ?( " (" *(AdditionalProperty ";") ")" )
```
E.g.
@ -136,7 +145,7 @@ grpc-java-android/0.9.1 (gingerbread/1.2.4; nexus5; tmobile)
All GRPC calls need to specify an internal ID. We will use HTTP2 stream-ids as call identifiers in this scheme. NOTE: These id’s are contextual to an open HTTP2 session and will not be unique within a given process that is handling more than one HTTP2 session nor can they be used as GUIDs.
#####Data Frames
DATA frame boundaries have no relation to **Delimited-Message** boundaries and implementations should make no assumptions about their alignment.
DATA frame boundaries have no relation to **Length-Prefixed-Message** boundaries and implementations should make no assumptions about their alignment.
#####Errors

@ -18,7 +18,7 @@ You can find quick start guides for each language, including installation instru
* [Android Java](https://github.com/grpc/grpc-java/tree/master/examples/android)
* [Python](python/helloworld)
* [C#](csharp)
* [Objective-C](objective-c/route_guide)
* [Objective-C](objective-c/helloworld)
* [PHP](php)
## What's in this repository?

@ -3,13 +3,13 @@ Pod::Spec.new do |s|
s.version = "0.0.1"
s.license = "New BSD"
s.ios.deployment_target = "6.0"
s.osx.deployment_target = "10.8"
s.ios.deployment_target = "7.1"
s.osx.deployment_target = "10.9"
# Base directory where the .proto files are.
src = "../../protos"
# Directory where the generated files will be place.
# Directory where the generated files will be placed.
dir = "Pods/" + s.name
# Run protoc with the Objective-C and gRPC plugins to generate protocol messages and gRPC clients.
@ -22,14 +22,14 @@ Pod::Spec.new do |s|
ms.source_files = "#{dir}/*.pbobjc.{h,m}", "#{dir}/**/*.pbobjc.{h,m}"
ms.header_mappings_dir = dir
ms.requires_arc = false
ms.dependency "Protobuf", "~> 3.0.0-alpha-3"
ms.dependency "Protobuf", "~> 3.0.0-alpha-4"
end
s.subspec "Services" do |ss|
ss.source_files = "#{dir}/*.pbrpc.{h,m}", "#{dir}/**/*.pbrpc.{h,m}"
ss.header_mappings_dir = dir
ss.requires_arc = true
ss.dependency "gRPC", "~> 0.6"
ss.dependency "gRPC", "~> 0.11"
ss.dependency "#{s.name}/Messages"
end
end

@ -35,7 +35,6 @@
#import <AuthTestService/AuthSample.pbrpc.h>
#import <Google/SignIn.h>
#include <grpc/status.h>
#import <ProtoRPC/ProtoRPC.h>
NSString * const kTestScope = @"https://www.googleapis.com/auth/xapi.zoo";
@ -49,10 +48,10 @@ static NSString * const kTestHostAddress = @"grpc-test.sandbox.google.com";
@implementation NSError (AuthSample)
- (NSString *)UIDescription {
if (self.code == GRPC_STATUS_UNAUTHENTICATED) {
if (self.code == GRPCErrorCodeUnauthenticated) {
// Authentication error. OAuth2 specifies we'll receive a challenge header.
// |userInfo[kGRPCStatusMetadataKey]| is the dictionary of response metadata.
NSString *challengeHeader = self.userInfo[kGRPCStatusMetadataKey][@"www-authenticate"] ?: @"";
// |userInfo[kGRPCHeadersKey]| is the dictionary of response headers.
NSString *challengeHeader = self.userInfo[kGRPCHeadersKey][@"www-authenticate"] ?: @"";
return [@"Invalid credentials. Server challenge:\n" stringByAppendingString:challengeHeader];
} else {
// Any other error.
@ -89,7 +88,7 @@ static NSString * const kTestHostAddress = @"grpc-test.sandbox.google.com";
// Set the access token to be used.
NSString *accessToken = GIDSignIn.sharedInstance.currentUser.authentication.accessToken;
call.requestMetadata[@"Authorization"] = [@"Bearer " stringByAppendingString:accessToken];
call.requestHeaders[@"Authorization"] = [@"Bearer " stringByAppendingString:accessToken];
// Start the RPC.
[call start];

@ -1,6 +1,9 @@
source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '8.0'
pod 'Protobuf', :path => "../../../third_party/protobuf"
pod 'gRPC', :path => "../../.."
target 'AuthSample' do
# Depend on the generated AuthTestService library.
pod 'AuthTestService', :path => '.'

@ -1,189 +1,3 @@
#OAuth2 on gRPC: Objective-C
This example application demostrates how to use OAuth2 on gRPC to make authenticated API calls on
behalf of a user. By walking through it you'll learn how to use the Objective-C gRPC API to:
- Initialize and configure a remote call object before the RPC is started.
- Set request metadata elements on a call, which are semantically equivalent to HTTP request
headers.
- Read response metadata from a call, which is equivalent to HTTP response headers and trailers.
It assumes you know the basics on how to make gRPC API calls using the Objective-C client library,
as shown in the [Hello World](../helloworld)
or [Route Guide](../route_guide) tutorials,
and are familiar with OAuth2 concepts like _access token_.
- [Example code and setup](#setup)
- [Try it out!](#try)
- [Create an RPC object and start it later](#rpc-object)
- [Set request metadata of a call: Authorization header with an access token](#request-metadata)
- [Get response metadata of a call: Auth challenge header](#response-metadata)
<a name="setup"></a>
## Example code and setup
The example code for our tutorial is in [examples/objective-c/auth_sample](.).
To download the example, clone this repository by running the following command:
```shell
$ git clone https://github.com/grpc/grpc.git
```
Then change your current directory to `examples/objective-c/auth_sample`:
```shell
$ cd examples/objective-c/auth_sample
```
Our example is a simple application with two views. The first one lets a user sign in and out using
the OAuth2 flow of Google's [iOS SignIn library](https://developers.google.com/identity/sign-in/ios/).
(Google's library is used in this example because the test gRPC service we are going to call expects
Google account credentials, but neither gRPC nor the Objective-C client library is tied to any
specific OAuth2 provider). The second view makes a gRPC request to the test server, using the
access token obtained by the first view.
Note: OAuth2 libraries need the application to register and obtain an ID from the identity provider
(in the case of this example app, Google). The app's XCode project is configured using that ID, so
you shouldn't copy this project "as is" for your own app: it would result in your app being
identified in the consent screen as "gRPC-AuthSample", and not having access to real Google
services. Instead, configure your own XCode project following the [instructions here](https://developers.google.com/identity/sign-in/ios/).
As with the other examples, you also should have [Cocoapods](https://cocoapods.org/#install)
installed, as well as the relevant tools to generate the client library code. You can obtain the
latter by following [these setup instructions](https://github.com/grpc/homebrew-grpc).
<a name="try"></a>
## Try it out!
To try the sample app, first have Cocoapods generate and install the client library for our .proto
files:
```shell
$ pod install
```
(This might have to compile OpenSSL, which takes around 15 minutes if Cocoapods doesn't have it yet
on your computer's cache).
Finally, open the XCode workspace created by Cocoapods, and run the app.
The first view, `SelectUserViewController.h/m`, asks you to sign in with your Google account, and to
give the "gRPC-AuthSample" app the following permissions:
- View your email address.
- View your basic profile info.
- "Test scope for access to the Zoo service".
This last permission, corresponding to the scope `https://www.googleapis.com/auth/xapi.zoo` doesn't
grant any real capability: it's only used for testing. You can log out at any time.
The second view, `MakeRPCViewController.h/m`, makes a gRPC request to a test server at
https://grpc-test.sandbox.google.com, sending the access token along with the request. The test
service simply validates the token and writes in its response which user it belongs to, and which
scopes it gives access to. (The client application already knows those two values; it's a way to
verify that everything went as expected).
The next sections guide you step-by-step through how the gRPC call in `MakeRPCViewController` is
performed.
<a name="rpc-object"></a>
## Create an RPC object and start it later
The other basic tutorials show how to invoke an RPC by calling an asynchronous method in a generated
client object. This shows how to initialize an object that represents the RPC, and configure it
before starting the network request.
Assume you have a proto service definition like this:
```protobuf
option objc_class_prefix = "AUTH";
service TestService {
rpc UnaryCall(Request) returns (Response);
}
```
A `unaryCallWithRequest:handler:` method, with which you're already familiar, is generated for the
`AUTHTestService` class:
```objective-c
[client unaryCallWithRequest:request handler:^(AUTHResponse *response, NSError *error) {
...
}];
```
In addition, an `RPCToUnaryCallWithRequest:handler:` method is generated, which returns a
not-yet-started RPC object:
```objective-c
#import <ProtoRPC/ProtoRPC.h>
ProtoRPC *call =
[client RPCToUnaryCallWithRequest:request handler:^(AUTHResponse *response, NSError *error) {
...
}];
```
The RPC represented by this object can be started at any later time like this:
```objective-c
[call start];
```
<a name="request-metadata"></a>
## Set request metadata of a call: Authorization header with an access token
The `ProtoRPC` class has a `requestMetadata` property (inherited from `GRPCCall`) defined like this:
```objective-c
- (NSMutableDictionary *)requestMetadata; // nonatomic
- (void)setRequestMetadata:(NSDictionary *)requestMetadata; // nonatomic, copy
```
Setting it to a dictionary of metadata keys and values will have them sent on the wire when the call
is started. gRPC metadata are pieces of information about the call sent by the client to the server
(and vice versa). They take the form of key-value pairs and are essentially opaque to gRPC itself.
```objective-c
call.requestMetadata = @{@"My-Header": @"Value for this header",
@"Another-Header": @"Its value"};
```
For convenience, the property is initialized with an empty `NSMutableDictionary`, so that request
metadata elements can be set like this:
```objective-c
call.requestMetadata[@"My-Header"] = @"Value for this header";
```
If you have an access token, OAuth2 specifies it is to be sent in this format:
```objective-c
call.requestMetadata[@"Authorization"] = [@"Bearer " stringByAppendingString:accessToken];
```
<a name="response-metadata"></a>
## Get response metadata of a call: Auth challenge header
The `ProtoRPC` class also inherits a `responseMetadata` property, analogous to the request metadata
we just looked at. It's defined like this:
```objective-c
@property(atomic, readonly) NSDictionary *responseMetadata;
```
To access OAuth2's authentication challenge header you write:
```objective-c
call.responseMetadata[@"www-authenticate"]
```
Note that, as gRPC metadata elements are mapped to HTTP/2 headers (or trailers), the keys of the
response metadata are always ASCII strings in lowercase.
Many uses cases of response metadata are getting more details about an RPC error. For convenience,
when a `NSError` instance is passed to an RPC handler block, the response metadata dictionary can
also be accessed this way:
```objective-c
error.userInfo[kGRPCStatusMetadataKey]
```
This is the supporting code for the tutorial "[OAuth2 on gRPC: Objective-C](http://www.grpc.io/docs/tutorials/auth/oauth2-objective-c.html)."

@ -12,11 +12,13 @@ Here's how to build and run the Objective-C implementation of the [Hello World](
example used in [Getting started](https://github.com/grpc/grpc/tree/master/examples).
The example code for this and our other examples lives in the `examples` directory. Clone
this repository to your local machine by running the following command:
this repository to your local machine by running the following commands:
```sh
$ git clone https://github.com/grpc/grpc.git
$ cd grpc
$ git submodule update --init
```
Change your current directory to `examples/objective-c/helloworld`
@ -53,4 +55,4 @@ responds with a `HLWHelloResponse`, which contains a string that is then output
## Tutorial
You can find a more detailed tutorial in [gRPC Basics: Objective-C](../route_guide/README.md).
You can find a more detailed tutorial in [gRPC Basics: Objective-C](http://www.grpc.io/docs/tutorials/basic/objective-c.html).

@ -2,6 +2,8 @@ source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '8.0'
target 'RouteGuideClient' do
pod 'Protobuf', :path => "../../../third_party/protobuf"
pod 'gRPC', :path => "../../.."
# Depend on the generated RouteGuide library.
pod 'RouteGuide', :path => '.'
end

@ -1,360 +1,4 @@
#gRPC Basics: Objective-C
This tutorial provides a basic Objective-C programmer's introduction to working with gRPC. By
walking through this example you'll learn how to:
This is the supporting code for the tutorial "[gRPC Basics: Objective-C](http://www.grpc.io/docs/tutorials/basic/objective-c.html)."
- Define a service in a .proto file.
- Generate client code using the protocol buffer compiler.
- Use the Objective-C gRPC API to write a simple client for your service.
It assumes a passing familiarity with [protocol buffers](https://developers.google.com/protocol-buffers/docs/overview).
Note that the example in this tutorial uses the proto3 version of the protocol buffers language,
which is currently in alpha release: you can find out more in the [proto3 language guide](https://developers.google.com/protocol-buffers/docs/proto3)
and see the [release notes](https://github.com/google/protobuf/releases) for the new version in the
protocol buffers Github repository.
This isn't a comprehensive guide to using gRPC in Objective-C: more reference documentation is
coming soon.
- [Why use gRPC?](#why-grpc)
- [Example code and setup](#setup)
- [Try it out!](#try)
- [Defining the service](#proto)
- [Generating client code](#protoc)
- [Creating the client](#client)
<a name="why-grpc"></a>
## Why use gRPC?
With gRPC you can define your service once in a .proto file and implement clients and servers in any
of gRPC's supported languages, which in turn can be run in environments ranging from servers inside
Google to your own tablet - all the complexity of communication between different languages and
environments is handled for you by gRPC. You also get all the advantages of working with protocol
buffers, including efficient serialization, a simple IDL, and easy interface updating.
gRPC and proto3 are specially suited for mobile clients: gRPC is implemented on top of HTTP/2, which
results in network bandwidth savings over using HTTP/1.1. Serialization and parsing of the proto
binary format is more efficient than the equivalent JSON, resulting in CPU and battery savings. And
proto3 uses a runtime that has been optimized over the years at Google to keep code size to a
minimum. The latter is important in Objective-C, because the ability of the compiler to strip unused
code is limited by the dynamic nature of the language.
<a name="setup"></a>
## Example code and setup
The example code for our tutorial is in [examples/objective-c/route_guide](.).
To download the example, clone this repository by running the following command:
```shell
$ git clone https://github.com/grpc/grpc.git
```
Then change your current directory to `examples/objective-c/route_guide`:
```shell
$ cd examples/objective-c/route_guide
```
Our example is a simple route mapping application that lets clients get information about features
on their route, create a summary of their route, and exchange route information such as traffic
updates with the server and other clients.
You also should have [Cocoapods](https://cocoapods.org/#install) installed, as well as the relevant
tools to generate the client library code (and a server in another language, for testing). You can
obtain the latter by following [these setup instructions](https://github.com/grpc/homebrew-grpc).
<a name="try"></a>
## Try it out!
To try the sample app, we need a gRPC server running locally. Let's compile and run, for example,
the C++ server in this repository:
```shell
$ pushd ../../cpp/route_guide
$ make
$ ./route_guide_server &
$ popd
```
Now have Cocoapods generate and install the client library for our .proto files:
```shell
$ pod install
```
(This might have to compile OpenSSL, which takes around 15 minutes if Cocoapods doesn't have it yet
on your computer's cache).
Finally, open the XCode workspace created by Cocoapods, and run the app. You can check the calling
code in `ViewControllers.m` and see the results in XCode's log console.
The next sections guide you step-by-step through how this proto service is defined, how to generate
a client library from it, and how to create an app that uses that library.
<a name="proto"></a>
## Defining the service
First let's look at how the service we're using is defined. A gRPC *service* and its method
*request* and *response* types using [protocol buffers](https://developers.google.com/protocol-buffers/docs/overview).
You can see the complete .proto file for our example in [`examples/protos/route_guide.proto`](../../protos/route_guide.proto).
To define a service, you specify a named `service` in your .proto file:
```protobuf
service RouteGuide {
...
}
```
Then you define `rpc` methods inside your service definition, specifying their request and response
types. Protocol buffers let you define four kinds of service method, all of which are used in the
`RouteGuide` service:
- A *simple RPC* where the client sends a request to the server and receives a response later, just
like a normal remote procedure call.
```protobuf
// Obtains the feature at a given position.
rpc GetFeature(Point) returns (Feature) {}
```
- A *response-streaming RPC* where the client sends a request to the server and gets back a stream
of response messages. You specify a response-streaming method by placing the `stream` keyword before
the *response* type.
```protobuf
// Obtains the Features available within the given Rectangle. Results are
// streamed rather than returned at once (e.g. in a response message with a
// repeated field), as the rectangle may cover a large area and contain a
// huge number of features.
rpc ListFeatures(Rectangle) returns (stream Feature) {}
```
- A *request-streaming RPC* where the client sends a sequence of messages to the server. Once the
client has finished writing the messages, it waits for the server to read them all and return its
response. You specify a request-streaming method by placing the `stream` keyword before the
*request* type.
```protobuf
// Accepts a stream of Points on a route being traversed, returning a
// RouteSummary when traversal is completed.
rpc RecordRoute(stream Point) returns (RouteSummary) {}
```
- A *bidirectional streaming RPC* where both sides send a sequence of messages to the other. The two
streams operate independently, so clients and servers can read and write in whatever order they
like: for example, the server could wait to receive all the client messages before writing its
responses, or it could alternately read a message then write a message, or some other combination of
reads and writes. The order of messages in each stream is preserved. You specify this type of method
by placing the `stream` keyword before both the request and the response.
```protobuf
// Accepts a stream of RouteNotes sent while a route is being traversed,
// while receiving other RouteNotes (e.g. from other users).
rpc RouteChat(stream RouteNote) returns (stream RouteNote) {}
```
Our .proto file also contains protocol buffer message type definitions for all the request and
response types used in our service methods - for example, here's the `Point` message type:
```protobuf
// Points are represented as latitude-longitude pairs in the E7 representation
// (degrees multiplied by 10**7 and rounded to the nearest integer).
// Latitudes should be in the range +/- 90 degrees and longitude should be in
// the range +/- 180 degrees (inclusive).
message Point {
int32 latitude = 1;
int32 longitude = 2;
}
```
You can specify a prefix to be used for your generated classes by adding the `objc_class_prefix`
option at the top of the file. For example:
```protobuf
option objc_class_prefix = "RTG";
```
<a name="protoc"></a>
## Generating client code
Next we need to generate the gRPC client interfaces from our .proto service definition. We do this
using the protocol buffer compiler (`protoc`) with a special gRPC Objective-C plugin.
For simplicity, we've provided a [Podspec file](RouteGuide.podspec)
that runs `protoc` for you with the appropriate plugin, input, and output, and describes how to
compile the generated files. You just need to run in this directory (`examples/objective-c/route_guide`):
```shell
$ pod install
```
which, before installing the generated library in the XCode project of this sample, runs:
```shell
$ protoc -I ../../protos --objc_out=Pods/RouteGuide --objcgrpc_out=Pods/RouteGuide ../../protos/route_guide.proto
```
Running this command generates the following files under `Pods/RouteGuide/`:
- `RouteGuide.pbobjc.h`, the header which declares your generated message classes.
- `RouteGuide.pbobjc.m`, which contains the implementation of your message classes.
- `RouteGuide.pbrpc.h`, the header which declares your generated service classes.
- `RouteGuide.pbrpc.m`, which contains the implementation of your service classes.
These contain:
- All the protocol buffer code to populate, serialize, and retrieve our request and response message
types.
- A class called `RTGRouteGuide` that lets clients call the methods defined in the `RouteGuide`
service.
You can also use the provided Podspec file to generate client code from any other proto service
definition; just replace the name (matching the file name), version, and other metadata.
<a name="client"></a>
## Creating the client
In this section, we'll look at creating an Objective-C client for our `RouteGuide` service. You can
see our complete example client code in [ViewControllers.m](ViewControllers.m).
(Note: In your apps, for maintainability and readability reasons, you shouldn't put all of your view
controllers in a single file; it's done here only to simplify the learning process).
### Constructing a client object
To call service methods, we first need to create a client object, an instance of the generated
`RTGRouteGuide` class. The designated initializer of the class expects a `NSString *` with the
server address and port we want to connect to:
```objective-c
#import <RouteGuide/RouteGuide.pbrpc.h>
static NSString * const kHostAddress = @"http://localhost:50051";
...
RTGRouteGuide *client = [[RTGRouteGuide alloc] initWithHost:kHostAddress];
```
Notice that we've specified the HTTP scheme in the host address. This is because the server we will
be using to test our client doesn't use [TLS](http://en.wikipedia.org/wiki/Transport_Layer_Security).
This is fine because it will be running locally on our development machine. The most common case,
though, is connecting with a gRPC server on the internet, running gRPC over TLS. For that case, the
HTTPS scheme can be specified (or no scheme at all, as HTTPS is the default value). The default
value of the port is that of the scheme selected: 443 for HTTPS and 80 for HTTP.
### Calling service methods
Now let's look at how we call our service methods. As you will see, all these methods are
asynchronous, so you can call them from the main thread of your app without worrying about freezing
your UI or the OS killing your app.
#### Simple RPC
Calling the simple RPC `GetFeature` is nearly as straightforward as calling any other asynchronous
method on Cocoa.
```objective-c
RTGPoint *point = [RTGPoint message];
point.latitude = 40E7;
point.longitude = -74E7;
[client getFeatureWithRequest:point handler:^(RTGFeature *response, NSError *error) {
if (response) {
// Successful response received
} else {
// RPC error
}
}];
```
As you can see, we create and populate a request protocol buffer object (in our case `RTGPoint`).
Then, we call the method on the client object, passing it the request, and a block to handle the
response (or any RPC error). If the RPC finishes successfully, the handler block is called with a
`nil` error argument, and we can read the response information from the server from the response
argument. If, instead, some RPC error happens, the handler block is called with a `nil` response
argument, and we can read the details of the problem from the error argument.
```objective-c
NSLog(@"Found feature called %@ at %@.", response.name, response.location);
```
#### Streaming RPCs
Now let's look at our streaming methods. Here's where we call the response-streaming method
`ListFeatures`, which results in our client receiving a stream of geographical `RTGFeature`s:
```objective-c
[client listFeaturesWithRequest:rectangle
eventHandler:^(BOOL done, RTGFeature *response, NSError *error) {
if (response) {
// Element of the stream of responses received
} else if (error) {
// RPC error; the stream is over.
}
if (done) {
// The stream is over (all the responses were received, or an error occured). Do any cleanup.
}
}];
```
Notice how the signature of the `eventHandler` block now includes a `BOOL done` parameter. The
`eventHandler` block can be called any number of times; only on the last call is the `done` argument
value set to `YES`. If an error occurs, the RPC finishes and the block is called with the arguments
`(YES, nil, error)`.
The request-streaming method `RecordRoute` expects a stream of `RTGPoint`s from the cient. This
stream is passed to the method as an object of class `GRXWriter`. The simplest way to create one is
to initialize one from a `NSArray` object:
```objective-c
#import <RxLibrary/GRXWriter+Immediate.h>
...
RTGPoint *point1 = [RTGPoint message];
point.latitude = 40E7;
point.longitude = -74E7;
RTGPoint *point2 = [RTGPoint message];
point.latitude = 40E7;
point.longitude = -74E7;
GRXWriter *locationsWriter = [GRXWriter writerWithContainer:@[point1, point2]];
[client recordRouteWithRequestsWriter:locationsWriter
handler:^(RTGRouteSummary *response, NSError *error) {
if (response) {
NSLog(@"Finished trip with %i points", response.pointCount);
NSLog(@"Passed %i features", response.featureCount);
NSLog(@"Travelled %i meters", response.distance);
NSLog(@"It took %i seconds", response.elapsedTime);
} else {
NSLog(@"RPC error: %@", error);
}
}];
```
The `GRXWriter` class is generic enough to allow for asynchronous streams, streams of future values,
or even infinite streams.
Finally, let's look at our bidirectional streaming RPC `RouteChat()`. The way to call a
bidirectional streaming RPC is just a combination of how to call request-streaming RPCs and
response-streaming RPCs.
```objective-c
[client routeChatWithRequestsWriter:notesWriter
eventHandler:^(BOOL done, RTGRouteNote *note, NSError *error) {
if (note) {
NSLog(@"Got message %@ at %@", note.message, note.location);
} else if (error) {
NSLog(@"RPC error: %@", error);
}
if (done) {
NSLog(@"Chat ended.");
}
}];
```
The semantics for the handler block and the `GRXWriter` argument here are exactly the same as for
our request-streaming and response-streaming methods. Although both client and server will always
get the other's messages in the order they were written, the two streams operate completely
independently.

@ -3,13 +3,13 @@ Pod::Spec.new do |s|
s.version = "0.0.1"
s.license = "New BSD"
s.ios.deployment_target = "6.0"
s.osx.deployment_target = "10.8"
s.ios.deployment_target = "7.1"
s.osx.deployment_target = "10.9"
# Base directory where the .proto files are.
src = "../../protos"
# Directory where the generated files will be place.
# Directory where the generated files will be placed.
dir = "Pods/" + s.name
# Run protoc with the Objective-C and gRPC plugins to generate protocol messages and gRPC clients.
@ -22,14 +22,14 @@ Pod::Spec.new do |s|
ms.source_files = "#{dir}/*.pbobjc.{h,m}", "#{dir}/**/*.pbobjc.{h,m}"
ms.header_mappings_dir = dir
ms.requires_arc = false
ms.dependency "Protobuf", "~> 3.0.0-alpha-3"
ms.dependency "Protobuf", "~> 3.0.0-alpha-4"
end
s.subspec "Services" do |ss|
ss.source_files = "#{dir}/*.pbrpc.{h,m}", "#{dir}/**/*.pbrpc.{h,m}"
ss.header_mappings_dir = dir
ss.requires_arc = true
ss.dependency "gRPC", "~> 0.6"
ss.dependency "gRPC", "~> 0.11"
ss.dependency "#{s.name}/Messages"
end
end

@ -121,6 +121,7 @@
6325277A1B1D0395003073D9 /* Frameworks */,
6325277B1B1D0395003073D9 /* Resources */,
FFE0BCF30339E7A50A989EAB /* Copy Pods Resources */,
B5388EC5A25E89021740B916 /* Embed Pods Frameworks */,
);
buildRules = (
);
@ -177,6 +178,21 @@
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
B5388EC5A25E89021740B916 /* Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Embed Pods Frameworks";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-RouteGuideClient/Pods-RouteGuideClient-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
C6FC30AD2376EC04317237C5 /* Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;

@ -32,13 +32,14 @@
*/
#import <UIKit/UIKit.h>
#import <GRPCClient/GRPCCall+Tests.h>
#import <RouteGuide/RouteGuide.pbrpc.h>
#import <RxLibrary/GRXWriter+Immediate.h>
#import <RxLibrary/GRXWriter+Transformations.h>
static NSString * const kHostAddress = @"http://localhost:50051";
static NSString * const kHostAddress = @"localhost:50051";
// Category to override RTGPoint's description.
/** Category to override RTGPoint's description. */
@interface RTGPoint (Description)
- (NSString *)description;
@end
@ -53,7 +54,7 @@ static NSString * const kHostAddress = @"http://localhost:50051";
}
@end
// Category to give RTGRouteNote a convenience constructor.
/** Category to give RTGRouteNote a convenience constructor. */
@interface RTGRouteNote (Constructors)
+ (instancetype)noteWithMessage:(NSString *)message
latitude:(float)latitude
@ -75,9 +76,10 @@ static NSString * const kHostAddress = @"http://localhost:50051";
#pragma mark Demo: Get Feature
// Run the getFeature demo. Calls getFeature with a point known to have a feature and a point known
// not to have a feature.
/**
* Run the getFeature demo. Calls getFeature with a point known to have a feature and a point known
* not to have a feature.
*/
@interface GetFeatureViewController : UIViewController
@end
@ -86,7 +88,10 @@ static NSString * const kHostAddress = @"http://localhost:50051";
- (void)viewDidLoad {
[super viewDidLoad];
RTGRouteGuide *client = [[RTGRouteGuide alloc] initWithHost:kHostAddress];
// This only needs to be done once per host, before creating service objects for that host.
[GRPCCall useInsecureConnectionsForHost:kHostAddress];
RTGRouteGuide *service = [[RTGRouteGuide alloc] initWithHost:kHostAddress];
void (^handler)(RTGFeature *response, NSError *error) = ^(RTGFeature *response, NSError *error) {
if (response.name.length) {
@ -102,8 +107,8 @@ static NSString * const kHostAddress = @"http://localhost:50051";
point.latitude = 409146138;
point.longitude = -746188906;
[client getFeatureWithRequest:point handler:handler];
[client getFeatureWithRequest:[RTGPoint message] handler:handler];
[service getFeatureWithRequest:point handler:handler];
[service getFeatureWithRequest:[RTGPoint message] handler:handler];
}
@end
@ -111,9 +116,10 @@ static NSString * const kHostAddress = @"http://localhost:50051";
#pragma mark Demo: List Features
// Run the listFeatures demo. Calls listFeatures with a rectangle containing all of the features in
// the pre-generated database. Prints each response as it comes in.
/**
* Run the listFeatures demo. Calls listFeatures with a rectangle containing all of the features in
* the pre-generated database. Prints each response as it comes in.
*/
@interface ListFeaturesViewController : UIViewController
@end
@ -122,7 +128,7 @@ static NSString * const kHostAddress = @"http://localhost:50051";
- (void)viewDidLoad {
[super viewDidLoad];
RTGRouteGuide *client = [[RTGRouteGuide alloc] initWithHost:kHostAddress];
RTGRouteGuide *service = [[RTGRouteGuide alloc] initWithHost:kHostAddress];
RTGRectangle *rectangle = [RTGRectangle message];
rectangle.lo.latitude = 405E6;
@ -131,7 +137,7 @@ static NSString * const kHostAddress = @"http://localhost:50051";
rectangle.hi.longitude = -745E6;
NSLog(@"Looking for features between %@ and %@", rectangle.lo, rectangle.hi);
[client listFeaturesWithRequest:rectangle
[service listFeaturesWithRequest:rectangle
eventHandler:^(BOOL done, RTGFeature *response, NSError *error) {
if (response) {
NSLog(@"Found feature at %@ called %@.", response.location, response.name);
@ -146,10 +152,11 @@ static NSString * const kHostAddress = @"http://localhost:50051";
#pragma mark Demo: Record Route
// Run the recordRoute demo. Sends several randomly chosen points from the pre-generated feature
// database with a variable delay in between. Prints the statistics when they are sent from the
// server.
/**
* Run the recordRoute demo. Sends several randomly chosen points from the pre-generated feature
* database with a variable delay in between. Prints the statistics when they are sent from the
* server.
*/
@interface RecordRouteViewController : UIViewController
@end
@ -171,9 +178,10 @@ static NSString * const kHostAddress = @"http://localhost:50051";
return location;
}];
RTGRouteGuide *client = [[RTGRouteGuide alloc] initWithHost:kHostAddress];
RTGRouteGuide *service = [[RTGRouteGuide alloc] initWithHost:kHostAddress];
[client recordRouteWithRequestsWriter:locations handler:^(RTGRouteSummary *response, NSError *error) {
[service recordRouteWithRequestsWriter:locations
handler:^(RTGRouteSummary *response, NSError *error) {
if (response) {
NSLog(@"Finished trip with %i points", response.pointCount);
NSLog(@"Passed %i features", response.featureCount);
@ -190,9 +198,10 @@ static NSString * const kHostAddress = @"http://localhost:50051";
#pragma mark Demo: Route Chat
// Run the routeChat demo. Send some chat messages, and print any chat messages that are sent from
// the server.
/**
* Run the routeChat demo. Send some chat messages, and print any chat messages that are sent from
* the server.
*/
@interface RouteChatViewController : UIViewController
@end
@ -210,9 +219,9 @@ static NSString * const kHostAddress = @"http://localhost:50051";
return note;
}];
RTGRouteGuide *client = [[RTGRouteGuide alloc] initWithHost:kHostAddress];
RTGRouteGuide *service = [[RTGRouteGuide alloc] initWithHost:kHostAddress];
[client routeChatWithRequestsWriter:notesWriter
[service routeChatWithRequestsWriter:notesWriter
eventHandler:^(BOOL done, RTGRouteNote *note, NSError *error) {
if (note) {
NSLog(@"Got message %@ at %@", note.message, note.location);

@ -566,8 +566,12 @@ Pod::Spec.new do |s|
ss.header_mappings_dir = '.'
# This isn't officially supported in Cocoapods. We've asked for an alternative:
# https://github.com/CocoaPods/CocoaPods/issues/4386
ss.xcconfig = { 'HEADER_SEARCH_PATHS' => '"$(PODS_ROOT)/Headers/Private/gRPC" ' +
'"$(PODS_ROOT)/Headers/Private/gRPC/include"' }
ss.xcconfig = {
'USE_HEADERMAP' => 'NO',
'ALWAYS_SEARCH_USER_PATHS' => 'NO',
'USER_HEADER_SEARCH_PATHS' => '"$(PODS_ROOT)/Headers/Private/gRPC"',
'HEADER_SEARCH_PATHS' => '"$(PODS_ROOT)/Headers/Private/gRPC/include"'
}
ss.requires_arc = false
ss.libraries = 'z'

@ -75,7 +75,6 @@ class ByteBuffer GRPC_FINAL {
// takes ownership
void set_buffer(grpc_byte_buffer* buf) {
if (buffer_) {
gpr_log(GPR_ERROR, "Overriding existing buffer");
Clear();
}
buffer_ = buf;

@ -56,7 +56,7 @@ class string_ref {
typedef std::reverse_iterator<const_iterator> const_reverse_iterator;
// constants
const static size_t npos = size_t(-1);
const static size_t npos;
// construct/copy.
string_ref() : data_(nullptr), length_(0) {}

@ -131,7 +131,7 @@ class ClientReader GRPC_FINAL : public ClientReaderInterface<R> {
cq_.Pluck(&ops);
}
void WaitForInitialMetadata() {
void WaitForInitialMetadata() GRPC_OVERRIDE {
GPR_ASSERT(!context_->initial_metadata_received_);
CallOpSet<CallOpRecvInitialMetadata> ops;
@ -257,7 +257,7 @@ class ClientReaderWriter GRPC_FINAL : public ClientReaderWriterInterface<W, R> {
cq_.Pluck(&ops);
}
void WaitForInitialMetadata() {
void WaitForInitialMetadata() GRPC_OVERRIDE {
GPR_ASSERT(!context_->initial_metadata_received_);
CallOpSet<CallOpRecvInitialMetadata> ops;

@ -106,6 +106,9 @@ void grpc_byte_buffer_reader_destroy(grpc_byte_buffer_reader *reader);
int grpc_byte_buffer_reader_next(grpc_byte_buffer_reader *reader,
gpr_slice *slice);
/** Merge all data from \a reader into single slice */
gpr_slice grpc_byte_buffer_reader_readall(grpc_byte_buffer_reader *reader);
/** Returns a RAW byte buffer instance from the output of \a reader. */
grpc_byte_buffer *grpc_raw_byte_buffer_from_reader(
grpc_byte_buffer_reader *reader);

@ -50,7 +50,7 @@ void gpr_histogram_add(gpr_histogram *h, double x);
/* The following merges the second histogram into the first. It only works
if they have the same buckets and resolution. Returns 0 on failure, 1
on success */
int gpr_histogram_merge(gpr_histogram *dst, gpr_histogram *src);
int gpr_histogram_merge(gpr_histogram *dst, const gpr_histogram *src);
double gpr_histogram_percentile(gpr_histogram *histogram, double percentile);
double gpr_histogram_mean(gpr_histogram *histogram);

@ -121,12 +121,14 @@ void grpc_pollset_kick_ext(grpc_pollset *p,
if ((flags & GRPC_POLLSET_REEVALUATE_POLLING_ON_WAKEUP) != 0) {
specific_worker->reevaluate_polling_on_wakeup = 1;
}
specific_worker->kicked_specifically = 1;
grpc_wakeup_fd_wakeup(&specific_worker->wakeup_fd);
} else if ((flags & GRPC_POLLSET_CAN_KICK_SELF) != 0) {
GPR_TIMER_MARK("kick_yoself", 0);
if ((flags & GRPC_POLLSET_REEVALUATE_POLLING_ON_WAKEUP) != 0) {
specific_worker->reevaluate_polling_on_wakeup = 1;
}
specific_worker->kicked_specifically = 1;
grpc_wakeup_fd_wakeup(&specific_worker->wakeup_fd);
}
} else if (gpr_tls_get(&g_current_thread_poller) != (gpr_intptr)p) {
@ -242,6 +244,7 @@ void grpc_pollset_work(grpc_exec_ctx *exec_ctx, grpc_pollset *pollset,
/* this must happen before we (potentially) drop pollset->mu */
worker->next = worker->prev = NULL;
worker->reevaluate_polling_on_wakeup = 0;
worker->kicked_specifically = 0;
/* TODO(ctiller): pool these */
grpc_wakeup_fd_init(&worker->wakeup_fd);
/* If there's work waiting for the pollset to be idle, and the
@ -308,7 +311,7 @@ void grpc_pollset_work(grpc_exec_ctx *exec_ctx, grpc_pollset *pollset,
if (worker->reevaluate_polling_on_wakeup) {
worker->reevaluate_polling_on_wakeup = 0;
pollset->kicked_without_pollers = 0;
if (queued_work) {
if (queued_work || worker->kicked_specifically) {
/* If there's queued work on the list, then set the deadline to be
immediate so we get back out of the polling loop quickly */
deadline = gpr_inf_past(GPR_CLOCK_MONOTONIC);

@ -51,6 +51,7 @@ struct grpc_fd;
typedef struct grpc_pollset_worker {
grpc_wakeup_fd wakeup_fd;
int reevaluate_polling_on_wakeup;
int kicked_specifically;
struct grpc_pollset_worker *next;
struct grpc_pollset_worker *prev;
} grpc_pollset_worker;

@ -125,7 +125,7 @@ void gpr_histogram_add(gpr_histogram *h, double x) {
h->buckets[bucket_for(h, x)]++;
}
int gpr_histogram_merge(gpr_histogram *dst, gpr_histogram *src) {
int gpr_histogram_merge(gpr_histogram *dst, const gpr_histogram *src) {
if ((dst->num_buckets != src->num_buckets) ||
(dst->multiplier != src->multiplier)) {
/* Fail because these histograms don't match */

@ -66,14 +66,12 @@ gpr_timespec gpr_now(gpr_clock_type clock) {
now_tv.tv_nsec = now_tb.millitm * 1000000;
break;
case GPR_CLOCK_MONOTONIC:
case GPR_CLOCK_PRECISE:
QueryPerformanceCounter(&timestamp);
now_dbl = (timestamp.QuadPart - g_start_time.QuadPart) * g_time_scale;
now_tv.tv_sec = (time_t)now_dbl;
now_tv.tv_nsec = (int)((now_dbl - (double)now_tv.tv_sec) * 1e9);
break;
case GPR_CLOCK_PRECISE:
gpr_precise_clock_now(&now_tv);
break;
}
return now_tv;
}

@ -31,6 +31,7 @@
*
*/
#include <string.h>
#include <grpc/byte_buffer_reader.h>
#include <grpc/compression.h>
@ -103,3 +104,21 @@ int grpc_byte_buffer_reader_next(grpc_byte_buffer_reader *reader,
}
return 0;
}
gpr_slice grpc_byte_buffer_reader_readall(grpc_byte_buffer_reader *reader) {
gpr_slice in_slice;
size_t bytes_read = 0;
const size_t input_size = grpc_byte_buffer_length(reader->buffer_out);
gpr_slice out_slice = gpr_slice_malloc(input_size);
gpr_uint8 *const outbuf = GPR_SLICE_START_PTR(out_slice); /* just an alias */
while (grpc_byte_buffer_reader_next(reader, &in_slice) != 0) {
const size_t slice_length = GPR_SLICE_LENGTH(in_slice);
memcpy(&(outbuf[bytes_read]), GPR_SLICE_START_PTR(in_slice), slice_length);
bytes_read += slice_length;
gpr_slice_unref(in_slice);
GPR_ASSERT(bytes_read <= input_size);
}
return out_slice;
}

@ -40,7 +40,7 @@
namespace grpc {
const size_t string_ref::npos;
const size_t string_ref::npos = size_t(-1);
string_ref& string_ref::operator=(const string_ref& rhs) {
data_ = rhs.data_;

@ -7,6 +7,7 @@ Grpc.v12.suo
Grpc.sdf
TestResult.xml
coverage_results.xml
/TestResults
.vs/
*.nupkg

@ -1,4 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="NUnit.Runners" version="2.6.4" />
<package id="OpenCover" version="4.6.166" />
<package id="ReportGenerator" version="2.3.2.0" />
</packages>

@ -38,6 +38,7 @@ using System.Threading;
using System.Threading.Tasks;
using Grpc.Core;
using Grpc.Core.Internal;
using Grpc.Core.Profiling;
using Grpc.Core.Utils;
using NUnit.Framework;
@ -201,19 +202,6 @@ namespace Grpc.Core.Tests
CollectionAssert.AreEqual(headers[1].ValueBytes, trailers[1].ValueBytes);
}
[Test]
public void UnaryCallPerformance()
{
helper.UnaryHandler = new UnaryServerMethod<string, string>(async (request, context) =>
{
return request;
});
var callDetails = helper.CreateUnaryCall();
BenchmarkUtil.RunBenchmark(1, 10,
() => { Calls.BlockingUnaryCall(callDetails, "ABC"); });
}
[Test]
public void UnknownMethodHandler()
{

@ -88,6 +88,7 @@
<Compile Include="CompressionTest.cs" />
<Compile Include="ContextPropagationTest.cs" />
<Compile Include="MetadataTest.cs" />
<Compile Include="PerformanceTest.cs" />
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<ItemGroup>

@ -34,6 +34,7 @@
using System;
using System.Runtime.InteropServices;
using Grpc.Core.Internal;
using Grpc.Core.Utils;
using NUnit.Framework;
namespace Grpc.Core.Internal.Tests
@ -198,5 +199,23 @@ namespace Grpc.Core.Internal.Tests
Console.WriteLine("Test cannot be run on this platform, skipping the test.");
}
}
// Test attribute commented out to prevent running as part of the default test suite.
// [Test]
// [Category("Performance")]
public void NowBenchmark()
{
// approx Timespec.Now latency <33ns
BenchmarkUtil.RunBenchmark(10000000, 1000000000, () => { var now = Timespec.Now; });
}
// Test attribute commented out to prevent running as part of the default test suite.
// [Test]
// [Category("Performance")]
public void PreciseNowBenchmark()
{
// approx Timespec.PreciseNow latency <18ns (when compiled with GRPC_TIMERS_RDTSC)
BenchmarkUtil.RunBenchmark(10000000, 1000000000, () => { var now = Timespec.PreciseNow; });
}
}
}

@ -0,0 +1,99 @@
#region Copyright notice and license
// 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.
#endregion
using System;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Grpc.Core;
using Grpc.Core.Internal;
using Grpc.Core.Profiling;
using Grpc.Core.Utils;
using NUnit.Framework;
namespace Grpc.Core.Tests
{
public class PerformanceTest
{
const string Host = "127.0.0.1";
MockServiceHelper helper;
Server server;
Channel channel;
[SetUp]
public void Init()
{
helper = new MockServiceHelper(Host);
server = helper.GetServer();
server.Start();
channel = helper.GetChannel();
}
[TearDown]
public void Cleanup()
{
channel.ShutdownAsync().Wait();
server.ShutdownAsync().Wait();
}
// Test attribute commented out to prevent running as part of the default test suite.
//[Test]
//[Category("Performance")]
public void UnaryCallPerformance()
{
var profiler = new BasicProfiler();
Profilers.SetForCurrentThread(profiler);
helper.UnaryHandler = new UnaryServerMethod<string, string>(async (request, context) =>
{
return request;
});
var callDetails = helper.CreateUnaryCall();
for(int i = 0; i < 3000; i++)
{
Calls.BlockingUnaryCall(callDetails, "ABC");
}
profiler.Reset();
for(int i = 0; i < 3000; i++)
{
Calls.BlockingUnaryCall(callDetails, "ABC");
}
profiler.Dump("latency_trace_csharp.txt");
}
}
}

@ -119,6 +119,10 @@
<Compile Include="CompressionLevel.cs" />
<Compile Include="WriteOptions.cs" />
<Compile Include="ContextPropagationToken.cs" />
<Compile Include="Profiling\ProfilerEntry.cs" />
<Compile Include="Profiling\ProfilerScope.cs" />
<Compile Include="Profiling\IProfiler.cs" />
<Compile Include="Profiling\Profilers.cs" />
</ItemGroup>
<ItemGroup>
<None Include="Grpc.Core.nuspec" />
@ -150,4 +154,7 @@
<Import Project="..\packages\grpc.dependencies.openssl.redist.1.0.2.2\build\portable-net45\grpc.dependencies.openssl.redist.targets" Condition="Exists('..\packages\grpc.dependencies.openssl.redist.1.0.2.2\build\portable-net45\grpc.dependencies.openssl.redist.targets')" />
<Import Project="..\packages\grpc.dependencies.zlib.redist.1.2.8.9\build\portable-net45\grpc.dependencies.zlib.redist.targets" Condition="Exists('..\packages\grpc.dependencies.zlib.redist.1.2.8.9\build\portable-net45\grpc.dependencies.zlib.redist.targets')" />
<ItemGroup />
<ItemGroup>
<Folder Include="Profiling\" />
</ItemGroup>
</Project>

@ -39,6 +39,7 @@ using System.Threading;
using System.Threading.Tasks;
using Grpc.Core.Internal;
using Grpc.Core.Logging;
using Grpc.Core.Profiling;
using Grpc.Core.Utils;
namespace Grpc.Core.Internal
@ -87,6 +88,9 @@ namespace Grpc.Core.Internal
/// </summary>
public TResponse UnaryCall(TRequest msg)
{
var profiler = Profilers.ForCurrentThread();
using (profiler.NewScope("AsyncCall.UnaryCall"))
using (CompletionQueueSafeHandle cq = CompletionQueueSafeHandle.Create())
{
byte[] payload = UnsafeSerialize(msg);
@ -104,23 +108,25 @@ namespace Grpc.Core.Internal
}
using (var metadataArray = MetadataArraySafeHandle.Create(details.Options.Headers))
{
using (var ctx = BatchContextSafeHandle.Create())
{
call.StartUnary(ctx, payload, metadataArray, GetWriteFlagsForCall());
var ev = cq.Pluck(ctx.Handle);
bool success = (ev.success != 0);
try
{
using (profiler.NewScope("AsyncCall.UnaryCall.HandleBatch"))
{
HandleUnaryResponse(success, ctx.GetReceivedStatusOnClient(), ctx.GetReceivedMessage(), ctx.GetReceivedInitialMetadata());
}
}
catch (Exception e)
{
Logger.Error(e, "Exception occured while invoking completion delegate.");
}
}
}
// Once the blocking call returns, the result should be available synchronously.
// Note that GetAwaiter().GetResult() doesn't wrap exceptions in AggregateException.
@ -328,14 +334,20 @@ namespace Grpc.Core.Internal
}
private void Initialize(CompletionQueueSafeHandle cq)
{
using (Profilers.ForCurrentThread().NewScope("AsyncCall.Initialize"))
{
var call = CreateNativeCall(cq);
details.Channel.AddCallReference(this);
InitializeInternal(call);
RegisterCancellationCallback();
}
}
private INativeCall CreateNativeCall(CompletionQueueSafeHandle cq)
{
using (Profilers.ForCurrentThread().NewScope("AsyncCall.CreateNativeCall"))
{
if (injectedNativeCall != null)
{
@ -347,9 +359,11 @@ namespace Grpc.Core.Internal
var credentials = details.Options.Credentials;
using (var nativeCredentials = credentials != null ? credentials.ToNativeCredentials() : null)
{
return details.Channel.Handle.CreateCall(environment.CompletionRegistry,
var result = details.Channel.Handle.CreateCall(environment.CompletionRegistry,
parentCall, ContextPropagationToken.DefaultMask, cq,
details.Method, details.Host, Timespec.FromDateTime(details.Options.Deadline.Value), nativeCredentials);
return result;
}
}
}
@ -384,6 +398,8 @@ namespace Grpc.Core.Internal
/// Handler for unary response completion.
/// </summary>
private void HandleUnaryResponse(bool success, ClientSideStatus receivedStatus, byte[] receivedMessage, Metadata responseHeaders)
{
using (Profilers.ForCurrentThread().NewScope("AsyncCall.HandleUnaryResponse"))
{
TResponse msg = default(TResponse);
var deserializeException = success ? TryDeserialize(receivedMessage, out msg) : null;
@ -399,6 +415,7 @@ namespace Grpc.Core.Internal
finishedStatus = receivedStatus;
ReleaseResourcesIfPossible();
}
responseHeadersTcs.SetResult(responseHeaders);
@ -413,6 +430,7 @@ namespace Grpc.Core.Internal
unaryResponseTcs.SetResult(msg);
}
}
/// <summary>
/// Handles receive status completion for calls with streaming response.

@ -41,6 +41,7 @@ using System.Threading.Tasks;
using Grpc.Core.Internal;
using Grpc.Core.Logging;
using Grpc.Core.Profiling;
using Grpc.Core.Utils;
namespace Grpc.Core.Internal
@ -166,6 +167,8 @@ namespace Grpc.Core.Internal
/// the underlying native resources.
/// </summary>
protected bool ReleaseResourcesIfPossible()
{
using (Profilers.ForCurrentThread().NewScope("AsyncCallBase.ReleaseResourcesIfPossible"))
{
if (!disposed && call != null)
{
@ -178,6 +181,7 @@ namespace Grpc.Core.Internal
}
return false;
}
}
protected abstract bool IsClient
{
@ -227,9 +231,12 @@ namespace Grpc.Core.Internal
}
protected byte[] UnsafeSerialize(TWrite msg)
{
using (Profilers.ForCurrentThread().NewScope("AsyncCallBase.UnsafeSerialize"))
{
return serializer(msg);
}
}
protected Exception TrySerialize(TWrite msg, out byte[] payload)
{
@ -246,11 +253,15 @@ namespace Grpc.Core.Internal
}
protected Exception TryDeserialize(byte[] payload, out TRead msg)
{
using (Profilers.ForCurrentThread().NewScope("AsyncCallBase.TryDeserialize"))
{
try
{
msg = deserializer(payload);
return null;
}
catch (Exception e)
{
@ -258,6 +269,7 @@ namespace Grpc.Core.Internal
return e;
}
}
}
protected void FireCompletion<T>(AsyncCompletionDelegate<T> completionDelegate, T value, Exception error)
{

@ -34,6 +34,7 @@ using System.Diagnostics;
using System.Runtime.InteropServices;
using Grpc.Core;
using Grpc.Core.Utils;
using Grpc.Core.Profiling;
namespace Grpc.Core.Internal
{
@ -130,10 +131,13 @@ namespace Grpc.Core.Internal
}
public void StartUnary(BatchContextSafeHandle ctx, byte[] payload, MetadataArraySafeHandle metadataArray, WriteFlags writeFlags)
{
using (Profilers.ForCurrentThread().NewScope("CallSafeHandle.StartUnary"))
{
grpcsharp_call_start_unary(this, ctx, payload, new UIntPtr((ulong)payload.Length), metadataArray, writeFlags)
.CheckOk();
}
}
public void StartClientStreaming(UnaryResponseClientHandler callback, MetadataArraySafeHandle metadataArray)
{

@ -32,6 +32,7 @@ using System;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using Grpc.Core.Profiling;
namespace Grpc.Core.Internal
{
@ -83,6 +84,8 @@ namespace Grpc.Core.Internal
}
public CallSafeHandle CreateCall(CompletionRegistry registry, CallSafeHandle parentCall, ContextPropagationFlags propagationMask, CompletionQueueSafeHandle cq, string method, string host, Timespec deadline, CredentialsSafeHandle credentials)
{
using (Profilers.ForCurrentThread().NewScope("ChannelSafeHandle.CreateCall"))
{
var result = grpcsharp_channel_create_call(this, parentCall, propagationMask, cq, method, host, deadline);
if (credentials != null)
@ -92,6 +95,7 @@ namespace Grpc.Core.Internal
result.SetCompletionRegistry(registry);
return result;
}
}
public ChannelState CheckConnectivityState(bool tryToConnect)
{

@ -31,6 +31,7 @@
using System;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using Grpc.Core.Profiling;
namespace Grpc.Core.Internal
{
@ -69,9 +70,12 @@ namespace Grpc.Core.Internal
}
public CompletionQueueEvent Pluck(IntPtr tag)
{
using (Profilers.ForCurrentThread().NewScope("CompletionQueueSafeHandle.Pluck"))
{
return grpcsharp_completion_queue_pluck(this, tag);
}
}
public void Shutdown()
{

@ -102,6 +102,9 @@ namespace Grpc.Core.Internal
/* Realtime clock */
Realtime,
/* Precise clock good for performance profiling. */
Precise,
/* Timespan - the distance between two time points */
Timespan
}

@ -31,6 +31,7 @@
using System;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using Grpc.Core.Profiling;
namespace Grpc.Core.Internal
{
@ -65,6 +66,8 @@ namespace Grpc.Core.Internal
}
public static MetadataArraySafeHandle Create(Metadata metadata)
{
using (Profilers.ForCurrentThread().NewScope("MetadataArraySafeHandle.Create"))
{
// TODO(jtattermusch): we might wanna check that the metadata is readonly
var metadataArray = grpcsharp_metadata_array_create(new UIntPtr((ulong)metadata.Count));
@ -75,6 +78,7 @@ namespace Grpc.Core.Internal
}
return metadataArray;
}
}
/// <summary>
/// Reads metadata from pointer to grpc_metadata_array

@ -239,6 +239,19 @@ namespace Grpc.Core.Internal
}
}
/// <summary>
/// Gets current timestamp using <c>GPRClockType.Precise</c>.
/// Only available internally because core needs to be compiled with
/// GRPC_TIMERS_RDTSC support for this to use RDTSC.
/// </summary>
internal static Timespec PreciseNow
{
get
{
return gprsharp_now(GPRClockType.Precise);
}
}
internal static int NativeSize
{
get

@ -0,0 +1,47 @@
#region Copyright notice and license
// 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.
#endregion
using System;
using System.IO;
using System.Threading;
using Grpc.Core.Internal;
namespace Grpc.Core.Profiling
{
internal interface IProfiler
{
void Begin(string tag);
void End(string tag);
void Mark(string tag);
}
}

@ -0,0 +1,87 @@
#region Copyright notice and license
// 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.
#endregion
using System;
using System.IO;
using System.Threading;
using Grpc.Core.Internal;
namespace Grpc.Core.Profiling
{
internal struct ProfilerEntry
{
public enum Type {
BEGIN,
END,
MARK
}
public ProfilerEntry(Timespec timespec, Type type, string tag)
{
this.timespec = timespec;
this.type = type;
this.tag = tag;
}
public Timespec timespec;
public Type type;
public string tag;
public override string ToString()
{
// mimic the output format used by C core.
return string.Format(
"{{\"t\": {0}.{1}, \"thd\":\"unknown\", \"type\": \"{2}\", \"tag\": \"{3}\", " +
"\"file\": \"unknown\", \"line\": 0, \"imp\": 0}}",
timespec.TimevalSeconds, timespec.TimevalNanos.ToString("D9"),
GetTypeAbbreviation(type), tag);
}
internal static string GetTypeAbbreviation(Type type)
{
switch (type)
{
case Type.BEGIN:
return "{";
case Type.END:
return "}";
case Type.MARK:
return ".";
default:
throw new ArgumentException("Unknown type");
}
}
}
}

@ -0,0 +1,60 @@
#region Copyright notice and license
// 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.
#endregion
using System;
using System.IO;
using System.Threading;
using Grpc.Core.Internal;
namespace Grpc.Core.Profiling
{
// Allows declaring Begin and End of a profiler scope with a using statement.
// declared as struct for better performance.
internal struct ProfilerScope : IDisposable
{
readonly IProfiler profiler;
readonly string tag;
public ProfilerScope(IProfiler profiler, string tag)
{
this.profiler = profiler;
this.tag = tag;
this.profiler.Begin(this.tag);
}
public void Dispose()
{
profiler.End(tag);
}
}
}

@ -0,0 +1,131 @@
#region Copyright notice and license
// 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.
#endregion
using System;
using System.IO;
using System.Threading;
using Grpc.Core.Internal;
namespace Grpc.Core.Profiling
{
internal static class Profilers
{
static readonly NopProfiler defaultProfiler = new NopProfiler();
static readonly ThreadLocal<IProfiler> profilers = new ThreadLocal<IProfiler>();
public static IProfiler ForCurrentThread()
{
return profilers.Value ?? defaultProfiler;
}
public static void SetForCurrentThread(IProfiler profiler)
{
profilers.Value = profiler;
}
public static ProfilerScope NewScope(this IProfiler profiler, string tag)
{
return new ProfilerScope(profiler, tag);
}
}
internal class NopProfiler : IProfiler
{
public void Begin(string tag)
{
}
public void End(string tag)
{
}
public void Mark(string tag)
{
}
}
// Profiler using Timespec.PreciseNow
internal class BasicProfiler : IProfiler
{
ProfilerEntry[] entries;
int count;
public BasicProfiler() : this(1024*1024)
{
}
public BasicProfiler(int capacity)
{
this.entries = new ProfilerEntry[capacity];
}
public void Begin(string tag) {
AddEntry(new ProfilerEntry(Timespec.PreciseNow, ProfilerEntry.Type.BEGIN, tag));
}
public void End(string tag) {
AddEntry(new ProfilerEntry(Timespec.PreciseNow, ProfilerEntry.Type.END, tag));
}
public void Mark(string tag) {
AddEntry(new ProfilerEntry(Timespec.PreciseNow, ProfilerEntry.Type.MARK, tag));
}
public void Reset()
{
count = 0;
}
public void Dump(string filepath)
{
using (var stream = new StreamWriter(filepath))
{
Dump(stream);
}
}
public void Dump(TextWriter stream)
{
for (int i = 0; i < count; i++)
{
var entry = entries[i];
stream.WriteLine(entry.ToString());
}
}
// NOT THREADSAFE!
void AddEntry(ProfilerEntry entry) {
entries[count++] = entry;
}
}
}

@ -1,3 +1,4 @@
[![Nuget](https://img.shields.io/nuget/v/Grpc.svg)](http://www.nuget.org/packages/Grpc/)
gRPC C#
=======

@ -1,3 +1,4 @@
[![npm](https://img.shields.io/npm/v/grpc.svg)](https://www.npmjs.com/package/grpc)
# Node.js gRPC Library
## Status

@ -33,6 +33,17 @@
/**
* Client module
*
* This module contains the factory method for creating Client classes, and the
* method calling code for all types of methods.
*
* For example, to create a client and call a method on it:
*
* var proto_obj = grpc.load(proto_file_path);
* var Client = proto_obj.package.subpackage.ServiceName;
* var client = new Client(server_address, client_credentials);
* var call = client.unaryMethod(arguments, callback);
*
* @module
*/
@ -601,7 +612,15 @@ exports.makeClientConstructor = function(methods, serviceName) {
if (!options) {
options = {};
}
options['grpc.primary_user_agent'] = 'grpc-node/' + version;
/* Append the grpc-node user agent string after the application user agent
* string, and put the combination at the beginning of the user agent string
*/
if (options['grpc.primary_user_agent']) {
options['grpc.primary_user_agent'] += ' ';
} else {
options['grpc.primary_user_agent'] = '';
}
options['grpc.primary_user_agent'] += 'grpc-node/' + version;
/* Private fields use $ as a prefix instead of _ because it is an invalid
* prefix of a method name */
this.$channel = new grpc.Channel(address, credentials, options);

@ -32,6 +32,8 @@
*/
/**
* This module contains functions that are common to client and server
* code. None of them should be used directly by gRPC users.
* @module
*/

@ -33,6 +33,15 @@
/**
* Metadata module
*
* This module defines the Metadata class, which represents header and trailer
* metadata for gRPC calls. Here is an example of how to use it:
*
* var metadata = new metadata_module.Metadata();
* metadata.set('key1', 'value1');
* metadata.add('key1', 'value2');
* metadata.get('key1') // returns ['value1', 'value2']
*
* @module
*/

@ -33,6 +33,17 @@
/**
* Server module
*
* This module contains all the server code for Node gRPC: both the Server
* class itself and the method handler code for all types of methods.
*
* For example, to create a Server, add a service, and start it:
*
* var server = new server_module.Server();
* server.addProtoService(protobuf_service_descriptor, service_implementation);
* server.bind('address:port', server_credential);
* server.start();
*
* @module
*/
@ -746,8 +757,8 @@ Server.prototype.addProtoService = function(service, implementation) {
* Binds the server to the given port, with SSL enabled if creds is given
* @param {string} port The port that the server should bind on, in the format
* "address:port"
* @param {boolean=} creds Server credential object to be used for SSL. Pass
* nothing for an insecure port
* @param {ServerCredentials=} creds Server credential object to be used for
* SSL. Pass an insecure credentials object for an insecure port.
*/
Server.prototype.bind = function(port, creds) {
if (this.started) {

@ -33,17 +33,19 @@
#import "GRPCCall.h"
// Helpers for setting and reading headers compatible with OAuth2.
/** Helpers for setting and reading headers compatible with OAuth2. */
@interface GRPCCall (OAuth2)
// Setting this property is equivalent to setting "Bearer <passed token>" as the value of the
// request header with key "authorization" (the authorization header). Setting it to nil removes the
// authorization header from the request.
// The value obtained by getting the property is the OAuth2 bearer token if the authorization header
// of the request has the form "Bearer <token>", or nil otherwise.
/**
* Setting this property is equivalent to setting "Bearer <passed token>" as the value of the
* request header with key "authorization" (the authorization header). Setting it to nil removes the
* authorization header from the request.
* The value obtained by getting the property is the OAuth2 bearer token if the authorization header
* of the request has the form "Bearer <token>", or nil otherwise.
*/
@property(atomic, copy) NSString *oauth2AccessToken;
// Returns the value (if any) of the "www-authenticate" response header (the challenge header).
/** Returns the value (if any) of the "www-authenticate" response header (the challenge header). */
@property(atomic, readonly) NSString *oauth2ChallengeHeader;
@end

@ -33,22 +33,28 @@
#import "GRPCCall.h"
// Methods to let tune down the security of gRPC connections for specific hosts. These shouldn't be
// used in releases, but are sometimes needed for testing.
/**
* Methods to let tune down the security of gRPC connections for specific hosts. These shouldn't be
* used in releases, but are sometimes needed for testing.
*/
@interface GRPCCall (Tests)
// Establish all SSL connections to the provided host using the passed SSL target name and the root
// certificates found in the file at |certsPath|.
//
// Must be called before any gRPC call to that host is made. It's illegal to pass the same host to
// more than one invocation of the methods of this category.
/**
* Establish all SSL connections to the provided host using the passed SSL target name and the root
* certificates found in the file at |certsPath|.
*
* Must be called before any gRPC call to that host is made. It's illegal to pass the same host to
* more than one invocation of the methods of this category.
*/
+ (void)useTestCertsPath:(NSString *)certsPath
testName:(NSString *)testName
forHost:(NSString *)host;
// Establish all connections to the provided host using cleartext instead of SSL.
//
// Must be called before any gRPC call to that host is made. It's illegal to pass the same host to
// more than one invocation of the methods of this category.
/**
* Establish all connections to the provided host using cleartext instead of SSL.
*
* Must be called before any gRPC call to that host is made. It's illegal to pass the same host to
* more than one invocation of the methods of this category.
*/
+ (void)useInsecureConnectionsForHost:(NSString *)host;
@end

@ -40,6 +40,9 @@
+ (void)useTestCertsPath:(NSString *)certsPath
testName:(NSString *)testName
forHost:(NSString *)host {
if (!host || !certsPath || !testName) {
[NSException raise:NSInvalidArgumentException format:@"host, path and name must be provided."];
}
GRPCHost *hostConfig = [GRPCHost hostWithAddress:host];
hostConfig.pathToCertificates = certsPath;
hostConfig.hostNameOverride = testName;

@ -31,117 +31,145 @@
*
*/
// The gRPC protocol is an RPC protocol on top of HTTP2.
//
// While the most common type of RPC receives only one request message and returns only one response
// message, the protocol also supports RPCs that return multiple individual messages in a streaming
// fashion, RPCs that accept a stream of request messages, or RPCs with both streaming requests and
// responses.
//
// Conceptually, each gRPC call consists of a bidirectional stream of binary messages, with RPCs of
// the "non-streaming type" sending only one message in the corresponding direction (the protocol
// doesn't make any distinction).
//
// Each RPC uses a different HTTP2 stream, and thus multiple simultaneous RPCs can be multiplexed
// transparently on the same TCP connection.
/**
* The gRPC protocol is an RPC protocol on top of HTTP2.
*
* While the most common type of RPC receives only one request message and returns only one response
* message, the protocol also supports RPCs that return multiple individual messages in a streaming
* fashion, RPCs that accept a stream of request messages, or RPCs with both streaming requests and
* responses.
*
* Conceptually, each gRPC call consists of a bidirectional stream of binary messages, with RPCs of
* the "non-streaming type" sending only one message in the corresponding direction (the protocol
* doesn't make any distinction).
*
* Each RPC uses a different HTTP2 stream, and thus multiple simultaneous RPCs can be multiplexed
* transparently on the same TCP connection.
*/
#import <Foundation/Foundation.h>
#import <RxLibrary/GRXWriter.h>
#pragma mark gRPC errors
// Domain of NSError objects produced by gRPC.
/** Domain of NSError objects produced by gRPC. */
extern NSString *const kGRPCErrorDomain;
// gRPC error codes.
// Note that a few of these are never produced by the gRPC libraries, but are of general utility for
// server applications to produce.
/**
* gRPC error codes.
* Note that a few of these are never produced by the gRPC libraries, but are of general utility for
* server applications to produce.
*/
typedef NS_ENUM(NSUInteger, GRPCErrorCode) {
// The operation was cancelled (typically by the caller).
/** The operation was cancelled (typically by the caller). */
GRPCErrorCodeCancelled = 1,
// Unknown error. Errors raised by APIs that do not return enough error information may be
// converted to this error.
/**
* Unknown error. Errors raised by APIs that do not return enough error information may be
* converted to this error.
*/
GRPCErrorCodeUnknown = 2,
// The client specified an invalid argument. Note that this differs from FAILED_PRECONDITION.
// INVALID_ARGUMENT indicates arguments that are problematic regardless of the state of the
// server (e.g., a malformed file name).
/**
* The client specified an invalid argument. Note that this differs from FAILED_PRECONDITION.
* INVALID_ARGUMENT indicates arguments that are problematic regardless of the state of the
* server (e.g., a malformed file name).
*/
GRPCErrorCodeInvalidArgument = 3,
// Deadline expired before operation could complete. For operations that change the state of the
// server, this error may be returned even if the operation has completed successfully. For
// example, a successful response from the server could have been delayed long enough for the
// deadline to expire.
/**
* Deadline expired before operation could complete. For operations that change the state of the
* server, this error may be returned even if the operation has completed successfully. For
* example, a successful response from the server could have been delayed long enough for the
* deadline to expire.
*/
GRPCErrorCodeDeadlineExceeded = 4,
// Some requested entity (e.g., file or directory) was not found.
/** Some requested entity (e.g., file or directory) was not found. */
GRPCErrorCodeNotFound = 5,
// Some entity that we attempted to create (e.g., file or directory) already exists.
/** Some entity that we attempted to create (e.g., file or directory) already exists. */
GRPCErrorCodeAlreadyExists = 6,
// The caller does not have permission to execute the specified operation. PERMISSION_DENIED isn't
// used for rejections caused by exhausting some resource (RESOURCE_EXHAUSTED is used instead for
// those errors). PERMISSION_DENIED doesn't indicate a failure to identify the caller
// (UNAUTHENTICATED is used instead for those errors).
/**
* The caller does not have permission to execute the specified operation. PERMISSION_DENIED isn't
* used for rejections caused by exhausting some resource (RESOURCE_EXHAUSTED is used instead for
* those errors). PERMISSION_DENIED doesn't indicate a failure to identify the caller
* (UNAUTHENTICATED is used instead for those errors).
*/
GRPCErrorCodePermissionDenied = 7,
// The request does not have valid authentication credentials for the operation (e.g. the caller's
// identity can't be verified).
/**
* The request does not have valid authentication credentials for the operation (e.g. the caller's
* identity can't be verified).
*/
GRPCErrorCodeUnauthenticated = 16,
// Some resource has been exhausted, perhaps a per-user quota.
/** Some resource has been exhausted, perhaps a per-user quota. */
GRPCErrorCodeResourceExhausted = 8,
// The RPC was rejected because the server is not in a state required for the procedure's
// execution. For example, a directory to be deleted may be non-empty, etc.
// The client should not retry until the server state has been explicitly fixed (e.g. by
// performing another RPC). The details depend on the service being called, and should be found in
// the NSError's userInfo.
/**
* The RPC was rejected because the server is not in a state required for the procedure's
* execution. For example, a directory to be deleted may be non-empty, etc.
* The client should not retry until the server state has been explicitly fixed (e.g. by
* performing another RPC). The details depend on the service being called, and should be found in
* the NSError's userInfo.
*/
GRPCErrorCodeFailedPrecondition = 9,
// The RPC was aborted, typically due to a concurrency issue like sequencer check failures,
// transaction aborts, etc. The client should retry at a higher-level (e.g., restarting a read-
// modify-write sequence).
/**
* The RPC was aborted, typically due to a concurrency issue like sequencer check failures,
* transaction aborts, etc. The client should retry at a higher-level (e.g., restarting a read-
* modify-write sequence).
*/
GRPCErrorCodeAborted = 10,
// The RPC was attempted past the valid range. E.g., enumerating past the end of a list.
// Unlike INVALID_ARGUMENT, this error indicates a problem that may be fixed if the system state
// changes. For example, an RPC to get elements of a list will generate INVALID_ARGUMENT if asked
// to return the element at a negative index, but it will generate OUT_OF_RANGE if asked to return
// the element at an index past the current size of the list.
/**
* The RPC was attempted past the valid range. E.g., enumerating past the end of a list.
* Unlike INVALID_ARGUMENT, this error indicates a problem that may be fixed if the system state
* changes. For example, an RPC to get elements of a list will generate INVALID_ARGUMENT if asked
* to return the element at a negative index, but it will generate OUT_OF_RANGE if asked to return
* the element at an index past the current size of the list.
*/
GRPCErrorCodeOutOfRange = 11,
// The procedure is not implemented or not supported/enabled in this server.
/** The procedure is not implemented or not supported/enabled in this server. */
GRPCErrorCodeUnimplemented = 12,
// Internal error. Means some invariant expected by the server application or the gRPC library has
// been broken.
/**
* Internal error. Means some invariant expected by the server application or the gRPC library has
* been broken.
*/
GRPCErrorCodeInternal = 13,
// The server is currently unavailable. This is most likely a transient condition and may be
// corrected by retrying with a backoff.
/**
* The server is currently unavailable. This is most likely a transient condition and may be
* corrected by retrying with a backoff.
*/
GRPCErrorCodeUnavailable = 14,
// Unrecoverable data loss or corruption.
/** Unrecoverable data loss or corruption. */
GRPCErrorCodeDataLoss = 15,
};
// Keys used in |NSError|'s |userInfo| dictionary to store the response headers and trailers sent by
// the server.
/**
* Keys used in |NSError|'s |userInfo| dictionary to store the response headers and trailers sent by
* the server.
*/
extern id const kGRPCHeadersKey;
extern id const kGRPCTrailersKey;
#pragma mark GRPCCall
// The container of the request headers of an RPC conforms to this protocol, which is a subset of
// NSMutableDictionary's interface. It will become a NSMutableDictionary later on.
// The keys of this container are the header names, which per the HTTP standard are case-
// insensitive. They are stored in lowercase (which is how HTTP/2 mandates them on the wire), and
// can only consist of ASCII characters.
// A header value is a NSString object (with only ASCII characters), unless the header name has the
// suffix "-bin", in which case the value has to be a NSData object.
/**
* The container of the request headers of an RPC conforms to this protocol, which is a subset of
* NSMutableDictionary's interface. It will become a NSMutableDictionary later on.
* The keys of this container are the header names, which per the HTTP standard are case-
* insensitive. They are stored in lowercase (which is how HTTP/2 mandates them on the wire), and
* can only consist of ASCII characters.
* A header value is a NSString object (with only ASCII characters), unless the header name has the
* suffix "-bin", in which case the value has to be a NSData object.
*/
@protocol GRPCRequestHeaders <NSObject>
@property(nonatomic, readonly) NSUInteger count;
@ -154,53 +182,63 @@ extern id const kGRPCTrailersKey;
@end
// Represents a single gRPC remote call.
/** Represents a single gRPC remote call. */
@interface GRPCCall : GRXWriter
// These HTTP headers will be passed to the server as part of this call. Each HTTP header is a
// name-value pair with string names and either string or binary values.
//
// The passed dictionary has to use NSString keys, corresponding to the header names. The value
// associated to each can be a NSString object or a NSData object. E.g.:
//
// call.requestHeaders = @{@"authorization": @"Bearer ..."};
//
// call.requestHeaders[@"my-header-bin"] = someData;
//
// After the call is started, trying to modify this property is an error.
//
// The property is initialized to an empty NSMutableDictionary.
/**
* These HTTP headers will be passed to the server as part of this call. Each HTTP header is a
* name-value pair with string names and either string or binary values.
*
* The passed dictionary has to use NSString keys, corresponding to the header names. The value
* associated to each can be a NSString object or a NSData object. E.g.:
*
* call.requestHeaders = @{@"authorization": @"Bearer ..."};
*
* call.requestHeaders[@"my-header-bin"] = someData;
*
* After the call is started, trying to modify this property is an error.
*
* The property is initialized to an empty NSMutableDictionary.
*/
@property(atomic, readonly) id<GRPCRequestHeaders> requestHeaders;
// This dictionary is populated with the HTTP headers received from the server. This happens before
// any response message is received from the server. It has the same structure as the request
// headers dictionary: Keys are NSString header names; names ending with the suffix "-bin" have a
// NSData value; the others have a NSString value.
//
// The value of this property is nil until all response headers are received, and will change before
// any of -writeValue: or -writesFinishedWithError: are sent to the writeable.
/**
* This dictionary is populated with the HTTP headers received from the server. This happens before
* any response message is received from the server. It has the same structure as the request
* headers dictionary: Keys are NSString header names; names ending with the suffix "-bin" have a
* NSData value; the others have a NSString value.
*
* The value of this property is nil until all response headers are received, and will change before
* any of -writeValue: or -writesFinishedWithError: are sent to the writeable.
*/
@property(atomic, readonly) NSDictionary *responseHeaders;
// Same as responseHeaders, but populated with the HTTP trailers received from the server before the
// call finishes.
//
// The value of this property is nil until all response trailers are received, and will change
// before -writesFinishedWithError: is sent to the writeable.
/**
* Same as responseHeaders, but populated with the HTTP trailers received from the server before the
* call finishes.
*
* The value of this property is nil until all response trailers are received, and will change
* before -writesFinishedWithError: is sent to the writeable.
*/
@property(atomic, readonly) NSDictionary *responseTrailers;
// The request writer has to write NSData objects into the provided Writeable. The server will
// receive each of those separately and in order as distinct messages.
// A gRPC call might not complete until the request writer finishes. On the other hand, the request
// finishing doesn't necessarily make the call to finish, as the server might continue sending
// messages to the response side of the call indefinitely (depending on the semantics of the
// specific remote method called).
// To finish a call right away, invoke cancel.
/**
* The request writer has to write NSData objects into the provided Writeable. The server will
* receive each of those separately and in order as distinct messages.
* A gRPC call might not complete until the request writer finishes. On the other hand, the request
* finishing doesn't necessarily make the call to finish, as the server might continue sending
* messages to the response side of the call indefinitely (depending on the semantics of the
* specific remote method called).
* To finish a call right away, invoke cancel.
*/
- (instancetype)initWithHost:(NSString *)host
path:(NSString *)path
requestsWriter:(GRXWriter *)requestsWriter NS_DESIGNATED_INITIALIZER;
// Finishes the request side of this call, notifies the server that the RPC should be cancelled, and
// finishes the response side of the call with an error of code CANCELED.
/**
* Finishes the request side of this call, notifies the server that the RPC should be cancelled, and
* finishes the response side of the call with an error of code CANCELED.
*/
- (void)cancel;
// TODO(jcanizales): Let specify a deadline. As a category of GRXWriter?

@ -35,12 +35,16 @@
struct grpc_channel;
// Each separate instance of this class represents at least one TCP connection to the provided host.
// Create them using one of the subclasses |GRPCSecureChannel| and |GRPCUnsecuredChannel|.
/**
* Each separate instance of this class represents at least one TCP connection to the provided host.
* Create them using one of the subclasses |GRPCSecureChannel| and |GRPCUnsecuredChannel|.
*/
@interface GRPCChannel : NSObject
@property(nonatomic, readonly) struct grpc_channel *unmanagedChannel;
// This initializer takes ownership of the passed channel, and will destroy it when this object is
// deallocated. It's illegal to pass the same grpc_channel to two different GRPCChannel objects.
/**
* This initializer takes ownership of the passed channel, and will destroy it when this object is
* deallocated. It's illegal to pass the same grpc_channel to two different GRPCChannel objects.
*/
- (instancetype)initWithChannel:(struct grpc_channel *)unmanagedChannel NS_DESIGNATED_INITIALIZER;
@end

@ -36,15 +36,17 @@
typedef void(^GRPCQueueCompletionHandler)(bool success);
// This class lets one more easily use |grpc_completion_queue|. To use it, pass the value of the
// |unmanagedQueue| property of an instance of this class to |grpc_channel_create_call|. Then for
// every |grpc_call_*| method that accepts a tag, you can pass a block of type
// |GRPCQueueCompletionHandler| (remembering to cast it using |__bridge_retained|). The block is
// guaranteed to eventually be called, by a concurrent queue, and then released. Each such block is
// passed a |bool| that tells if the operation was successful.
//
// Release the GRPCCompletionQueue object only after you are not going to pass any more blocks to
// the |grpc_call| that's using it.
/**
* This class lets one more easily use |grpc_completion_queue|. To use it, pass the value of the
* |unmanagedQueue| property of an instance of this class to |grpc_channel_create_call|. Then for
* every |grpc_call_*| method that accepts a tag, you can pass a block of type
* |GRPCQueueCompletionHandler| (remembering to cast it using |__bridge_retained|). The block is
* guaranteed to eventually be called, by a concurrent queue, and then released. Each such block is
* passed a |bool| that tells if the operation was successful.
*
* Release the GRPCCompletionQueue object only after you are not going to pass any more blocks to
* the |grpc_call| that's using it.
*/
@interface GRPCCompletionQueue : NSObject
@property(nonatomic, readonly) grpc_completion_queue *unmanagedQueue;

@ -40,18 +40,18 @@ struct grpc_call;
@property(nonatomic, readonly) NSString *address;
// The following properties should only be modified for testing:
/** The following properties should only be modified for testing: */
@property(nonatomic, getter=isSecure) BOOL secure;
@property(nonatomic, copy) NSString *pathToCertificates;
@property(nonatomic, copy) NSString *hostNameOverride;
// Host objects initialized with the same address are the same.
/** Host objects initialized with the same address are the same. */
+ (instancetype)hostWithAddress:(NSString *)address;
- (instancetype)initWithAddress:(NSString *)address NS_DESIGNATED_INITIALIZER;
// Create a grpc_call object to the provided path on this host.
/** Create a grpc_call object to the provided path on this host. */
- (struct grpc_call *)unmanagedCallWithPath:(NSString *)path
completionQueue:(GRPCCompletionQueue *)queue;

@ -40,13 +40,15 @@ struct grpc_credentials;
@interface GRPCSecureChannel : GRPCChannel
- (instancetype)initWithHost:(NSString *)host;
// Only in tests shouldn't pathToCertificates or hostNameOverride be nil. Passing nil for
// pathToCertificates results in using the default root certificates distributed with the library.
/**
* Only in tests shouldn't pathToCertificates or hostNameOverride be nil. Passing nil for
* pathToCertificates results in using the default root certificates distributed with the library.
*/
- (instancetype)initWithHost:(NSString *)host
pathToCertificates:(NSString *)path
hostNameOverride:(NSString *)hostNameOverride;
// The passed arguments aren't required to be valid beyond the invocation of this initializer.
/** The passed arguments aren't required to be valid beyond the invocation of this initializer. */
- (instancetype)initWithHost:(NSString *)host
credentials:(struct grpc_credentials *)credentials
args:(grpc_channel_args *)args NS_DESIGNATED_INITIALIZER;

@ -39,7 +39,7 @@
@interface GRPCOperation : NSObject
@property(nonatomic, readonly) grpc_op op;
// Guaranteed to be called when the operation has finished.
/** Guaranteed to be called when the operation has finished. */
- (void)finish;
@end

@ -35,7 +35,9 @@
#include <grpc/grpc.h>
@interface NSError (GRPC)
// Returns nil if the status code is OK. Otherwise, a NSError whose code is one of |GRPCErrorCode|
// and whose domain is |kGRPCErrorDomain|.
/**
* Returns nil if the status code is OK. Otherwise, a NSError whose code is one of |GRPCErrorCode|
* and whose domain is |kGRPCErrorDomain|.
*/
+ (instancetype)grpc_errorFromStatusCode:(grpc_status_code)statusCode details:(char *)details;
@end

@ -33,8 +33,10 @@
#import <Foundation/Foundation.h>
// A fully-qualified proto service method name. Full qualification is needed because a gRPC endpoint
// can implement multiple services.
/**
* A fully-qualified proto service method name. Full qualification is needed because a gRPC endpoint
* can implement multiple services.
*/
@interface ProtoMethod : NSObject
@property(nonatomic, readonly) NSString *package;
@property(nonatomic, readonly) NSString *service;

@ -1,3 +1,4 @@
[![Cocoapods](https://img.shields.io/cocoapods/v/gRPC.svg)](https://cocoapods.org/pods/gRPC)
# gRPC for Objective-C
- [Install protoc with the gRPC plugin](#install)

@ -36,25 +36,27 @@
#import "GRXWriteable.h"
#import "GRXWriter.h"
// A buffered pipe is a Writer that also acts as a Writeable.
// Once it is started, whatever values are written into it (via -writeValue:) will be propagated
// immediately, unless flow control prevents it.
// If it is throttled and keeps receiving values, as well as if it receives values before being
// started, it will buffer them and propagate them in order as soon as its state becomes Started.
// If it receives an error (via -writesFinishedWithError:), it will drop any buffered values and
// propagate the error immediately.
//
// Beware that a pipe of this type can't prevent receiving more values when it is paused (for
// example if used to write data to a congested network connection). Because in such situations the
// pipe will keep buffering all data written to it, your application could run out of memory and
// crash. If you want to react to flow control signals to prevent that, instead of using this class
// you can implement an object that conforms to GRXWriter.
//
// Thread-safety:
// The methods of an object of this class should not be called concurrently from different threads.
/**
* A buffered pipe is a Writer that also acts as a Writeable.
* Once it is started, whatever values are written into it (via -writeValue:) will be propagated
* immediately, unless flow control prevents it.
* If it is throttled and keeps receiving values, as well as if it receives values before being
* started, it will buffer them and propagate them in order as soon as its state becomes Started.
* If it receives an error (via -writesFinishedWithError:), it will drop any buffered values and
* propagate the error immediately.
*
* Beware that a pipe of this type can't prevent receiving more values when it is paused (for
* example if used to write data to a congested network connection). Because in such situations the
* pipe will keep buffering all data written to it, your application could run out of memory and
* crash. If you want to react to flow control signals to prevent that, instead of using this class
* you can implement an object that conforms to GRXWriter.
*
* Thread-safety:
* The methods of an object of this class should not be called concurrently from different threads.
*/
@interface GRXBufferedPipe : GRXWriter<GRXWriteable>
// Convenience constructor.
/** Convenience constructor. */
+ (instancetype)pipe;
@end

@ -36,36 +36,48 @@
#import "GRXWriter.h"
#import "GRXWriteable.h"
// This is a thread-safe wrapper over a GRXWriteable instance. It lets one enqueue calls to a
// GRXWriteable instance for the main thread, guaranteeing that writesFinishedWithError: is the last
// message sent to it (no matter what messages are sent to the wrapper, in what order, nor from
// which thread). It also guarantees that, if cancelWithError: is called from the main thread (e.g.
// by the app cancelling the writes), no further messages are sent to the writeable except
// writesFinishedWithError:.
//
// TODO(jcanizales): Let the user specify another queue for the writeable callbacks.
/**
* This is a thread-safe wrapper over a GRXWriteable instance. It lets one enqueue calls to a
* GRXWriteable instance for the main thread, guaranteeing that writesFinishedWithError: is the last
* message sent to it (no matter what messages are sent to the wrapper, in what order, nor from
* which thread). It also guarantees that, if cancelWithError: is called from the main thread (e.g.
* by the app cancelling the writes), no further messages are sent to the writeable except
* writesFinishedWithError:.
*
* TODO(jcanizales): Let the user specify another queue for the writeable callbacks.
*/
@interface GRXConcurrentWriteable : NSObject
// The GRXWriteable passed is the wrapped writeable.
// The GRXWriteable instance is retained until writesFinishedWithError: is sent to it, and released
// after that.
/**
* The GRXWriteable passed is the wrapped writeable.
* The GRXWriteable instance is retained until writesFinishedWithError: is sent to it, and released
* after that.
*/
- (instancetype)initWithWriteable:(id<GRXWriteable>)writeable NS_DESIGNATED_INITIALIZER;
// Enqueues writeValue: to be sent to the writeable in the main thread.
// The passed handler is invoked from the main thread after writeValue: returns.
/**
* Enqueues writeValue: to be sent to the writeable in the main thread.
* The passed handler is invoked from the main thread after writeValue: returns.
*/
- (void)enqueueValue:(id)value completionHandler:(void (^)())handler;
// Enqueues writesFinishedWithError:nil to be sent to the writeable in the main thread. After that
// message is sent to the writeable, all other methods of this object are effectively noops.
/**
* Enqueues writesFinishedWithError:nil to be sent to the writeable in the main thread. After that
* message is sent to the writeable, all other methods of this object are effectively noops.
*/
- (void)enqueueSuccessfulCompletion;
// If the writeable has not yet received a writesFinishedWithError: message, this will enqueue one
// to be sent to it in the main thread, and cancel all other pending messages to the writeable
// enqueued by this object (both past and future).
// The error argument cannot be nil.
/**
* If the writeable has not yet received a writesFinishedWithError: message, this will enqueue one
* to be sent to it in the main thread, and cancel all other pending messages to the writeable
* enqueued by this object (both past and future).
* The error argument cannot be nil.
*/
- (void)cancelWithError:(NSError *)error;
// Cancels all pending messages to the writeable enqueued by this object (both past and future).
// Because the writeable won't receive writesFinishedWithError:, this also releases the writeable.
/**
* Cancels all pending messages to the writeable enqueued by this object (both past and future).
* Because the writeable won't receive writesFinishedWithError:, this also releases the writeable.
*/
- (void)cancelSilently;
@end

@ -33,17 +33,19 @@
#import "GRXWriter.h"
// A "proxy" class that simply forwards values, completion, and errors from its input writer to its
// writeable.
// It is useful as a superclass for pipes that act as a transformation of their
// input writer, and for classes that represent objects with input and
// output sequences of values, like an RPC.
//
// Thread-safety:
// All messages sent to this object need to be serialized. When it is started, the writer it wraps
// is started in the same thread. Manual state changes are propagated to the wrapped writer in the
// same thread too. Importantly, all messages the wrapped writer sends to its writeable need to be
// serialized with any message sent to this object.
/**
* A "proxy" class that simply forwards values, completion, and errors from its input writer to its
* writeable.
* It is useful as a superclass for pipes that act as a transformation of their
* input writer, and for classes that represent objects with input and
* output sequences of values, like an RPC.
*
* Thread-safety:
* All messages sent to this object need to be serialized. When it is started, the writer it wraps
* is started in the same thread. Manual state changes are propagated to the wrapped writer in the
* same thread too. Importantly, all messages the wrapped writer sends to its writeable need to be
* serialized with any message sent to this object.
*/
@interface GRXForwardingWriter : GRXWriter
- (instancetype)initWithWriter:(GRXWriter *)writer NS_DESIGNATED_INITIALIZER;
@end

@ -35,46 +35,60 @@
#import "GRXWriter.h"
// Utility to construct GRXWriter instances from values that are immediately available when
// required.
//
// Thread-safety:
//
// An object of this class shouldn't be messaged concurrently by more than one thread. It will start
// messaging the writeable before |startWithWriteable:| returns, in the same thread. That is the
// only place where the writer can be paused or stopped prematurely.
//
// If a paused writer of this class is resumed, it will start messaging the writeable, in the same
// thread, before |setState:| returns. Because the object can't be legally accessed concurrently,
// that's the only place where it can be paused again (or stopped).
/**
* Utility to construct GRXWriter instances from values that are immediately available when
* required.
*
* Thread-safety:
*
* An object of this class shouldn't be messaged concurrently by more than one thread. It will start
* messaging the writeable before |startWithWriteable:| returns, in the same thread. That is the
* only place where the writer can be paused or stopped prematurely.
*
* If a paused writer of this class is resumed, it will start messaging the writeable, in the same
* thread, before |setState:| returns. Because the object can't be legally accessed concurrently,
* that's the only place where it can be paused again (or stopped).
*/
@interface GRXImmediateWriter : GRXWriter
// Returns a writer that pulls values from the passed NSEnumerator instance and pushes them to
// its writeable. The NSEnumerator is released when it finishes.
/**
* Returns a writer that pulls values from the passed NSEnumerator instance and pushes them to
* its writeable. The NSEnumerator is released when it finishes.
*/
+ (GRXWriter *)writerWithEnumerator:(NSEnumerator *)enumerator;
// Returns a writer that pushes to its writeable the successive values returned by the passed
// block. When the block first returns nil, it is released.
/**
* Returns a writer that pushes to its writeable the successive values returned by the passed
* block. When the block first returns nil, it is released.
*/
+ (GRXWriter *)writerWithValueSupplier:(id (^)())block;
// Returns a writer that iterates over the values of the passed container and pushes them to
// its writeable. The container is released when the iteration is over.
//
// Note that the usual speed gain of NSFastEnumeration over NSEnumerator results from not having to
// call one method per element. Because GRXWriteable instances accept values one by one, that speed
// gain doesn't happen here.
/**
* Returns a writer that iterates over the values of the passed container and pushes them to
* its writeable. The container is released when the iteration is over.
*
* Note that the usual speed gain of NSFastEnumeration over NSEnumerator results from not having to
* call one method per element. Because GRXWriteable instances accept values one by one, that speed
* gain doesn't happen here.
*/
+ (GRXWriter *)writerWithContainer:(id<NSFastEnumeration>)container;
// Returns a writer that sends the passed value to its writeable and then finishes (releasing the
// value).
/**
* Returns a writer that sends the passed value to its writeable and then finishes (releasing the
* value).
*/
+ (GRXWriter *)writerWithValue:(id)value;
// Returns a writer that, as part of its start method, sends the passed error to the writeable
// (then releasing the error).
/**
* Returns a writer that, as part of its start method, sends the passed error to the writeable
* (then releasing the error).
*/
+ (GRXWriter *)writerWithError:(NSError *)error;
// Returns a writer that, as part of its start method, finishes immediately without sending any
// values to its writeable.
/**
* Returns a writer that, as part of its start method, finishes immediately without sending any
* values to its writeable.
*/
+ (GRXWriter *)emptyWriter;
@end

@ -33,16 +33,20 @@
#import <Foundation/Foundation.h>
// A GRXWriteable is an object to which a sequence of values can be sent. The
// sequence finishes with an optional error.
/**
* A GRXWriteable is an object to which a sequence of values can be sent. The
* sequence finishes with an optional error.
*/
@protocol GRXWriteable <NSObject>
// Push the next value of the sequence to the receiving object.
/** Push the next value of the sequence to the receiving object. */
- (void)writeValue:(id)value;
// Signal that the sequence is completed, or that an error ocurred. After this
// message is sent to the instance, neither it nor writeValue: may be
// called again.
/**
* Signal that the sequence is completed, or that an error ocurred. After this
* message is sent to the instance, neither it nor writeValue: may be
* called again.
*/
- (void)writesFinishedWithError:(NSError *)errorOrNil;
@end
@ -51,8 +55,10 @@ typedef void (^GRXCompletionHandler)(NSError *errorOrNil);
typedef void (^GRXSingleHandler)(id value, NSError *errorOrNil);
typedef void (^GRXEventHandler)(BOOL done, id value, NSError *error);
// Utility to create objects that conform to the GRXWriteable protocol, from
// blocks that handle each of the two methods of the protocol.
/**
* Utility to create objects that conform to the GRXWriteable protocol, from
* blocks that handle each of the two methods of the protocol.
*/
@interface GRXWriteable : NSObject<GRXWriteable>
+ (instancetype)writeableWithSingleHandler:(GRXSingleHandler)handler;

@ -35,32 +35,44 @@
@interface GRXWriter (Immediate)
// Returns a writer that pulls values from the passed NSEnumerator instance and pushes them to
// its writeable. The NSEnumerator is released when it finishes.
/**
* Returns a writer that pulls values from the passed NSEnumerator instance and pushes them to
* its writeable. The NSEnumerator is released when it finishes.
*/
+ (instancetype)writerWithEnumerator:(NSEnumerator *)enumerator;
// Returns a writer that pushes to its writeable the successive values returned by the passed
// block. When the block first returns nil, it is released.
/**
* Returns a writer that pushes to its writeable the successive values returned by the passed
* block. When the block first returns nil, it is released.
*/
+ (instancetype)writerWithValueSupplier:(id (^)())block;
// Returns a writer that iterates over the values of the passed container and pushes them to
// its writeable. The container is released when the iteration is over.
//
// Note that the usual speed gain of NSFastEnumeration over NSEnumerator results from not having to
// call one method per element. Because GRXWriteable instances accept values one by one, that speed
// gain doesn't happen here.
/**
* Returns a writer that iterates over the values of the passed container and pushes them to
* its writeable. The container is released when the iteration is over.
*
* Note that the usual speed gain of NSFastEnumeration over NSEnumerator results from not having to
* call one method per element. Because GRXWriteable instances accept values one by one, that speed
* gain doesn't happen here.
*/
+ (instancetype)writerWithContainer:(id<NSFastEnumeration>)container;
// Returns a writer that sends the passed value to its writeable and then finishes (releasing the
// value).
/**
* Returns a writer that sends the passed value to its writeable and then finishes (releasing the
* value).
*/
+ (instancetype)writerWithValue:(id)value;
// Returns a writer that, as part of its start method, sends the passed error to the writeable
// (then releasing the error).
/**
* Returns a writer that, as part of its start method, sends the passed error to the writeable
* (then releasing the error).
*/
+ (instancetype)writerWithError:(NSError *)error;
// Returns a writer that, as part of its start method, finishes immediately without sending any
// values to its writeable.
/**
* Returns a writer that, as part of its start method, finishes immediately without sending any
* values to its writeable.
*/
+ (instancetype)emptyWriter;
@end

@ -35,8 +35,10 @@
@interface GRXWriter (Transformations)
// Returns a writer that wraps the receiver, and has all the values the receiver would write
// transformed by the provided mapping function.
/**
* Returns a writer that wraps the receiver, and has all the values the receiver would write
* transformed by the provided mapping function.
*/
- (GRXWriter *)map:(id (^)(id value))map;
@end

@ -35,73 +35,87 @@
#import "GRXWriteable.h"
// States of a writer.
/** States of a writer. */
typedef NS_ENUM(NSInteger, GRXWriterState) {
// The writer has not yet been given a writeable to which it can push its values. To have a writer
// transition to the Started state, send it a startWithWriteable: message.
//
// A writer's state cannot be manually set to this value.
/**
* The writer has not yet been given a writeable to which it can push its values. To have a writer
* transition to the Started state, send it a startWithWriteable: message.
*
* A writer's state cannot be manually set to this value.
*/
GRXWriterStateNotStarted,
// The writer might push values to the writeable at any moment.
/** The writer might push values to the writeable at any moment. */
GRXWriterStateStarted,
// The writer is temporarily paused, and won't send any more values to the writeable unless its
// state is set back to Started. The writer might still transition to the Finished state at any
// moment, and is allowed to send writesFinishedWithError: to its writeable.
/**
* The writer is temporarily paused, and won't send any more values to the writeable unless its
* state is set back to Started. The writer might still transition to the Finished state at any
* moment, and is allowed to send writesFinishedWithError: to its writeable.
*/
GRXWriterStatePaused,
// The writer has released its writeable and won't interact with it anymore.
//
// One seldomly wants to set a writer's state to this value, as its writeable isn't notified with
// a writesFinishedWithError: message. Instead, sending finishWithError: to the writer will make
// it notify the writeable and then transition to this state.
/**
* The writer has released its writeable and won't interact with it anymore.
*
* One seldomly wants to set a writer's state to this value, as its writeable isn't notified with
* a writesFinishedWithError: message. Instead, sending finishWithError: to the writer will make
* it notify the writeable and then transition to this state.
*/
GRXWriterStateFinished
};
// An GRXWriter object can produce, on demand, a sequence of values. The sequence may be produced
// asynchronously, and it may consist of any number of elements, including none or an infinite
// number.
//
// GRXWriter is the active dual of NSEnumerator. The difference between them is thus whether the
// object plays an active or passive role during usage: A user of NSEnumerator pulls values off it,
// and passes the values to a writeable. A user of GRXWriter, though, just gives it a writeable, and
// the GRXWriter instance pushes values to the writeable. This makes this protocol suitable to
// represent a sequence of future values, as well as collections with internal iteration.
//
// An instance of GRXWriter can start producing values after a writeable is passed to it. It can
// also be commanded to finish the sequence immediately (with an optional error). Finally, it can be
// asked to pause, and resumed later. All GRXWriter objects support pausing and early termination.
//
// Thread-safety:
//
// State transitions take immediate effect if the object is used from a single thread. Subclasses
// might offer stronger guarantees.
//
// Unless otherwise indicated by a conforming subclass, no messages should be sent concurrently to a
// GRXWriter. I.e., conforming classes aren't required to be thread-safe.
/**
* An GRXWriter object can produce, on demand, a sequence of values. The sequence may be produced
* asynchronously, and it may consist of any number of elements, including none or an infinite
* number.
*
* GRXWriter is the active dual of NSEnumerator. The difference between them is thus whether the
* object plays an active or passive role during usage: A user of NSEnumerator pulls values off it,
* and passes the values to a writeable. A user of GRXWriter, though, just gives it a writeable, and
* the GRXWriter instance pushes values to the writeable. This makes this protocol suitable to
* represent a sequence of future values, as well as collections with internal iteration.
*
* An instance of GRXWriter can start producing values after a writeable is passed to it. It can
* also be commanded to finish the sequence immediately (with an optional error). Finally, it can be
* asked to pause, and resumed later. All GRXWriter objects support pausing and early termination.
*
* Thread-safety:
*
* State transitions take immediate effect if the object is used from a single thread. Subclasses
* might offer stronger guarantees.
*
* Unless otherwise indicated by a conforming subclass, no messages should be sent concurrently to a
* GRXWriter. I.e., conforming classes aren't required to be thread-safe.
*/
@interface GRXWriter : NSObject
// This property can be used to query the current state of the writer, which determines how it might
// currently use its writeable. Some state transitions can be triggered by setting this property to
// the corresponding value, and that's useful for advanced use cases like pausing an writer. For
// more details, see the documentation of the enum further down.
/**
* This property can be used to query the current state of the writer, which determines how it might
* currently use its writeable. Some state transitions can be triggered by setting this property to
* the corresponding value, and that's useful for advanced use cases like pausing an writer. For
* more details, see the documentation of the enum further down.
*/
@property(nonatomic) GRXWriterState state;
// Transition to the Started state, and start sending messages to the writeable (a reference to it
// is retained). Messages to the writeable may be sent before the method returns, or they may be
// sent later in the future. See GRXWriteable.h for the different messages a writeable can receive.
//
// If this writer draws its values from an external source (e.g. from the filesystem or from a
// server), calling this method will commonly trigger side effects (like network connections).
//
// This method might only be called on writers in the NotStarted state.
/**
* Transition to the Started state, and start sending messages to the writeable (a reference to it
* is retained). Messages to the writeable may be sent before the method returns, or they may be
* sent later in the future. See GRXWriteable.h for the different messages a writeable can receive.
*
* If this writer draws its values from an external source (e.g. from the filesystem or from a
* server), calling this method will commonly trigger side effects (like network connections).
*
* This method might only be called on writers in the NotStarted state.
*/
- (void)startWithWriteable:(id<GRXWriteable>)writeable;
// Send writesFinishedWithError:errorOrNil to the writeable. Then release the reference to it and
// transition to the Finished state.
//
// This method might only be called on writers in the Started or Paused state.
/**
* Send writesFinishedWithError:errorOrNil to the writeable. Then release the reference to it and
* transition to the Finished state.
*
* This method might only be called on writers in the Started or Paused state.
*/
- (void)finishWithError:(NSError *)errorOrNil;
@end

@ -35,17 +35,23 @@
@interface NSEnumerator (GRXUtil)
// Returns a NSEnumerator instance that iterates through the elements of the passed container that
// supports fast enumeration. Note that this negates the speed benefits of fast enumeration over
// NSEnumerator. It's only intended for the rare cases when one needs the latter and only has the
// former, e.g. for iteration that needs to be paused and resumed later.
/**
* Returns a NSEnumerator instance that iterates through the elements of the passed container that
* supports fast enumeration. Note that this negates the speed benefits of fast enumeration over
* NSEnumerator. It's only intended for the rare cases when one needs the latter and only has the
* former, e.g. for iteration that needs to be paused and resumed later.
*/
+ (NSEnumerator *)grx_enumeratorWithContainer:(id<NSFastEnumeration>)container;
// Returns a NSEnumerator instance that provides a single object before finishing. The value is then
// released.
/**
* Returns a NSEnumerator instance that provides a single object before finishing. The value is then
* released.
*/
+ (NSEnumerator *)grx_enumeratorWithSingleValue:(id)value;
// Returns a NSEnumerator instance that delegates the invocations of nextObject to the passed block.
// When the block first returns nil, it is released.
/**
* Returns a NSEnumerator instance that delegates the invocations of nextObject to the passed block.
* When the block first returns nil, it is released.
*/
+ (NSEnumerator *)grx_enumeratorWithValueSupplier:(id (^)())block;
@end

@ -33,10 +33,14 @@
#import <Foundation/Foundation.h>
// Concrete subclass of NSEnumerator that delegates the invocations of nextObject to a block passed
// on initialization.
/**
* Concrete subclass of NSEnumerator that delegates the invocations of nextObject to a block passed
* on initialization.
*/
@interface GRXNSBlockEnumerator : NSEnumerator
// The first time the passed block returns nil, the enumeration will end and the block will be
// released.
/**
* The first time the passed block returns nil, the enumeration will end and the block will be
* released.
*/
- (instancetype)initWithValueSupplier:(id (^)())block;
@end

@ -33,11 +33,15 @@
#import <Foundation/Foundation.h>
// This is a bridge to interact through NSEnumerator's interface with objects that only conform to
// NSFastEnumeration. (There's nothing specifically fast about it - you certainly don't win any
// speed by using this instead of a NSEnumerator provided by your container).
/**
* This is a bridge to interact through NSEnumerator's interface with objects that only conform to
* NSFastEnumeration. (There's nothing specifically fast about it - you certainly don't win any
* speed by using this instead of a NSEnumerator provided by your container).
*/
@interface GRXNSFastEnumerator : NSEnumerator
// After the iteration of the container (via the NSFastEnumeration protocol) is over, the container
// is released. If the container is modified during enumeration, an exception is thrown.
/**
* After the iteration of the container (via the NSFastEnumeration protocol) is over, the container
* is released. If the container is modified during enumeration, an exception is thrown.
*/
- (instancetype)initWithContainer:(id<NSFastEnumeration>)container;
@end

@ -33,9 +33,11 @@
#import <Foundation/Foundation.h>
// Concrete subclass of NSEnumerator whose instances return a single object before finishing.
/** Concrete subclass of NSEnumerator whose instances return a single object before finishing. */
@interface GRXNSScalarEnumerator : NSEnumerator
// Param value: the single object this instance will produce. After the first invocation of
// nextObject, the value is released.
/**
* Param value: the single object this instance will produce. After the first invocation of
* nextObject, the value is released.
*/
- (instancetype)initWithValue:(id)value;
@end

@ -33,7 +33,7 @@
#import "RxLibrary/GRXForwardingWriter.h"
// A "proxy" writer that transforms all the values of its input writer by using a mapping function.
/** A "proxy" writer that transforms all the values of its input writer by using a mapping function. */
@interface GRXMappingWriter : GRXForwardingWriter
- (instancetype)initWithWriter:(GRXWriter *)writer map:(id (^)(id value))map
NS_DESIGNATED_INITIALIZER;

@ -0,0 +1,128 @@
#!/usr/bin/env python2.7
# 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.
"""Change comments style of source files from // to /** */"""
import re
import sys
if len(sys.argv) < 2:
print("Please provide at least one source file name as argument.")
sys.exit()
for file_name in sys.argv[1:]:
print("Modifying format of {file} comments in place...".format(
file=file_name,
))
# Input
with open(file_name, "r") as input_file:
lines = input_file.readlines()
def peek():
return lines[0]
def read_line():
return lines.pop(0)
def more_input_available():
return lines
# Output
output_lines = []
def write(line):
output_lines.append(line)
def flush_output():
with open(file_name, "w") as output_file:
for line in output_lines:
output_file.write(line)
# Pattern matching
comment_regex = r'^(\s*)//\s(.*)$'
def is_comment(line):
return re.search(comment_regex, line)
def isnt_comment(line):
return not is_comment(line)
def next_line(predicate):
return more_input_available() and predicate(peek())
# Transformation
def indentation_of(line):
match = re.search(comment_regex, line)
return match.group(1)
def content(line):
match = re.search(comment_regex, line)
return match.group(2)
def format_as_block(comment_block):
if len(comment_block) == 0:
return []
indent = indentation_of(comment_block[0])
if len(comment_block) == 1:
return [indent + "/** " + content(comment_block[0]) + " */\n"]
block = ["/**"] + [" * " + content(line) for line in comment_block] + [" */"]
return [indent + line.rstrip() + "\n" for line in block]
# Main algorithm
while more_input_available():
while next_line(isnt_comment):
write(read_line())
comment_block = []
# Get all lines in the same comment block. We could restrict the indentation
# to be the same as the first line of the block, but it's probably ok.
while (next_line(is_comment)):
comment_block.append(read_line())
for line in format_as_block(comment_block):
write(line)
flush_output()

@ -0,0 +1,31 @@
#!/bin/bash
# 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.
find . -type f -name "*.h" ! -path "*/Pods/*" ! -path "./generated_libraries/*" ! -path "./examples/*" ! -path "./tests/*" | xargs ./change-comments.py

@ -42,9 +42,6 @@
#import <RxLibrary/GRXWriteable.h>
#import <RxLibrary/GRXWriter+Immediate.h>
// These are a few tests similar to InteropTests, but which use the generic gRPC client (GRPCCall)
// rather than a generated proto library on top of it.
static NSString * const kHostAddress = @"localhost:5050";
static NSString * const kPackage = @"grpc.testing";
static NSString * const kService = @"TestService";
@ -53,11 +50,10 @@ static ProtoMethod *kInexistentMethod;
static ProtoMethod *kEmptyCallMethod;
static ProtoMethod *kUnaryCallMethod;
// This is an observer class for testing that responseMetadata is KVO-compliant
/** Observer class for testing that responseMetadata is KVO-compliant */
@interface PassthroughObserver : NSObject
- (instancetype) initWithCallback:(void (^)(NSString*, id, NSDictionary*))callback;
- (instancetype) initWithCallback:(void (^)(NSString*, id, NSDictionary*))callback
NS_DESIGNATED_INITIALIZER;
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change
context:(void *)context;
@ -67,23 +63,38 @@ static ProtoMethod *kUnaryCallMethod;
void (^_callback)(NSString*, id, NSDictionary*);
}
- (instancetype)init {
return [self initWithCallback:nil];
}
- (instancetype)initWithCallback:(void (^)(NSString *, id, NSDictionary *))callback {
self = [super init];
if (self) {
if (!callback) {
return nil;
}
if ((self = [super init])) {
_callback = callback;
}
return self;
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context {
_callback(keyPath, object, change);
[object removeObserver:self forKeyPath:keyPath];
}
@end
# pragma mark Tests
/**
* A few tests similar to InteropTests, but which use the generic gRPC client (GRPCCall) rather than
* a generated proto library on top of it. Its RPCs are sent to a local cleartext server.
*
* TODO(jcanizales): Run them also against a local SSL server and against a remote server.
*/
@interface GRPCClientTests : XCTestCase
@end
@ -180,6 +191,7 @@ static ProtoMethod *kUnaryCallMethod;
[self waitForExpectationsWithTimeout:8 handler:nil];
}
// TODO(jcanizales): Activate this test against the remote server.
- (void)testMetadata {
__weak XCTestExpectation *expectation = [self expectationWithDescription:@"RPC unauthorized."];

@ -33,11 +33,17 @@
#import <XCTest/XCTest.h>
// Implements tests as described here:
// https://github.com/grpc/grpc/blob/master/doc/interop-test-descriptions.md
/**
* Implements tests as described here:
* https://github.com/grpc/grpc/blob/master/doc/interop-test-descriptions.md
*
* This is an abstract class that needs to be subclassed. See |+host|.
*/
@interface InteropTests : XCTestCase
// Returns @"grpc-test.sandbox.google.com".
// Override in a subclass to perform the same tests against a different address.
/**
* Host to send the RPCs to. The base implementation returns nil, which would make all tests to
* fail.
* Override in a subclass to perform these tests against a specific address.
*/
+ (NSString *)host;
@end

@ -78,21 +78,20 @@
#pragma mark Tests
static NSString * const kRemoteSSLHost = @"grpc-test.sandbox.google.com";
@implementation InteropTests {
RMTTestService *_service;
}
+ (NSString *)host {
return kRemoteSSLHost;
return nil;
}
- (void)setUp {
_service = [RMTTestService serviceWithHost:self.class.host];
_service = self.class.host ? [RMTTestService serviceWithHost:self.class.host] : nil;
}
- (void)testEmptyUnaryRPC {
XCTAssertNotNil(self.class.host);
__weak XCTestExpectation *expectation = [self expectationWithDescription:@"EmptyUnary"];
RMTEmpty *request = [RMTEmpty message];
@ -110,6 +109,7 @@ static NSString * const kRemoteSSLHost = @"grpc-test.sandbox.google.com";
}
- (void)testLargeUnaryRPC {
XCTAssertNotNil(self.class.host);
__weak XCTestExpectation *expectation = [self expectationWithDescription:@"LargeUnary"];
RMTSimpleRequest *request = [RMTSimpleRequest message];
@ -132,6 +132,7 @@ static NSString * const kRemoteSSLHost = @"grpc-test.sandbox.google.com";
}
- (void)testClientStreamingRPC {
XCTAssertNotNil(self.class.host);
__weak XCTestExpectation *expectation = [self expectationWithDescription:@"ClientStreaming"];
RMTStreamingInputCallRequest *request1 = [RMTStreamingInputCallRequest message];
@ -164,6 +165,7 @@ static NSString * const kRemoteSSLHost = @"grpc-test.sandbox.google.com";
}
- (void)testServerStreamingRPC {
XCTAssertNotNil(self.class.host);
__weak XCTestExpectation *expectation = [self expectationWithDescription:@"ServerStreaming"];
NSArray *expectedSizes = @[@31415, @9, @2653, @58979];
@ -200,6 +202,7 @@ static NSString * const kRemoteSSLHost = @"grpc-test.sandbox.google.com";
}
- (void)testPingPongRPC {
XCTAssertNotNil(self.class.host);
__weak XCTestExpectation *expectation = [self expectationWithDescription:@"PingPong"];
NSArray *requests = @[@27182, @8, @1828, @45904];
@ -243,6 +246,7 @@ static NSString * const kRemoteSSLHost = @"grpc-test.sandbox.google.com";
}
- (void)testEmptyStreamRPC {
XCTAssertNotNil(self.class.host);
__weak XCTestExpectation *expectation = [self expectationWithDescription:@"EmptyStream"];
[_service fullDuplexCallWithRequestsWriter:[GRXWriter emptyWriter]
eventHandler:^(BOOL done,
@ -256,6 +260,7 @@ static NSString * const kRemoteSSLHost = @"grpc-test.sandbox.google.com";
}
- (void)testCancelAfterBeginRPC {
XCTAssertNotNil(self.class.host);
__weak XCTestExpectation *expectation = [self expectationWithDescription:@"CancelAfterBegin"];
// A buffered pipe to which we never write any value acts as a writer that just hangs.
@ -273,6 +278,7 @@ static NSString * const kRemoteSSLHost = @"grpc-test.sandbox.google.com";
}
- (void)testCancelAfterFirstResponseRPC {
XCTAssertNotNil(self.class.host);
__weak XCTestExpectation *expectation = [self expectationWithDescription:@"CancelAfterFirstResponse"];
// A buffered pipe to which we write a single value but never close

@ -31,15 +31,13 @@
*
*/
// Repeat of the tests in InteropTests.m, but sending the RPCs to a local cleartext server instead
// of the remote SSL one.
#import <GRPCClient/GRPCCall+Tests.h>
#import "InteropTests.h"
static NSString * const kLocalCleartextHost = @"localhost:5050";
/** Tests in InteropTests.m, sending the RPCs to a local cleartext server. */
@interface InteropTestsLocalCleartext : InteropTests
@end

@ -31,15 +31,13 @@
*
*/
// Repeat of the tests in InteropTests.m, but sending the RPCs to a local SSL server instead of the
// remote one.
#import <GRPCClient/GRPCCall+Tests.h>
#import "InteropTests.h"
static NSString * const kLocalSSLHost = @"localhost:5051";
/** Tests in InteropTests.m, sending the RPCs to a local SSL server. */
@interface InteropTestsLocalSSL : InteropTests
@end

@ -0,0 +1,50 @@
/*
*
* 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.
*
*/
#import <GRPCClient/GRPCCall+Tests.h>
#import "InteropTests.h"
static NSString * const kRemoteSSLHost = @"grpc-test.sandbox.google.com";
/** Tests in InteropTests.m, sending the RPCs to a remote SSL server. */
@interface InteropTestsRemote : InteropTests
@end
@implementation InteropTestsRemote
+ (NSString *)host {
return kRemoteSSLHost;
}
@end

@ -1,164 +0,0 @@
/*
*
* 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.
*
*/
#import <UIKit/UIKit.h>
#import <XCTest/XCTest.h>
#import <GRPCClient/GRPCCall.h>
#import <ProtoRPC/ProtoMethod.h>
#import <RouteGuide/RouteGuide.pbobjc.h>
#import <RouteGuide/RouteGuide.pbrpc.h>
#import <RxLibrary/GRXWriteable.h>
#import <RxLibrary/GRXWriter+Immediate.h>
// These tests require a gRPC "RouteGuide" sample server to be running locally. You can compile and
// run one by following the instructions here: https://github.com/grpc/grpc/blob/master/examples/cpp/cpptutorial.md#try-it-out
// Be sure to have the C gRPC library installed in your system (for example, by having followed the
// instructions at https://github.com/grpc/homebrew-grpc
static NSString * const kRouteGuideHost = @"http://localhost:50051";
static NSString * const kPackage = @"routeguide";
static NSString * const kService = @"RouteGuide";
@interface LocalClearTextTests : XCTestCase
@end
@implementation LocalClearTextTests
// This test currently fails: see Issue #1907.
//- (void)testConnectionToLocalServer {
// __weak XCTestExpectation *expectation = [self expectationWithDescription:@"Server reachable."];
//
// // This method isn't implemented by the local server.
// GRPCMethodName *method = [[GRPCMethodName alloc] initWithPackage:kPackage
// interface:kService
// method:@"EmptyCall"];
//
// GRXWriter *requestsWriter = [GRXWriter writerWithValue:[NSData data]];
//
// GRPCCall *call = [[GRPCCall alloc] initWithHost:kRouteGuideHost
// method:method
// requestsWriter:requestsWriter];
//
// id<GRXWriteable> responsesWriteable = [[GRXWriteable alloc] initWithValueHandler:^(NSData *value) {
// XCTFail(@"Received unexpected response: %@", value);
// } completionHandler:^(NSError *errorOrNil) {
// XCTAssertNotNil(errorOrNil, @"Finished without error!");
// XCTAssertEqual(errorOrNil.code, 12, @"Finished with unexpected error: %@", errorOrNil);
// [expectation fulfill];
// }];
//
// [call startWithWriteable:responsesWriteable];
//
// [self waitForExpectationsWithTimeout:8.0 handler:nil];
//}
- (void)testEmptyRPC {
__weak XCTestExpectation *response = [self expectationWithDescription:@"Empty response received."];
__weak XCTestExpectation *completion = [self expectationWithDescription:@"Empty RPC completed."];
ProtoMethod *method = [[ProtoMethod alloc] initWithPackage:kPackage
service:kService
method:@"RecordRoute"];
GRXWriter *requestsWriter = [GRXWriter emptyWriter];
GRPCCall *call = [[GRPCCall alloc] initWithHost:kRouteGuideHost
path:method.HTTPPath
requestsWriter:requestsWriter];
id<GRXWriteable> responsesWriteable = [[GRXWriteable alloc] initWithValueHandler:^(NSData *value) {
XCTAssertNotNil(value, @"nil value received as response.");
XCTAssertEqual([value length], 0, @"Non-empty response received: %@", value);
[response fulfill];
} completionHandler:^(NSError *errorOrNil) {
XCTAssertNil(errorOrNil, @"Finished with unexpected error: %@", errorOrNil);
[completion fulfill];
}];
[call startWithWriteable:responsesWriteable];
[self waitForExpectationsWithTimeout:2.0 handler:nil];
}
- (void)testSimpleProtoRPC {
__weak XCTestExpectation *response = [self expectationWithDescription:@"Response received."];
__weak XCTestExpectation *completion = [self expectationWithDescription:@"RPC completed."];
ProtoMethod *method = [[ProtoMethod alloc] initWithPackage:kPackage
service:kService
method:@"GetFeature"];
RGDPoint *point = [RGDPoint message];
point.latitude = 28E7;
point.longitude = -15E7;
GRXWriter *requestsWriter = [GRXWriter writerWithValue:[point data]];
GRPCCall *call = [[GRPCCall alloc] initWithHost:kRouteGuideHost
path:method.HTTPPath
requestsWriter:requestsWriter];
id<GRXWriteable> responsesWriteable = [[GRXWriteable alloc] initWithValueHandler:^(NSData *value) {
XCTAssertNotNil(value, @"nil value received as response.");
RGDFeature *feature = [RGDFeature parseFromData:value error:NULL];
XCTAssertEqualObjects(point, feature.location);
XCTAssertNotNil(feature.name, @"Response's name is nil.");
[response fulfill];
} completionHandler:^(NSError *errorOrNil) {
XCTAssertNil(errorOrNil, @"Finished with unexpected error: %@", errorOrNil);
[completion fulfill];
}];
[call startWithWriteable:responsesWriteable];
[self waitForExpectationsWithTimeout:2.0 handler:nil];
}
- (void)testSimpleProtoRPCUsingGeneratedService {
__weak XCTestExpectation *completion = [self expectationWithDescription:@"RPC completed."];
RGDPoint *point = [RGDPoint message];
point.latitude = 28E7;
point.longitude = -15E7;
RGDRouteGuide *service = [[RGDRouteGuide alloc] initWithHost:kRouteGuideHost];
[service getFeatureWithRequest:point handler:^(RGDFeature *response, NSError *error) {
XCTAssertNil(error, @"Finished with unexpected error: %@", error);
XCTAssertEqualObjects(point, response.location);
XCTAssertNotNil(response.name, @"Response's name is nil.");
[completion fulfill];
}];
[self waitForExpectationsWithTimeout:2.0 handler:nil];
}
@end

@ -6,10 +6,26 @@ pod 'gRPC', :path => "../../.."
pod 'RemoteTest', :path => "../generated_libraries/RemoteTestClient"
pod 'RouteGuide', :path => "../generated_libraries/RouteGuideClient"
link_with 'AllTests'
link_with 'AllTests',
'RxLibraryUnitTests',
'InteropTests',
'InteropTestsLocalSSL',
'InteropTestsLocalCleartext'
target 'Tests' do
end
target 'AllTests' do
end
target 'RxLibraryUnitTests' do
end
target 'InteropTestsRemote' do
end
target 'InteropTestsLocalSSL' do
end
target 'InteropTestsLocalCleartext' do
end

@ -7,16 +7,33 @@
objects = {
/* Begin PBXBuildFile section */
036D953EE34B1FD523647ACD /* libPods.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 35F2B6BF3BAE8F0DC4AFD76E /* libPods.a */; };
08A8BB02D19A53D902B214B8 /* libPods.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 35F2B6BF3BAE8F0DC4AFD76E /* libPods.a */; };
50267643BA114A2A724D4FDF /* libPods.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 35F2B6BF3BAE8F0DC4AFD76E /* libPods.a */; };
6312AE4E1B1BF49B00341DEE /* GRPCClientTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 6312AE4D1B1BF49B00341DEE /* GRPCClientTests.m */; };
63175DFF1B1B9FAF00027841 /* LocalClearTextTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 63175DFE1B1B9FAF00027841 /* LocalClearTextTests.m */; };
63423F4A1B150A5F006CF63C /* libTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 635697C71B14FC11007A7283 /* libTests.a */; };
63423F511B151B77006CF63C /* RxLibraryUnitTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 63423F501B151B77006CF63C /* RxLibraryUnitTests.m */; };
635697CD1B14FC11007A7283 /* Tests.m in Sources */ = {isa = PBXBuildFile; fileRef = 635697CC1B14FC11007A7283 /* Tests.m */; };
635ED2EC1B1A3BC400FDE5C3 /* InteropTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 635ED2EB1B1A3BC400FDE5C3 /* InteropTests.m */; };
63715F561B780C020029CB0B /* InteropTestsLocalCleartext.m in Sources */ = {isa = PBXBuildFile; fileRef = 63715F551B780C020029CB0B /* InteropTestsLocalCleartext.m */; };
6379CC4D1BE1662A001BC0A1 /* InteropTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 635ED2EB1B1A3BC400FDE5C3 /* InteropTests.m */; settings = {ASSET_TAGS = (); }; };
6379CC4E1BE1662B001BC0A1 /* InteropTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 635ED2EB1B1A3BC400FDE5C3 /* InteropTests.m */; settings = {ASSET_TAGS = (); }; };
6379CC501BE16703001BC0A1 /* InteropTestsRemote.m in Sources */ = {isa = PBXBuildFile; fileRef = 6379CC4F1BE16703001BC0A1 /* InteropTestsRemote.m */; settings = {ASSET_TAGS = (); }; };
6379CC511BE1683B001BC0A1 /* InteropTestsRemote.m in Sources */ = {isa = PBXBuildFile; fileRef = 6379CC4F1BE16703001BC0A1 /* InteropTestsRemote.m */; settings = {ASSET_TAGS = (); }; };
6379CC531BE17709001BC0A1 /* TestCertificates.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 63E240CF1B6C63DC005F3B0E /* TestCertificates.bundle */; settings = {ASSET_TAGS = (); }; };
63DC84181BE15179000708E8 /* libTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 635697C71B14FC11007A7283 /* libTests.a */; settings = {ASSET_TAGS = (); }; };
63DC841E1BE15180000708E8 /* RxLibraryUnitTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 63423F501B151B77006CF63C /* RxLibraryUnitTests.m */; settings = {ASSET_TAGS = (); }; };
63DC84281BE15267000708E8 /* libTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 635697C71B14FC11007A7283 /* libTests.a */; settings = {ASSET_TAGS = (); }; };
63DC842E1BE15278000708E8 /* RxLibraryUnitTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 63423F501B151B77006CF63C /* RxLibraryUnitTests.m */; settings = {ASSET_TAGS = (); }; };
63DC842F1BE1527D000708E8 /* InteropTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 635ED2EB1B1A3BC400FDE5C3 /* InteropTests.m */; settings = {ASSET_TAGS = (); }; };
63DC84391BE15294000708E8 /* libTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 635697C71B14FC11007A7283 /* libTests.a */; settings = {ASSET_TAGS = (); }; };
63DC84481BE152B5000708E8 /* libTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 635697C71B14FC11007A7283 /* libTests.a */; settings = {ASSET_TAGS = (); }; };
63DC844E1BE15350000708E8 /* InteropTestsLocalCleartext.m in Sources */ = {isa = PBXBuildFile; fileRef = 63715F551B780C020029CB0B /* InteropTestsLocalCleartext.m */; settings = {ASSET_TAGS = (); }; };
63DC844F1BE15353000708E8 /* InteropTestsLocalSSL.m in Sources */ = {isa = PBXBuildFile; fileRef = 63E240CD1B6C4E2B005F3B0E /* InteropTestsLocalSSL.m */; settings = {ASSET_TAGS = (); }; };
63DC84501BE153AA000708E8 /* GRPCClientTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 6312AE4D1B1BF49B00341DEE /* GRPCClientTests.m */; settings = {ASSET_TAGS = (); }; };
63E240CE1B6C4E2B005F3B0E /* InteropTestsLocalSSL.m in Sources */ = {isa = PBXBuildFile; fileRef = 63E240CD1B6C4E2B005F3B0E /* InteropTestsLocalSSL.m */; };
63E240D01B6C63DC005F3B0E /* TestCertificates.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 63E240CF1B6C63DC005F3B0E /* TestCertificates.bundle */; };
7D8A186224D39101F90230F6 /* libPods.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 35F2B6BF3BAE8F0DC4AFD76E /* libPods.a */; };
DCFAE001609CCBFE69DFA6A1 /* libPods.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 35F2B6BF3BAE8F0DC4AFD76E /* libPods.a */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@ -27,6 +44,34 @@
remoteGlobalIDString = 635697C61B14FC11007A7283;
remoteInfo = Tests;
};
63DC84191BE15179000708E8 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 635697BF1B14FC11007A7283 /* Project object */;
proxyType = 1;
remoteGlobalIDString = 635697C61B14FC11007A7283;
remoteInfo = Tests;
};
63DC84291BE15267000708E8 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 635697BF1B14FC11007A7283 /* Project object */;
proxyType = 1;
remoteGlobalIDString = 635697C61B14FC11007A7283;
remoteInfo = Tests;
};
63DC843A1BE15294000708E8 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 635697BF1B14FC11007A7283 /* Project object */;
proxyType = 1;
remoteGlobalIDString = 635697C61B14FC11007A7283;
remoteInfo = Tests;
};
63DC84491BE152B5000708E8 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 635697BF1B14FC11007A7283 /* Project object */;
proxyType = 1;
remoteGlobalIDString = 635697C61B14FC11007A7283;
remoteInfo = Tests;
};
/* End PBXContainerItemProxy section */
/* Begin PBXCopyFilesBuildPhase section */
@ -45,7 +90,6 @@
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>"; };
35F2B6BF3BAE8F0DC4AFD76E /* libPods.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libPods.a; sourceTree = BUILT_PRODUCTS_DIR; };
6312AE4D1B1BF49B00341DEE /* GRPCClientTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GRPCClientTests.m; sourceTree = "<group>"; };
63175DFE1B1B9FAF00027841 /* LocalClearTextTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LocalClearTextTests.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>"; };
635697C71B14FC11007A7283 /* libTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libTests.a; sourceTree = BUILT_PRODUCTS_DIR; };
@ -53,6 +97,11 @@
635697D81B14FC11007A7283 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
635ED2EB1B1A3BC400FDE5C3 /* InteropTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = InteropTests.m; sourceTree = "<group>"; };
63715F551B780C020029CB0B /* InteropTestsLocalCleartext.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = InteropTestsLocalCleartext.m; sourceTree = "<group>"; };
6379CC4F1BE16703001BC0A1 /* InteropTestsRemote.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = InteropTestsRemote.m; sourceTree = "<group>"; };
63DC84131BE15179000708E8 /* RxLibraryUnitTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RxLibraryUnitTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
63DC84231BE15267000708E8 /* InteropTestsRemote.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = InteropTestsRemote.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
63DC84341BE15294000708E8 /* InteropTestsLocalSSL.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = InteropTestsLocalSSL.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
63DC84431BE152B5000708E8 /* InteropTestsLocalCleartext.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = InteropTestsLocalCleartext.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
63E240CC1B6C4D3A005F3B0E /* InteropTests.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = InteropTests.h; sourceTree = "<group>"; };
63E240CD1B6C4E2B005F3B0E /* InteropTestsLocalSSL.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = InteropTestsLocalSSL.m; sourceTree = "<group>"; };
63E240CF1B6C63DC005F3B0E /* TestCertificates.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = TestCertificates.bundle; sourceTree = "<group>"; };
@ -76,6 +125,42 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
63DC84101BE15179000708E8 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
63DC84181BE15179000708E8 /* libTests.a in Frameworks */,
036D953EE34B1FD523647ACD /* libPods.a in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
63DC84201BE15267000708E8 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
63DC84281BE15267000708E8 /* libTests.a in Frameworks */,
DCFAE001609CCBFE69DFA6A1 /* libPods.a in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
63DC84311BE15294000708E8 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
63DC84391BE15294000708E8 /* libTests.a in Frameworks */,
08A8BB02D19A53D902B214B8 /* libPods.a in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
63DC84401BE152B5000708E8 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
63DC84481BE152B5000708E8 /* libTests.a in Frameworks */,
50267643BA114A2A724D4FDF /* libPods.a in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
@ -112,6 +197,10 @@
children = (
635697C71B14FC11007A7283 /* libTests.a */,
63423F441B150A5F006CF63C /* AllTests.xctest */,
63DC84131BE15179000708E8 /* RxLibraryUnitTests.xctest */,
63DC84231BE15267000708E8 /* InteropTestsRemote.xctest */,
63DC84341BE15294000708E8 /* InteropTestsLocalSSL.xctest */,
63DC84431BE152B5000708E8 /* InteropTestsLocalCleartext.xctest */,
);
name = Products;
sourceTree = "<group>";
@ -122,10 +211,10 @@
6312AE4D1B1BF49B00341DEE /* GRPCClientTests.m */,
63E240CC1B6C4D3A005F3B0E /* InteropTests.h */,
635ED2EB1B1A3BC400FDE5C3 /* InteropTests.m */,
6379CC4F1BE16703001BC0A1 /* InteropTestsRemote.m */,
63E240CD1B6C4E2B005F3B0E /* InteropTestsLocalSSL.m */,
63715F551B780C020029CB0B /* InteropTestsLocalCleartext.m */,
63423F501B151B77006CF63C /* RxLibraryUnitTests.m */,
63175DFE1B1B9FAF00027841 /* LocalClearTextTests.m */,
635697CC1B14FC11007A7283 /* Tests.m */,
635697D71B14FC11007A7283 /* Supporting Files */,
);
@ -152,6 +241,7 @@
63423F411B150A5F006CF63C /* Frameworks */,
63423F421B150A5F006CF63C /* Resources */,
A441F71824DCB9D0CA297748 /* Copy Pods Resources */,
5F14F59509E10C2852014F9E /* Embed Pods Frameworks */,
);
buildRules = (
);
@ -180,6 +270,90 @@
productReference = 635697C71B14FC11007A7283 /* libTests.a */;
productType = "com.apple.product-type.library.static";
};
63DC84121BE15179000708E8 /* RxLibraryUnitTests */ = {
isa = PBXNativeTarget;
buildConfigurationList = 63DC841B1BE15179000708E8 /* Build configuration list for PBXNativeTarget "RxLibraryUnitTests" */;
buildPhases = (
B2986CEEE8CDD4901C97598B /* Check Pods Manifest.lock */,
63DC840F1BE15179000708E8 /* Sources */,
63DC84101BE15179000708E8 /* Frameworks */,
63DC84111BE15179000708E8 /* Resources */,
4F5690DC0E6AD6663FE78B8B /* Embed Pods Frameworks */,
C977426A8727267BBAC7D48E /* Copy Pods Resources */,
);
buildRules = (
);
dependencies = (
63DC841A1BE15179000708E8 /* PBXTargetDependency */,
);
name = RxLibraryUnitTests;
productName = RxLibraryUnitTests;
productReference = 63DC84131BE15179000708E8 /* RxLibraryUnitTests.xctest */;
productType = "com.apple.product-type.bundle.unit-test";
};
63DC84221BE15267000708E8 /* InteropTestsRemote */ = {
isa = PBXNativeTarget;
buildConfigurationList = 63DC842B1BE15267000708E8 /* Build configuration list for PBXNativeTarget "InteropTestsRemote" */;
buildPhases = (
4C406327D3907A5E5FBA8AC9 /* Check Pods Manifest.lock */,
63DC841F1BE15267000708E8 /* Sources */,
63DC84201BE15267000708E8 /* Frameworks */,
63DC84211BE15267000708E8 /* Resources */,
900B6EDD4D16BE7D765C3885 /* Embed Pods Frameworks */,
C2E09DC4BD239F71160F0CC1 /* Copy Pods Resources */,
);
buildRules = (
);
dependencies = (
63DC842A1BE15267000708E8 /* PBXTargetDependency */,
);
name = InteropTestsRemote;
productName = InteropTests;
productReference = 63DC84231BE15267000708E8 /* InteropTestsRemote.xctest */;
productType = "com.apple.product-type.bundle.unit-test";
};
63DC84331BE15294000708E8 /* InteropTestsLocalSSL */ = {
isa = PBXNativeTarget;
buildConfigurationList = 63DC843C1BE15294000708E8 /* Build configuration list for PBXNativeTarget "InteropTestsLocalSSL" */;
buildPhases = (
5C20DCCB71C3991E6FE78C22 /* Check Pods Manifest.lock */,
63DC84301BE15294000708E8 /* Sources */,
63DC84311BE15294000708E8 /* Frameworks */,
63DC84321BE15294000708E8 /* Resources */,
C591129ACE9F6CC5EE03FCDE /* Embed Pods Frameworks */,
693DD0B453431D64EA24FD66 /* Copy Pods Resources */,
);
buildRules = (
);
dependencies = (
63DC843B1BE15294000708E8 /* PBXTargetDependency */,
);
name = InteropTestsLocalSSL;
productName = InteropTestsLocalSSL;
productReference = 63DC84341BE15294000708E8 /* InteropTestsLocalSSL.xctest */;
productType = "com.apple.product-type.bundle.unit-test";
};
63DC84421BE152B5000708E8 /* InteropTestsLocalCleartext */ = {
isa = PBXNativeTarget;
buildConfigurationList = 63DC844B1BE152B5000708E8 /* Build configuration list for PBXNativeTarget "InteropTestsLocalCleartext" */;
buildPhases = (
7418AC7B3844B29E48D24FC7 /* Check Pods Manifest.lock */,
63DC843F1BE152B5000708E8 /* Sources */,
63DC84401BE152B5000708E8 /* Frameworks */,
63DC84411BE152B5000708E8 /* Resources */,
A8E3AC66DF770B774114A30E /* Embed Pods Frameworks */,
8AD3130D3C58A0FB32FF2A36 /* Copy Pods Resources */,
);
buildRules = (
);
dependencies = (
63DC844A1BE152B5000708E8 /* PBXTargetDependency */,
);
name = InteropTestsLocalCleartext;
productName = InteropTestsLocalCleartext;
productReference = 63DC84431BE152B5000708E8 /* InteropTestsLocalCleartext.xctest */;
productType = "com.apple.product-type.bundle.unit-test";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
@ -195,6 +369,18 @@
635697C61B14FC11007A7283 = {
CreatedOnToolsVersion = 6.3.1;
};
63DC84121BE15179000708E8 = {
CreatedOnToolsVersion = 7.0.1;
};
63DC84221BE15267000708E8 = {
CreatedOnToolsVersion = 7.0.1;
};
63DC84331BE15294000708E8 = {
CreatedOnToolsVersion = 7.0.1;
};
63DC84421BE152B5000708E8 = {
CreatedOnToolsVersion = 7.0.1;
};
};
};
buildConfigurationList = 635697C21B14FC11007A7283 /* Build configuration list for PBXProject "Tests" */;
@ -211,6 +397,10 @@
targets = (
635697C61B14FC11007A7283 /* Tests */,
63423F431B150A5F006CF63C /* AllTests */,
63DC84121BE15179000708E8 /* RxLibraryUnitTests */,
63DC84221BE15267000708E8 /* InteropTestsRemote */,
63DC84331BE15294000708E8 /* InteropTestsLocalSSL */,
63DC84421BE152B5000708E8 /* InteropTestsLocalCleartext */,
);
};
/* End PBXProject section */
@ -224,9 +414,158 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
63DC84111BE15179000708E8 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
63DC84211BE15267000708E8 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
63DC84321BE15294000708E8 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
6379CC531BE17709001BC0A1 /* TestCertificates.bundle in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
63DC84411BE152B5000708E8 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
4C406327D3907A5E5FBA8AC9 /* Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "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;
};
4F5690DC0E6AD6663FE78B8B /* Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Embed Pods Frameworks";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods/Pods-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
5C20DCCB71C3991E6FE78C22 /* Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "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;
};
5F14F59509E10C2852014F9E /* Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Embed Pods Frameworks";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods/Pods-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
693DD0B453431D64EA24FD66 /* Copy Pods Resources */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Copy Pods Resources";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods/Pods-resources.sh\"\n";
showEnvVarsInLog = 0;
};
7418AC7B3844B29E48D24FC7 /* Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "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;
};
8AD3130D3C58A0FB32FF2A36 /* Copy Pods Resources */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Copy Pods Resources";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods/Pods-resources.sh\"\n";
showEnvVarsInLog = 0;
};
900B6EDD4D16BE7D765C3885 /* Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Embed Pods Frameworks";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods/Pods-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
914ADDD7106BA9BB8A7E569F /* Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
@ -257,6 +596,81 @@
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods/Pods-resources.sh\"\n";
showEnvVarsInLog = 0;
};
A8E3AC66DF770B774114A30E /* Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Embed Pods Frameworks";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods/Pods-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
B2986CEEE8CDD4901C97598B /* Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "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;
};
C2E09DC4BD239F71160F0CC1 /* Copy Pods Resources */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Copy Pods Resources";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods/Pods-resources.sh\"\n";
showEnvVarsInLog = 0;
};
C591129ACE9F6CC5EE03FCDE /* Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Embed Pods Frameworks";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods/Pods-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
C977426A8727267BBAC7D48E /* Copy Pods Resources */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Copy Pods Resources";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods/Pods-resources.sh\"\n";
showEnvVarsInLog = 0;
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
@ -265,11 +679,11 @@
buildActionMask = 2147483647;
files = (
63715F561B780C020029CB0B /* InteropTestsLocalCleartext.m in Sources */,
63175DFF1B1B9FAF00027841 /* LocalClearTextTests.m in Sources */,
63423F511B151B77006CF63C /* RxLibraryUnitTests.m in Sources */,
6379CC511BE1683B001BC0A1 /* InteropTestsRemote.m in Sources */,
63E240CE1B6C4E2B005F3B0E /* InteropTestsLocalSSL.m in Sources */,
6312AE4E1B1BF49B00341DEE /* GRPCClientTests.m in Sources */,
635ED2EC1B1A3BC400FDE5C3 /* InteropTests.m in Sources */,
63DC842E1BE15278000708E8 /* RxLibraryUnitTests.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -281,6 +695,42 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
63DC840F1BE15179000708E8 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
63DC841E1BE15180000708E8 /* RxLibraryUnitTests.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
63DC841F1BE15267000708E8 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
63DC842F1BE1527D000708E8 /* InteropTests.m in Sources */,
6379CC501BE16703001BC0A1 /* InteropTestsRemote.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
63DC84301BE15294000708E8 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
63DC844F1BE15353000708E8 /* InteropTestsLocalSSL.m in Sources */,
6379CC4D1BE1662A001BC0A1 /* InteropTests.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
63DC843F1BE152B5000708E8 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
63DC84501BE153AA000708E8 /* GRPCClientTests.m in Sources */,
63DC844E1BE15350000708E8 /* InteropTestsLocalCleartext.m in Sources */,
6379CC4E1BE1662B001BC0A1 /* InteropTests.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
@ -289,6 +739,26 @@
target = 635697C61B14FC11007A7283 /* Tests */;
targetProxy = 63423F4B1B150A5F006CF63C /* PBXContainerItemProxy */;
};
63DC841A1BE15179000708E8 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 635697C61B14FC11007A7283 /* Tests */;
targetProxy = 63DC84191BE15179000708E8 /* PBXContainerItemProxy */;
};
63DC842A1BE15267000708E8 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 635697C61B14FC11007A7283 /* Tests */;
targetProxy = 63DC84291BE15267000708E8 /* PBXContainerItemProxy */;
};
63DC843B1BE15294000708E8 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 635697C61B14FC11007A7283 /* Tests */;
targetProxy = 63DC843A1BE15294000708E8 /* PBXContainerItemProxy */;
};
63DC844A1BE152B5000708E8 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 635697C61B14FC11007A7283 /* Tests */;
targetProxy = 63DC84491BE152B5000708E8 /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin XCBuildConfiguration section */
@ -418,6 +888,110 @@
};
name = Release;
};
63DC841C1BE15179000708E8 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = FF7B5489BCFE40111D768DD0 /* Pods.debug.xcconfig */;
buildSettings = {
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_TESTABILITY = YES;
INFOPLIST_FILE = Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = io.grpc.RxLibraryUnitTests;
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Debug;
};
63DC841D1BE15179000708E8 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 0A4F89D9C90E9C30990218F0 /* Pods.release.xcconfig */;
buildSettings = {
INFOPLIST_FILE = Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = io.grpc.RxLibraryUnitTests;
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Release;
};
63DC842C1BE15267000708E8 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = FF7B5489BCFE40111D768DD0 /* Pods.debug.xcconfig */;
buildSettings = {
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_TESTABILITY = YES;
INFOPLIST_FILE = Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = io.grpc.InteropTests;
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Debug;
};
63DC842D1BE15267000708E8 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 0A4F89D9C90E9C30990218F0 /* Pods.release.xcconfig */;
buildSettings = {
INFOPLIST_FILE = Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = io.grpc.InteropTests;
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Release;
};
63DC843D1BE15294000708E8 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = FF7B5489BCFE40111D768DD0 /* Pods.debug.xcconfig */;
buildSettings = {
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_TESTABILITY = YES;
INFOPLIST_FILE = Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = io.grpc.InteropTestsLocalSSL;
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Debug;
};
63DC843E1BE15294000708E8 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 0A4F89D9C90E9C30990218F0 /* Pods.release.xcconfig */;
buildSettings = {
INFOPLIST_FILE = Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = io.grpc.InteropTestsLocalSSL;
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Release;
};
63DC844C1BE152B5000708E8 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = FF7B5489BCFE40111D768DD0 /* Pods.debug.xcconfig */;
buildSettings = {
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_TESTABILITY = YES;
INFOPLIST_FILE = Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = io.grpc.InteropTestsLocalCleartext;
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Debug;
};
63DC844D1BE152B5000708E8 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 0A4F89D9C90E9C30990218F0 /* Pods.release.xcconfig */;
buildSettings = {
INFOPLIST_FILE = Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = io.grpc.InteropTestsLocalCleartext;
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
@ -448,6 +1022,42 @@
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
63DC841B1BE15179000708E8 /* Build configuration list for PBXNativeTarget "RxLibraryUnitTests" */ = {
isa = XCConfigurationList;
buildConfigurations = (
63DC841C1BE15179000708E8 /* Debug */,
63DC841D1BE15179000708E8 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
63DC842B1BE15267000708E8 /* Build configuration list for PBXNativeTarget "InteropTestsRemote" */ = {
isa = XCConfigurationList;
buildConfigurations = (
63DC842C1BE15267000708E8 /* Debug */,
63DC842D1BE15267000708E8 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
63DC843C1BE15294000708E8 /* Build configuration list for PBXNativeTarget "InteropTestsLocalSSL" */ = {
isa = XCConfigurationList;
buildConfigurations = (
63DC843D1BE15294000708E8 /* Debug */,
63DC843E1BE15294000708E8 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
63DC844B1BE152B5000708E8 /* Build configuration list for PBXNativeTarget "InteropTestsLocalCleartext" */ = {
isa = XCConfigurationList;
buildConfigurations = (
63DC844C1BE152B5000708E8 /* Debug */,
63DC844D1BE152B5000708E8 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 635697BF1B14FC11007A7283 /* Project object */;

@ -23,10 +23,10 @@
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
buildConfiguration = "Debug">
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
<TestableReference
skipped = "NO">
@ -44,6 +44,9 @@
<Test
Identifier = "GRPCClientTests/testMetadata">
</Test>
<Test
Identifier = "InteropTests">
</Test>
<Test
Identifier = "LocalClearTextTests">
</Test>
@ -62,15 +65,18 @@
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"
buildConfiguration = "Debug"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<MacroExpansion>
<BuildableReference
@ -85,10 +91,10 @@
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
buildConfiguration = "Release"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference

@ -0,0 +1,101 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0700"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "NO"
buildForArchiving = "NO"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "63DC84421BE152B5000708E8"
BuildableName = "InteropTestsLocalCleartext.xctest"
BlueprintName = "InteropTestsLocalCleartext"
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 = "63DC84421BE152B5000708E8"
BuildableName = "InteropTestsLocalCleartext.xctest"
BlueprintName = "InteropTestsLocalCleartext"
ReferencedContainer = "container:Tests.xcodeproj">
</BuildableReference>
<SkippedTests>
<Test
Identifier = "GRPCClientTests/testConnectionToRemoteServer">
</Test>
<Test
Identifier = "GRPCClientTests/testMetadata">
</Test>
<Test
Identifier = "InteropTests">
</Test>
</SkippedTests>
</TestableReference>
</Testables>
<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 = "63DC84421BE152B5000708E8"
BuildableName = "InteropTestsLocalCleartext.xctest"
BlueprintName = "InteropTestsLocalCleartext"
ReferencedContainer = "container:Tests.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "63DC84421BE152B5000708E8"
BuildableName = "InteropTestsLocalCleartext.xctest"
BlueprintName = "InteropTestsLocalCleartext"
ReferencedContainer = "container:Tests.xcodeproj">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

@ -0,0 +1,95 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0700"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "NO"
buildForArchiving = "NO"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "63DC84331BE15294000708E8"
BuildableName = "InteropTestsLocalSSL.xctest"
BlueprintName = "InteropTestsLocalSSL"
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 = "63DC84331BE15294000708E8"
BuildableName = "InteropTestsLocalSSL.xctest"
BlueprintName = "InteropTestsLocalSSL"
ReferencedContainer = "container:Tests.xcodeproj">
</BuildableReference>
<SkippedTests>
<Test
Identifier = "InteropTests">
</Test>
</SkippedTests>
</TestableReference>
</Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "63DC84331BE15294000708E8"
BuildableName = "InteropTestsLocalSSL.xctest"
BlueprintName = "InteropTestsLocalSSL"
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 = "63DC84331BE15294000708E8"
BuildableName = "InteropTestsLocalSSL.xctest"
BlueprintName = "InteropTestsLocalSSL"
ReferencedContainer = "container:Tests.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

@ -0,0 +1,95 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0700"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "NO"
buildForArchiving = "NO"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "63DC84221BE15267000708E8"
BuildableName = "InteropTestsRemote.xctest"
BlueprintName = "InteropTestsRemote"
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 = "63DC84221BE15267000708E8"
BuildableName = "InteropTestsRemote.xctest"
BlueprintName = "InteropTestsRemote"
ReferencedContainer = "container:Tests.xcodeproj">
</BuildableReference>
<SkippedTests>
<Test
Identifier = "InteropTests">
</Test>
</SkippedTests>
</TestableReference>
</Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "63DC84221BE15267000708E8"
BuildableName = "InteropTestsRemote.xctest"
BlueprintName = "InteropTestsRemote"
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 = "63DC84221BE15267000708E8"
BuildableName = "InteropTestsRemote.xctest"
BlueprintName = "InteropTestsRemote"
ReferencedContainer = "container:Tests.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

@ -0,0 +1,90 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0700"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "NO"
buildForArchiving = "NO"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "63DC84121BE15179000708E8"
BuildableName = "RxLibraryUnitTests.xctest"
BlueprintName = "RxLibraryUnitTests"
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 = "63DC84121BE15179000708E8"
BuildableName = "RxLibraryUnitTests.xctest"
BlueprintName = "RxLibraryUnitTests"
ReferencedContainer = "container:Tests.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "63DC84121BE15179000708E8"
BuildableName = "RxLibraryUnitTests.xctest"
BlueprintName = "RxLibraryUnitTests"
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 = "63DC84121BE15179000708E8"
BuildableName = "RxLibraryUnitTests.xctest"
BlueprintName = "RxLibraryUnitTests"
ReferencedContainer = "container:Tests.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

@ -0,0 +1,40 @@
#!/bin/bash
# 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.
set -e
command -v php-cs-fixer > /dev/null || {
echo "Cannot find php-cs-fixer. Exiting..."
exit 1
}
cd $(dirname $0)/..
php-cs-fixer fix lib/Grpc || true
php-cs-fixer fix tests/generated_code || true
php-cs-fixer fix tests/interop || true
php-cs-fixer fix tests/unit_tests || true

@ -31,23 +31,30 @@
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
namespace Grpc;
abstract class AbstractCall {
namespace Grpc;
abstract class AbstractCall
{
protected $call;
protected $deserialize;
protected $metadata;
/**
* Create a new Call wrapper object.
*
* @param Channel $channel The channel to communicate on
* @param string $method The method to call on the remote server
* @param string $method The method to call on the
* remote server
* @param callback $deserialize A callback function to deserialize
* the response
* @param (optional) long $timeout Timeout in microseconds
*/
public function __construct(Channel $channel, $method, $deserialize, $timeout = false) {
public function __construct(Channel $channel,
$method,
$deserialize,
$timeout = false)
{
if ($timeout) {
$now = Timeval::now();
$delta = new Timeval($timeout);
@ -63,33 +70,40 @@ abstract class AbstractCall {
/**
* @return The metadata sent by the server.
*/
public function getMetadata() {
public function getMetadata()
{
return $this->metadata;
}
/**
* @return string The URI of the endpoint.
*/
public function getPeer() {
public function getPeer()
{
return $this->call->getPeer();
}
/**
* Cancels the call
* Cancels the call.
*/
public function cancel() {
public function cancel()
{
$this->call->cancel();
}
/**
* Deserialize a response value to an object.
*
* @param string $value The binary value to deserialize
*
* @return The deserialized value
*/
protected function deserializeResponse($value) {
protected function deserializeResponse($value)
{
if ($value === null) {
return null;
return;
}
return call_user_func($this->deserialize, $value);
}
}

@ -31,14 +31,15 @@
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
namespace Grpc;
/**
* Base class for generated client stubs. Stub methods are expected to call
* _simpleRequest or _streamRequest and return the result.
*/
class BaseStub {
class BaseStub
{
private $hostname;
private $channel;
@ -51,7 +52,8 @@ class BaseStub {
* - 'update_metadata': (optional) a callback function which takes in a
* metadata array, and returns an updated metadata array
*/
public function __construct($hostname, $opts) {
public function __construct($hostname, $opts)
{
$this->hostname = $hostname;
$this->update_metadata = null;
if (isset($opts['update_metadata'])) {
@ -70,24 +72,29 @@ class BaseStub {
/**
* @return string The URI of the endpoint.
*/
public function getTarget() {
public function getTarget()
{
return $this->channel->getTarget();
}
/**
* @param $try_to_connect bool
*
* @return int The grpc connectivity state
*/
public function getConnectivityState($try_to_connect = false) {
public function getConnectivityState($try_to_connect = false)
{
return $this->channel->getConnectivityState($try_to_connect);
}
/**
* @param $timeout in microseconds
*
* @return bool true if channel is ready
* @throw Exception if channel is in FATAL_ERROR state
*/
public function waitForReady($timeout) {
public function waitForReady($timeout)
{
$new_state = $this->getConnectivityState(true);
if ($this->_checkConnectivityState($new_state)) {
return true;
@ -106,63 +113,75 @@ class BaseStub {
}
// deadline has passed
$new_state = $this->getConnectivityState();
return $this->_checkConnectivityState($new_state);
}
private function _checkConnectivityState($new_state) {
private function _checkConnectivityState($new_state)
{
if ($new_state == \Grpc\CHANNEL_READY) {
return true;
}
if ($new_state == \Grpc\CHANNEL_FATAL_FAILURE) {
// @codeCoverageIgnoreStart
throw new \Exception('Failed to connect to server');
// @codeCoverageIgnoreEnd
}
return false;
}
/**
* Close the communication channel associated with this stub
* Close the communication channel associated with this stub.
*/
public function close() {
public function close()
{
$this->channel->close();
}
/**
* constructs the auth uri for the jwt
* constructs the auth uri for the jwt.
*/
private function _get_jwt_aud_uri($method) {
private function _get_jwt_aud_uri($method)
{
$last_slash_idx = strrpos($method, '/');
if ($last_slash_idx === false) {
throw new \InvalidArgumentException('service name must have a slash');
throw new \InvalidArgumentException(
'service name must have a slash');
}
$service_name = substr($method, 0, $last_slash_idx);
return "https://" . $this->hostname . $service_name;
return 'https://'.$this->hostname.$service_name;
}
/**
* extract $timeout from $metadata
* extract $timeout from $metadata.
*
* @param $metadata The metadata map
*
* @return list($metadata_copy, $timeout)
*/
private function _extract_timeout_from_metadata($metadata) {
private function _extract_timeout_from_metadata($metadata)
{
$timeout = false;
$metadata_copy = $metadata;
if (isset($metadata['timeout'])) {
$timeout = $metadata['timeout'];
unset($metadata_copy['timeout']);
}
return array($metadata_copy, $timeout);
return [$metadata_copy, $timeout];
}
/**
* validate and normalize the metadata array
* validate and normalize the metadata array.
*
* @param $metadata The metadata map
*
* @return $metadata Validated and key-normalized metadata map
* @throw InvalidArgumentException if key contains invalid characters
*/
private function _validate_and_normalize_metadata($metadata) {
$metadata_copy = array();
private function _validate_and_normalize_metadata($metadata)
{
$metadata_copy = [];
foreach ($metadata as $key => $value) {
if (!preg_match('/^[A-Za-z\d_-]+$/', $key)) {
throw new \InvalidArgumentException(
@ -171,115 +190,149 @@ class BaseStub {
}
$metadata_copy[strtolower($key)] = $value;
}
return $metadata_copy;
}
/* This class is intended to be subclassed by generated code, so all functions
begin with "_" to avoid name collisions. */
/* This class is intended to be subclassed by generated code, so
* all functions begin with "_" to avoid name collisions. */
/**
* Call a remote method that takes a single argument and has a single output
* Call a remote method that takes a single argument and has a
* single output.
*
* @param string $method The name of the method to call
* @param $argument The argument to the method
* @param callable $deserialize A function that deserializes the response
* @param array $metadata A metadata map to send to the server
*
* @return SimpleSurfaceActiveCall The active call object
*/
public function _simpleRequest($method,
$argument,
callable $deserialize,
$metadata = array(),
$options = array()) {
list($actual_metadata, $timeout) = $this->_extract_timeout_from_metadata($metadata);
$call = new UnaryCall($this->channel, $method, $deserialize, $timeout);
$metadata = [],
$options = [])
{
list($actual_metadata, $timeout) =
$this->_extract_timeout_from_metadata($metadata);
$call = new UnaryCall($this->channel,
$method,
$deserialize,
$timeout);
$jwt_aud_uri = $this->_get_jwt_aud_uri($method);
if (is_callable($this->update_metadata)) {
$actual_metadata = call_user_func($this->update_metadata,
$actual_metadata,
$jwt_aud_uri);
}
$actual_metadata = $this->_validate_and_normalize_metadata($actual_metadata);
$actual_metadata = $this->_validate_and_normalize_metadata(
$actual_metadata);
$call->start($argument, $actual_metadata, $options);
return $call;
}
/**
* Call a remote method that takes a stream of arguments and has a single
* output
* output.
*
* @param string $method The name of the method to call
* @param $arguments An array or Traversable of arguments to stream to the
* server
* @param callable $deserialize A function that deserializes the response
* @param array $metadata A metadata map to send to the server
*
* @return ClientStreamingSurfaceActiveCall The active call object
*/
public function _clientStreamRequest($method,
callable $deserialize,
$metadata = array()) {
list($actual_metadata, $timeout) = $this->_extract_timeout_from_metadata($metadata);
$call = new ClientStreamingCall($this->channel, $method, $deserialize, $timeout);
$metadata = [])
{
list($actual_metadata, $timeout) =
$this->_extract_timeout_from_metadata($metadata);
$call = new ClientStreamingCall($this->channel,
$method,
$deserialize,
$timeout);
$jwt_aud_uri = $this->_get_jwt_aud_uri($method);
if (is_callable($this->update_metadata)) {
$actual_metadata = call_user_func($this->update_metadata,
$actual_metadata,
$jwt_aud_uri);
}
$actual_metadata = $this->_validate_and_normalize_metadata($actual_metadata);
$actual_metadata = $this->_validate_and_normalize_metadata(
$actual_metadata);
$call->start($actual_metadata);
return $call;
}
/**
* Call a remote method that takes a single argument and returns a stream of
* responses
* responses.
*
* @param string $method The name of the method to call
* @param $argument The argument to the method
* @param callable $deserialize A function that deserializes the responses
* @param array $metadata A metadata map to send to the server
*
* @return ServerStreamingSurfaceActiveCall The active call object
*/
public function _serverStreamRequest($method,
$argument,
callable $deserialize,
$metadata = array(),
$options = array()) {
list($actual_metadata, $timeout) = $this->_extract_timeout_from_metadata($metadata);
$call = new ServerStreamingCall($this->channel, $method, $deserialize, $timeout);
$metadata = [],
$options = [])
{
list($actual_metadata, $timeout) =
$this->_extract_timeout_from_metadata($metadata);
$call = new ServerStreamingCall($this->channel,
$method,
$deserialize,
$timeout);
$jwt_aud_uri = $this->_get_jwt_aud_uri($method);
if (is_callable($this->update_metadata)) {
$actual_metadata = call_user_func($this->update_metadata,
$actual_metadata,
$jwt_aud_uri);
}
$actual_metadata = $this->_validate_and_normalize_metadata($actual_metadata);
$actual_metadata = $this->_validate_and_normalize_metadata(
$actual_metadata);
$call->start($argument, $actual_metadata, $options);
return $call;
}
/**
* Call a remote method with messages streaming in both directions
* Call a remote method with messages streaming in both directions.
*
* @param string $method The name of the method to call
* @param callable $deserialize A function that deserializes the responses
* @param array $metadata A metadata map to send to the server
*
* @return BidiStreamingSurfaceActiveCall The active call object
*/
public function _bidiRequest($method,
callable $deserialize,
$metadata = array()) {
list($actual_metadata, $timeout) = $this->_extract_timeout_from_metadata($metadata);
$call = new BidiStreamingCall($this->channel, $method, $deserialize, $timeout);
$metadata = [])
{
list($actual_metadata, $timeout) =
$this->_extract_timeout_from_metadata($metadata);
$call = new BidiStreamingCall($this->channel,
$method,
$deserialize,
$timeout);
$jwt_aud_uri = $this->_get_jwt_aud_uri($method);
if (is_callable($this->update_metadata)) {
$actual_metadata = call_user_func($this->update_metadata,
$actual_metadata,
$jwt_aud_uri);
}
$actual_metadata = $this->_validate_and_normalize_metadata($actual_metadata);
$actual_metadata = $this->_validate_and_normalize_metadata(
$actual_metadata);
$call->start($actual_metadata);
return $call;
}
}

@ -31,26 +31,34 @@
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
namespace Grpc;
/**
* Represents an active call that allows for sending and recieving messages in
* streams in any order.
*/
class BidiStreamingCall extends AbstractCall {
class BidiStreamingCall extends AbstractCall
{
/**
* Start the call
* Start the call.
*
* @param array $metadata Metadata to send with the call, if applicable
*/
public function start($metadata = array()) {
$this->call->startBatch([OP_SEND_INITIAL_METADATA => $metadata]);
public function start($metadata = [])
{
$this->call->startBatch([
OP_SEND_INITIAL_METADATA => $metadata,
]);
}
/**
* Reads the next value from the server.
*
* @return The next value from the server, or null if there is none
*/
public function read() {
public function read()
{
$batch = [OP_RECV_MESSAGE => true];
if ($this->metadata === null) {
$batch[OP_RECV_INITIAL_METADATA] = true;
@ -59,40 +67,51 @@ class BidiStreamingCall extends AbstractCall {
if ($this->metadata === null) {
$this->metadata = $read_event->metadata;
}
return $this->deserializeResponse($read_event->message);
}
/**
* Write a single message to the server. This cannot be called after
* writesDone is called.
*
* @param ByteBuffer $data The data to write
* @param array $options an array of options, possible keys:
* 'flags' => a number
*/
public function write($data, $options = array()) {
public function write($data, $options = [])
{
$message_array = ['message' => $data->serialize()];
if (isset($options['flags'])) {
$message_array['flags'] = $options['flags'];
}
$this->call->startBatch([OP_SEND_MESSAGE => $message_array]);
$this->call->startBatch([
OP_SEND_MESSAGE => $message_array,
]);
}
/**
* Indicate that no more writes will be sent.
*/
public function writesDone() {
$this->call->startBatch([OP_SEND_CLOSE_FROM_CLIENT => true]);
public function writesDone()
{
$this->call->startBatch([
OP_SEND_CLOSE_FROM_CLIENT => true,
]);
}
/**
* Wait for the server to send the status, and return it.
*
* @return object The status object, with integer $code, string $details,
* and array $metadata members
*/
public function getStatus() {
public function getStatus()
{
$status_event = $this->call->startBatch([
OP_RECV_STATUS_ON_CLIENT => true
OP_RECV_STATUS_ON_CLIENT => true,
]);
return $status_event->status;
}
}

@ -31,47 +31,61 @@
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
namespace Grpc;
/**
* Represents an active call that sends a stream of messages and then gets a
* single response.
*/
class ClientStreamingCall extends AbstractCall {
class ClientStreamingCall extends AbstractCall
{
/**
* Start the call.
*
* @param array $metadata Metadata to send with the call, if applicable
*/
public function start($metadata = array()) {
$this->call->startBatch([OP_SEND_INITIAL_METADATA => $metadata]);
public function start($metadata = [])
{
$this->call->startBatch([
OP_SEND_INITIAL_METADATA => $metadata,
]);
}
/**
* Write a single message to the server. This cannot be called after
* wait is called.
*
* @param ByteBuffer $data The data to write
* @param array $options an array of options, possible keys:
* 'flags' => a number
*/
public function write($data, $options = array()) {
public function write($data, $options = [])
{
$message_array = ['message' => $data->serialize()];
if (isset($options['flags'])) {
$message_array['flags'] = $options['flags'];
}
$this->call->startBatch([OP_SEND_MESSAGE => $message_array]);
$this->call->startBatch([
OP_SEND_MESSAGE => $message_array,
]);
}
/**
* Wait for the server to respond with data and a status
* Wait for the server to respond with data and a status.
*
* @return [response data, status]
*/
public function wait() {
public function wait()
{
$event = $this->call->startBatch([
OP_SEND_CLOSE_FROM_CLIENT => true,
OP_RECV_INITIAL_METADATA => true,
OP_RECV_MESSAGE => true,
OP_RECV_STATUS_ON_CLIENT => true]);
OP_RECV_STATUS_ON_CLIENT => true,
]);
$this->metadata = $event->metadata;
return array($this->deserializeResponse($event->message), $event->status);
return [$this->deserializeResponse($event->message), $event->status];
}
}

@ -31,21 +31,25 @@
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
namespace Grpc;
/**
* Represents an active call that sends a single message and then gets a stream
* of reponses
* of reponses.
*/
class ServerStreamingCall extends AbstractCall {
class ServerStreamingCall extends AbstractCall
{
/**
* Start the call
* Start the call.
*
* @param $data The data to send
* @param array $metadata Metadata to send with the call, if applicable
* @param array $options an array of options, possible keys:
* 'flags' => a number
*/
public function start($data, $metadata = array(), $options = array()) {
public function start($data, $metadata = [], $options = [])
{
$message_array = ['message' => $data->serialize()];
if (isset($options['flags'])) {
$message_array['flags'] = $options['flags'];
@ -54,30 +58,39 @@ class ServerStreamingCall extends AbstractCall {
OP_SEND_INITIAL_METADATA => $metadata,
OP_RECV_INITIAL_METADATA => true,
OP_SEND_MESSAGE => $message_array,
OP_SEND_CLOSE_FROM_CLIENT => true]);
OP_SEND_CLOSE_FROM_CLIENT => true,
]);
$this->metadata = $event->metadata;
}
/**
* @return An iterator of response values
*/
public function responses() {
$response = $this->call->startBatch([OP_RECV_MESSAGE => true])->message;
public function responses()
{
$response = $this->call->startBatch([
OP_RECV_MESSAGE => true,
])->message;
while ($response !== null) {
yield $this->deserializeResponse($response);
$response = $this->call->startBatch([OP_RECV_MESSAGE => true])->message;
$response = $this->call->startBatch([
OP_RECV_MESSAGE => true,
])->message;
}
}
/**
* Wait for the server to send the status, and return it.
*
* @return object The status object, with integer $code, string $details,
* and array $metadata members
*/
public function getStatus() {
public function getStatus()
{
$status_event = $this->call->startBatch([
OP_RECV_STATUS_ON_CLIENT => true
OP_RECV_STATUS_ON_CLIENT => true,
]);
return $status_event->status;
}
}

@ -31,21 +31,25 @@
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
namespace Grpc;
/**
* Represents an active call that sends a single message and then gets a single
* response.
*/
class UnaryCall extends AbstractCall {
class UnaryCall extends AbstractCall
{
/**
* Start the call
* Start the call.
*
* @param $data The data to send
* @param array $metadata Metadata to send with the call, if applicable
* @param array $options an array of options, possible keys:
* 'flags' => a number
*/
public function start($data, $metadata = array(), $options = array()) {
public function start($data, $metadata = [], $options = [])
{
$message_array = ['message' => $data->serialize()];
if (isset($options['flags'])) {
$message_array['flags'] = $options['flags'];
@ -54,18 +58,23 @@ class UnaryCall extends AbstractCall {
OP_SEND_INITIAL_METADATA => $metadata,
OP_RECV_INITIAL_METADATA => true,
OP_SEND_MESSAGE => $message_array,
OP_SEND_CLOSE_FROM_CLIENT => true]);
OP_SEND_CLOSE_FROM_CLIENT => true,
]);
$this->metadata = $event->metadata;
}
/**
* Wait for the server to respond with data and a status
* Wait for the server to respond with data and a status.
*
* @return [response data, status]
*/
public function wait() {
public function wait()
{
$event = $this->call->startBatch([
OP_RECV_MESSAGE => true,
OP_RECV_STATUS_ON_CLIENT => true]);
return array($this->deserializeResponse($event->message), $event->status);
OP_RECV_STATUS_ON_CLIENT => true,
]);
return [$this->deserializeResponse($event->message), $event->status];
}
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save