clang-format format clean

pull/8441/head
Muxi Yan 8 years ago
parent ad93106da0
commit bd19fc7e30
  1. 17
      src/objective-c/GRPCClient/GRPCCall+ChannelArg.h
  2. 16
      src/objective-c/GRPCClient/GRPCCall+ChannelCredentials.h
  3. 14
      src/objective-c/GRPCClient/GRPCCall+OAuth2.h
  4. 25
      src/objective-c/GRPCClient/GRPCCall+Tests.h
  5. 189
      src/objective-c/GRPCClient/GRPCCall.h
  6. 202
      src/objective-c/GRPCClient/GRPCCall.m
  7. 17
      src/objective-c/GRPCClient/private/GRPCCompletionQueue.h
  8. 11
      src/objective-c/GRPCClient/private/GRPCConnectivityMonitor.h
  9. 15
      src/objective-c/GRPCClient/private/GRPCConnectivityMonitor.m
  10. 104
      src/objective-c/GRPCClient/private/GRPCHost.m
  11. 41
      src/objective-c/GRPCClient/private/GRPCRequestHeaders.m
  12. 86
      src/objective-c/GRPCClient/private/GRPCWrappedCall.m
  13. 7
      src/objective-c/GRPCClient/private/NSError+GRPC.h

@ -40,15 +40,18 @@
@interface GRPCCall (ChannelArg) @interface GRPCCall (ChannelArg)
/** /**
* Use the provided @c userAgentPrefix at the beginning of the HTTP User Agent string for all calls * Use the provided @c userAgentPrefix at the beginning of the HTTP User Agent
* to the specified @c host. * string for all calls to the specified @c host.
*/ */
+ (void)setUserAgentPrefix:(nonnull NSString *)userAgentPrefix forHost:(nonnull NSString *)host; + (void)setUserAgentPrefix:(nonnull NSString *)userAgentPrefix
forHost:(nonnull NSString *)host;
/** The default response size limit is 4MB. Set this to override that default. */ /** The default response size limit is 4MB. Set this to override that default.
*/
+ (void)setResponseSizeLimit:(NSUInteger)limit forHost:(nonnull NSString *)host; + (void)setResponseSizeLimit:(NSUInteger)limit forHost:(nonnull NSString *)host;
+ (void)closeOpenConnections DEPRECATED_MSG_ATTRIBUTE("The API for this feature is experimental, " + (void)closeOpenConnections DEPRECATED_MSG_ATTRIBUTE(
"and might be removed or modified at any " "The API for this feature is experimental, "
"time."); "and might be removed or modified at any "
"time.");
@end @end

@ -33,24 +33,26 @@
#import "GRPCCall.h" #import "GRPCCall.h"
/** Helpers for setting TLS Trusted Roots, Client Certificates, and Private Key */ /** Helpers for setting TLS Trusted Roots, Client Certificates, and Private Key
*/
@interface GRPCCall (ChannelCredentials) @interface GRPCCall (ChannelCredentials)
/** /**
* Use the provided @c pemRootCert as the set of trusted root Certificate Authorities for @c host. * Use the provided @c pemRootCert as the set of trusted root Certificate
* Authorities for @c host.
*/ */
+ (BOOL)setTLSPEMRootCerts:(nullable NSString *)pemRootCert + (BOOL)setTLSPEMRootCerts:(nullable NSString *)pemRootCert
forHost:(nonnull NSString *)host forHost:(nonnull NSString *)host
error:(NSError * _Nullable * _Nullable)errorPtr; error:(NSError *_Nullable *_Nullable)errorPtr;
/** /**
* Configures @c host with TLS/SSL Client Credentials and optionally trusted root Certificate * Configures @c host with TLS/SSL Client Credentials and optionally trusted
* Authorities. If @c pemRootCerts is nil, the default CA Certificates bundled with gRPC will be * root Certificate Authorities. If @c pemRootCerts is nil, the default CA
* used. * Certificates bundled with gRPC will be used.
*/ */
+ (BOOL)setTLSPEMRootCerts:(nullable NSString *)pemRootCerts + (BOOL)setTLSPEMRootCerts:(nullable NSString *)pemRootCerts
withPrivateKey:(nullable NSString *)pemPrivateKey withPrivateKey:(nullable NSString *)pemPrivateKey
withCertChain:(nullable NSString *)pemCertChain withCertChain:(nullable NSString *)pemCertChain
forHost:(nonnull NSString *)host forHost:(nonnull NSString *)host
error:(NSError * _Nullable * _Nullable)errorPtr; error:(NSError *_Nullable *_Nullable)errorPtr;
@end @end

@ -37,15 +37,17 @@
@interface GRPCCall (OAuth2) @interface GRPCCall (OAuth2)
/** /**
* Setting this property is equivalent to setting "Bearer <passed token>" as the value of the * Setting this property is equivalent to setting "Bearer <passed token>" as the
* request header with key "authorization" (the authorization header). Setting it to nil removes the * value of the request header with key "authorization" (the authorization
* authorization header from the request. * 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 * The value obtained by getting the property is the OAuth2 bearer token if the
* of the request has the form "Bearer <token>", or nil otherwise. * authorization header of the request has the form "Bearer <token>", or nil
* otherwise.
*/ */
@property(atomic, copy) NSString *oauth2AccessToken; @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; @property(atomic, readonly) NSString *oauth2ChallengeHeader;
@end @end

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

@ -34,17 +34,18 @@
/** /**
* The gRPC protocol is an RPC protocol on top of HTTP2. * 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 * While the most common type of RPC receives only one request message and
* message, the protocol also supports RPCs that return multiple individual messages in a streaming * returns only one response message, the protocol also supports RPCs that
* fashion, RPCs that accept a stream of request messages, or RPCs with both streaming requests and * return multiple individual messages in a streaming fashion, RPCs that accept
* a stream of request messages, or RPCs with both streaming requests and
* responses. * responses.
* *
* Conceptually, each gRPC call consists of a bidirectional stream of binary messages, with RPCs of * Conceptually, each gRPC call consists of a bidirectional stream of binary
* the "non-streaming type" sending only one message in the corresponding direction (the protocol * messages, with RPCs of the "non-streaming type" sending only one message in
* doesn't make any distinction). * 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 * Each RPC uses a different HTTP2 stream, and thus multiple simultaneous RPCs
* transparently on the same TCP connection. * can be multiplexed transparently on the same TCP connection.
*/ */
#import <Foundation/Foundation.h> #import <Foundation/Foundation.h>
@ -59,51 +60,56 @@ extern NSString *const kGRPCErrorDomain;
/** /**
* gRPC error codes. * gRPC error codes.
* Note that a few of these are never produced by the gRPC libraries, but are of general utility for * Note that a few of these are never produced by the gRPC libraries, but are of
* server applications to produce. * general utility for server applications to produce.
*/ */
typedef NS_ENUM(NSUInteger, GRPCErrorCode) { typedef NS_ENUM(NSUInteger, GRPCErrorCode) {
/** The operation was cancelled (typically by the caller). */ /** The operation was cancelled (typically by the caller). */
GRPCErrorCodeCancelled = 1, GRPCErrorCodeCancelled = 1,
/** /**
* Unknown error. Errors raised by APIs that do not return enough error information may be * Unknown error. Errors raised by APIs that do not return enough error
* information may be
* converted to this error. * converted to this error.
*/ */
GRPCErrorCodeUnknown = 2, GRPCErrorCodeUnknown = 2,
/** /**
* The client specified an invalid argument. Note that this differs from FAILED_PRECONDITION. * The client specified an invalid argument. Note that this differs from
* INVALID_ARGUMENT indicates arguments that are problematic regardless of the state of the * FAILED_PRECONDITION.
* server (e.g., a malformed file name). * INVALID_ARGUMENT indicates arguments that are problematic regardless of the
* state of the server (e.g., a malformed file name).
*/ */
GRPCErrorCodeInvalidArgument = 3, GRPCErrorCodeInvalidArgument = 3,
/** /**
* Deadline expired before operation could complete. For operations that change the state of the * Deadline expired before operation could complete. For operations that
* server, this error may be returned even if the operation has completed successfully. For * change the state of the server, this error may be returned even if the
* example, a successful response from the server could have been delayed long enough for the * operation has completed successfully. For example, a successful response
* deadline to expire. * from the server could have been delayed long enough for the deadline to
* expire.
*/ */
GRPCErrorCodeDeadlineExceeded = 4, 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, 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, GRPCErrorCodeAlreadyExists = 6,
/** /**
* The caller does not have permission to execute the specified operation. PERMISSION_DENIED isn't * The caller does not have permission to execute the specified operation.
* used for rejections caused by exhausting some resource (RESOURCE_EXHAUSTED is used instead for * PERMISSION_DENIED isn't used for rejections caused by exhausting some
* those errors). PERMISSION_DENIED doesn't indicate a failure to identify the caller * 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). * (UNAUTHENTICATED is used instead for those errors).
*/ */
GRPCErrorCodePermissionDenied = 7, GRPCErrorCodePermissionDenied = 7,
/** /**
* The request does not have valid authentication credentials for the operation (e.g. the caller's * The request does not have valid authentication credentials for the
* identity can't be verified). * operation (e.g. the caller's identity can't be verified).
*/ */
GRPCErrorCodeUnauthenticated = 16, GRPCErrorCodeUnauthenticated = 16,
@ -111,42 +117,47 @@ typedef NS_ENUM(NSUInteger, GRPCErrorCode) {
GRPCErrorCodeResourceExhausted = 8, GRPCErrorCodeResourceExhausted = 8,
/** /**
* The RPC was rejected because the server is not in a state required for the procedure's * 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. * 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 * The client should not retry until the server state has been explicitly
* performing another RPC). The details depend on the service being called, and should be found in * fixed (e.g. by
* the NSError's userInfo. * performing another RPC). The details depend on the service being called,
* and should be found in the NSError's userInfo.
*/ */
GRPCErrorCodeFailedPrecondition = 9, GRPCErrorCodeFailedPrecondition = 9,
/** /**
* The RPC was aborted, typically due to a concurrency issue like sequencer check failures, * The RPC was aborted, typically due to a concurrency issue like sequencer
* transaction aborts, etc. The client should retry at a higher-level (e.g., restarting a read- * check failures, transaction aborts, etc. The client should retry at a
* modify-write sequence). * higher-level (e.g., restarting a read-modify-write sequence).
*/ */
GRPCErrorCodeAborted = 10, GRPCErrorCodeAborted = 10,
/** /**
* The RPC was attempted past the valid range. E.g., enumerating past the end of a list. * The RPC was attempted past the valid range. E.g., enumerating past the end
* Unlike INVALID_ARGUMENT, this error indicates a problem that may be fixed if the system state * of a list.
* changes. For example, an RPC to get elements of a list will generate INVALID_ARGUMENT if asked * Unlike INVALID_ARGUMENT, this error indicates a problem that may be fixed
* to return the element at a negative index, but it will generate OUT_OF_RANGE if asked to return * if the system state changes. For example, an RPC to get elements of a list
* the element at an index past the current size of the 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, 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, GRPCErrorCodeUnimplemented = 12,
/** /**
* Internal error. Means some invariant expected by the server application or the gRPC library has * Internal error. Means some invariant expected by the server application or
* been broken. * the gRPC library has been broken.
*/ */
GRPCErrorCodeInternal = 13, GRPCErrorCodeInternal = 13,
/** /**
* The server is currently unavailable. This is most likely a transient condition and may be * The server is currently unavailable. This is most likely a transient
* corrected by retrying with a backoff. * condition and may be corrected by retrying with a backoff.
*/ */
GRPCErrorCodeUnavailable = 14, GRPCErrorCodeUnavailable = 14,
@ -158,17 +169,19 @@ typedef NS_ENUM(NSUInteger, GRPCErrorCode) {
* Safety remark of a gRPC method as defined in RFC 2616 Section 9.1 * Safety remark of a gRPC method as defined in RFC 2616 Section 9.1
*/ */
typedef NS_ENUM(NSUInteger, GRPCCallSafety) { typedef NS_ENUM(NSUInteger, GRPCCallSafety) {
/** Signal that there is no guarantees on how the call affects the server state. */ /** Signal that there is no guarantees on how the call affects the server
state. */
GRPCCallSafetyDefault = 0, GRPCCallSafetyDefault = 0,
/** Signal that the call is idempotent. gRPC is free to use PUT verb. */ /** Signal that the call is idempotent. gRPC is free to use PUT verb. */
GRPCCallSafetyIdempotentRequest = 1, GRPCCallSafetyIdempotentRequest = 1,
/** Signal that the call is cacheable and will not affect server state. gRPC is free to use GET verb. */ /** Signal that the call is cacheable and will not affect server state. gRPC
is free to use GET verb. */
GRPCCallSafetyCacheableRequest = 2, GRPCCallSafetyCacheableRequest = 2,
}; };
/** /**
* Keys used in |NSError|'s |userInfo| dictionary to store the response headers and trailers sent by * Keys used in |NSError|'s |userInfo| dictionary to store the response headers
* the server. * and trailers sent by the server.
*/ */
extern id const kGRPCHeadersKey; extern id const kGRPCHeadersKey;
extern id const kGRPCTrailersKey; extern id const kGRPCTrailersKey;
@ -179,20 +192,24 @@ extern id const kGRPCTrailersKey;
@interface GRPCCall : GRXWriter @interface GRPCCall : GRXWriter
/** /**
* The container of the request headers of an RPC conforms to this protocol, which is a subset of * The container of the request headers of an RPC conforms to this protocol,
* NSMutableDictionary's interface. It will become a NSMutableDictionary later on. * which is a subset of NSMutableDictionary's interface. It will become a
* The keys of this container are the header names, which per the HTTP standard are case- * NSMutableDictionary later on. The keys of this container are the header
* insensitive. They are stored in lowercase (which is how HTTP/2 mandates them on the wire), and * names, which per the HTTP standard are case-insensitive. They are stored in
* can only consist of ASCII characters. * lowercase (which is how HTTP/2 mandates them on the wire), and can only
* A header value is a NSString object (with only ASCII characters), unless the header name has the * consist of ASCII characters.
* suffix "-bin", in which case the value has to be a NSData object. * 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.
*/ */
/** /**
* These HTTP headers will be passed to the server as part of this call. Each HTTP header is a * These HTTP headers will be passed to the server as part of this call. Each
* name-value pair with string names and either string or binary values. * 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 * The passed dictionary has to use NSString keys, corresponding to the header
* associated to each can be a NSString object or a NSData object. E.g.: * names. The value associated to each can be a NSString object or a NSData
* object. E.g.:
* *
* call.requestHeaders = @{@"authorization": @"Bearer ..."}; * call.requestHeaders = @{@"authorization": @"Bearer ..."};
* *
@ -205,53 +222,61 @@ extern id const kGRPCTrailersKey;
@property(atomic, readonly) NSMutableDictionary *requestHeaders; @property(atomic, readonly) NSMutableDictionary *requestHeaders;
/** /**
* This dictionary is populated with the HTTP headers received from the server. This happens before * This dictionary is populated with the HTTP headers received from the server.
* any response message is received from the server. It has the same structure as the request * This happens before any response message is received from the server. It has
* headers dictionary: Keys are NSString header names; names ending with the suffix "-bin" have a * the same structure as the request headers dictionary: Keys are NSString
* NSData value; the others have a NSString value. * 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 * The value of this property is nil until all response headers are received,
* any of -writeValue: or -writesFinishedWithError: are sent to the writeable. * and will change before any of -writeValue: or -writesFinishedWithError: are
* sent to the writeable.
*/ */
@property(atomic, readonly) NSDictionary *responseHeaders; @property(atomic, readonly) NSDictionary *responseHeaders;
/** /**
* Same as responseHeaders, but populated with the HTTP trailers received from the server before the * Same as responseHeaders, but populated with the HTTP trailers received from
* call finishes. * the server before the call finishes.
* *
* The value of this property is nil until all response trailers are received, and will change * The value of this property is nil until all response trailers are received,
* before -writesFinishedWithError: is sent to the writeable. * and will change before -writesFinishedWithError: is sent to the writeable.
*/ */
@property(atomic, readonly) NSDictionary *responseTrailers; @property(atomic, readonly) NSDictionary *responseTrailers;
/** /**
* The request writer has to write NSData objects into the provided Writeable. The server will * The request writer has to write NSData objects into the provided Writeable.
* receive each of those separately and in order as distinct messages. * The server will receive each of those separately and in order as distinct
* A gRPC call might not complete until the request writer finishes. On the other hand, the request * messages.
* finishing doesn't necessarily make the call to finish, as the server might continue sending * A gRPC call might not complete until the request writer finishes. On the
* messages to the response side of the call indefinitely (depending on the semantics of the * other hand, the request finishing doesn't necessarily make the call to
* specific remote method called). * 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. * To finish a call right away, invoke cancel.
* host parameter should not contain the scheme (http:// or https://), only the name or IP addr * host parameter should not contain the scheme (http:// or https://), only the
* and the port number, for example @"localhost:5050". * name or IP addr and the port number, for example @"localhost:5050".
*/ */
- (instancetype)initWithHost:(NSString *)host - (instancetype)initWithHost:(NSString *)host
path:(NSString *)path path:(NSString *)path
requestsWriter:(GRXWriter *)requestsWriter NS_DESIGNATED_INITIALIZER; 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 request side of this call, notifies the server that the RPC
* finishes the response side of the call with an error of code CANCELED. * should be cancelled, and finishes the response side of the call with an error
* of code CANCELED.
*/ */
- (void)cancel; - (void)cancel;
/** /**
* Set the call flag for a specific host path. * Set the call flag for a specific host path.
* *
* Host parameter should not contain the scheme (http:// or https://), only the name or IP addr * Host parameter should not contain the scheme (http:// or https://), only the
* and the port number, for example @"localhost:5050". * name or IP addr and the port number, for example @"localhost:5050".
*/ */
+ (void)setCallSafety:(GRPCCallSafety)callSafety host:(NSString *)host path:(NSString *)path; + (void)setCallSafety:(GRPCCallSafety)callSafety
host:(NSString *)host
path:(NSString *)path;
// TODO(jcanizales): Let specify a deadline. As a category of GRXWriter? // TODO(jcanizales): Let specify a deadline. As a category of GRXWriter?
@end @end
@ -260,7 +285,7 @@ extern id const kGRPCTrailersKey;
/** This protocol is kept for backwards compatibility with existing code. */ /** This protocol is kept for backwards compatibility with existing code. */
DEPRECATED_MSG_ATTRIBUTE("Use NSDictionary or NSMutableDictionary instead.") DEPRECATED_MSG_ATTRIBUTE("Use NSDictionary or NSMutableDictionary instead.")
@protocol GRPCRequestHeaders <NSObject> @protocol GRPCRequestHeaders<NSObject>
@property(nonatomic, readonly) NSUInteger count; @property(nonatomic, readonly) NSUInteger count;
- (id)objectForKeyedSubscript:(id)key; - (id)objectForKeyedSubscript:(id)key;
@ -273,6 +298,6 @@ DEPRECATED_MSG_ATTRIBUTE("Use NSDictionary or NSMutableDictionary instead.")
#pragma clang diagnostic push #pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated" #pragma clang diagnostic ignored "-Wdeprecated"
/** This is only needed for backwards-compatibility. */ /** This is only needed for backwards-compatibility. */
@interface NSMutableDictionary (GRPCRequestHeaders) <GRPCRequestHeaders> @interface NSMutableDictionary (GRPCRequestHeaders)<GRPCRequestHeaders>
@end @end
#pragma clang diagnostic pop #pragma clang diagnostic pop

@ -33,9 +33,9 @@
#import "GRPCCall.h" #import "GRPCCall.h"
#import <RxLibrary/GRXConcurrentWriteable.h>
#include <grpc/grpc.h> #include <grpc/grpc.h>
#include <grpc/support/time.h> #include <grpc/support/time.h>
#import <RxLibrary/GRXConcurrentWriteable.h>
#import "private/GRPCConnectivityMonitor.h" #import "private/GRPCConnectivityMonitor.h"
#import "private/GRPCHost.h" #import "private/GRPCHost.h"
@ -45,11 +45,11 @@
#import "private/NSDictionary+GRPC.h" #import "private/NSDictionary+GRPC.h"
#import "private/NSError+GRPC.h" #import "private/NSError+GRPC.h"
NSString * const kGRPCHeadersKey = @"io.grpc.HeadersKey"; NSString *const kGRPCHeadersKey = @"io.grpc.HeadersKey";
NSString * const kGRPCTrailersKey = @"io.grpc.TrailersKey"; NSString *const kGRPCTrailersKey = @"io.grpc.TrailersKey";
static NSMutableDictionary *callFlags; static NSMutableDictionary *callFlags;
@interface GRPCCall () <GRXWriteable> @interface GRPCCall ()<GRXWriteable>
// Make them read-write. // Make them read-write.
@property(atomic, strong) NSDictionary *responseHeaders; @property(atomic, strong) NSDictionary *responseHeaders;
@property(atomic, strong) NSDictionary *responseTrailers; @property(atomic, strong) NSDictionary *responseTrailers;
@ -85,17 +85,21 @@ static NSMutableDictionary *callFlags;
// correct ordering. // correct ordering.
GRXConcurrentWriteable *_responseWriteable; GRXConcurrentWriteable *_responseWriteable;
// The network thread wants the requestWriter to resume (when the server is ready for more input), // The network thread wants the requestWriter to resume (when the server is
// or to stop (on errors), concurrently with user threads that want to start it, pause it or stop // ready for more input), or to stop (on errors), concurrently with user
// it. Because a writer isn't thread-safe, we'll synchronize those operations on it. // threads that want to start it, pause it or stop it. Because a writer isn't
// We don't use a dispatch queue for that purpose, because the writer can call writeValue: or // thread-safe, we'll synchronize those operations on it.
// writesFinishedWithError: on this GRPCCall as part of those operations. We want to be able to // We don't use a dispatch queue for that purpose, because the writer can call
// pause the writer immediately on writeValue:, so we need our locking to be recursive. // writeValue: or writesFinishedWithError: on this GRPCCall as part of those
// operations. We want to be able to pause the writer immediately on
// writeValue:, so we need our locking to be recursive.
GRXWriter *_requestWriter; GRXWriter *_requestWriter;
// To create a retain cycle when a call is started, up until it finishes. See // To create a retain cycle when a call is started, up until it finishes. See
// |startWithWriteable:| and |finishWithError:|. This saves users from having to retain a // |startWithWriteable:| and |finishWithError:|. This saves users from having
// reference to the call object if all they're interested in is the handler being executed when // to retain a
// reference to the call object if all they're interested in is the handler
// being executed when
// the response arrives. // the response arrives.
GRPCCall *_retainSelf; GRPCCall *_retainSelf;
@ -104,13 +108,16 @@ static NSMutableDictionary *callFlags;
@synthesize state = _state; @synthesize state = _state;
// TODO(jcanizales): If grpc_init is idempotent, this should be changed from load to initialize. // TODO(jcanizales): If grpc_init is idempotent, this should be changed from
// load to initialize.
+ (void)load { + (void)load {
grpc_init(); grpc_init();
callFlags = [NSMutableDictionary dictionary]; callFlags = [NSMutableDictionary dictionary];
} }
+ (void)setCallSafety:(GRPCCallSafety)callSafety host:(NSString *)host path:(NSString *)path { + (void)setCallSafety:(GRPCCallSafety)callSafety
host:(NSString *)host
path:(NSString *)path {
NSString *hostAndPath = [NSString stringWithFormat:@"%@/%@", host, path]; NSString *hostAndPath = [NSString stringWithFormat:@"%@/%@", host, path];
switch (callSafety) { switch (callSafety) {
case GRPCCallSafetyDefault: case GRPCCallSafetyDefault:
@ -141,7 +148,8 @@ static NSMutableDictionary *callFlags;
path:(NSString *)path path:(NSString *)path
requestsWriter:(GRXWriter *)requestWriter { requestsWriter:(GRXWriter *)requestWriter {
if (!host || !path) { if (!host || !path) {
[NSException raise:NSInvalidArgumentException format:@"Neither host nor path can be nil."]; [NSException raise:NSInvalidArgumentException
format:@"Neither host nor path can be nil."];
} }
if (requestWriter.state != GRXWriterStateNotStarted) { if (requestWriter.state != GRXWriterStateNotStarted) {
[NSException raise:NSInvalidArgumentException [NSException raise:NSInvalidArgumentException
@ -191,7 +199,10 @@ static NSMutableDictionary *callFlags;
- (void)cancel { - (void)cancel {
[self finishWithError:[NSError errorWithDomain:kGRPCErrorDomain [self finishWithError:[NSError errorWithDomain:kGRPCErrorDomain
code:GRPCErrorCodeCancelled code:GRPCErrorCodeCancelled
userInfo:@{NSLocalizedDescriptionKey: @"Canceled by app"}]]; userInfo:@{
NSLocalizedDescriptionKey :
@"Canceled by app"
}]];
[self cancelCall]; [self cancelCall];
} }
@ -206,15 +217,18 @@ static NSMutableDictionary *callFlags;
// Only called from the call queue. // Only called from the call queue.
// The handler will be called from the network queue. // The handler will be called from the network queue.
- (void)startReadWithHandler:(void(^)(grpc_byte_buffer *))handler { - (void)startReadWithHandler:(void (^)(grpc_byte_buffer *))handler {
// TODO(jcanizales): Add error handlers for async failures // TODO(jcanizales): Add error handlers for async failures
[_wrappedCall startBatchWithOperations:@[[[GRPCOpRecvMessage alloc] initWithHandler:handler]]]; [_wrappedCall startBatchWithOperations:@[ [[GRPCOpRecvMessage alloc]
initWithHandler:handler] ]];
} }
// Called initially from the network queue once response headers are received, // Called initially from the network queue once response headers are received,
// then "recursively" from the responseWriteable queue after each response from the // then "recursively" from the responseWriteable queue after each response from
// the
// server has been written. // server has been written.
// If the call is currently paused, this is a noop. Restarting the call will invoke this // If the call is currently paused, this is a noop. Restarting the call will
// invoke this
// method. // method.
// TODO(jcanizales): Rename to readResponseIfNotPaused. // TODO(jcanizales): Rename to readResponseIfNotPaused.
- (void)startNextRead { - (void)startNextRead {
@ -237,15 +251,23 @@ static NSMutableDictionary *callFlags;
// don't want to throw, because the app shouldn't crash for a behavior // don't want to throw, because the app shouldn't crash for a behavior
// that's on the hands of any server to have. Instead we finish and ask // that's on the hands of any server to have. Instead we finish and ask
// the server to cancel. // the server to cancel.
[weakSelf finishWithError:[NSError errorWithDomain:kGRPCErrorDomain [weakSelf
code:GRPCErrorCodeResourceExhausted finishWithError:[NSError
userInfo:@{NSLocalizedDescriptionKey: @"Client does not have enough memory to hold the server response."}]]; errorWithDomain:kGRPCErrorDomain
code:GRPCErrorCodeResourceExhausted
userInfo:@{
NSLocalizedDescriptionKey :
@"Client does not have enough "
@"memory to hold the server "
@"response."
}]];
[weakSelf cancelCall]; [weakSelf cancelCall];
return; return;
} }
[weakWriteable enqueueValue:data completionHandler:^{ [weakWriteable enqueueValue:data
[weakSelf startNextRead]; completionHandler:^{
}]; [weakSelf startNextRead];
}];
}]; }];
}); });
} }
@ -254,19 +276,22 @@ static NSMutableDictionary *callFlags;
- (void)sendHeaders:(NSDictionary *)headers { - (void)sendHeaders:(NSDictionary *)headers {
// TODO(jcanizales): Add error handlers for async failures // TODO(jcanizales): Add error handlers for async failures
[_wrappedCall startBatchWithOperations:@[[[GRPCOpSendMetadata alloc] initWithMetadata:headers [_wrappedCall startBatchWithOperations:@[
flags:[GRPCCall callFlagsForHost:_host path:_path] [[GRPCOpSendMetadata alloc]
handler:nil]]]; initWithMetadata:headers
flags:[GRPCCall callFlagsForHost:_host path:_path]
handler:nil]
]];
} }
#pragma mark GRXWriteable implementation #pragma mark GRXWriteable implementation
// Only called from the call queue. The error handler will be called from the // Only called from the call queue. The error handler will be called from the
// network queue if the write didn't succeed. // network queue if the write didn't succeed.
- (void)writeMessage:(NSData *)message withErrorHandler:(void (^)())errorHandler { - (void)writeMessage:(NSData *)message
withErrorHandler:(void (^)())errorHandler {
__weak GRPCCall *weakSelf = self; __weak GRPCCall *weakSelf = self;
void(^resumingHandler)(void) = ^{ void (^resumingHandler)(void) = ^{
// Resume the request writer. // Resume the request writer.
GRPCCall *strongSelf = weakSelf; GRPCCall *strongSelf = weakSelf;
if (strongSelf) { if (strongSelf) {
@ -275,8 +300,9 @@ static NSMutableDictionary *callFlags;
} }
} }
}; };
[_wrappedCall startBatchWithOperations:@[[[GRPCOpSendMessage alloc] initWithMessage:message [_wrappedCall startBatchWithOperations:@[ [[GRPCOpSendMessage alloc]
handler:resumingHandler]] initWithMessage:message
handler:resumingHandler] ]
errorHandler:errorHandler]; errorHandler:errorHandler];
} }
@ -291,18 +317,20 @@ static NSMutableDictionary *callFlags;
__weak GRPCCall *weakSelf = self; __weak GRPCCall *weakSelf = self;
dispatch_async(_callQueue, ^{ dispatch_async(_callQueue, ^{
[weakSelf writeMessage:value withErrorHandler:^{ [weakSelf writeMessage:value
[weakSelf finishWithError:[NSError errorWithDomain:kGRPCErrorDomain withErrorHandler:^{
[weakSelf
finishWithError:[NSError errorWithDomain:kGRPCErrorDomain
code:GRPCErrorCodeInternal code:GRPCErrorCodeInternal
userInfo:nil]]; userInfo:nil]];
}]; }];
}); });
} }
// Only called from the call queue. The error handler will be called from the // Only called from the call queue. The error handler will be called from the
// network queue if the requests stream couldn't be closed successfully. // network queue if the requests stream couldn't be closed successfully.
- (void)finishRequestWithErrorHandler:(void (^)())errorHandler { - (void)finishRequestWithErrorHandler:(void (^)())errorHandler {
[_wrappedCall startBatchWithOperations:@[[[GRPCOpSendClose alloc] init]] [_wrappedCall startBatchWithOperations:@[ [[GRPCOpSendClose alloc] init] ]
errorHandler:errorHandler]; errorHandler:errorHandler];
} }
@ -323,17 +351,19 @@ static NSMutableDictionary *callFlags;
#pragma mark Invoke #pragma mark Invoke
// Both handlers will eventually be called, from the network queue. Writes can start immediately // Both handlers will eventually be called, from the network queue. Writes can
// after this. // start immediately after this.
// The first one (headersHandler), when the response headers are received. // The first one (headersHandler), when the response headers are received.
// The second one (completionHandler), whenever the RPC finishes for any reason. // The second one (completionHandler), whenever the RPC finishes for any reason.
- (void)invokeCallWithHeadersHandler:(void(^)(NSDictionary *))headersHandler - (void)invokeCallWithHeadersHandler:(void (^)(NSDictionary *))headersHandler
completionHandler:(void(^)(NSError *, NSDictionary *))completionHandler { completionHandler:
(void (^)(NSError *, NSDictionary *))completionHandler {
// TODO(jcanizales): Add error handlers for async failures // TODO(jcanizales): Add error handlers for async failures
[_wrappedCall startBatchWithOperations:@[[[GRPCOpRecvMetadata alloc] [_wrappedCall startBatchWithOperations:@[ [[GRPCOpRecvMetadata alloc]
initWithHandler:headersHandler]]]; initWithHandler:headersHandler] ]];
[_wrappedCall startBatchWithOperations:@[[[GRPCOpRecvStatus alloc] [_wrappedCall
initWithHandler:completionHandler]]]; startBatchWithOperations:@[ [[GRPCOpRecvStatus alloc]
initWithHandler:completionHandler] ]];
} }
- (void)invokeCall { - (void)invokeCall {
@ -341,27 +371,31 @@ static NSMutableDictionary *callFlags;
// Response headers received. // Response headers received.
self.responseHeaders = headers; self.responseHeaders = headers;
[self startNextRead]; [self startNextRead];
} completionHandler:^(NSError *error, NSDictionary *trailers) { }
self.responseTrailers = trailers; completionHandler:^(NSError *error, NSDictionary *trailers) {
self.responseTrailers = trailers;
if (error) {
NSMutableDictionary *userInfo = [NSMutableDictionary dictionary]; if (error) {
if (error.userInfo) { NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
[userInfo addEntriesFromDictionary:error.userInfo]; if (error.userInfo) {
} [userInfo addEntriesFromDictionary:error.userInfo];
userInfo[kGRPCTrailersKey] = self.responseTrailers; }
// TODO(jcanizales): The C gRPC library doesn't guarantee that the headers block will be userInfo[kGRPCTrailersKey] = self.responseTrailers;
// called before this one, so an error might end up with trailers but no headers. We // TODO(jcanizales): The C gRPC library doesn't guarantee that the
// shouldn't call finishWithError until ater both blocks are called. It is also when this is // headers block will be called before this one, so an error might end
// done that we can provide a merged view of response headers and trailers in a thread-safe // up with trailers but no headers. We shouldn't call finishWithError
// way. // until ater both blocks are called. It is also when this is done
if (self.responseHeaders) { // that we can provide a merged view of response headers and trailers
userInfo[kGRPCHeadersKey] = self.responseHeaders; // in a thread-safe way.
} if (self.responseHeaders) {
error = [NSError errorWithDomain:error.domain code:error.code userInfo:userInfo]; userInfo[kGRPCHeadersKey] = self.responseHeaders;
} }
[self finishWithError:error]; error = [NSError errorWithDomain:error.domain
}]; code:error.code
userInfo:userInfo];
}
[self finishWithError:error];
}];
// Now that the RPC has been initiated, request writes can start. // Now that the RPC has been initiated, request writes can start.
@synchronized(_requestWriter) { @synchronized(_requestWriter) {
[_requestWriter startWithWriteable:self]; [_requestWriter startWithWriteable:self];
@ -376,32 +410,37 @@ static NSMutableDictionary *callFlags;
} }
// TODO(jcanizales): Extract this logic somewhere common. // TODO(jcanizales): Extract this logic somewhere common.
NSString *host = [NSURL URLWithString:[@"https://" stringByAppendingString:_host]].host; NSString *host =
[NSURL URLWithString:[@"https://" stringByAppendingString:_host]].host;
if (!host) { if (!host) {
// TODO(jcanizales): Check this on init. // TODO(jcanizales): Check this on init.
[NSException raise:NSInvalidArgumentException format:@"host of %@ is nil", _host]; [NSException raise:NSInvalidArgumentException
format:@"host of %@ is nil", _host];
} }
__weak typeof(self) weakSelf = self; __weak typeof(self) weakSelf = self;
_connectivityMonitor = [GRPCConnectivityMonitor monitorWithHost:host]; _connectivityMonitor = [GRPCConnectivityMonitor monitorWithHost:host];
void (^handler)() = ^{ void (^handler)() = ^{
typeof(self) strongSelf = weakSelf; typeof(self) strongSelf = weakSelf;
if (strongSelf) { if (strongSelf) {
[strongSelf finishWithError:[NSError errorWithDomain:kGRPCErrorDomain [strongSelf
code:GRPCErrorCodeUnavailable finishWithError:[NSError errorWithDomain:kGRPCErrorDomain
userInfo:@{NSLocalizedDescriptionKey: @"Connectivity lost."}]]; code:GRPCErrorCodeUnavailable
userInfo:@{
NSLocalizedDescriptionKey :
@"Connectivity lost."
}]];
} }
}; };
[_connectivityMonitor handleLossWithHandler:handler [_connectivityMonitor handleLossWithHandler:handler
wifiStatusChangeHandler:^{}]; wifiStatusChangeHandler:^{
}];
// Create a retain cycle so that this instance lives until the RPC finishes // Create a retain cycle so that this instance lives until the RPC finishes
// (or is cancelled). // (or is cancelled). This makes RPCs in which the call isn't externally
// This makes RPCs in which the call isn't externally retained possible (as // retained possible (as long as it is started before being autoreleased).
// long as it is started
// before being autoreleased).
// Care is taken not to retain self strongly in any of the blocks used in this // Care is taken not to retain self strongly in any of the blocks used in this
// implementation, so // implementation, so that the life of the instance is determined by this
// that the life of the instance is determined by this retain cycle. // retain cycle.
_retainSelf = self; _retainSelf = self;
_responseWriteable = _responseWriteable =
@ -417,7 +456,8 @@ static NSMutableDictionary *callFlags;
- (void)setState:(GRXWriterState)newState { - (void)setState:(GRXWriterState)newState {
@synchronized(self) { @synchronized(self) {
// Manual transitions are only allowed from the started or paused states. // Manual transitions are only allowed from the started or paused states.
if (_state == GRXWriterStateNotStarted || _state == GRXWriterStateFinished) { if (_state == GRXWriterStateNotStarted ||
_state == GRXWriterStateFinished) {
return; return;
} }

@ -34,18 +34,19 @@
#import <Foundation/Foundation.h> #import <Foundation/Foundation.h>
#include <grpc/grpc.h> #include <grpc/grpc.h>
typedef void(^GRPCQueueCompletionHandler)(bool success); typedef void (^GRPCQueueCompletionHandler)(bool success);
/** /**
* This class lets one more easily use |grpc_completion_queue|. To use it, pass the value of the * This class lets one more easily use |grpc_completion_queue|. To use it, pass
* |unmanagedQueue| property of an instance of this class to |grpc_channel_create_call|. Then for * the value of the |unmanagedQueue| property of an instance of this class to
* every |grpc_call_*| method that accepts a tag, you can pass a block of type * |grpc_channel_create_call|. Then for every |grpc_call_*| method that accepts
* |GRPCQueueCompletionHandler| (remembering to cast it using |__bridge_retained|). The block is * a tag, you can pass a block of type |GRPCQueueCompletionHandler| (remembering
* guaranteed to eventually be called, by a concurrent queue, and then released. Each such block is * 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. * 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 * Release the GRPCCompletionQueue object only after you are not going to pass
* the |grpc_call| that's using it. * any more blocks to the |grpc_call| that's using it.
*/ */
@interface GRPCCompletionQueue : NSObject @interface GRPCCompletionQueue : NSObject
@property(nonatomic, readonly) grpc_completion_queue *unmanagedQueue; @property(nonatomic, readonly) grpc_completion_queue *unmanagedQueue;

@ -61,20 +61,17 @@
/** /**
* Queue on which callbacks will be dispatched. Default is the main queue. Set * Queue on which callbacks will be dispatched. Default is the main queue. Set
* it before calling * it before calling handleLossWithHandler:.
* handleLossWithHandler:.
*/ */
// TODO(jcanizales): Default to a serial background queue instead. // TODO(jcanizales): Default to a serial background queue instead.
@property(nonatomic, strong, null_resettable) dispatch_queue_t queue; @property(nonatomic, strong, null_resettable) dispatch_queue_t queue;
/** /**
* Calls handler every time the connectivity to this instance's host is lost. If * Calls handler every time the connectivity to this instance's host is lost. If
* this instance is * this instance is released before that happens, the handler won't be called.
* released before that happens, the handler won't be called.
* Only one handler is active at a time, so if this method is called again * Only one handler is active at a time, so if this method is called again
* before the previous * before the previous handler has been called, it might never be called at all
* handler has been called, it might never be called at all (or yes, if it has * (or yes, if it has already been queued).
* already been queued).
*/ */
- (void)handleLossWithHandler:(nonnull void (^)())handler - (void)handleLossWithHandler:(nonnull void (^)())handler
wifiStatusChangeHandler:(nonnull void (^)())wifiStatusChangeHandler; wifiStatusChangeHandler:(nonnull void (^)())wifiStatusChangeHandler;

@ -67,11 +67,9 @@
- (BOOL)isHostReachable { - (BOOL)isHostReachable {
// Note: connectionOnDemand means it'll be reachable only if using the // Note: connectionOnDemand means it'll be reachable only if using the
// CFSocketStream API or APIs // CFSocketStream API or APIs on top of it.
// on top of it.
// connectionRequired means we can't tell until a connection is attempted // connectionRequired means we can't tell until a connection is attempted
// (e.g. for VPN on // (e.g. for VPN on demand).
// demand).
return self.reachable && !self.interventionRequired && return self.reachable && !self.interventionRequired &&
!self.connectionOnDemand; !self.connectionOnDemand;
} }
@ -112,15 +110,13 @@ if (self.isCell) {
#pragma mark Connectivity Monitor #pragma mark Connectivity Monitor
// Assumes the third argument is a block that accepts a GRPCReachabilityFlags // Assumes the third argument is a block that accepts a GRPCReachabilityFlags
// object, and passes the // object, and passes the received ones to it.
// received ones to it.
static void PassFlagsToContextInfoBlock(SCNetworkReachabilityRef target, static void PassFlagsToContextInfoBlock(SCNetworkReachabilityRef target,
SCNetworkReachabilityFlags flags, SCNetworkReachabilityFlags flags,
void *info) { void *info) {
#pragma unused(target) #pragma unused(target)
// This can be called many times with the same info. The info is retained by // This can be called many times with the same info. The info is retained by
// SCNetworkReachability // SCNetworkReachability while this function is being executed.
// while this function is being executed.
void (^handler)(GRPCReachabilityFlags *) = void (^handler)(GRPCReachabilityFlags *) =
(__bridge void (^)(GRPCReachabilityFlags *))info; (__bridge void (^)(GRPCReachabilityFlags *))info;
handler([[GRPCReachabilityFlags alloc] initWithFlags:flags]); handler([[GRPCReachabilityFlags alloc] initWithFlags:flags]);
@ -181,8 +177,7 @@ static void PassFlagsToContextInfoBlock(SCNetworkReachabilityRef target,
- (void)startListeningWithHandler:(void (^)(GRPCReachabilityFlags *))handler { - (void)startListeningWithHandler:(void (^)(GRPCReachabilityFlags *))handler {
// Copy to ensure the handler block is in the heap (and so can't be // Copy to ensure the handler block is in the heap (and so can't be
// deallocated when this method // deallocated when this method returns).
// returns).
void (^copiedHandler)(GRPCReachabilityFlags *) = [handler copy]; void (^copiedHandler)(GRPCReachabilityFlags *) = [handler copy];
SCNetworkReachabilityContext context = { SCNetworkReachabilityContext context = {
.version = 0, .version = 0,

@ -33,9 +33,9 @@
#import "GRPCHost.h" #import "GRPCHost.h"
#import <GRPCClient/GRPCCall.h>
#include <grpc/grpc.h> #include <grpc/grpc.h>
#include <grpc/grpc_security.h> #include <grpc/grpc_security.h>
#import <GRPCClient/GRPCCall.h>
#ifdef GRPC_COMPILE_WITH_CRONET #ifdef GRPC_COMPILE_WITH_CRONET
#import <GRPCClient/GRPCCall+ChannelArg.h> #import <GRPCClient/GRPCCall+ChannelArg.h>
#import <GRPCClient/GRPCCall+Cronet.h> #import <GRPCClient/GRPCCall+Cronet.h>
@ -48,7 +48,8 @@
NS_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_BEGIN
// TODO(jcanizales): Generate the version in a standalone header, from templates. Like // TODO(jcanizales): Generate the version in a standalone header, from
// templates. Like
// templates/src/core/surface/version.c.template . // templates/src/core/surface/version.c.template .
#define GRPC_OBJC_VERSION_STRING @"1.0.0" #define GRPC_OBJC_VERSION_STRING @"1.0.0"
@ -56,7 +57,8 @@ static NSMutableDictionary *kHostCache;
static GRPCConnectivityMonitor *connectivityMonitor = nil; static GRPCConnectivityMonitor *connectivityMonitor = nil;
@implementation GRPCHost { @implementation GRPCHost {
// TODO(mlumish): Investigate whether caching channels with strong links is a good idea. // TODO(mlumish): Investigate whether caching channels with strong links is a
// good idea.
GRPCChannel *_channel; GRPCChannel *_channel;
} }
@ -76,11 +78,13 @@ static GRPCConnectivityMonitor *connectivityMonitor = nil;
return nil; return nil;
} }
// To provide a default port, we try to interpret the address. If it's just a host name without // To provide a default port, we try to interpret the address. If it's just a
// scheme and without port, we'll use port 443. If it has a scheme, we pass it untouched to the C // host name without scheme and without port, we'll use port 443. If it has a
// gRPC library. // scheme, we pass it untouched to the C gRPC library.
// TODO(jcanizales): Add unit tests for the types of addresses we want to let pass untouched. // TODO(jcanizales): Add unit tests for the types of addresses we want to let
NSURL *hostURL = [NSURL URLWithString:[@"https://" stringByAppendingString:address]]; // pass untouched.
NSURL *hostURL =
[NSURL URLWithString:[@"https://" stringByAppendingString:address]];
if (hostURL.host && !hostURL.port) { if (hostURL.host && !hostURL.port) {
address = [hostURL.host stringByAppendingString:@":443"]; address = [hostURL.host stringByAppendingString:@":443"];
} }
@ -126,7 +130,7 @@ static GRPCConnectivityMonitor *connectivityMonitor = nil;
} }
+ (void)resetAllHostSettings { + (void)resetAllHostSettings {
@synchronized (kHostCache) { @synchronized(kHostCache) {
kHostCache = [NSMutableDictionary dictionary]; kHostCache = [NSMutableDictionary dictionary];
} }
} }
@ -152,16 +156,19 @@ static GRPCConnectivityMonitor *connectivityMonitor = nil;
static NSError *kDefaultRootsError; static NSError *kDefaultRootsError;
static dispatch_once_t loading; static dispatch_once_t loading;
dispatch_once(&loading, ^{ dispatch_once(&loading, ^{
NSString *defaultPath = @"gRPCCertificates.bundle/roots"; // .pem NSString *defaultPath = @"gRPCCertificates.bundle/roots"; // .pem
// Do not use NSBundle.mainBundle, as it's nil for tests of library projects. // Do not use NSBundle.mainBundle, as it's nil for tests of library
// projects.
NSBundle *bundle = [NSBundle bundleForClass:self.class]; NSBundle *bundle = [NSBundle bundleForClass:self.class];
NSString *path = [bundle pathForResource:defaultPath ofType:@"pem"]; NSString *path = [bundle pathForResource:defaultPath ofType:@"pem"];
NSError *error; NSError *error;
// Files in PEM format can have non-ASCII characters in their comments (e.g. for the name of the // Files in PEM format can have non-ASCII characters in their comments (e.g.
// issuer). Load them as UTF8 and produce an ASCII equivalent. // for the name of the issuer). Load them as UTF8 and produce an ASCII
NSString *contentInUTF8 = [NSString stringWithContentsOfFile:path // equivalent.
encoding:NSUTF8StringEncoding NSString *contentInUTF8 =
error:&error]; [NSString stringWithContentsOfFile:path
encoding:NSUTF8StringEncoding
error:&error];
if (contentInUTF8 == nil) { if (contentInUTF8 == nil) {
kDefaultRootsError = error; kDefaultRootsError = error;
return; return;
@ -173,17 +180,21 @@ static GRPCConnectivityMonitor *connectivityMonitor = nil;
NSData *rootsASCII; NSData *rootsASCII;
if (pemRootCerts != nil) { if (pemRootCerts != nil) {
rootsASCII = [pemRootCerts dataUsingEncoding:NSASCIIStringEncoding rootsASCII = [pemRootCerts dataUsingEncoding:NSASCIIStringEncoding
allowLossyConversion:YES]; allowLossyConversion:YES];
} else { } else {
if (kDefaultRootsASCII == nil) { if (kDefaultRootsASCII == nil) {
if (errorPtr) { if (errorPtr) {
*errorPtr = kDefaultRootsError; *errorPtr = kDefaultRootsError;
} }
NSAssert(kDefaultRootsASCII, @"Could not read gRPCCertificates.bundle/roots.pem. This file, " NSAssert(kDefaultRootsASCII,
"with the root certificates, is needed to establish secure (TLS) connections. " @"Could not read gRPCCertificates.bundle/roots.pem. This file, "
"Because the file is distributed with the gRPC library, this error is usually a sign " "with the root certificates, is needed to establish secure "
"that the library wasn't configured correctly for your project. Error: %@", "(TLS) connections. "
kDefaultRootsError); "Because the file is distributed with the gRPC library, this "
"error is usually a sign "
"that the library wasn't configured correctly for your "
"project. Error: %@",
kDefaultRootsError);
return NO; return NO;
} }
rootsASCII = kDefaultRootsASCII; rootsASCII = kDefaultRootsASCII;
@ -194,10 +205,12 @@ static GRPCConnectivityMonitor *connectivityMonitor = nil;
creds = grpc_ssl_credentials_create(rootsASCII.bytes, NULL, NULL); creds = grpc_ssl_credentials_create(rootsASCII.bytes, NULL, NULL);
} else { } else {
grpc_ssl_pem_key_cert_pair key_cert_pair; grpc_ssl_pem_key_cert_pair key_cert_pair;
NSData *privateKeyASCII = [pemPrivateKey dataUsingEncoding:NSASCIIStringEncoding NSData *privateKeyASCII =
allowLossyConversion:YES]; [pemPrivateKey dataUsingEncoding:NSASCIIStringEncoding
NSData *certChainASCII = [pemCertChain dataUsingEncoding:NSASCIIStringEncoding allowLossyConversion:YES];
allowLossyConversion:YES]; NSData *certChainASCII =
[pemCertChain dataUsingEncoding:NSASCIIStringEncoding
allowLossyConversion:YES];
key_cert_pair.private_key = privateKeyASCII.bytes; key_cert_pair.private_key = privateKeyASCII.bytes;
key_cert_pair.cert_chain = certChainASCII.bytes; key_cert_pair.cert_chain = certChainASCII.bytes;
creds = grpc_ssl_credentials_create(rootsASCII.bytes, &key_cert_pair, NULL); creds = grpc_ssl_credentials_create(rootsASCII.bytes, &key_cert_pair, NULL);
@ -217,7 +230,8 @@ static GRPCConnectivityMonitor *connectivityMonitor = nil;
NSMutableDictionary *args = [NSMutableDictionary dictionary]; NSMutableDictionary *args = [NSMutableDictionary dictionary];
// TODO(jcanizales): Add OS and device information (see // TODO(jcanizales): Add OS and device information (see
// https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md#user-agents ). // https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md#user-agents
// ).
NSString *userAgent = @"grpc-objc/" GRPC_OBJC_VERSION_STRING; NSString *userAgent = @"grpc-objc/" GRPC_OBJC_VERSION_STRING;
if (_userAgentPrefix) { if (_userAgentPrefix) {
userAgent = [_userAgentPrefix stringByAppendingFormat:@" %@", userAgent]; userAgent = [_userAgentPrefix stringByAppendingFormat:@" %@", userAgent];
@ -242,31 +256,35 @@ static GRPCConnectivityMonitor *connectivityMonitor = nil;
BOOL useCronet = [GRPCCall isUsingCronet]; BOOL useCronet = [GRPCCall isUsingCronet];
#endif #endif
if (_secure) { if (_secure) {
GRPCChannel *channel; GRPCChannel *channel;
@synchronized(self) { @synchronized(self) {
if (_channelCreds == nil) { if (_channelCreds == nil) {
[self setTLSPEMRootCerts:nil withPrivateKey:nil withCertChain:nil error:nil]; [self setTLSPEMRootCerts:nil
} withPrivateKey:nil
withCertChain:nil
error:nil];
}
#ifdef GRPC_COMPILE_WITH_CRONET #ifdef GRPC_COMPILE_WITH_CRONET
if (useCronet) { if (useCronet) {
channel = [GRPCChannel secureCronetChannelWithHost:_address channel =
channelArgs:args]; [GRPCChannel secureCronetChannelWithHost:_address channelArgs:args];
} else } else
#endif #endif
{ {
channel = [GRPCChannel secureChannelWithHost:_address channel = [GRPCChannel secureChannelWithHost:_address
credentials:_channelCreds credentials:_channelCreds
channelArgs:args]; channelArgs:args];
}
} }
return channel; }
return channel;
} else { } else {
return [GRPCChannel insecureChannelWithHost:_address channelArgs:args]; return [GRPCChannel insecureChannelWithHost:_address channelArgs:args];
} }
} }
- (NSString *)hostName { - (NSString *)hostName {
// TODO(jcanizales): Default to nil instead of _address when Issue #2635 is clarified. // TODO(jcanizales): Default to nil instead of _address when Issue #2635 is
// clarified.
return _hostNameOverride ?: _address; return _hostNameOverride ?: _address;
} }

@ -38,9 +38,10 @@
#import "NSDictionary+GRPC.h" #import "NSDictionary+GRPC.h"
// Used by the setter. // Used by the setter.
static void CheckIsNonNilASCII(NSString *name, NSString* value) { static void CheckIsNonNilASCII(NSString *name, NSString *value) {
if (!value) { if (!value) {
[NSException raise:NSInvalidArgumentException format:@"%@ cannot be nil", name]; [NSException raise:NSInvalidArgumentException
format:@"%@ cannot be nil", name];
} }
if (![value canBeConvertedToEncoding:NSASCIIStringEncoding]) { if (![value canBeConvertedToEncoding:NSASCIIStringEncoding]) {
[NSException raise:NSInvalidArgumentException [NSException raise:NSInvalidArgumentException
@ -52,15 +53,20 @@ static void CheckIsNonNilASCII(NSString *name, NSString* value) {
static void CheckKeyValuePairIsValid(NSString *key, id value) { static void CheckKeyValuePairIsValid(NSString *key, id value) {
if ([key hasSuffix:@"-bin"]) { if ([key hasSuffix:@"-bin"]) {
if (![value isKindOfClass:NSData.class]) { if (![value isKindOfClass:NSData.class]) {
[NSException raise:NSInvalidArgumentException [NSException
format:@"Expected NSData value for header %@ ending in \"-bin\", " raise:NSInvalidArgumentException
@"instead got %@", key, value]; format:@"Expected NSData value for header %@ ending in \"-bin\", "
@"instead got %@",
key, value];
} }
} else { } else {
if (![value isKindOfClass:NSString.class]) { if (![value isKindOfClass:NSString.class]) {
[NSException raise:NSInvalidArgumentException [NSException
format:@"Expected NSString value for header %@ not ending in \"-bin\", " raise:NSInvalidArgumentException
@"instead got %@", key, value]; format:
@"Expected NSString value for header %@ not ending in \"-bin\", "
@"instead got %@",
key, value];
} }
CheckIsNonNilASCII(@"Text header value", (NSString *)value); CheckIsNonNilASCII(@"Text header value", (NSString *)value);
} }
@ -68,9 +74,10 @@ static void CheckKeyValuePairIsValid(NSString *key, id value) {
@implementation GRPCRequestHeaders { @implementation GRPCRequestHeaders {
__weak GRPCCall *_call; __weak GRPCCall *_call;
// The NSMutableDictionary superclass doesn't hold any storage (so that people can implement their // The NSMutableDictionary superclass doesn't hold any storage (so that people
// own in subclasses). As that's not the reason we're subclassing, we just delegate storage to the // can implement their own in subclasses). As that's not the reason we're
// default NSMutableDictionary subclass returned by the cluster (e.g. __NSDictionaryM on iOS 9). // subclassing, we just delegate storage to the default NSMutableDictionary
// subclass returned by the cluster (e.g. __NSDictionaryM on iOS 9).
NSMutableDictionary *_delegate; NSMutableDictionary *_delegate;
} }
@ -91,7 +98,8 @@ static void CheckKeyValuePairIsValid(NSString *key, id value) {
} }
// Designated initializer // Designated initializer
- (instancetype)initWithCall:(GRPCCall *)call storage:(NSMutableDictionary *)storage { - (instancetype)initWithCall:(GRPCCall *)call
storage:(NSMutableDictionary *)storage {
// TODO(jcanizales): Throw if call or storage are nil. // TODO(jcanizales): Throw if call or storage are nil.
if ((self = [super init])) { if ((self = [super init])) {
_call = call; _call = call;
@ -100,9 +108,10 @@ static void CheckKeyValuePairIsValid(NSString *key, id value) {
return self; return self;
} }
- (instancetype)initWithObjects:(const id _Nonnull __unsafe_unretained *)objects - (instancetype)
forKeys:(const id<NSCopying> _Nonnull __unsafe_unretained *)keys initWithObjects:(const id _Nonnull __unsafe_unretained *)objects
count:(NSUInteger)cnt { forKeys:(const id<NSCopying> _Nonnull __unsafe_unretained *)keys
count:(NSUInteger)cnt {
return [self init]; return [self init];
} }
@ -134,7 +143,7 @@ static void CheckKeyValuePairIsValid(NSString *key, id value) {
return _delegate.count; return _delegate.count;
} }
- (NSEnumerator * _Nonnull)keyEnumerator { - (NSEnumerator *_Nonnull)keyEnumerator {
return [_delegate keyEnumerator]; return [_delegate keyEnumerator];
} }

@ -34,27 +34,27 @@
#import "GRPCWrappedCall.h" #import "GRPCWrappedCall.h"
#import <Foundation/Foundation.h> #import <Foundation/Foundation.h>
#include <grpc/grpc.h>
#include <grpc/byte_buffer.h> #include <grpc/byte_buffer.h>
#include <grpc/grpc.h>
#include <grpc/support/alloc.h> #include <grpc/support/alloc.h>
#import "GRPCCompletionQueue.h" #import "GRPCCompletionQueue.h"
#import "GRPCHost.h" #import "GRPCHost.h"
#import "NSDictionary+GRPC.h"
#import "NSData+GRPC.h" #import "NSData+GRPC.h"
#import "NSDictionary+GRPC.h"
#import "NSError+GRPC.h" #import "NSError+GRPC.h"
@implementation GRPCOperation { @implementation GRPCOperation {
@protected @protected
// Most operation subclasses don't set any flags in the grpc_op, and rely on the flag member being // Most operation subclasses don't set any flags in the grpc_op, and rely on
// initialized to zero. // the flag member being initialized to zero.
grpc_op _op; grpc_op _op;
void(^_handler)(); void (^_handler)();
} }
- (void)finish { - (void)finish {
if (_handler) { if (_handler) {
void(^handler)() = _handler; void (^handler)() = _handler;
_handler = nil; _handler = nil;
handler(); handler();
} }
@ -101,7 +101,8 @@
- (instancetype)initWithMessage:(NSData *)message handler:(void (^)())handler { - (instancetype)initWithMessage:(NSData *)message handler:(void (^)())handler {
if (!message) { if (!message) {
[NSException raise:NSInvalidArgumentException format:@"message cannot be nil"]; [NSException raise:NSInvalidArgumentException
format:@"message cannot be nil"];
} }
if (self = [super init]) { if (self = [super init]) {
_op.op = GRPC_OP_SEND_MESSAGE; _op.op = GRPC_OP_SEND_MESSAGE;
@ -137,11 +138,11 @@
grpc_metadata_array _headers; grpc_metadata_array _headers;
} }
- (instancetype) init { - (instancetype)init {
return [self initWithHandler:nil]; return [self initWithHandler:nil];
} }
- (instancetype) initWithHandler:(void (^)(NSDictionary *))handler { - (instancetype)initWithHandler:(void (^)(NSDictionary *))handler {
if (self = [super init]) { if (self = [super init]) {
_op.op = GRPC_OP_RECV_INITIAL_METADATA; _op.op = GRPC_OP_RECV_INITIAL_METADATA;
grpc_metadata_array_init(&_headers); grpc_metadata_array_init(&_headers);
@ -152,7 +153,7 @@
_handler = ^{ _handler = ^{
__strong typeof(self) strongSelf = weakSelf; __strong typeof(self) strongSelf = weakSelf;
NSDictionary *metadata = [NSDictionary NSDictionary *metadata = [NSDictionary
grpc_dictionaryFromMetadataArray:strongSelf->_headers]; grpc_dictionaryFromMetadataArray:strongSelf->_headers];
handler(metadata); handler(metadata);
}; };
} }
@ -166,7 +167,7 @@
@end @end
@implementation GRPCOpRecvMessage{ @implementation GRPCOpRecvMessage {
grpc_byte_buffer *_receivedMessage; grpc_byte_buffer *_receivedMessage;
} }
@ -192,18 +193,18 @@
@end @end
@implementation GRPCOpRecvStatus{ @implementation GRPCOpRecvStatus {
grpc_status_code _statusCode; grpc_status_code _statusCode;
char *_details; char *_details;
size_t _detailsCapacity; size_t _detailsCapacity;
grpc_metadata_array _trailers; grpc_metadata_array _trailers;
} }
- (instancetype) init { - (instancetype)init {
return [self initWithHandler:nil]; return [self initWithHandler:nil];
} }
- (instancetype) initWithHandler:(void (^)(NSError *, NSDictionary *))handler { - (instancetype)initWithHandler:(void (^)(NSError *, NSDictionary *))handler {
if (self = [super init]) { if (self = [super init]) {
_op.op = GRPC_OP_RECV_STATUS_ON_CLIENT; _op.op = GRPC_OP_RECV_STATUS_ON_CLIENT;
_op.data.recv_status_on_client.status = &_statusCode; _op.data.recv_status_on_client.status = &_statusCode;
@ -216,10 +217,11 @@
__weak typeof(self) weakSelf = self; __weak typeof(self) weakSelf = self;
_handler = ^{ _handler = ^{
__strong typeof(self) strongSelf = weakSelf; __strong typeof(self) strongSelf = weakSelf;
NSError *error = [NSError grpc_errorFromStatusCode:strongSelf->_statusCode NSError *error =
details:strongSelf->_details]; [NSError grpc_errorFromStatusCode:strongSelf->_statusCode
details:strongSelf->_details];
NSDictionary *trailers = [NSDictionary NSDictionary *trailers = [NSDictionary
grpc_dictionaryFromMetadataArray:strongSelf->_trailers]; grpc_dictionaryFromMetadataArray:strongSelf->_trailers];
handler(error, trailers); handler(error, trailers);
}; };
} }
@ -245,20 +247,21 @@
return [self initWithHost:nil path:nil]; return [self initWithHost:nil path:nil];
} }
- (instancetype)initWithHost:(NSString *)host - (instancetype)initWithHost:(NSString *)host path:(NSString *)path {
path:(NSString *)path {
if (!path || !host) { if (!path || !host) {
[NSException raise:NSInvalidArgumentException [NSException raise:NSInvalidArgumentException
format:@"path and host cannot be nil."]; format:@"path and host cannot be nil."];
} }
if (self = [super init]) { if (self = [super init]) {
// Each completion queue consumes one thread. There's a trade to be made between creating and // Each completion queue consumes one thread. There's a trade to be made
// consuming too many threads and having contention of multiple calls in a single completion // between creating and consuming too many threads and having contention of
// queue. Currently we use a singleton queue. // multiple calls in a single completion queue. Currently we use a singleton
// queue.
_queue = [GRPCCompletionQueue completionQueue]; _queue = [GRPCCompletionQueue completionQueue];
_call = [[GRPCHost hostWithAddress:host] unmanagedCallWithPath:path completionQueue:_queue]; _call = [[GRPCHost hostWithAddress:host] unmanagedCallWithPath:path
completionQueue:_queue];
if (_call == NULL) { if (_call == NULL) {
return nil; return nil;
} }
@ -270,32 +273,35 @@
[self startBatchWithOperations:operations errorHandler:nil]; [self startBatchWithOperations:operations errorHandler:nil];
} }
- (void)startBatchWithOperations:(NSArray *)operations errorHandler:(void (^)())errorHandler { - (void)startBatchWithOperations:(NSArray *)operations
errorHandler:(void (^)())errorHandler {
size_t nops = operations.count; size_t nops = operations.count;
grpc_op *ops_array = gpr_malloc(nops * sizeof(grpc_op)); grpc_op *ops_array = gpr_malloc(nops * sizeof(grpc_op));
size_t i = 0; size_t i = 0;
for (GRPCOperation *operation in operations) { for (GRPCOperation *operation in operations) {
ops_array[i++] = operation.op; ops_array[i++] = operation.op;
} }
grpc_call_error error = grpc_call_start_batch(_call, ops_array, nops, grpc_call_error error = grpc_call_start_batch(
(__bridge_retained void *)(^(bool success){ _call, ops_array, nops, (__bridge_retained void *)(^(bool success) {
if (!success) { if (!success) {
if (errorHandler) { if (errorHandler) {
errorHandler(); errorHandler();
} else { } else {
return; return;
} }
} }
for (GRPCOperation *operation in operations) { for (GRPCOperation *operation in operations) {
[operation finish]; [operation finish];
} }
}), NULL); }),
NULL);
gpr_free(ops_array); gpr_free(ops_array);
if (error != GRPC_CALL_OK) { if (error != GRPC_CALL_OK) {
[NSException raise:NSInternalInconsistencyException [NSException raise:NSInternalInconsistencyException
format:@"A precondition for calling grpc_call_start_batch wasn't met. Error %i", format:@"A precondition for calling grpc_call_start_batch "
error]; @"wasn't met. Error %i",
error];
} }
} }

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

Loading…
Cancel
Save