mirror of https://github.com/grpc/grpc.git
Merge pull request #16190 from muxi/config-isolation
Implement L38: gRPC Objective-C API Upgradepull/17615/head^2
commit
36b47ce0de
72 changed files with 6396 additions and 792 deletions
@ -0,0 +1,348 @@ |
||||
/*
|
||||
* |
||||
* Copyright 2018 gRPC authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
* |
||||
*/ |
||||
|
||||
#import <Foundation/Foundation.h> |
||||
|
||||
NS_ASSUME_NONNULL_BEGIN |
||||
|
||||
/**
|
||||
* Safety remark of a gRPC method as defined in RFC 2616 Section 9.1 |
||||
*/ |
||||
typedef NS_ENUM(NSUInteger, GRPCCallSafety) { |
||||
/** Signal that there is no guarantees on how the call affects the server state. */ |
||||
GRPCCallSafetyDefault = 0, |
||||
/** Signal that the call is idempotent. gRPC is free to use PUT verb. */ |
||||
GRPCCallSafetyIdempotentRequest = 1, |
||||
/**
|
||||
* Signal that the call is cacheable and will not affect server state. gRPC is free to use GET |
||||
* verb. |
||||
*/ |
||||
GRPCCallSafetyCacheableRequest = 2, |
||||
}; |
||||
|
||||
// Compression algorithm to be used by a gRPC call
|
||||
typedef NS_ENUM(NSUInteger, GRPCCompressionAlgorithm) { |
||||
GRPCCompressNone = 0, |
||||
GRPCCompressDeflate, |
||||
GRPCCompressGzip, |
||||
GRPCStreamCompressGzip, |
||||
}; |
||||
|
||||
// GRPCCompressAlgorithm is deprecated; use GRPCCompressionAlgorithm
|
||||
typedef GRPCCompressionAlgorithm GRPCCompressAlgorithm; |
||||
|
||||
/** The transport to be used by a gRPC call */ |
||||
typedef NS_ENUM(NSUInteger, GRPCTransportType) { |
||||
GRPCTransportTypeDefault = 0, |
||||
/** gRPC internal HTTP/2 stack with BoringSSL */ |
||||
GRPCTransportTypeChttp2BoringSSL = 0, |
||||
/** Cronet stack */ |
||||
GRPCTransportTypeCronet, |
||||
/** Insecure channel. FOR TEST ONLY! */ |
||||
GRPCTransportTypeInsecure, |
||||
}; |
||||
|
||||
/**
|
||||
* Implement this protocol to provide a token to gRPC when a call is initiated. |
||||
*/ |
||||
@protocol GRPCAuthorizationProtocol |
||||
|
||||
/**
|
||||
* This method is called when gRPC is about to start the call. When OAuth token is acquired, |
||||
* \a handler is expected to be called with \a token being the new token to be used for this call. |
||||
*/ |
||||
- (void)getTokenWithHandler:(void (^)(NSString *_Nullable token))handler; |
||||
|
||||
@end |
||||
|
||||
@interface GRPCCallOptions : NSObject<NSCopying, NSMutableCopying> |
||||
|
||||
// Call parameters
|
||||
/**
|
||||
* The authority for the RPC. If nil, the default authority will be used. |
||||
* |
||||
* Note: This property does not have effect on Cronet transport and will be ignored. |
||||
* Note: This property cannot be used to validate a self-signed server certificate. It control the |
||||
* :authority header field of the call and performs an extra check that server's certificate |
||||
* matches the :authority header. |
||||
*/ |
||||
@property(copy, readonly, nullable) NSString *serverAuthority; |
||||
|
||||
/**
|
||||
* The timeout for the RPC call in seconds. If set to 0, the call will not timeout. If set to |
||||
* positive, the gRPC call returns with status GRPCErrorCodeDeadlineExceeded if it is not completed |
||||
* within \a timeout seconds. A negative value is not allowed. |
||||
*/ |
||||
@property(readonly) NSTimeInterval timeout; |
||||
|
||||
// OAuth2 parameters. Users of gRPC may specify one of the following two parameters.
|
||||
|
||||
/**
|
||||
* The OAuth2 access token string. The string is prefixed with "Bearer " then used as value of the |
||||
* request's "authorization" header field. This parameter should not be used simultaneously with |
||||
* \a authTokenProvider. |
||||
*/ |
||||
@property(copy, readonly, nullable) NSString *oauth2AccessToken; |
||||
|
||||
/**
|
||||
* The interface to get the OAuth2 access token string. gRPC will attempt to acquire token when |
||||
* initiating the call. This parameter should not be used simultaneously with \a oauth2AccessToken. |
||||
*/ |
||||
@property(readonly, nullable) id<GRPCAuthorizationProtocol> authTokenProvider; |
||||
|
||||
/**
|
||||
* Initial metadata key-value pairs that should be included in the request. |
||||
*/ |
||||
@property(copy, readonly, nullable) NSDictionary *initialMetadata; |
||||
|
||||
// Channel parameters; take into account of channel signature.
|
||||
|
||||
/**
|
||||
* Custom string that is prefixed to a request's user-agent header field before gRPC's internal |
||||
* user-agent string. |
||||
*/ |
||||
@property(copy, readonly, nullable) NSString *userAgentPrefix; |
||||
|
||||
/**
|
||||
* The size limit for the response received from server. If it is exceeded, an error with status |
||||
* code GRPCErrorCodeResourceExhausted is returned. |
||||
*/ |
||||
@property(readonly) NSUInteger responseSizeLimit; |
||||
|
||||
/**
|
||||
* The compression algorithm to be used by the gRPC call. For more details refer to |
||||
* https://github.com/grpc/grpc/blob/master/doc/compression.md
|
||||
*/ |
||||
@property(readonly) GRPCCompressionAlgorithm compressionAlgorithm; |
||||
|
||||
/**
|
||||
* Enable/Disable gRPC call's retry feature. The default is enabled. For details of this feature |
||||
* refer to |
||||
* https://github.com/grpc/proposal/blob/master/A6-client-retries.md
|
||||
*/ |
||||
@property(readonly) BOOL retryEnabled; |
||||
|
||||
// HTTP/2 keep-alive feature. The parameter \a keepaliveInterval specifies the interval between two
|
||||
// PING frames. The parameter \a keepaliveTimeout specifies the length of the period for which the
|
||||
// call should wait for PING ACK. If PING ACK is not received after this period, the call fails.
|
||||
// Negative values are not allowed.
|
||||
@property(readonly) NSTimeInterval keepaliveInterval; |
||||
@property(readonly) NSTimeInterval keepaliveTimeout; |
||||
|
||||
// Parameters for connection backoff. Negative values are not allowed.
|
||||
// For details of gRPC's backoff behavior, refer to
|
||||
// https://github.com/grpc/grpc/blob/master/doc/connection-backoff.md
|
||||
@property(readonly) NSTimeInterval connectMinTimeout; |
||||
@property(readonly) NSTimeInterval connectInitialBackoff; |
||||
@property(readonly) NSTimeInterval connectMaxBackoff; |
||||
|
||||
/**
|
||||
* Specify channel args to be used for this call. For a list of channel args available, see |
||||
* grpc/grpc_types.h |
||||
*/ |
||||
@property(copy, readonly, nullable) NSDictionary *additionalChannelArgs; |
||||
|
||||
// Parameters for SSL authentication.
|
||||
|
||||
/**
|
||||
* PEM format root certifications that is trusted. If set to nil, gRPC uses a list of default |
||||
* root certificates. |
||||
*/ |
||||
@property(copy, readonly, nullable) NSString *PEMRootCertificates; |
||||
|
||||
/**
|
||||
* PEM format private key for client authentication, if required by the server. |
||||
*/ |
||||
@property(copy, readonly, nullable) NSString *PEMPrivateKey; |
||||
|
||||
/**
|
||||
* PEM format certificate chain for client authentication, if required by the server. |
||||
*/ |
||||
@property(copy, readonly, nullable) NSString *PEMCertificateChain; |
||||
|
||||
/**
|
||||
* Select the transport type to be used for this call. |
||||
*/ |
||||
@property(readonly) GRPCTransportType transportType; |
||||
|
||||
/**
|
||||
* Override the hostname during the TLS hostname validation process. |
||||
*/ |
||||
@property(copy, readonly, nullable) NSString *hostNameOverride; |
||||
|
||||
/**
|
||||
* A string that specify the domain where channel is being cached. Channels with different domains |
||||
* will not get cached to the same connection. |
||||
*/ |
||||
@property(copy, readonly, nullable) NSString *channelPoolDomain; |
||||
|
||||
/**
|
||||
* Channel id allows control of channel caching within a channelPoolDomain. A call with a unique |
||||
* channelID will create a new channel (connection) instead of reusing an existing one. Multiple |
||||
* calls in the same channelPoolDomain using identical channelID are allowed to share connection |
||||
* if other channel options are also the same. |
||||
*/ |
||||
@property(readonly) NSUInteger channelID; |
||||
|
||||
/**
|
||||
* Return if the channel options are equal to another object. |
||||
*/ |
||||
- (BOOL)hasChannelOptionsEqualTo:(GRPCCallOptions *)callOptions; |
||||
|
||||
/**
|
||||
* Hash for channel options. |
||||
*/ |
||||
@property(readonly) NSUInteger channelOptionsHash; |
||||
|
||||
@end |
||||
|
||||
@interface GRPCMutableCallOptions : GRPCCallOptions<NSCopying, NSMutableCopying> |
||||
|
||||
// Call parameters
|
||||
/**
|
||||
* The authority for the RPC. If nil, the default authority will be used. |
||||
* |
||||
* Note: This property does not have effect on Cronet transport and will be ignored. |
||||
* Note: This property cannot be used to validate a self-signed server certificate. It control the |
||||
* :authority header field of the call and performs an extra check that server's certificate |
||||
* matches the :authority header. |
||||
*/ |
||||
@property(copy, readwrite, nullable) NSString *serverAuthority; |
||||
|
||||
/**
|
||||
* The timeout for the RPC call in seconds. If set to 0, the call will not timeout. If set to |
||||
* positive, the gRPC call returns with status GRPCErrorCodeDeadlineExceeded if it is not completed |
||||
* within \a timeout seconds. Negative value is invalid; setting the parameter to negative value |
||||
* will reset the parameter to 0. |
||||
*/ |
||||
@property(readwrite) NSTimeInterval timeout; |
||||
|
||||
// OAuth2 parameters. Users of gRPC may specify one of the following two parameters.
|
||||
|
||||
/**
|
||||
* The OAuth2 access token string. The string is prefixed with "Bearer " then used as value of the |
||||
* request's "authorization" header field. This parameter should not be used simultaneously with |
||||
* \a authTokenProvider. |
||||
*/ |
||||
@property(copy, readwrite, nullable) NSString *oauth2AccessToken; |
||||
|
||||
/**
|
||||
* The interface to get the OAuth2 access token string. gRPC will attempt to acquire token when |
||||
* initiating the call. This parameter should not be used simultaneously with \a oauth2AccessToken. |
||||
*/ |
||||
@property(readwrite, nullable) id<GRPCAuthorizationProtocol> authTokenProvider; |
||||
|
||||
/**
|
||||
* Initial metadata key-value pairs that should be included in the request. |
||||
*/ |
||||
@property(copy, readwrite, nullable) NSDictionary *initialMetadata; |
||||
|
||||
// Channel parameters; take into account of channel signature.
|
||||
|
||||
/**
|
||||
* Custom string that is prefixed to a request's user-agent header field before gRPC's internal |
||||
* user-agent string. |
||||
*/ |
||||
@property(copy, readwrite, nullable) NSString *userAgentPrefix; |
||||
|
||||
/**
|
||||
* The size limit for the response received from server. If it is exceeded, an error with status |
||||
* code GRPCErrorCodeResourceExhausted is returned. |
||||
*/ |
||||
@property(readwrite) NSUInteger responseSizeLimit; |
||||
|
||||
/**
|
||||
* The compression algorithm to be used by the gRPC call. For more details refer to |
||||
* https://github.com/grpc/grpc/blob/master/doc/compression.md
|
||||
*/ |
||||
@property(readwrite) GRPCCompressionAlgorithm compressionAlgorithm; |
||||
|
||||
/**
|
||||
* Enable/Disable gRPC call's retry feature. The default is enabled. For details of this feature |
||||
* refer to |
||||
* https://github.com/grpc/proposal/blob/master/A6-client-retries.md
|
||||
*/ |
||||
@property(readwrite) BOOL retryEnabled; |
||||
|
||||
// HTTP/2 keep-alive feature. The parameter \a keepaliveInterval specifies the interval between two
|
||||
// PING frames. The parameter \a keepaliveTimeout specifies the length of the period for which the
|
||||
// call should wait for PING ACK. If PING ACK is not received after this period, the call fails.
|
||||
// Negative values are invalid; setting these parameters to negative value will reset the
|
||||
// corresponding parameter to 0.
|
||||
@property(readwrite) NSTimeInterval keepaliveInterval; |
||||
@property(readwrite) NSTimeInterval keepaliveTimeout; |
||||
|
||||
// Parameters for connection backoff. Negative value is invalid; setting the parameters to negative
|
||||
// value will reset corresponding parameter to 0.
|
||||
// For details of gRPC's backoff behavior, refer to
|
||||
// https://github.com/grpc/grpc/blob/master/doc/connection-backoff.md
|
||||
@property(readwrite) NSTimeInterval connectMinTimeout; |
||||
@property(readwrite) NSTimeInterval connectInitialBackoff; |
||||
@property(readwrite) NSTimeInterval connectMaxBackoff; |
||||
|
||||
/**
|
||||
* Specify channel args to be used for this call. For a list of channel args available, see |
||||
* grpc/grpc_types.h |
||||
*/ |
||||
@property(copy, readwrite, nullable) NSDictionary *additionalChannelArgs; |
||||
|
||||
// Parameters for SSL authentication.
|
||||
|
||||
/**
|
||||
* PEM format root certifications that is trusted. If set to nil, gRPC uses a list of default |
||||
* root certificates. |
||||
*/ |
||||
@property(copy, readwrite, nullable) NSString *PEMRootCertificates; |
||||
|
||||
/**
|
||||
* PEM format private key for client authentication, if required by the server. |
||||
*/ |
||||
@property(copy, readwrite, nullable) NSString *PEMPrivateKey; |
||||
|
||||
/**
|
||||
* PEM format certificate chain for client authentication, if required by the server. |
||||
*/ |
||||
@property(copy, readwrite, nullable) NSString *PEMCertificateChain; |
||||
|
||||
/**
|
||||
* Select the transport type to be used for this call. |
||||
*/ |
||||
@property(readwrite) GRPCTransportType transportType; |
||||
|
||||
/**
|
||||
* Override the hostname during the TLS hostname validation process. |
||||
*/ |
||||
@property(copy, readwrite, nullable) NSString *hostNameOverride; |
||||
|
||||
/**
|
||||
* A string that specify the domain where channel is being cached. Channels with different domains |
||||
* will not get cached to the same channel. For example, a gRPC example app may use the channel pool |
||||
* domain 'io.grpc.example' so that its calls do not reuse the channel created by other modules in |
||||
* the same process. |
||||
*/ |
||||
@property(copy, readwrite, nullable) NSString *channelPoolDomain; |
||||
|
||||
/**
|
||||
* Channel id allows a call to force creating a new channel (connection) rather than using a cached |
||||
* channel. Calls using distinct channelID's will not get cached to the same channel. |
||||
*/ |
||||
@property(readwrite) NSUInteger channelID; |
||||
|
||||
@end |
||||
|
||||
NS_ASSUME_NONNULL_END |
@ -0,0 +1,525 @@ |
||||
/* |
||||
* |
||||
* Copyright 2018 gRPC authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0 |
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
* |
||||
*/ |
||||
|
||||
#import "GRPCCallOptions.h" |
||||
#import "internal/GRPCCallOptions+Internal.h" |
||||
|
||||
// The default values for the call options. |
||||
static NSString *const kDefaultServerAuthority = nil; |
||||
static const NSTimeInterval kDefaultTimeout = 0; |
||||
static NSDictionary *const kDefaultInitialMetadata = nil; |
||||
static NSString *const kDefaultUserAgentPrefix = nil; |
||||
static const NSUInteger kDefaultResponseSizeLimit = 0; |
||||
static const GRPCCompressionAlgorithm kDefaultCompressionAlgorithm = GRPCCompressNone; |
||||
static const BOOL kDefaultRetryEnabled = YES; |
||||
static const NSTimeInterval kDefaultKeepaliveInterval = 0; |
||||
static const NSTimeInterval kDefaultKeepaliveTimeout = 0; |
||||
static const NSTimeInterval kDefaultConnectMinTimeout = 0; |
||||
static const NSTimeInterval kDefaultConnectInitialBackoff = 0; |
||||
static const NSTimeInterval kDefaultConnectMaxBackoff = 0; |
||||
static NSDictionary *const kDefaultAdditionalChannelArgs = nil; |
||||
static NSString *const kDefaultPEMRootCertificates = nil; |
||||
static NSString *const kDefaultPEMPrivateKey = nil; |
||||
static NSString *const kDefaultPEMCertificateChain = nil; |
||||
static NSString *const kDefaultOauth2AccessToken = nil; |
||||
static const id<GRPCAuthorizationProtocol> kDefaultAuthTokenProvider = nil; |
||||
static const GRPCTransportType kDefaultTransportType = GRPCTransportTypeChttp2BoringSSL; |
||||
static NSString *const kDefaultHostNameOverride = nil; |
||||
static const id kDefaultLogContext = nil; |
||||
static NSString *const kDefaultChannelPoolDomain = nil; |
||||
static const NSUInteger kDefaultChannelID = 0; |
||||
|
||||
// Check if two objects are equal. Returns YES if both are nil; |
||||
static BOOL areObjectsEqual(id obj1, id obj2) { |
||||
if (obj1 == obj2) { |
||||
return YES; |
||||
} |
||||
if (obj1 == nil || obj2 == nil) { |
||||
return NO; |
||||
} |
||||
return [obj1 isEqual:obj2]; |
||||
} |
||||
|
||||
@implementation GRPCCallOptions { |
||||
@protected |
||||
NSString *_serverAuthority; |
||||
NSTimeInterval _timeout; |
||||
NSString *_oauth2AccessToken; |
||||
id<GRPCAuthorizationProtocol> _authTokenProvider; |
||||
NSDictionary *_initialMetadata; |
||||
NSString *_userAgentPrefix; |
||||
NSUInteger _responseSizeLimit; |
||||
GRPCCompressionAlgorithm _compressionAlgorithm; |
||||
BOOL _retryEnabled; |
||||
NSTimeInterval _keepaliveInterval; |
||||
NSTimeInterval _keepaliveTimeout; |
||||
NSTimeInterval _connectMinTimeout; |
||||
NSTimeInterval _connectInitialBackoff; |
||||
NSTimeInterval _connectMaxBackoff; |
||||
NSDictionary *_additionalChannelArgs; |
||||
NSString *_PEMRootCertificates; |
||||
NSString *_PEMPrivateKey; |
||||
NSString *_PEMCertificateChain; |
||||
GRPCTransportType _transportType; |
||||
NSString *_hostNameOverride; |
||||
id<NSObject> _logContext; |
||||
NSString *_channelPoolDomain; |
||||
NSUInteger _channelID; |
||||
} |
||||
|
||||
@synthesize serverAuthority = _serverAuthority; |
||||
@synthesize timeout = _timeout; |
||||
@synthesize oauth2AccessToken = _oauth2AccessToken; |
||||
@synthesize authTokenProvider = _authTokenProvider; |
||||
@synthesize initialMetadata = _initialMetadata; |
||||
@synthesize userAgentPrefix = _userAgentPrefix; |
||||
@synthesize responseSizeLimit = _responseSizeLimit; |
||||
@synthesize compressionAlgorithm = _compressionAlgorithm; |
||||
@synthesize retryEnabled = _retryEnabled; |
||||
@synthesize keepaliveInterval = _keepaliveInterval; |
||||
@synthesize keepaliveTimeout = _keepaliveTimeout; |
||||
@synthesize connectMinTimeout = _connectMinTimeout; |
||||
@synthesize connectInitialBackoff = _connectInitialBackoff; |
||||
@synthesize connectMaxBackoff = _connectMaxBackoff; |
||||
@synthesize additionalChannelArgs = _additionalChannelArgs; |
||||
@synthesize PEMRootCertificates = _PEMRootCertificates; |
||||
@synthesize PEMPrivateKey = _PEMPrivateKey; |
||||
@synthesize PEMCertificateChain = _PEMCertificateChain; |
||||
@synthesize transportType = _transportType; |
||||
@synthesize hostNameOverride = _hostNameOverride; |
||||
@synthesize logContext = _logContext; |
||||
@synthesize channelPoolDomain = _channelPoolDomain; |
||||
@synthesize channelID = _channelID; |
||||
|
||||
- (instancetype)init { |
||||
return [self initWithServerAuthority:kDefaultServerAuthority |
||||
timeout:kDefaultTimeout |
||||
oauth2AccessToken:kDefaultOauth2AccessToken |
||||
authTokenProvider:kDefaultAuthTokenProvider |
||||
initialMetadata:kDefaultInitialMetadata |
||||
userAgentPrefix:kDefaultUserAgentPrefix |
||||
responseSizeLimit:kDefaultResponseSizeLimit |
||||
compressionAlgorithm:kDefaultCompressionAlgorithm |
||||
retryEnabled:kDefaultRetryEnabled |
||||
keepaliveInterval:kDefaultKeepaliveInterval |
||||
keepaliveTimeout:kDefaultKeepaliveTimeout |
||||
connectMinTimeout:kDefaultConnectMinTimeout |
||||
connectInitialBackoff:kDefaultConnectInitialBackoff |
||||
connectMaxBackoff:kDefaultConnectMaxBackoff |
||||
additionalChannelArgs:kDefaultAdditionalChannelArgs |
||||
PEMRootCertificates:kDefaultPEMRootCertificates |
||||
PEMPrivateKey:kDefaultPEMPrivateKey |
||||
PEMCertificateChain:kDefaultPEMCertificateChain |
||||
transportType:kDefaultTransportType |
||||
hostNameOverride:kDefaultHostNameOverride |
||||
logContext:kDefaultLogContext |
||||
channelPoolDomain:kDefaultChannelPoolDomain |
||||
channelID:kDefaultChannelID]; |
||||
} |
||||
|
||||
- (instancetype)initWithServerAuthority:(NSString *)serverAuthority |
||||
timeout:(NSTimeInterval)timeout |
||||
oauth2AccessToken:(NSString *)oauth2AccessToken |
||||
authTokenProvider:(id<GRPCAuthorizationProtocol>)authTokenProvider |
||||
initialMetadata:(NSDictionary *)initialMetadata |
||||
userAgentPrefix:(NSString *)userAgentPrefix |
||||
responseSizeLimit:(NSUInteger)responseSizeLimit |
||||
compressionAlgorithm:(GRPCCompressionAlgorithm)compressionAlgorithm |
||||
retryEnabled:(BOOL)retryEnabled |
||||
keepaliveInterval:(NSTimeInterval)keepaliveInterval |
||||
keepaliveTimeout:(NSTimeInterval)keepaliveTimeout |
||||
connectMinTimeout:(NSTimeInterval)connectMinTimeout |
||||
connectInitialBackoff:(NSTimeInterval)connectInitialBackoff |
||||
connectMaxBackoff:(NSTimeInterval)connectMaxBackoff |
||||
additionalChannelArgs:(NSDictionary *)additionalChannelArgs |
||||
PEMRootCertificates:(NSString *)PEMRootCertificates |
||||
PEMPrivateKey:(NSString *)PEMPrivateKey |
||||
PEMCertificateChain:(NSString *)PEMCertificateChain |
||||
transportType:(GRPCTransportType)transportType |
||||
hostNameOverride:(NSString *)hostNameOverride |
||||
logContext:(id)logContext |
||||
channelPoolDomain:(NSString *)channelPoolDomain |
||||
channelID:(NSUInteger)channelID { |
||||
if ((self = [super init])) { |
||||
_serverAuthority = [serverAuthority copy]; |
||||
_timeout = timeout < 0 ? 0 : timeout; |
||||
_oauth2AccessToken = [oauth2AccessToken copy]; |
||||
_authTokenProvider = authTokenProvider; |
||||
_initialMetadata = |
||||
initialMetadata == nil |
||||
? nil |
||||
: [[NSDictionary alloc] initWithDictionary:initialMetadata copyItems:YES]; |
||||
_userAgentPrefix = [userAgentPrefix copy]; |
||||
_responseSizeLimit = responseSizeLimit; |
||||
_compressionAlgorithm = compressionAlgorithm; |
||||
_retryEnabled = retryEnabled; |
||||
_keepaliveInterval = keepaliveInterval < 0 ? 0 : keepaliveInterval; |
||||
_keepaliveTimeout = keepaliveTimeout < 0 ? 0 : keepaliveTimeout; |
||||
_connectMinTimeout = connectMinTimeout < 0 ? 0 : connectMinTimeout; |
||||
_connectInitialBackoff = connectInitialBackoff < 0 ? 0 : connectInitialBackoff; |
||||
_connectMaxBackoff = connectMaxBackoff < 0 ? 0 : connectMaxBackoff; |
||||
_additionalChannelArgs = |
||||
additionalChannelArgs == nil |
||||
? nil |
||||
: [[NSDictionary alloc] initWithDictionary:additionalChannelArgs copyItems:YES]; |
||||
_PEMRootCertificates = [PEMRootCertificates copy]; |
||||
_PEMPrivateKey = [PEMPrivateKey copy]; |
||||
_PEMCertificateChain = [PEMCertificateChain copy]; |
||||
_transportType = transportType; |
||||
_hostNameOverride = [hostNameOverride copy]; |
||||
_logContext = logContext; |
||||
_channelPoolDomain = [channelPoolDomain copy]; |
||||
_channelID = channelID; |
||||
} |
||||
return self; |
||||
} |
||||
|
||||
- (nonnull id)copyWithZone:(NSZone *)zone { |
||||
GRPCCallOptions *newOptions = |
||||
[[GRPCCallOptions allocWithZone:zone] initWithServerAuthority:_serverAuthority |
||||
timeout:_timeout |
||||
oauth2AccessToken:_oauth2AccessToken |
||||
authTokenProvider:_authTokenProvider |
||||
initialMetadata:_initialMetadata |
||||
userAgentPrefix:_userAgentPrefix |
||||
responseSizeLimit:_responseSizeLimit |
||||
compressionAlgorithm:_compressionAlgorithm |
||||
retryEnabled:_retryEnabled |
||||
keepaliveInterval:_keepaliveInterval |
||||
keepaliveTimeout:_keepaliveTimeout |
||||
connectMinTimeout:_connectMinTimeout |
||||
connectInitialBackoff:_connectInitialBackoff |
||||
connectMaxBackoff:_connectMaxBackoff |
||||
additionalChannelArgs:_additionalChannelArgs |
||||
PEMRootCertificates:_PEMRootCertificates |
||||
PEMPrivateKey:_PEMPrivateKey |
||||
PEMCertificateChain:_PEMCertificateChain |
||||
transportType:_transportType |
||||
hostNameOverride:_hostNameOverride |
||||
logContext:_logContext |
||||
channelPoolDomain:_channelPoolDomain |
||||
channelID:_channelID]; |
||||
return newOptions; |
||||
} |
||||
|
||||
- (nonnull id)mutableCopyWithZone:(NSZone *)zone { |
||||
GRPCMutableCallOptions *newOptions = [[GRPCMutableCallOptions allocWithZone:zone] |
||||
initWithServerAuthority:[_serverAuthority copy] |
||||
timeout:_timeout |
||||
oauth2AccessToken:[_oauth2AccessToken copy] |
||||
authTokenProvider:_authTokenProvider |
||||
initialMetadata:[[NSDictionary alloc] initWithDictionary:_initialMetadata |
||||
copyItems:YES] |
||||
userAgentPrefix:[_userAgentPrefix copy] |
||||
responseSizeLimit:_responseSizeLimit |
||||
compressionAlgorithm:_compressionAlgorithm |
||||
retryEnabled:_retryEnabled |
||||
keepaliveInterval:_keepaliveInterval |
||||
keepaliveTimeout:_keepaliveTimeout |
||||
connectMinTimeout:_connectMinTimeout |
||||
connectInitialBackoff:_connectInitialBackoff |
||||
connectMaxBackoff:_connectMaxBackoff |
||||
additionalChannelArgs:[[NSDictionary alloc] initWithDictionary:_additionalChannelArgs |
||||
copyItems:YES] |
||||
PEMRootCertificates:[_PEMRootCertificates copy] |
||||
PEMPrivateKey:[_PEMPrivateKey copy] |
||||
PEMCertificateChain:[_PEMCertificateChain copy] |
||||
transportType:_transportType |
||||
hostNameOverride:[_hostNameOverride copy] |
||||
logContext:_logContext |
||||
channelPoolDomain:[_channelPoolDomain copy] |
||||
channelID:_channelID]; |
||||
return newOptions; |
||||
} |
||||
|
||||
- (BOOL)hasChannelOptionsEqualTo:(GRPCCallOptions *)callOptions { |
||||
if (callOptions == nil) return NO; |
||||
if (!areObjectsEqual(callOptions.userAgentPrefix, _userAgentPrefix)) return NO; |
||||
if (!(callOptions.responseSizeLimit == _responseSizeLimit)) return NO; |
||||
if (!(callOptions.compressionAlgorithm == _compressionAlgorithm)) return NO; |
||||
if (!(callOptions.retryEnabled == _retryEnabled)) return NO; |
||||
if (!(callOptions.keepaliveInterval == _keepaliveInterval)) return NO; |
||||
if (!(callOptions.keepaliveTimeout == _keepaliveTimeout)) return NO; |
||||
if (!(callOptions.connectMinTimeout == _connectMinTimeout)) return NO; |
||||
if (!(callOptions.connectInitialBackoff == _connectInitialBackoff)) return NO; |
||||
if (!(callOptions.connectMaxBackoff == _connectMaxBackoff)) return NO; |
||||
if (!areObjectsEqual(callOptions.additionalChannelArgs, _additionalChannelArgs)) return NO; |
||||
if (!areObjectsEqual(callOptions.PEMRootCertificates, _PEMRootCertificates)) return NO; |
||||
if (!areObjectsEqual(callOptions.PEMPrivateKey, _PEMPrivateKey)) return NO; |
||||
if (!areObjectsEqual(callOptions.PEMCertificateChain, _PEMCertificateChain)) return NO; |
||||
if (!areObjectsEqual(callOptions.hostNameOverride, _hostNameOverride)) return NO; |
||||
if (!(callOptions.transportType == _transportType)) return NO; |
||||
if (!areObjectsEqual(callOptions.logContext, _logContext)) return NO; |
||||
if (!areObjectsEqual(callOptions.channelPoolDomain, _channelPoolDomain)) return NO; |
||||
if (!(callOptions.channelID == _channelID)) return NO; |
||||
|
||||
return YES; |
||||
} |
||||
|
||||
- (NSUInteger)channelOptionsHash { |
||||
NSUInteger result = 0; |
||||
result ^= _userAgentPrefix.hash; |
||||
result ^= _responseSizeLimit; |
||||
result ^= _compressionAlgorithm; |
||||
result ^= _retryEnabled; |
||||
result ^= (unsigned int)(_keepaliveInterval * 1000); |
||||
result ^= (unsigned int)(_keepaliveTimeout * 1000); |
||||
result ^= (unsigned int)(_connectMinTimeout * 1000); |
||||
result ^= (unsigned int)(_connectInitialBackoff * 1000); |
||||
result ^= (unsigned int)(_connectMaxBackoff * 1000); |
||||
result ^= _additionalChannelArgs.hash; |
||||
result ^= _PEMRootCertificates.hash; |
||||
result ^= _PEMPrivateKey.hash; |
||||
result ^= _PEMCertificateChain.hash; |
||||
result ^= _hostNameOverride.hash; |
||||
result ^= _transportType; |
||||
result ^= _logContext.hash; |
||||
result ^= _channelPoolDomain.hash; |
||||
result ^= _channelID; |
||||
|
||||
return result; |
||||
} |
||||
|
||||
@end |
||||
|
||||
@implementation GRPCMutableCallOptions |
||||
|
||||
@dynamic serverAuthority; |
||||
@dynamic timeout; |
||||
@dynamic oauth2AccessToken; |
||||
@dynamic authTokenProvider; |
||||
@dynamic initialMetadata; |
||||
@dynamic userAgentPrefix; |
||||
@dynamic responseSizeLimit; |
||||
@dynamic compressionAlgorithm; |
||||
@dynamic retryEnabled; |
||||
@dynamic keepaliveInterval; |
||||
@dynamic keepaliveTimeout; |
||||
@dynamic connectMinTimeout; |
||||
@dynamic connectInitialBackoff; |
||||
@dynamic connectMaxBackoff; |
||||
@dynamic additionalChannelArgs; |
||||
@dynamic PEMRootCertificates; |
||||
@dynamic PEMPrivateKey; |
||||
@dynamic PEMCertificateChain; |
||||
@dynamic transportType; |
||||
@dynamic hostNameOverride; |
||||
@dynamic logContext; |
||||
@dynamic channelPoolDomain; |
||||
@dynamic channelID; |
||||
|
||||
- (instancetype)init { |
||||
return [self initWithServerAuthority:kDefaultServerAuthority |
||||
timeout:kDefaultTimeout |
||||
oauth2AccessToken:kDefaultOauth2AccessToken |
||||
authTokenProvider:kDefaultAuthTokenProvider |
||||
initialMetadata:kDefaultInitialMetadata |
||||
userAgentPrefix:kDefaultUserAgentPrefix |
||||
responseSizeLimit:kDefaultResponseSizeLimit |
||||
compressionAlgorithm:kDefaultCompressionAlgorithm |
||||
retryEnabled:kDefaultRetryEnabled |
||||
keepaliveInterval:kDefaultKeepaliveInterval |
||||
keepaliveTimeout:kDefaultKeepaliveTimeout |
||||
connectMinTimeout:kDefaultConnectMinTimeout |
||||
connectInitialBackoff:kDefaultConnectInitialBackoff |
||||
connectMaxBackoff:kDefaultConnectMaxBackoff |
||||
additionalChannelArgs:kDefaultAdditionalChannelArgs |
||||
PEMRootCertificates:kDefaultPEMRootCertificates |
||||
PEMPrivateKey:kDefaultPEMPrivateKey |
||||
PEMCertificateChain:kDefaultPEMCertificateChain |
||||
transportType:kDefaultTransportType |
||||
hostNameOverride:kDefaultHostNameOverride |
||||
logContext:kDefaultLogContext |
||||
channelPoolDomain:kDefaultChannelPoolDomain |
||||
channelID:kDefaultChannelID]; |
||||
} |
||||
|
||||
- (nonnull id)copyWithZone:(NSZone *)zone { |
||||
GRPCCallOptions *newOptions = |
||||
[[GRPCCallOptions allocWithZone:zone] initWithServerAuthority:_serverAuthority |
||||
timeout:_timeout |
||||
oauth2AccessToken:_oauth2AccessToken |
||||
authTokenProvider:_authTokenProvider |
||||
initialMetadata:_initialMetadata |
||||
userAgentPrefix:_userAgentPrefix |
||||
responseSizeLimit:_responseSizeLimit |
||||
compressionAlgorithm:_compressionAlgorithm |
||||
retryEnabled:_retryEnabled |
||||
keepaliveInterval:_keepaliveInterval |
||||
keepaliveTimeout:_keepaliveTimeout |
||||
connectMinTimeout:_connectMinTimeout |
||||
connectInitialBackoff:_connectInitialBackoff |
||||
connectMaxBackoff:_connectMaxBackoff |
||||
additionalChannelArgs:_additionalChannelArgs |
||||
PEMRootCertificates:_PEMRootCertificates |
||||
PEMPrivateKey:_PEMPrivateKey |
||||
PEMCertificateChain:_PEMCertificateChain |
||||
transportType:_transportType |
||||
hostNameOverride:_hostNameOverride |
||||
logContext:_logContext |
||||
channelPoolDomain:_channelPoolDomain |
||||
channelID:_channelID]; |
||||
return newOptions; |
||||
} |
||||
|
||||
- (nonnull id)mutableCopyWithZone:(NSZone *)zone { |
||||
GRPCMutableCallOptions *newOptions = [[GRPCMutableCallOptions allocWithZone:zone] |
||||
initWithServerAuthority:_serverAuthority |
||||
timeout:_timeout |
||||
oauth2AccessToken:_oauth2AccessToken |
||||
authTokenProvider:_authTokenProvider |
||||
initialMetadata:_initialMetadata |
||||
userAgentPrefix:_userAgentPrefix |
||||
responseSizeLimit:_responseSizeLimit |
||||
compressionAlgorithm:_compressionAlgorithm |
||||
retryEnabled:_retryEnabled |
||||
keepaliveInterval:_keepaliveInterval |
||||
keepaliveTimeout:_keepaliveTimeout |
||||
connectMinTimeout:_connectMinTimeout |
||||
connectInitialBackoff:_connectInitialBackoff |
||||
connectMaxBackoff:_connectMaxBackoff |
||||
additionalChannelArgs:[_additionalChannelArgs copy] |
||||
PEMRootCertificates:_PEMRootCertificates |
||||
PEMPrivateKey:_PEMPrivateKey |
||||
PEMCertificateChain:_PEMCertificateChain |
||||
transportType:_transportType |
||||
hostNameOverride:_hostNameOverride |
||||
logContext:_logContext |
||||
channelPoolDomain:_channelPoolDomain |
||||
channelID:_channelID]; |
||||
return newOptions; |
||||
} |
||||
|
||||
- (void)setServerAuthority:(NSString *)serverAuthority { |
||||
_serverAuthority = [serverAuthority copy]; |
||||
} |
||||
|
||||
- (void)setTimeout:(NSTimeInterval)timeout { |
||||
if (timeout < 0) { |
||||
_timeout = 0; |
||||
} else { |
||||
_timeout = timeout; |
||||
} |
||||
} |
||||
|
||||
- (void)setOauth2AccessToken:(NSString *)oauth2AccessToken { |
||||
_oauth2AccessToken = [oauth2AccessToken copy]; |
||||
} |
||||
|
||||
- (void)setAuthTokenProvider:(id<GRPCAuthorizationProtocol>)authTokenProvider { |
||||
_authTokenProvider = authTokenProvider; |
||||
} |
||||
|
||||
- (void)setInitialMetadata:(NSDictionary *)initialMetadata { |
||||
_initialMetadata = [[NSDictionary alloc] initWithDictionary:initialMetadata copyItems:YES]; |
||||
} |
||||
|
||||
- (void)setUserAgentPrefix:(NSString *)userAgentPrefix { |
||||
_userAgentPrefix = [userAgentPrefix copy]; |
||||
} |
||||
|
||||
- (void)setResponseSizeLimit:(NSUInteger)responseSizeLimit { |
||||
_responseSizeLimit = responseSizeLimit; |
||||
} |
||||
|
||||
- (void)setCompressionAlgorithm:(GRPCCompressionAlgorithm)compressionAlgorithm { |
||||
_compressionAlgorithm = compressionAlgorithm; |
||||
} |
||||
|
||||
- (void)setRetryEnabled:(BOOL)retryEnabled { |
||||
_retryEnabled = retryEnabled; |
||||
} |
||||
|
||||
- (void)setKeepaliveInterval:(NSTimeInterval)keepaliveInterval { |
||||
if (keepaliveInterval < 0) { |
||||
_keepaliveInterval = 0; |
||||
} else { |
||||
_keepaliveInterval = keepaliveInterval; |
||||
} |
||||
} |
||||
|
||||
- (void)setKeepaliveTimeout:(NSTimeInterval)keepaliveTimeout { |
||||
if (keepaliveTimeout < 0) { |
||||
_keepaliveTimeout = 0; |
||||
} else { |
||||
_keepaliveTimeout = keepaliveTimeout; |
||||
} |
||||
} |
||||
|
||||
- (void)setConnectMinTimeout:(NSTimeInterval)connectMinTimeout { |
||||
if (connectMinTimeout < 0) { |
||||
_connectMinTimeout = 0; |
||||
} else { |
||||
_connectMinTimeout = connectMinTimeout; |
||||
} |
||||
} |
||||
|
||||
- (void)setConnectInitialBackoff:(NSTimeInterval)connectInitialBackoff { |
||||
if (connectInitialBackoff < 0) { |
||||
_connectInitialBackoff = 0; |
||||
} else { |
||||
_connectInitialBackoff = connectInitialBackoff; |
||||
} |
||||
} |
||||
|
||||
- (void)setConnectMaxBackoff:(NSTimeInterval)connectMaxBackoff { |
||||
if (connectMaxBackoff < 0) { |
||||
_connectMaxBackoff = 0; |
||||
} else { |
||||
_connectMaxBackoff = connectMaxBackoff; |
||||
} |
||||
} |
||||
|
||||
- (void)setAdditionalChannelArgs:(NSDictionary *)additionalChannelArgs { |
||||
_additionalChannelArgs = |
||||
[[NSDictionary alloc] initWithDictionary:additionalChannelArgs copyItems:YES]; |
||||
} |
||||
|
||||
- (void)setPEMRootCertificates:(NSString *)PEMRootCertificates { |
||||
_PEMRootCertificates = [PEMRootCertificates copy]; |
||||
} |
||||
|
||||
- (void)setPEMPrivateKey:(NSString *)PEMPrivateKey { |
||||
_PEMPrivateKey = [PEMPrivateKey copy]; |
||||
} |
||||
|
||||
- (void)setPEMCertificateChain:(NSString *)PEMCertificateChain { |
||||
_PEMCertificateChain = [PEMCertificateChain copy]; |
||||
} |
||||
|
||||
- (void)setTransportType:(GRPCTransportType)transportType { |
||||
_transportType = transportType; |
||||
} |
||||
|
||||
- (void)setHostNameOverride:(NSString *)hostNameOverride { |
||||
_hostNameOverride = [hostNameOverride copy]; |
||||
} |
||||
|
||||
- (void)setLogContext:(id)logContext { |
||||
_logContext = logContext; |
||||
} |
||||
|
||||
- (void)setChannelPoolDomain:(NSString *)channelPoolDomain { |
||||
_channelPoolDomain = [channelPoolDomain copy]; |
||||
} |
||||
|
||||
- (void)setChannelID:(NSUInteger)channelID { |
||||
_channelID = channelID; |
||||
} |
||||
|
||||
@end |
@ -0,0 +1,39 @@ |
||||
/*
|
||||
* |
||||
* Copyright 2018 gRPC authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
* |
||||
*/ |
||||
|
||||
#import <Foundation/Foundation.h> |
||||
|
||||
#import "../GRPCCallOptions.h" |
||||
|
||||
@interface GRPCCallOptions () |
||||
|
||||
/**
|
||||
* Parameter used for internal logging. |
||||
*/ |
||||
@property(readonly) id logContext; |
||||
|
||||
@end |
||||
|
||||
@interface GRPCMutableCallOptions () |
||||
|
||||
/**
|
||||
* Parameter used for internal logging. |
||||
*/ |
||||
@property(readwrite) id logContext; |
||||
|
||||
@end |
@ -0,0 +1,38 @@ |
||||
/*
|
||||
* |
||||
* Copyright 2018 gRPC authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
* |
||||
*/ |
||||
|
||||
#import <Foundation/Foundation.h> |
||||
|
||||
#include <grpc/impl/codegen/grpc_types.h> |
||||
|
||||
/** Free resources in the grpc core struct grpc_channel_args */ |
||||
void GRPCFreeChannelArgs(grpc_channel_args* channel_args); |
||||
|
||||
/**
|
||||
* Allocates a @c grpc_channel_args and populates it with the options specified |
||||
* in the |
||||
* @c dictionary. Keys must be @c NSString, @c NSNumber, or a pointer. If the |
||||
* value responds to |
||||
* @c @selector(UTF8String) then it will be mapped to @c GRPC_ARG_STRING. If the |
||||
* value responds to |
||||
* @c @selector(intValue), it will be mapped to @c GRPC_ARG_INTEGER. Otherwise, |
||||
* if the value is not nil, it is mapped as a pointer. The caller of this |
||||
* function is responsible for calling |
||||
* @c GRPCFreeChannelArgs to free the @c grpc_channel_args struct. |
||||
*/ |
||||
grpc_channel_args* GRPCBuildChannelArgs(NSDictionary* dictionary); |
@ -0,0 +1,94 @@ |
||||
/* |
||||
* |
||||
* Copyright 2018 gRPC authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0 |
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
* |
||||
*/ |
||||
|
||||
#import "ChannelArgsUtil.h" |
||||
|
||||
#include <grpc/support/alloc.h> |
||||
#include <grpc/support/string_util.h> |
||||
|
||||
#include <limits.h> |
||||
|
||||
static void *copy_pointer_arg(void *p) { |
||||
// Add ref count to the object when making copy |
||||
id obj = (__bridge id)p; |
||||
return (__bridge_retained void *)obj; |
||||
} |
||||
|
||||
static void destroy_pointer_arg(void *p) { |
||||
// Decrease ref count to the object when destroying |
||||
CFRelease((CFTypeRef)p); |
||||
} |
||||
|
||||
static int cmp_pointer_arg(void *p, void *q) { return p == q; } |
||||
|
||||
static const grpc_arg_pointer_vtable objc_arg_vtable = {copy_pointer_arg, destroy_pointer_arg, |
||||
cmp_pointer_arg}; |
||||
|
||||
void GRPCFreeChannelArgs(grpc_channel_args *channel_args) { |
||||
for (size_t i = 0; i < channel_args->num_args; ++i) { |
||||
grpc_arg *arg = &channel_args->args[i]; |
||||
gpr_free(arg->key); |
||||
if (arg->type == GRPC_ARG_STRING) { |
||||
gpr_free(arg->value.string); |
||||
} |
||||
} |
||||
gpr_free(channel_args->args); |
||||
gpr_free(channel_args); |
||||
} |
||||
|
||||
grpc_channel_args *GRPCBuildChannelArgs(NSDictionary *dictionary) { |
||||
if (dictionary.count == 0) { |
||||
return NULL; |
||||
} |
||||
|
||||
NSArray *keys = [dictionary allKeys]; |
||||
NSUInteger argCount = [keys count]; |
||||
|
||||
grpc_channel_args *channelArgs = gpr_malloc(sizeof(grpc_channel_args)); |
||||
channelArgs->args = gpr_malloc(argCount * sizeof(grpc_arg)); |
||||
|
||||
// TODO(kriswuollett) Check that keys adhere to GRPC core library requirements |
||||
|
||||
NSUInteger j = 0; |
||||
for (NSUInteger i = 0; i < argCount; ++i) { |
||||
grpc_arg *arg = &channelArgs->args[j]; |
||||
arg->key = gpr_strdup([keys[i] UTF8String]); |
||||
|
||||
id value = dictionary[keys[i]]; |
||||
if ([value respondsToSelector:@selector(UTF8String)]) { |
||||
arg->type = GRPC_ARG_STRING; |
||||
arg->value.string = gpr_strdup([value UTF8String]); |
||||
j++; |
||||
} else if ([value respondsToSelector:@selector(intValue)]) { |
||||
int64_t value64 = [value longLongValue]; |
||||
if (value64 <= INT_MAX || value64 >= INT_MIN) { |
||||
arg->type = GRPC_ARG_INTEGER; |
||||
arg->value.integer = (int)value64; |
||||
j++; |
||||
} |
||||
} else if (value != nil) { |
||||
arg->type = GRPC_ARG_POINTER; |
||||
arg->value.pointer.p = (__bridge_retained void *)value; |
||||
arg->value.pointer.vtable = &objc_arg_vtable; |
||||
j++; |
||||
} |
||||
} |
||||
channelArgs->num_args = j; |
||||
|
||||
return channelArgs; |
||||
} |
@ -0,0 +1,34 @@ |
||||
/*
|
||||
* |
||||
* Copyright 2018 gRPC authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
* |
||||
*/ |
||||
|
||||
#import <Foundation/Foundation.h> |
||||
|
||||
#include <grpc/impl/codegen/grpc_types.h> |
||||
|
||||
NS_ASSUME_NONNULL_BEGIN |
||||
|
||||
/** A factory interface which generates new channel. */ |
||||
@protocol GRPCChannelFactory |
||||
|
||||
/** Create a channel with specific channel args to a specific host. */ |
||||
- (nullable grpc_channel *)createChannelWithHost:(NSString *)host |
||||
channelArgs:(nullable NSDictionary *)args; |
||||
|
||||
@end |
||||
|
||||
NS_ASSUME_NONNULL_END |
@ -0,0 +1,51 @@ |
||||
/*
|
||||
* |
||||
* Copyright 2018 gRPC authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
* |
||||
*/ |
||||
|
||||
#import "GRPCChannelPool.h" |
||||
|
||||
NS_ASSUME_NONNULL_BEGIN |
||||
|
||||
/** Test-only interface for \a GRPCPooledChannel. */ |
||||
@interface GRPCPooledChannel (Test) |
||||
|
||||
/**
|
||||
* Initialize a pooled channel with non-default destroy delay for testing purpose. |
||||
*/ |
||||
- (nullable instancetype)initWithChannelConfiguration: |
||||
(GRPCChannelConfiguration *)channelConfiguration |
||||
destroyDelay:(NSTimeInterval)destroyDelay; |
||||
|
||||
/**
|
||||
* Return the pointer to the raw channel wrapped. |
||||
*/ |
||||
@property(atomic, readonly, nullable) GRPCChannel *wrappedChannel; |
||||
|
||||
@end |
||||
|
||||
/** Test-only interface for \a GRPCChannelPool. */ |
||||
@interface GRPCChannelPool (Test) |
||||
|
||||
/**
|
||||
* Get an instance of pool isolated from the global shared pool with channels' destroy delay being |
||||
* \a destroyDelay. |
||||
*/ |
||||
- (nullable instancetype)initTestPool; |
||||
|
||||
@end |
||||
|
||||
NS_ASSUME_NONNULL_END |
@ -0,0 +1,101 @@ |
||||
/*
|
||||
* |
||||
* Copyright 2018 gRPC authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
* |
||||
*/ |
||||
|
||||
#import <GRPCClient/GRPCCallOptions.h> |
||||
|
||||
#import "GRPCChannelFactory.h" |
||||
|
||||
NS_ASSUME_NONNULL_BEGIN |
||||
|
||||
@protocol GRPCChannel; |
||||
@class GRPCChannel; |
||||
@class GRPCChannelPool; |
||||
@class GRPCCompletionQueue; |
||||
@class GRPCChannelConfiguration; |
||||
@class GRPCWrappedCall; |
||||
|
||||
/**
|
||||
* A proxied channel object that can be retained and used to create GRPCWrappedCall object |
||||
* regardless of the current connection status. If a connection is not established when a |
||||
* GRPCWrappedCall object is requested, it issues a connection/reconnection. This behavior is to |
||||
* follow that of gRPC core's channel object. |
||||
*/ |
||||
@interface GRPCPooledChannel : NSObject |
||||
|
||||
- (nullable instancetype)init NS_UNAVAILABLE; |
||||
|
||||
+ (nullable instancetype) new NS_UNAVAILABLE; |
||||
|
||||
/**
|
||||
* Initialize with an actual channel object \a channel and a reference to the channel pool. |
||||
*/ |
||||
- (nullable instancetype)initWithChannelConfiguration: |
||||
(GRPCChannelConfiguration *)channelConfiguration; |
||||
|
||||
/**
|
||||
* Create a GRPCWrappedCall object (grpc_call) from this channel. If channel is disconnected, get a |
||||
* new channel object from the channel pool. |
||||
*/ |
||||
- (nullable GRPCWrappedCall *)wrappedCallWithPath:(NSString *)path |
||||
completionQueue:(GRPCCompletionQueue *)queue |
||||
callOptions:(GRPCCallOptions *)callOptions; |
||||
|
||||
/**
|
||||
* Notify the pooled channel that a wrapped call object is no longer referenced and will be |
||||
* dealloc'ed. |
||||
*/ |
||||
- (void)notifyWrappedCallDealloc:(GRPCWrappedCall *)wrappedCall; |
||||
|
||||
/**
|
||||
* Force the channel to disconnect immediately. GRPCWrappedCall objects previously created with |
||||
* \a wrappedCallWithPath are failed if not already finished. Subsequent calls to |
||||
* unmanagedCallWithPath: will attempt to reconnect to the remote channel. |
||||
*/ |
||||
- (void)disconnect; |
||||
|
||||
@end |
||||
|
||||
/**
|
||||
* Manage the pool of connected channels. When a channel is no longer referenced by any call, |
||||
* destroy the channel after a certain period of time elapsed. |
||||
*/ |
||||
@interface GRPCChannelPool : NSObject |
||||
|
||||
- (nullable instancetype)init NS_UNAVAILABLE; |
||||
|
||||
+ (nullable instancetype) new NS_UNAVAILABLE; |
||||
|
||||
/**
|
||||
* Get the global channel pool. |
||||
*/ |
||||
+ (nullable instancetype)sharedInstance; |
||||
|
||||
/**
|
||||
* Return a channel with a particular configuration. The channel may be a cached channel. |
||||
*/ |
||||
- (nullable GRPCPooledChannel *)channelWithHost:(NSString *)host |
||||
callOptions:(GRPCCallOptions *)callOptions; |
||||
|
||||
/**
|
||||
* Disconnect all channels in this pool. |
||||
*/ |
||||
- (void)disconnectAllChannels; |
||||
|
||||
@end |
||||
|
||||
NS_ASSUME_NONNULL_END |
@ -0,0 +1,276 @@ |
||||
/* |
||||
* |
||||
* Copyright 2015 gRPC authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0 |
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
* |
||||
*/ |
||||
|
||||
#import <Foundation/Foundation.h> |
||||
|
||||
#import "../internal/GRPCCallOptions+Internal.h" |
||||
#import "GRPCChannel.h" |
||||
#import "GRPCChannelFactory.h" |
||||
#import "GRPCChannelPool+Test.h" |
||||
#import "GRPCChannelPool.h" |
||||
#import "GRPCCompletionQueue.h" |
||||
#import "GRPCConnectivityMonitor.h" |
||||
#import "GRPCCronetChannelFactory.h" |
||||
#import "GRPCInsecureChannelFactory.h" |
||||
#import "GRPCSecureChannelFactory.h" |
||||
#import "GRPCWrappedCall.h" |
||||
#import "version.h" |
||||
|
||||
#import <GRPCClient/GRPCCall+Cronet.h> |
||||
#include <grpc/support/log.h> |
||||
|
||||
extern const char *kCFStreamVarName; |
||||
|
||||
static GRPCChannelPool *gChannelPool; |
||||
static dispatch_once_t gInitChannelPool; |
||||
|
||||
/** When all calls of a channel are destroyed, destroy the channel after this much seconds. */ |
||||
static const NSTimeInterval kDefaultChannelDestroyDelay = 30; |
||||
|
||||
@implementation GRPCPooledChannel { |
||||
GRPCChannelConfiguration *_channelConfiguration; |
||||
NSTimeInterval _destroyDelay; |
||||
|
||||
NSHashTable<GRPCWrappedCall *> *_wrappedCalls; |
||||
GRPCChannel *_wrappedChannel; |
||||
NSDate *_lastTimedDestroy; |
||||
dispatch_queue_t _timerQueue; |
||||
} |
||||
|
||||
- (instancetype)initWithChannelConfiguration:(GRPCChannelConfiguration *)channelConfiguration { |
||||
return [self initWithChannelConfiguration:channelConfiguration |
||||
destroyDelay:kDefaultChannelDestroyDelay]; |
||||
} |
||||
|
||||
- (nullable instancetype)initWithChannelConfiguration: |
||||
(GRPCChannelConfiguration *)channelConfiguration |
||||
destroyDelay:(NSTimeInterval)destroyDelay { |
||||
NSAssert(channelConfiguration != nil, @"channelConfiguration cannot be empty."); |
||||
if (channelConfiguration == nil) { |
||||
return nil; |
||||
} |
||||
|
||||
if ((self = [super init])) { |
||||
_channelConfiguration = [channelConfiguration copy]; |
||||
_destroyDelay = destroyDelay; |
||||
_wrappedCalls = [NSHashTable weakObjectsHashTable]; |
||||
_wrappedChannel = nil; |
||||
_lastTimedDestroy = nil; |
||||
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 110000 || __MAC_OS_X_VERSION_MAX_ALLOWED >= 101300 |
||||
if (@available(iOS 8.0, macOS 10.10, *)) { |
||||
_timerQueue = dispatch_queue_create(NULL, dispatch_queue_attr_make_with_qos_class( |
||||
DISPATCH_QUEUE_SERIAL, QOS_CLASS_DEFAULT, 0)); |
||||
} else { |
||||
#else |
||||
{ |
||||
#endif |
||||
_timerQueue = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL); |
||||
} |
||||
} |
||||
|
||||
return self; |
||||
} |
||||
|
||||
- (void)dealloc { |
||||
// Disconnect GRPCWrappedCall objects created but not yet removed |
||||
if (_wrappedCalls.allObjects.count != 0) { |
||||
for (GRPCWrappedCall *wrappedCall in _wrappedCalls.allObjects) { |
||||
[wrappedCall channelDisconnected]; |
||||
}; |
||||
} |
||||
} |
||||
|
||||
- (GRPCWrappedCall *)wrappedCallWithPath:(NSString *)path |
||||
completionQueue:(GRPCCompletionQueue *)queue |
||||
callOptions:(GRPCCallOptions *)callOptions { |
||||
NSAssert(path.length > 0, @"path must not be empty."); |
||||
NSAssert(queue != nil, @"completionQueue must not be empty."); |
||||
NSAssert(callOptions, @"callOptions must not be empty."); |
||||
if (path.length == 0 || queue == nil || callOptions == nil) { |
||||
return nil; |
||||
} |
||||
|
||||
GRPCWrappedCall *call = nil; |
||||
|
||||
@synchronized(self) { |
||||
if (_wrappedChannel == nil) { |
||||
_wrappedChannel = [[GRPCChannel alloc] initWithChannelConfiguration:_channelConfiguration]; |
||||
if (_wrappedChannel == nil) { |
||||
NSAssert(_wrappedChannel != nil, @"Unable to get a raw channel for proxy."); |
||||
return nil; |
||||
} |
||||
} |
||||
_lastTimedDestroy = nil; |
||||
|
||||
grpc_call *unmanagedCall = |
||||
[_wrappedChannel unmanagedCallWithPath:path |
||||
completionQueue:[GRPCCompletionQueue completionQueue] |
||||
callOptions:callOptions]; |
||||
if (unmanagedCall == NULL) { |
||||
NSAssert(unmanagedCall != NULL, @"Unable to create grpc_call object"); |
||||
return nil; |
||||
} |
||||
|
||||
call = [[GRPCWrappedCall alloc] initWithUnmanagedCall:unmanagedCall pooledChannel:self]; |
||||
if (call == nil) { |
||||
NSAssert(call != nil, @"Unable to create GRPCWrappedCall object"); |
||||
grpc_call_unref(unmanagedCall); |
||||
return nil; |
||||
} |
||||
|
||||
[_wrappedCalls addObject:call]; |
||||
} |
||||
return call; |
||||
} |
||||
|
||||
- (void)notifyWrappedCallDealloc:(GRPCWrappedCall *)wrappedCall { |
||||
NSAssert(wrappedCall != nil, @"wrappedCall cannot be empty."); |
||||
if (wrappedCall == nil) { |
||||
return; |
||||
} |
||||
@synchronized(self) { |
||||
// Detect if all objects weakly referenced in _wrappedCalls are (implicitly) removed. |
||||
// _wrappedCalls.count does not work here since the hash table may include deallocated weak |
||||
// references. _wrappedCalls.allObjects forces removal of those objects. |
||||
if (_wrappedCalls.allObjects.count == 0) { |
||||
// No more call has reference to this channel. We may start the timer for destroying the |
||||
// channel now. |
||||
NSDate *now = [NSDate date]; |
||||
NSAssert(now != nil, @"Unable to create NSDate object 'now'."); |
||||
_lastTimedDestroy = now; |
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)_destroyDelay * NSEC_PER_SEC), |
||||
_timerQueue, ^{ |
||||
@synchronized(self) { |
||||
// Check _lastTimedDestroy against now in case more calls are created (and |
||||
// maybe destroyed) after this dispatch_async. In that case the current |
||||
// dispatch_after block should be discarded; the channel should be |
||||
// destroyed in a later dispatch_after block. |
||||
if (now != nil && self->_lastTimedDestroy == now) { |
||||
self->_wrappedChannel = nil; |
||||
self->_lastTimedDestroy = nil; |
||||
} |
||||
} |
||||
}); |
||||
} |
||||
} |
||||
} |
||||
|
||||
- (void)disconnect { |
||||
NSArray<GRPCWrappedCall *> *copiedWrappedCalls = nil; |
||||
@synchronized(self) { |
||||
if (_wrappedChannel != nil) { |
||||
_wrappedChannel = nil; |
||||
copiedWrappedCalls = _wrappedCalls.allObjects; |
||||
[_wrappedCalls removeAllObjects]; |
||||
} |
||||
} |
||||
for (GRPCWrappedCall *wrappedCall in copiedWrappedCalls) { |
||||
[wrappedCall channelDisconnected]; |
||||
} |
||||
} |
||||
|
||||
- (GRPCChannel *)wrappedChannel { |
||||
GRPCChannel *channel = nil; |
||||
@synchronized(self) { |
||||
channel = _wrappedChannel; |
||||
} |
||||
return channel; |
||||
} |
||||
|
||||
@end |
||||
|
||||
@interface GRPCChannelPool () |
||||
|
||||
- (instancetype)initPrivate NS_DESIGNATED_INITIALIZER; |
||||
|
||||
@end |
||||
|
||||
@implementation GRPCChannelPool { |
||||
NSMutableDictionary<GRPCChannelConfiguration *, GRPCPooledChannel *> *_channelPool; |
||||
} |
||||
|
||||
+ (instancetype)sharedInstance { |
||||
dispatch_once(&gInitChannelPool, ^{ |
||||
gChannelPool = [[GRPCChannelPool alloc] initPrivate]; |
||||
NSAssert(gChannelPool != nil, @"Cannot initialize global channel pool."); |
||||
}); |
||||
return gChannelPool; |
||||
} |
||||
|
||||
- (instancetype)initPrivate { |
||||
if ((self = [super init])) { |
||||
_channelPool = [NSMutableDictionary dictionary]; |
||||
|
||||
// Connectivity monitor is not required for CFStream |
||||
char *enableCFStream = getenv(kCFStreamVarName); |
||||
if (enableCFStream == nil || enableCFStream[0] != '1') { |
||||
[GRPCConnectivityMonitor registerObserver:self selector:@selector(connectivityChange:)]; |
||||
} |
||||
} |
||||
return self; |
||||
} |
||||
|
||||
- (void)dealloc { |
||||
[GRPCConnectivityMonitor unregisterObserver:self]; |
||||
} |
||||
|
||||
- (GRPCPooledChannel *)channelWithHost:(NSString *)host callOptions:(GRPCCallOptions *)callOptions { |
||||
NSAssert(host.length > 0, @"Host must not be empty."); |
||||
NSAssert(callOptions != nil, @"callOptions must not be empty."); |
||||
if (host.length == 0 || callOptions == nil) { |
||||
return nil; |
||||
} |
||||
|
||||
GRPCPooledChannel *pooledChannel = nil; |
||||
GRPCChannelConfiguration *configuration = |
||||
[[GRPCChannelConfiguration alloc] initWithHost:host callOptions:callOptions]; |
||||
@synchronized(self) { |
||||
pooledChannel = _channelPool[configuration]; |
||||
if (pooledChannel == nil) { |
||||
pooledChannel = [[GRPCPooledChannel alloc] initWithChannelConfiguration:configuration]; |
||||
_channelPool[configuration] = pooledChannel; |
||||
} |
||||
} |
||||
return pooledChannel; |
||||
} |
||||
|
||||
- (void)disconnectAllChannels { |
||||
NSArray<GRPCPooledChannel *> *copiedPooledChannels; |
||||
@synchronized(self) { |
||||
copiedPooledChannels = _channelPool.allValues; |
||||
} |
||||
|
||||
// Disconnect pooled channels. |
||||
for (GRPCPooledChannel *pooledChannel in copiedPooledChannels) { |
||||
[pooledChannel disconnect]; |
||||
} |
||||
} |
||||
|
||||
- (void)connectivityChange:(NSNotification *)note { |
||||
[self disconnectAllChannels]; |
||||
} |
||||
|
||||
@end |
||||
|
||||
@implementation GRPCChannelPool (Test) |
||||
|
||||
- (instancetype)initTestPool { |
||||
return [self initPrivate]; |
||||
} |
||||
|
||||
@end |
@ -0,0 +1,36 @@ |
||||
/*
|
||||
* |
||||
* Copyright 2018 gRPC authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
* |
||||
*/ |
||||
#import "GRPCChannelFactory.h" |
||||
|
||||
@class GRPCChannel; |
||||
typedef struct stream_engine stream_engine; |
||||
|
||||
NS_ASSUME_NONNULL_BEGIN |
||||
|
||||
@interface GRPCCronetChannelFactory : NSObject<GRPCChannelFactory> |
||||
|
||||
+ (nullable instancetype)sharedInstance; |
||||
|
||||
- (nullable grpc_channel *)createChannelWithHost:(NSString *)host |
||||
channelArgs:(nullable NSDictionary *)args; |
||||
|
||||
- (nullable instancetype)init NS_UNAVAILABLE; |
||||
|
||||
@end |
||||
|
||||
NS_ASSUME_NONNULL_END |
@ -0,0 +1,79 @@ |
||||
/* |
||||
* |
||||
* Copyright 2018 gRPC authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0 |
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
* |
||||
*/ |
||||
|
||||
#import "GRPCCronetChannelFactory.h" |
||||
|
||||
#import "ChannelArgsUtil.h" |
||||
#import "GRPCChannel.h" |
||||
|
||||
#ifdef GRPC_COMPILE_WITH_CRONET |
||||
|
||||
#import <Cronet/Cronet.h> |
||||
#include <grpc/grpc_cronet.h> |
||||
|
||||
@implementation GRPCCronetChannelFactory { |
||||
stream_engine *_cronetEngine; |
||||
} |
||||
|
||||
+ (instancetype)sharedInstance { |
||||
static GRPCCronetChannelFactory *instance; |
||||
static dispatch_once_t onceToken; |
||||
dispatch_once(&onceToken, ^{ |
||||
instance = [[self alloc] initWithEngine:[Cronet getGlobalEngine]]; |
||||
}); |
||||
return instance; |
||||
} |
||||
|
||||
- (instancetype)initWithEngine:(stream_engine *)engine { |
||||
NSAssert(engine != NULL, @"Cronet engine cannot be empty."); |
||||
if (!engine) { |
||||
return nil; |
||||
} |
||||
if ((self = [super init])) { |
||||
_cronetEngine = engine; |
||||
} |
||||
return self; |
||||
} |
||||
|
||||
- (grpc_channel *)createChannelWithHost:(NSString *)host channelArgs:(NSDictionary *)args { |
||||
grpc_channel_args *channelArgs = GRPCBuildChannelArgs(args); |
||||
grpc_channel *unmanagedChannel = |
||||
grpc_cronet_secure_channel_create(_cronetEngine, host.UTF8String, channelArgs, NULL); |
||||
GRPCFreeChannelArgs(channelArgs); |
||||
return unmanagedChannel; |
||||
} |
||||
|
||||
@end |
||||
|
||||
#else |
||||
|
||||
@implementation GRPCCronetChannelFactory |
||||
|
||||
+ (instancetype)sharedInstance { |
||||
NSAssert(NO, @"Must enable macro GRPC_COMPILE_WITH_CRONET to build Cronet channel."); |
||||
return nil; |
||||
} |
||||
|
||||
- (grpc_channel *)createChannelWithHost:(NSString *)host channelArgs:(NSDictionary *)args { |
||||
NSAssert(NO, @"Must enable macro GRPC_COMPILE_WITH_CRONET to build Cronet channel."); |
||||
return NULL; |
||||
} |
||||
|
||||
@end |
||||
|
||||
#endif |
@ -0,0 +1,35 @@ |
||||
/*
|
||||
* |
||||
* Copyright 2018 gRPC authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
* |
||||
*/ |
||||
#import "GRPCChannelFactory.h" |
||||
|
||||
@class GRPCChannel; |
||||
|
||||
NS_ASSUME_NONNULL_BEGIN |
||||
|
||||
@interface GRPCInsecureChannelFactory : NSObject<GRPCChannelFactory> |
||||
|
||||
+ (nullable instancetype)sharedInstance; |
||||
|
||||
- (nullable grpc_channel *)createChannelWithHost:(NSString *)host |
||||
channelArgs:(nullable NSDictionary *)args; |
||||
|
||||
- (nullable instancetype)init NS_UNAVAILABLE; |
||||
|
||||
@end |
||||
|
||||
NS_ASSUME_NONNULL_END |
@ -0,0 +1,43 @@ |
||||
/* |
||||
* |
||||
* Copyright 2018 gRPC authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0 |
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
* |
||||
*/ |
||||
|
||||
#import "GRPCInsecureChannelFactory.h" |
||||
|
||||
#import "ChannelArgsUtil.h" |
||||
#import "GRPCChannel.h" |
||||
|
||||
@implementation GRPCInsecureChannelFactory |
||||
|
||||
+ (instancetype)sharedInstance { |
||||
static GRPCInsecureChannelFactory *instance; |
||||
static dispatch_once_t onceToken; |
||||
dispatch_once(&onceToken, ^{ |
||||
instance = [[self alloc] init]; |
||||
}); |
||||
return instance; |
||||
} |
||||
|
||||
- (grpc_channel *)createChannelWithHost:(NSString *)host channelArgs:(NSDictionary *)args { |
||||
grpc_channel_args *coreChannelArgs = GRPCBuildChannelArgs(args); |
||||
grpc_channel *unmanagedChannel = |
||||
grpc_insecure_channel_create(host.UTF8String, coreChannelArgs, NULL); |
||||
GRPCFreeChannelArgs(coreChannelArgs); |
||||
return unmanagedChannel; |
||||
} |
||||
|
||||
@end |
@ -0,0 +1,38 @@ |
||||
/*
|
||||
* |
||||
* Copyright 2018 gRPC authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
* |
||||
*/ |
||||
#import "GRPCChannelFactory.h" |
||||
|
||||
@class GRPCChannel; |
||||
|
||||
NS_ASSUME_NONNULL_BEGIN |
||||
|
||||
@interface GRPCSecureChannelFactory : NSObject<GRPCChannelFactory> |
||||
|
||||
+ (nullable instancetype)factoryWithPEMRootCertificates:(nullable NSString *)rootCerts |
||||
privateKey:(nullable NSString *)privateKey |
||||
certChain:(nullable NSString *)certChain |
||||
error:(NSError **)errorPtr; |
||||
|
||||
- (nullable grpc_channel *)createChannelWithHost:(NSString *)host |
||||
channelArgs:(nullable NSDictionary *)args; |
||||
|
||||
- (nullable instancetype)init NS_UNAVAILABLE; |
||||
|
||||
@end |
||||
|
||||
NS_ASSUME_NONNULL_END |
@ -0,0 +1,135 @@ |
||||
/* |
||||
* |
||||
* Copyright 2018 gRPC authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0 |
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
* |
||||
*/ |
||||
|
||||
#import "GRPCSecureChannelFactory.h" |
||||
|
||||
#include <grpc/grpc_security.h> |
||||
|
||||
#import "ChannelArgsUtil.h" |
||||
#import "GRPCChannel.h" |
||||
|
||||
@implementation GRPCSecureChannelFactory { |
||||
grpc_channel_credentials *_channelCreds; |
||||
} |
||||
|
||||
+ (instancetype)factoryWithPEMRootCertificates:(NSString *)rootCerts |
||||
privateKey:(NSString *)privateKey |
||||
certChain:(NSString *)certChain |
||||
error:(NSError **)errorPtr { |
||||
return [[self alloc] initWithPEMRootCerts:rootCerts |
||||
privateKey:privateKey |
||||
certChain:certChain |
||||
error:errorPtr]; |
||||
} |
||||
|
||||
- (NSData *)nullTerminatedDataWithString:(NSString *)string { |
||||
// dataUsingEncoding: does not return a null-terminated string. |
||||
NSData *data = [string dataUsingEncoding:NSASCIIStringEncoding allowLossyConversion:YES]; |
||||
if (data == nil) { |
||||
return nil; |
||||
} |
||||
NSMutableData *nullTerminated = [NSMutableData dataWithData:data]; |
||||
[nullTerminated appendBytes:"\0" length:1]; |
||||
return nullTerminated; |
||||
} |
||||
|
||||
- (instancetype)initWithPEMRootCerts:(NSString *)rootCerts |
||||
privateKey:(NSString *)privateKey |
||||
certChain:(NSString *)certChain |
||||
error:(NSError **)errorPtr { |
||||
static NSData *defaultRootsASCII; |
||||
static NSError *defaultRootsError; |
||||
static dispatch_once_t loading; |
||||
dispatch_once(&loading, ^{ |
||||
NSString *defaultPath = @"gRPCCertificates.bundle/roots"; // .pem |
||||
// Do not use NSBundle.mainBundle, as it's nil for tests of library projects. |
||||
NSBundle *bundle = [NSBundle bundleForClass:[self class]]; |
||||
NSString *path = [bundle pathForResource:defaultPath ofType:@"pem"]; |
||||
NSError *error; |
||||
// Files in PEM format can have non-ASCII characters in their comments (e.g. for the name of the |
||||
// issuer). Load them as UTF8 and produce an ASCII equivalent. |
||||
NSString *contentInUTF8 = |
||||
[NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:&error]; |
||||
if (contentInUTF8 == nil) { |
||||
defaultRootsError = error; |
||||
return; |
||||
} |
||||
defaultRootsASCII = [self nullTerminatedDataWithString:contentInUTF8]; |
||||
}); |
||||
|
||||
NSData *rootsASCII; |
||||
if (rootCerts != nil) { |
||||
rootsASCII = [self nullTerminatedDataWithString:rootCerts]; |
||||
} else { |
||||
if (defaultRootsASCII == nil) { |
||||
if (errorPtr) { |
||||
*errorPtr = defaultRootsError; |
||||
} |
||||
NSAssert( |
||||
defaultRootsASCII, NSObjectNotAvailableException, |
||||
@"Could not read gRPCCertificates.bundle/roots.pem. This file, " |
||||
"with the root certificates, is needed to establish secure (TLS) connections. " |
||||
"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: %@", |
||||
defaultRootsError); |
||||
return nil; |
||||
} |
||||
rootsASCII = defaultRootsASCII; |
||||
} |
||||
|
||||
grpc_channel_credentials *creds = NULL; |
||||
if (privateKey.length == 0 && certChain.length == 0) { |
||||
creds = grpc_ssl_credentials_create(rootsASCII.bytes, NULL, NULL, NULL); |
||||
} else { |
||||
grpc_ssl_pem_key_cert_pair key_cert_pair; |
||||
NSData *privateKeyASCII = [self nullTerminatedDataWithString:privateKey]; |
||||
NSData *certChainASCII = [self nullTerminatedDataWithString:certChain]; |
||||
key_cert_pair.private_key = privateKeyASCII.bytes; |
||||
key_cert_pair.cert_chain = certChainASCII.bytes; |
||||
if (key_cert_pair.private_key == NULL || key_cert_pair.cert_chain == NULL) { |
||||
creds = grpc_ssl_credentials_create(rootsASCII.bytes, NULL, NULL, NULL); |
||||
} else { |
||||
creds = grpc_ssl_credentials_create(rootsASCII.bytes, &key_cert_pair, NULL, NULL); |
||||
} |
||||
} |
||||
|
||||
if ((self = [super init])) { |
||||
_channelCreds = creds; |
||||
} |
||||
return self; |
||||
} |
||||
|
||||
- (grpc_channel *)createChannelWithHost:(NSString *)host channelArgs:(NSDictionary *)args { |
||||
NSAssert(host.length != 0, @"host cannot be empty"); |
||||
if (host.length == 0) { |
||||
return NULL; |
||||
} |
||||
grpc_channel_args *coreChannelArgs = GRPCBuildChannelArgs(args); |
||||
grpc_channel *unmanagedChannel = |
||||
grpc_secure_channel_create(_channelCreds, host.UTF8String, coreChannelArgs, NULL); |
||||
GRPCFreeChannelArgs(coreChannelArgs); |
||||
return unmanagedChannel; |
||||
} |
||||
|
||||
- (void)dealloc { |
||||
if (_channelCreds != NULL) { |
||||
grpc_channel_credentials_release(_channelCreds); |
||||
} |
||||
} |
||||
|
||||
@end |
@ -0,0 +1,478 @@ |
||||
/* |
||||
* |
||||
* Copyright 2018 gRPC authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0 |
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
* |
||||
*/ |
||||
|
||||
#import <GRPCClient/GRPCCall.h> |
||||
#import <ProtoRPC/ProtoMethod.h> |
||||
#import <RemoteTest/Messages.pbobjc.h> |
||||
#import <XCTest/XCTest.h> |
||||
|
||||
#include <grpc/grpc.h> |
||||
|
||||
#import "../version.h" |
||||
|
||||
// The server address is derived from preprocessor macro, which is |
||||
// in turn derived from environment variable of the same name. |
||||
#define NSStringize_helper(x) #x |
||||
#define NSStringize(x) @NSStringize_helper(x) |
||||
static NSString *const kHostAddress = NSStringize(HOST_PORT_LOCAL); |
||||
static NSString *const kRemoteSSLHost = NSStringize(HOST_PORT_REMOTE); |
||||
|
||||
// Package and service name of test server |
||||
static NSString *const kPackage = @"grpc.testing"; |
||||
static NSString *const kService = @"TestService"; |
||||
|
||||
static GRPCProtoMethod *kInexistentMethod; |
||||
static GRPCProtoMethod *kEmptyCallMethod; |
||||
static GRPCProtoMethod *kUnaryCallMethod; |
||||
static GRPCProtoMethod *kFullDuplexCallMethod; |
||||
|
||||
static const int kSimpleDataLength = 100; |
||||
|
||||
static const NSTimeInterval kTestTimeout = 16; |
||||
|
||||
// Reveal the _class ivar for testing access |
||||
@interface GRPCCall2 () { |
||||
@public |
||||
GRPCCall *_call; |
||||
} |
||||
|
||||
@end |
||||
|
||||
// Convenience class to use blocks as callbacks |
||||
@interface ClientTestsBlockCallbacks : NSObject<GRPCResponseHandler> |
||||
|
||||
- (instancetype)initWithInitialMetadataCallback:(void (^)(NSDictionary *))initialMetadataCallback |
||||
messageCallback:(void (^)(id))messageCallback |
||||
closeCallback:(void (^)(NSDictionary *, NSError *))closeCallback; |
||||
|
||||
@end |
||||
|
||||
@implementation ClientTestsBlockCallbacks { |
||||
void (^_initialMetadataCallback)(NSDictionary *); |
||||
void (^_messageCallback)(id); |
||||
void (^_closeCallback)(NSDictionary *, NSError *); |
||||
dispatch_queue_t _dispatchQueue; |
||||
} |
||||
|
||||
- (instancetype)initWithInitialMetadataCallback:(void (^)(NSDictionary *))initialMetadataCallback |
||||
messageCallback:(void (^)(id))messageCallback |
||||
closeCallback:(void (^)(NSDictionary *, NSError *))closeCallback { |
||||
if ((self = [super init])) { |
||||
_initialMetadataCallback = initialMetadataCallback; |
||||
_messageCallback = messageCallback; |
||||
_closeCallback = closeCallback; |
||||
_dispatchQueue = dispatch_queue_create(nil, DISPATCH_QUEUE_SERIAL); |
||||
} |
||||
return self; |
||||
} |
||||
|
||||
- (void)didReceiveInitialMetadata:(NSDictionary *)initialMetadata { |
||||
if (self->_initialMetadataCallback) { |
||||
self->_initialMetadataCallback(initialMetadata); |
||||
} |
||||
} |
||||
|
||||
- (void)didReceiveRawMessage:(GPBMessage *)message { |
||||
if (self->_messageCallback) { |
||||
self->_messageCallback(message); |
||||
} |
||||
} |
||||
|
||||
- (void)didCloseWithTrailingMetadata:(NSDictionary *)trailingMetadata error:(NSError *)error { |
||||
if (self->_closeCallback) { |
||||
self->_closeCallback(trailingMetadata, error); |
||||
} |
||||
} |
||||
|
||||
- (dispatch_queue_t)dispatchQueue { |
||||
return _dispatchQueue; |
||||
} |
||||
|
||||
@end |
||||
|
||||
@interface CallAPIv2Tests : XCTestCase<GRPCAuthorizationProtocol> |
||||
|
||||
@end |
||||
|
||||
@implementation CallAPIv2Tests |
||||
|
||||
- (void)setUp { |
||||
// This method isn't implemented by the remote server. |
||||
kInexistentMethod = |
||||
[[GRPCProtoMethod alloc] initWithPackage:kPackage service:kService method:@"Inexistent"]; |
||||
kEmptyCallMethod = |
||||
[[GRPCProtoMethod alloc] initWithPackage:kPackage service:kService method:@"EmptyCall"]; |
||||
kUnaryCallMethod = |
||||
[[GRPCProtoMethod alloc] initWithPackage:kPackage service:kService method:@"UnaryCall"]; |
||||
kFullDuplexCallMethod = |
||||
[[GRPCProtoMethod alloc] initWithPackage:kPackage service:kService method:@"FullDuplexCall"]; |
||||
} |
||||
|
||||
- (void)testMetadata { |
||||
__weak XCTestExpectation *expectation = [self expectationWithDescription:@"RPC unauthorized."]; |
||||
|
||||
RMTSimpleRequest *request = [RMTSimpleRequest message]; |
||||
request.fillUsername = YES; |
||||
request.fillOauthScope = YES; |
||||
|
||||
GRPCRequestOptions *callRequest = |
||||
[[GRPCRequestOptions alloc] initWithHost:(NSString *)kRemoteSSLHost |
||||
path:kUnaryCallMethod.HTTPPath |
||||
safety:GRPCCallSafetyDefault]; |
||||
__block NSDictionary *init_md; |
||||
__block NSDictionary *trailing_md; |
||||
GRPCMutableCallOptions *options = [[GRPCMutableCallOptions alloc] init]; |
||||
options.oauth2AccessToken = @"bogusToken"; |
||||
GRPCCall2 *call = [[GRPCCall2 alloc] |
||||
initWithRequestOptions:callRequest |
||||
responseHandler:[[ClientTestsBlockCallbacks alloc] |
||||
initWithInitialMetadataCallback:^(NSDictionary *initialMetadata) { |
||||
init_md = initialMetadata; |
||||
} |
||||
messageCallback:^(id message) { |
||||
XCTFail(@"Received unexpected response."); |
||||
} |
||||
closeCallback:^(NSDictionary *trailingMetadata, NSError *error) { |
||||
trailing_md = trailingMetadata; |
||||
if (error) { |
||||
XCTAssertEqual(error.code, 16, |
||||
@"Finished with unexpected error: %@", error); |
||||
XCTAssertEqualObjects(init_md, |
||||
error.userInfo[kGRPCHeadersKey]); |
||||
XCTAssertEqualObjects(trailing_md, |
||||
error.userInfo[kGRPCTrailersKey]); |
||||
NSString *challengeHeader = init_md[@"www-authenticate"]; |
||||
XCTAssertGreaterThan(challengeHeader.length, 0, |
||||
@"No challenge in response headers %@", |
||||
init_md); |
||||
[expectation fulfill]; |
||||
} |
||||
}] |
||||
callOptions:options]; |
||||
|
||||
[call start]; |
||||
[call writeData:[request data]]; |
||||
[call finish]; |
||||
|
||||
[self waitForExpectationsWithTimeout:kTestTimeout handler:nil]; |
||||
} |
||||
|
||||
- (void)testUserAgentPrefix { |
||||
__weak XCTestExpectation *completion = [self expectationWithDescription:@"Empty RPC completed."]; |
||||
__weak XCTestExpectation *recvInitialMd = |
||||
[self expectationWithDescription:@"Did not receive initial md."]; |
||||
|
||||
GRPCRequestOptions *request = [[GRPCRequestOptions alloc] initWithHost:kHostAddress |
||||
path:kEmptyCallMethod.HTTPPath |
||||
safety:GRPCCallSafetyDefault]; |
||||
NSDictionary *headers = |
||||
[NSDictionary dictionaryWithObjectsAndKeys:@"", @"x-grpc-test-echo-useragent", nil]; |
||||
GRPCMutableCallOptions *options = [[GRPCMutableCallOptions alloc] init]; |
||||
options.transportType = GRPCTransportTypeInsecure; |
||||
options.userAgentPrefix = @"Foo"; |
||||
options.initialMetadata = headers; |
||||
GRPCCall2 *call = [[GRPCCall2 alloc] |
||||
initWithRequestOptions:request |
||||
responseHandler:[[ClientTestsBlockCallbacks alloc] initWithInitialMetadataCallback:^( |
||||
NSDictionary *initialMetadata) { |
||||
NSString *userAgent = initialMetadata[@"x-grpc-test-echo-useragent"]; |
||||
// Test the regex is correct |
||||
NSString *expectedUserAgent = @"Foo grpc-objc/"; |
||||
expectedUserAgent = |
||||
[expectedUserAgent stringByAppendingString:GRPC_OBJC_VERSION_STRING]; |
||||
expectedUserAgent = [expectedUserAgent stringByAppendingString:@" grpc-c/"]; |
||||
expectedUserAgent = |
||||
[expectedUserAgent stringByAppendingString:GRPC_C_VERSION_STRING]; |
||||
expectedUserAgent = [expectedUserAgent stringByAppendingString:@" (ios; chttp2; "]; |
||||
expectedUserAgent = [expectedUserAgent |
||||
stringByAppendingString:[NSString stringWithUTF8String:grpc_g_stands_for()]]; |
||||
expectedUserAgent = [expectedUserAgent stringByAppendingString:@")"]; |
||||
XCTAssertEqualObjects(userAgent, expectedUserAgent); |
||||
|
||||
NSError *error = nil; |
||||
// Change in format of user-agent field in a direction that does not match |
||||
// the regex will likely cause problem for certain gRPC users. For details, |
||||
// refer to internal doc https://goo.gl/c2diBc |
||||
NSRegularExpression *regex = [NSRegularExpression |
||||
regularExpressionWithPattern: |
||||
@" grpc-[a-zA-Z0-9]+(-[a-zA-Z0-9]+)?/[^ ,]+( \\([^)]*\\))?" |
||||
options:0 |
||||
error:&error]; |
||||
|
||||
NSString *customUserAgent = |
||||
[regex stringByReplacingMatchesInString:userAgent |
||||
options:0 |
||||
range:NSMakeRange(0, [userAgent length]) |
||||
withTemplate:@""]; |
||||
XCTAssertEqualObjects(customUserAgent, @"Foo"); |
||||
[recvInitialMd fulfill]; |
||||
} |
||||
messageCallback:^(id message) { |
||||
XCTAssertNotNil(message); |
||||
XCTAssertEqual([message length], 0, |
||||
@"Non-empty response received: %@", message); |
||||
} |
||||
closeCallback:^(NSDictionary *trailingMetadata, NSError *error) { |
||||
if (error) { |
||||
XCTFail(@"Finished with unexpected error: %@", error); |
||||
} else { |
||||
[completion fulfill]; |
||||
} |
||||
}] |
||||
callOptions:options]; |
||||
[call writeData:[NSData data]]; |
||||
[call start]; |
||||
|
||||
[self waitForExpectationsWithTimeout:kTestTimeout handler:nil]; |
||||
} |
||||
|
||||
- (void)getTokenWithHandler:(void (^)(NSString *token))handler { |
||||
dispatch_queue_t queue = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL); |
||||
dispatch_sync(queue, ^{ |
||||
handler(@"test-access-token"); |
||||
}); |
||||
} |
||||
|
||||
- (void)testOAuthToken { |
||||
__weak XCTestExpectation *completion = [self expectationWithDescription:@"RPC completed."]; |
||||
|
||||
GRPCRequestOptions *requestOptions = |
||||
[[GRPCRequestOptions alloc] initWithHost:kHostAddress |
||||
path:kEmptyCallMethod.HTTPPath |
||||
safety:GRPCCallSafetyDefault]; |
||||
GRPCMutableCallOptions *options = [[GRPCMutableCallOptions alloc] init]; |
||||
options.transportType = GRPCTransportTypeInsecure; |
||||
options.authTokenProvider = self; |
||||
__block GRPCCall2 *call = [[GRPCCall2 alloc] |
||||
initWithRequestOptions:requestOptions |
||||
responseHandler:[[ClientTestsBlockCallbacks alloc] |
||||
initWithInitialMetadataCallback:nil |
||||
messageCallback:nil |
||||
closeCallback:^(NSDictionary *trailingMetadata, |
||||
NSError *error) { |
||||
[completion fulfill]; |
||||
}] |
||||
callOptions:options]; |
||||
[call writeData:[NSData data]]; |
||||
[call start]; |
||||
[call finish]; |
||||
|
||||
[self waitForExpectationsWithTimeout:kTestTimeout handler:nil]; |
||||
} |
||||
|
||||
- (void)testResponseSizeLimitExceeded { |
||||
__weak XCTestExpectation *completion = [self expectationWithDescription:@"RPC completed."]; |
||||
|
||||
GRPCRequestOptions *requestOptions = |
||||
[[GRPCRequestOptions alloc] initWithHost:kHostAddress |
||||
path:kUnaryCallMethod.HTTPPath |
||||
safety:GRPCCallSafetyDefault]; |
||||
GRPCMutableCallOptions *options = [[GRPCMutableCallOptions alloc] init]; |
||||
options.responseSizeLimit = kSimpleDataLength; |
||||
options.transportType = GRPCTransportTypeInsecure; |
||||
|
||||
RMTSimpleRequest *request = [RMTSimpleRequest message]; |
||||
request.payload.body = [NSMutableData dataWithLength:options.responseSizeLimit]; |
||||
request.responseSize = (int32_t)(options.responseSizeLimit * 2); |
||||
|
||||
GRPCCall2 *call = [[GRPCCall2 alloc] |
||||
initWithRequestOptions:requestOptions |
||||
responseHandler:[[ClientTestsBlockCallbacks alloc] |
||||
initWithInitialMetadataCallback:nil |
||||
messageCallback:nil |
||||
closeCallback:^(NSDictionary *trailingMetadata, |
||||
NSError *error) { |
||||
XCTAssertNotNil(error, |
||||
@"Expecting non-nil error"); |
||||
XCTAssertEqual(error.code, |
||||
GRPCErrorCodeResourceExhausted); |
||||
[completion fulfill]; |
||||
}] |
||||
callOptions:options]; |
||||
[call writeData:[request data]]; |
||||
[call start]; |
||||
[call finish]; |
||||
|
||||
[self waitForExpectationsWithTimeout:kTestTimeout handler:nil]; |
||||
} |
||||
|
||||
- (void)testIdempotentProtoRPC { |
||||
__weak XCTestExpectation *response = [self expectationWithDescription:@"Expected response."]; |
||||
__weak XCTestExpectation *completion = [self expectationWithDescription:@"RPC completed."]; |
||||
|
||||
RMTSimpleRequest *request = [RMTSimpleRequest message]; |
||||
request.responseSize = kSimpleDataLength; |
||||
request.fillUsername = YES; |
||||
request.fillOauthScope = YES; |
||||
GRPCRequestOptions *requestOptions = |
||||
[[GRPCRequestOptions alloc] initWithHost:kHostAddress |
||||
path:kUnaryCallMethod.HTTPPath |
||||
safety:GRPCCallSafetyIdempotentRequest]; |
||||
|
||||
GRPCMutableCallOptions *options = [[GRPCMutableCallOptions alloc] init]; |
||||
options.transportType = GRPCTransportTypeInsecure; |
||||
GRPCCall2 *call = [[GRPCCall2 alloc] |
||||
initWithRequestOptions:requestOptions |
||||
responseHandler:[[ClientTestsBlockCallbacks alloc] initWithInitialMetadataCallback:nil |
||||
messageCallback:^(id message) { |
||||
NSData *data = (NSData *)message; |
||||
XCTAssertNotNil(data, @"nil value received as response."); |
||||
XCTAssertGreaterThan(data.length, 0, |
||||
@"Empty response received."); |
||||
RMTSimpleResponse *responseProto = |
||||
[RMTSimpleResponse parseFromData:data error:NULL]; |
||||
// We expect empty strings, not nil: |
||||
XCTAssertNotNil(responseProto.username, |
||||
@"Response's username is nil."); |
||||
XCTAssertNotNil(responseProto.oauthScope, |
||||
@"Response's OAuth scope is nil."); |
||||
[response fulfill]; |
||||
} |
||||
closeCallback:^(NSDictionary *trailingMetadata, NSError *error) { |
||||
XCTAssertNil(error, @"Finished with unexpected error: %@", |
||||
error); |
||||
[completion fulfill]; |
||||
}] |
||||
callOptions:options]; |
||||
|
||||
[call start]; |
||||
[call writeData:[request data]]; |
||||
[call finish]; |
||||
|
||||
[self waitForExpectationsWithTimeout:kTestTimeout handler:nil]; |
||||
} |
||||
|
||||
- (void)testTimeout { |
||||
__weak XCTestExpectation *completion = [self expectationWithDescription:@"RPC completed."]; |
||||
|
||||
GRPCMutableCallOptions *options = [[GRPCMutableCallOptions alloc] init]; |
||||
options.timeout = 0.001; |
||||
GRPCRequestOptions *requestOptions = |
||||
[[GRPCRequestOptions alloc] initWithHost:kHostAddress |
||||
path:kFullDuplexCallMethod.HTTPPath |
||||
safety:GRPCCallSafetyDefault]; |
||||
|
||||
GRPCCall2 *call = [[GRPCCall2 alloc] |
||||
initWithRequestOptions:requestOptions |
||||
responseHandler: |
||||
[[ClientTestsBlockCallbacks alloc] initWithInitialMetadataCallback:nil |
||||
messageCallback:^(NSData *data) { |
||||
XCTFail(@"Failure: response received; Expect: no response received."); |
||||
} |
||||
closeCallback:^(NSDictionary *trailingMetadata, NSError *error) { |
||||
XCTAssertNotNil(error, |
||||
@"Failure: no error received; Expect: receive " |
||||
@"deadline exceeded."); |
||||
XCTAssertEqual(error.code, GRPCErrorCodeDeadlineExceeded); |
||||
[completion fulfill]; |
||||
}] |
||||
callOptions:options]; |
||||
|
||||
[call start]; |
||||
|
||||
[self waitForExpectationsWithTimeout:kTestTimeout handler:nil]; |
||||
} |
||||
|
||||
- (void)testTimeoutBackoffWithTimeout:(double)timeout Backoff:(double)backoff { |
||||
const double maxConnectTime = timeout > backoff ? timeout : backoff; |
||||
const double kMargin = 0.1; |
||||
|
||||
__weak XCTestExpectation *completion = [self expectationWithDescription:@"Timeout in a second."]; |
||||
NSString *const kDummyAddress = [NSString stringWithFormat:@"127.0.0.1:10000"]; |
||||
GRPCRequestOptions *requestOptions = |
||||
[[GRPCRequestOptions alloc] initWithHost:kDummyAddress |
||||
path:@"/dummy/path" |
||||
safety:GRPCCallSafetyDefault]; |
||||
GRPCMutableCallOptions *options = [[GRPCMutableCallOptions alloc] init]; |
||||
options.connectMinTimeout = timeout; |
||||
options.connectInitialBackoff = backoff; |
||||
options.connectMaxBackoff = 0; |
||||
|
||||
NSDate *startTime = [NSDate date]; |
||||
GRPCCall2 *call = [[GRPCCall2 alloc] |
||||
initWithRequestOptions:requestOptions |
||||
responseHandler:[[ClientTestsBlockCallbacks alloc] initWithInitialMetadataCallback:nil |
||||
messageCallback:^(NSData *data) { |
||||
XCTFail(@"Received message. Should not reach here."); |
||||
} |
||||
closeCallback:^(NSDictionary *trailingMetadata, NSError *error) { |
||||
XCTAssertNotNil(error, |
||||
@"Finished with no error; expecting error"); |
||||
XCTAssertLessThan( |
||||
[[NSDate date] timeIntervalSinceDate:startTime], |
||||
maxConnectTime + kMargin); |
||||
[completion fulfill]; |
||||
}] |
||||
callOptions:options]; |
||||
|
||||
[call start]; |
||||
|
||||
[self waitForExpectationsWithTimeout:kTestTimeout handler:nil]; |
||||
} |
||||
|
||||
- (void)testTimeoutBackoff1 { |
||||
[self testTimeoutBackoffWithTimeout:0.7 Backoff:0.4]; |
||||
} |
||||
|
||||
- (void)testTimeoutBackoff2 { |
||||
[self testTimeoutBackoffWithTimeout:0.3 Backoff:0.8]; |
||||
} |
||||
|
||||
- (void)testCompression { |
||||
__weak XCTestExpectation *completion = [self expectationWithDescription:@"RPC completed."]; |
||||
|
||||
RMTSimpleRequest *request = [RMTSimpleRequest message]; |
||||
request.expectCompressed = [RMTBoolValue message]; |
||||
request.expectCompressed.value = YES; |
||||
request.responseCompressed = [RMTBoolValue message]; |
||||
request.expectCompressed.value = YES; |
||||
request.responseSize = kSimpleDataLength; |
||||
request.payload.body = [NSMutableData dataWithLength:kSimpleDataLength]; |
||||
GRPCRequestOptions *requestOptions = |
||||
[[GRPCRequestOptions alloc] initWithHost:kHostAddress |
||||
path:kUnaryCallMethod.HTTPPath |
||||
safety:GRPCCallSafetyDefault]; |
||||
|
||||
GRPCMutableCallOptions *options = [[GRPCMutableCallOptions alloc] init]; |
||||
options.transportType = GRPCTransportTypeInsecure; |
||||
options.compressionAlgorithm = GRPCCompressGzip; |
||||
GRPCCall2 *call = [[GRPCCall2 alloc] |
||||
initWithRequestOptions:requestOptions |
||||
responseHandler:[[ClientTestsBlockCallbacks alloc] initWithInitialMetadataCallback:nil |
||||
messageCallback:^(NSData *data) { |
||||
NSError *error; |
||||
RMTSimpleResponse *response = |
||||
[RMTSimpleResponse parseFromData:data error:&error]; |
||||
XCTAssertNil(error, @"Error when parsing response: %@", error); |
||||
XCTAssertEqual(response.payload.body.length, kSimpleDataLength); |
||||
} |
||||
closeCallback:^(NSDictionary *trailingMetadata, NSError *error) { |
||||
XCTAssertNil(error, @"Received failure: %@", error); |
||||
[completion fulfill]; |
||||
}] |
||||
|
||||
callOptions:options]; |
||||
|
||||
[call start]; |
||||
[call writeData:[request data]]; |
||||
[call finish]; |
||||
|
||||
[self waitForExpectationsWithTimeout:kTestTimeout handler:nil]; |
||||
} |
||||
|
||||
@end |
@ -0,0 +1,22 @@ |
||||
<?xml version="1.0" encoding="UTF-8"?> |
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> |
||||
<plist version="1.0"> |
||||
<dict> |
||||
<key>CFBundleDevelopmentRegion</key> |
||||
<string>$(DEVELOPMENT_LANGUAGE)</string> |
||||
<key>CFBundleExecutable</key> |
||||
<string>$(EXECUTABLE_NAME)</string> |
||||
<key>CFBundleIdentifier</key> |
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string> |
||||
<key>CFBundleInfoDictionaryVersion</key> |
||||
<string>6.0</string> |
||||
<key>CFBundleName</key> |
||||
<string>$(PRODUCT_NAME)</string> |
||||
<key>CFBundlePackageType</key> |
||||
<string>BNDL</string> |
||||
<key>CFBundleShortVersionString</key> |
||||
<string>1.0</string> |
||||
<key>CFBundleVersion</key> |
||||
<string>1</string> |
||||
</dict> |
||||
</plist> |
@ -0,0 +1,63 @@ |
||||
/* |
||||
* |
||||
* Copyright 2018 gRPC authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0 |
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
* |
||||
*/ |
||||
|
||||
#import <XCTest/XCTest.h> |
||||
|
||||
#import "../../GRPCClient/private/GRPCChannel.h" |
||||
#import "../../GRPCClient/private/GRPCChannelPool+Test.h" |
||||
#import "../../GRPCClient/private/GRPCCompletionQueue.h" |
||||
|
||||
#define TEST_TIMEOUT 32 |
||||
|
||||
static NSString *kDummyHost = @"dummy.host"; |
||||
static NSString *kDummyHost2 = @"dummy.host.2"; |
||||
static NSString *kDummyPath = @"/dummy/path"; |
||||
|
||||
@interface ChannelPoolTest : XCTestCase |
||||
|
||||
@end |
||||
|
||||
@implementation ChannelPoolTest |
||||
|
||||
+ (void)setUp { |
||||
grpc_init(); |
||||
} |
||||
|
||||
- (void)testCreateAndCacheChannel { |
||||
GRPCChannelPool *pool = [[GRPCChannelPool alloc] initTestPool]; |
||||
GRPCCallOptions *options1 = [[GRPCCallOptions alloc] init]; |
||||
GRPCCallOptions *options2 = [options1 copy]; |
||||
GRPCMutableCallOptions *options3 = [options1 mutableCopy]; |
||||
options3.transportType = GRPCTransportTypeInsecure; |
||||
|
||||
GRPCPooledChannel *channel1 = [pool channelWithHost:kDummyHost callOptions:options1]; |
||||
GRPCPooledChannel *channel2 = [pool channelWithHost:kDummyHost callOptions:options2]; |
||||
GRPCPooledChannel *channel3 = [pool channelWithHost:kDummyHost callOptions:options3]; |
||||
GRPCPooledChannel *channel4 = [pool channelWithHost:kDummyHost2 callOptions:options1]; |
||||
|
||||
XCTAssertNotNil(channel1); |
||||
XCTAssertNotNil(channel2); |
||||
XCTAssertNotNil(channel3); |
||||
XCTAssertNotNil(channel4); |
||||
XCTAssertEqual(channel1, channel2); |
||||
XCTAssertNotEqual(channel1, channel3); |
||||
XCTAssertNotEqual(channel1, channel4); |
||||
XCTAssertNotEqual(channel3, channel4); |
||||
} |
||||
|
||||
@end |
@ -0,0 +1,112 @@ |
||||
/* |
||||
* |
||||
* Copyright 2018 gRPC authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0 |
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
* |
||||
*/ |
||||
|
||||
#import <XCTest/XCTest.h> |
||||
|
||||
#import "../../GRPCClient/GRPCCallOptions.h" |
||||
#import "../../GRPCClient/private/GRPCChannel.h" |
||||
#import "../../GRPCClient/private/GRPCChannelPool+Test.h" |
||||
#import "../../GRPCClient/private/GRPCChannelPool.h" |
||||
#import "../../GRPCClient/private/GRPCCompletionQueue.h" |
||||
#import "../../GRPCClient/private/GRPCWrappedCall.h" |
||||
|
||||
static NSString *kDummyHost = @"dummy.host"; |
||||
static NSString *kDummyPath = @"/dummy/path"; |
||||
|
||||
@interface ChannelTests : XCTestCase |
||||
|
||||
@end |
||||
|
||||
@implementation ChannelTests |
||||
|
||||
+ (void)setUp { |
||||
grpc_init(); |
||||
} |
||||
|
||||
- (void)testPooledChannelCreatingChannel { |
||||
GRPCCallOptions *options = [[GRPCCallOptions alloc] init]; |
||||
GRPCChannelConfiguration *config = |
||||
[[GRPCChannelConfiguration alloc] initWithHost:kDummyHost callOptions:options]; |
||||
GRPCPooledChannel *channel = [[GRPCPooledChannel alloc] initWithChannelConfiguration:config]; |
||||
GRPCCompletionQueue *cq = [GRPCCompletionQueue completionQueue]; |
||||
GRPCWrappedCall *wrappedCall = |
||||
[channel wrappedCallWithPath:kDummyPath completionQueue:cq callOptions:options]; |
||||
XCTAssertNotNil(channel.wrappedChannel); |
||||
(void)wrappedCall; |
||||
} |
||||
|
||||
- (void)testTimedDestroyChannel { |
||||
const NSTimeInterval kDestroyDelay = 1.0; |
||||
GRPCCallOptions *options = [[GRPCCallOptions alloc] init]; |
||||
GRPCChannelConfiguration *config = |
||||
[[GRPCChannelConfiguration alloc] initWithHost:kDummyHost callOptions:options]; |
||||
GRPCPooledChannel *channel = |
||||
[[GRPCPooledChannel alloc] initWithChannelConfiguration:config destroyDelay:kDestroyDelay]; |
||||
GRPCCompletionQueue *cq = [GRPCCompletionQueue completionQueue]; |
||||
GRPCWrappedCall *wrappedCall; |
||||
GRPCChannel *wrappedChannel; |
||||
@autoreleasepool { |
||||
wrappedCall = [channel wrappedCallWithPath:kDummyPath completionQueue:cq callOptions:options]; |
||||
XCTAssertNotNil(channel.wrappedChannel); |
||||
|
||||
// Unref and ref channel immediately; expect using the same raw channel. |
||||
wrappedChannel = channel.wrappedChannel; |
||||
|
||||
wrappedCall = nil; |
||||
wrappedCall = [channel wrappedCallWithPath:kDummyPath completionQueue:cq callOptions:options]; |
||||
XCTAssertEqual(channel.wrappedChannel, wrappedChannel); |
||||
|
||||
// Unref and ref channel after destroy delay; expect a new raw channel. |
||||
wrappedCall = nil; |
||||
} |
||||
sleep(kDestroyDelay + 1); |
||||
XCTAssertNil(channel.wrappedChannel); |
||||
wrappedCall = [channel wrappedCallWithPath:kDummyPath completionQueue:cq callOptions:options]; |
||||
XCTAssertNotEqual(channel.wrappedChannel, wrappedChannel); |
||||
} |
||||
|
||||
- (void)testDisconnect { |
||||
const NSTimeInterval kDestroyDelay = 1.0; |
||||
GRPCCallOptions *options = [[GRPCCallOptions alloc] init]; |
||||
GRPCChannelConfiguration *config = |
||||
[[GRPCChannelConfiguration alloc] initWithHost:kDummyHost callOptions:options]; |
||||
GRPCPooledChannel *channel = |
||||
[[GRPCPooledChannel alloc] initWithChannelConfiguration:config destroyDelay:kDestroyDelay]; |
||||
GRPCCompletionQueue *cq = [GRPCCompletionQueue completionQueue]; |
||||
GRPCWrappedCall *wrappedCall = |
||||
[channel wrappedCallWithPath:kDummyPath completionQueue:cq callOptions:options]; |
||||
XCTAssertNotNil(channel.wrappedChannel); |
||||
|
||||
// Disconnect; expect wrapped channel to be dropped |
||||
[channel disconnect]; |
||||
XCTAssertNil(channel.wrappedChannel); |
||||
|
||||
// Create a new call and unref the old call; confirm that destroy of the old call does not make |
||||
// the channel disconnect, even after the destroy delay. |
||||
GRPCWrappedCall *wrappedCall2 = |
||||
[channel wrappedCallWithPath:kDummyPath completionQueue:cq callOptions:options]; |
||||
XCTAssertNotNil(channel.wrappedChannel); |
||||
GRPCChannel *wrappedChannel = channel.wrappedChannel; |
||||
wrappedCall = nil; |
||||
sleep(kDestroyDelay + 1); |
||||
XCTAssertNotNil(channel.wrappedChannel); |
||||
XCTAssertEqual(wrappedChannel, channel.wrappedChannel); |
||||
(void)wrappedCall2; |
||||
} |
||||
|
||||
@end |
@ -0,0 +1,22 @@ |
||||
<?xml version="1.0" encoding="UTF-8"?> |
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> |
||||
<plist version="1.0"> |
||||
<dict> |
||||
<key>CFBundleDevelopmentRegion</key> |
||||
<string>$(DEVELOPMENT_LANGUAGE)</string> |
||||
<key>CFBundleExecutable</key> |
||||
<string>$(EXECUTABLE_NAME)</string> |
||||
<key>CFBundleIdentifier</key> |
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string> |
||||
<key>CFBundleInfoDictionaryVersion</key> |
||||
<string>6.0</string> |
||||
<key>CFBundleName</key> |
||||
<string>$(PRODUCT_NAME)</string> |
||||
<key>CFBundlePackageType</key> |
||||
<string>BNDL</string> |
||||
<key>CFBundleShortVersionString</key> |
||||
<string>1.0</string> |
||||
<key>CFBundleVersion</key> |
||||
<string>1</string> |
||||
</dict> |
||||
</plist> |
@ -0,0 +1,22 @@ |
||||
<?xml version="1.0" encoding="UTF-8"?> |
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> |
||||
<plist version="1.0"> |
||||
<dict> |
||||
<key>CFBundleDevelopmentRegion</key> |
||||
<string>$(DEVELOPMENT_LANGUAGE)</string> |
||||
<key>CFBundleExecutable</key> |
||||
<string>$(EXECUTABLE_NAME)</string> |
||||
<key>CFBundleIdentifier</key> |
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string> |
||||
<key>CFBundleInfoDictionaryVersion</key> |
||||
<string>6.0</string> |
||||
<key>CFBundleName</key> |
||||
<string>$(PRODUCT_NAME)</string> |
||||
<key>CFBundlePackageType</key> |
||||
<string>BNDL</string> |
||||
<key>CFBundleShortVersionString</key> |
||||
<string>1.0</string> |
||||
<key>CFBundleVersion</key> |
||||
<string>1</string> |
||||
</dict> |
||||
</plist> |
@ -0,0 +1,116 @@ |
||||
/* |
||||
* |
||||
* Copyright 2018 gRPC authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0 |
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
* |
||||
*/ |
||||
|
||||
#import <XCTest/XCTest.h> |
||||
|
||||
#import <RemoteTest/Messages.pbobjc.h> |
||||
#import <RemoteTest/Test.pbobjc.h> |
||||
#import <RemoteTest/Test.pbrpc.h> |
||||
#import <RxLibrary/GRXBufferedPipe.h> |
||||
#import <RxLibrary/GRXWriter+Immediate.h> |
||||
#import <grpc/grpc.h> |
||||
|
||||
#define NSStringize_helper(x) #x |
||||
#define NSStringize(x) @NSStringize_helper(x) |
||||
static NSString *kRemoteHost = NSStringize(HOST_PORT_REMOTE); |
||||
const int32_t kRemoteInteropServerOverhead = 12; |
||||
|
||||
static const NSTimeInterval TEST_TIMEOUT = 16000; |
||||
|
||||
@interface InteropTestsCallOptions : XCTestCase |
||||
|
||||
@end |
||||
|
||||
@implementation InteropTestsCallOptions { |
||||
RMTTestService *_service; |
||||
} |
||||
|
||||
- (void)setUp { |
||||
self.continueAfterFailure = NO; |
||||
_service = [RMTTestService serviceWithHost:kRemoteHost]; |
||||
_service.options = [[GRPCCallOptions alloc] init]; |
||||
} |
||||
|
||||
- (void)test4MBResponsesAreAccepted { |
||||
__weak XCTestExpectation *expectation = [self expectationWithDescription:@"MaxResponseSize"]; |
||||
|
||||
RMTSimpleRequest *request = [RMTSimpleRequest message]; |
||||
const int32_t kPayloadSize = |
||||
4 * 1024 * 1024 - kRemoteInteropServerOverhead; // 4MB - encoding overhead |
||||
request.responseSize = kPayloadSize; |
||||
|
||||
[_service unaryCallWithRequest:request |
||||
handler:^(RMTSimpleResponse *response, NSError *error) { |
||||
XCTAssertNil(error, @"Finished with unexpected error: %@", error); |
||||
XCTAssertEqual(response.payload.body.length, kPayloadSize); |
||||
[expectation fulfill]; |
||||
}]; |
||||
|
||||
[self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil]; |
||||
} |
||||
|
||||
- (void)testResponsesOverMaxSizeFailWithActionableMessage { |
||||
__weak XCTestExpectation *expectation = [self expectationWithDescription:@"ResponseOverMaxSize"]; |
||||
|
||||
RMTSimpleRequest *request = [RMTSimpleRequest message]; |
||||
const int32_t kPayloadSize = |
||||
4 * 1024 * 1024 - kRemoteInteropServerOverhead + 1; // 1B over max size |
||||
request.responseSize = kPayloadSize; |
||||
|
||||
[_service unaryCallWithRequest:request |
||||
handler:^(RMTSimpleResponse *response, NSError *error) { |
||||
XCTAssertEqualObjects( |
||||
error.localizedDescription, |
||||
@"Received message larger than max (4194305 vs. 4194304)"); |
||||
[expectation fulfill]; |
||||
}]; |
||||
|
||||
[self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil]; |
||||
} |
||||
|
||||
- (void)testResponsesOver4MBAreAcceptedIfOptedIn { |
||||
__weak XCTestExpectation *expectation = |
||||
[self expectationWithDescription:@"HigherResponseSizeLimit"]; |
||||
|
||||
RMTSimpleRequest *request = [RMTSimpleRequest message]; |
||||
const size_t kPayloadSize = 5 * 1024 * 1024; // 5MB |
||||
request.responseSize = kPayloadSize; |
||||
|
||||
GRPCProtoCall *rpc = [_service |
||||
RPCToUnaryCallWithRequest:request |
||||
handler:^(RMTSimpleResponse *response, NSError *error) { |
||||
XCTAssertNil(error, @"Finished with unexpected error: %@", error); |
||||
XCTAssertEqual(response.payload.body.length, kPayloadSize); |
||||
[expectation fulfill]; |
||||
}]; |
||||
GRPCCallOptions *options = rpc.options; |
||||
options.responseSizeLimit = 6 * 1024 * 1024; |
||||
|
||||
[rpc start]; |
||||
|
||||
[self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil]; |
||||
} |
||||
|
||||
- (void)testPerformanceExample { |
||||
// This is an example of a performance test case. |
||||
[self measureBlock:^{ |
||||
// Put the code you want to measure the time of here. |
||||
}]; |
||||
} |
||||
|
||||
@end |
@ -0,0 +1,22 @@ |
||||
<?xml version="1.0" encoding="UTF-8"?> |
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> |
||||
<plist version="1.0"> |
||||
<dict> |
||||
<key>CFBundleDevelopmentRegion</key> |
||||
<string>$(DEVELOPMENT_LANGUAGE)</string> |
||||
<key>CFBundleExecutable</key> |
||||
<string>$(EXECUTABLE_NAME)</string> |
||||
<key>CFBundleIdentifier</key> |
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string> |
||||
<key>CFBundleInfoDictionaryVersion</key> |
||||
<string>6.0</string> |
||||
<key>CFBundleName</key> |
||||
<string>$(PRODUCT_NAME)</string> |
||||
<key>CFBundlePackageType</key> |
||||
<string>BNDL</string> |
||||
<key>CFBundleShortVersionString</key> |
||||
<string>1.0</string> |
||||
<key>CFBundleVersion</key> |
||||
<string>1</string> |
||||
</dict> |
||||
</plist> |
@ -0,0 +1,259 @@ |
||||
/* |
||||
* |
||||
* Copyright 2018 gRPC authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0 |
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
* |
||||
*/ |
||||
|
||||
#import <XCTest/XCTest.h> |
||||
|
||||
#import <Cronet/Cronet.h> |
||||
#import <RemoteTest/Messages.pbobjc.h> |
||||
#import <RemoteTest/Test.pbobjc.h> |
||||
#import <RemoteTest/Test.pbrpc.h> |
||||
#import <RxLibrary/GRXBufferedPipe.h> |
||||
|
||||
#define NSStringize_helper(x) #x |
||||
#define NSStringize(x) @NSStringize_helper(x) |
||||
static NSString *const kRemoteSSLHost = NSStringize(HOST_PORT_REMOTE); |
||||
static NSString *const kLocalSSLHost = NSStringize(HOST_PORT_LOCALSSL); |
||||
static NSString *const kLocalCleartextHost = NSStringize(HOST_PORT_LOCAL); |
||||
|
||||
static const NSTimeInterval TEST_TIMEOUT = 8000; |
||||
|
||||
@interface RMTStreamingOutputCallRequest (Constructors) |
||||
+ (instancetype)messageWithPayloadSize:(NSNumber *)payloadSize |
||||
requestedResponseSize:(NSNumber *)responseSize; |
||||
@end |
||||
|
||||
@implementation RMTStreamingOutputCallRequest (Constructors) |
||||
+ (instancetype)messageWithPayloadSize:(NSNumber *)payloadSize |
||||
requestedResponseSize:(NSNumber *)responseSize { |
||||
RMTStreamingOutputCallRequest *request = [self message]; |
||||
RMTResponseParameters *parameters = [RMTResponseParameters message]; |
||||
parameters.size = responseSize.intValue; |
||||
[request.responseParametersArray addObject:parameters]; |
||||
request.payload.body = [NSMutableData dataWithLength:payloadSize.unsignedIntegerValue]; |
||||
return request; |
||||
} |
||||
@end |
||||
|
||||
@interface RMTStreamingOutputCallResponse (Constructors) |
||||
+ (instancetype)messageWithPayloadSize:(NSNumber *)payloadSize; |
||||
@end |
||||
|
||||
@implementation RMTStreamingOutputCallResponse (Constructors) |
||||
+ (instancetype)messageWithPayloadSize:(NSNumber *)payloadSize { |
||||
RMTStreamingOutputCallResponse *response = [self message]; |
||||
response.payload.type = RMTPayloadType_Compressable; |
||||
response.payload.body = [NSMutableData dataWithLength:payloadSize.unsignedIntegerValue]; |
||||
return response; |
||||
} |
||||
@end |
||||
|
||||
@interface InteropTestsMultipleChannels : XCTestCase |
||||
|
||||
@end |
||||
|
||||
dispatch_once_t initCronet; |
||||
|
||||
@implementation InteropTestsMultipleChannels { |
||||
RMTTestService *_remoteService; |
||||
RMTTestService *_remoteCronetService; |
||||
RMTTestService *_localCleartextService; |
||||
RMTTestService *_localSSLService; |
||||
} |
||||
|
||||
- (void)setUp { |
||||
[super setUp]; |
||||
|
||||
self.continueAfterFailure = NO; |
||||
|
||||
// Default stack with remote host |
||||
_remoteService = [RMTTestService serviceWithHost:kRemoteSSLHost]; |
||||
|
||||
// Cronet stack with remote host |
||||
_remoteCronetService = [RMTTestService serviceWithHost:kRemoteSSLHost]; |
||||
|
||||
dispatch_once(&initCronet, ^{ |
||||
[Cronet setHttp2Enabled:YES]; |
||||
[Cronet start]; |
||||
}); |
||||
|
||||
GRPCCallOptions *options = [[GRPCCallOptions alloc] init]; |
||||
options.transportType = GRPCTransportTypeCronet; |
||||
options.cronetEngine = [Cronet getGlobalEngine]; |
||||
_remoteCronetService.options = options; |
||||
|
||||
// Local stack with no SSL |
||||
_localCleartextService = [RMTTestService serviceWithHost:kLocalCleartextHost]; |
||||
options = [[GRPCCallOptions alloc] init]; |
||||
options.transportType = GRPCTransportTypeInsecure; |
||||
_localCleartextService.options = options; |
||||
|
||||
// Local stack with SSL |
||||
_localSSLService = [RMTTestService serviceWithHost:kLocalSSLHost]; |
||||
|
||||
NSBundle *bundle = [NSBundle bundleForClass:[self class]]; |
||||
NSString *certsPath = |
||||
[bundle pathForResource:@"TestCertificates.bundle/test-certificates" ofType:@"pem"]; |
||||
NSError *error = nil; |
||||
NSString *certs = |
||||
[NSString stringWithContentsOfFile:certsPath encoding:NSUTF8StringEncoding error:&error]; |
||||
XCTAssertNil(error); |
||||
|
||||
options = [[GRPCCallOptions alloc] init]; |
||||
options.transportType = GRPCTransportTypeChttp2BoringSSL; |
||||
options.PEMRootCertificates = certs; |
||||
options.hostNameOverride = @"foo.test.google.fr"; |
||||
_localSSLService.options = options; |
||||
} |
||||
|
||||
- (void)testEmptyUnaryRPC { |
||||
__weak XCTestExpectation *expectRemote = [self expectationWithDescription:@"Remote RPC finish"]; |
||||
__weak XCTestExpectation *expectCronetRemote = |
||||
[self expectationWithDescription:@"Remote RPC finish"]; |
||||
__weak XCTestExpectation *expectCleartext = |
||||
[self expectationWithDescription:@"Remote RPC finish"]; |
||||
__weak XCTestExpectation *expectSSL = [self expectationWithDescription:@"Remote RPC finish"]; |
||||
|
||||
GPBEmpty *request = [GPBEmpty message]; |
||||
|
||||
void (^handler)(GPBEmpty *response, NSError *error) = ^(GPBEmpty *response, NSError *error) { |
||||
XCTAssertNil(error, @"Finished with unexpected error: %@", error); |
||||
|
||||
id expectedResponse = [GPBEmpty message]; |
||||
XCTAssertEqualObjects(response, expectedResponse); |
||||
}; |
||||
|
||||
[_remoteService emptyCallWithRequest:request |
||||
handler:^(GPBEmpty *response, NSError *error) { |
||||
handler(response, error); |
||||
[expectRemote fulfill]; |
||||
}]; |
||||
[_remoteCronetService emptyCallWithRequest:request |
||||
handler:^(GPBEmpty *response, NSError *error) { |
||||
handler(response, error); |
||||
[expectCronetRemote fulfill]; |
||||
}]; |
||||
[_localCleartextService emptyCallWithRequest:request |
||||
handler:^(GPBEmpty *response, NSError *error) { |
||||
handler(response, error); |
||||
[expectCleartext fulfill]; |
||||
}]; |
||||
[_localSSLService emptyCallWithRequest:request |
||||
handler:^(GPBEmpty *response, NSError *error) { |
||||
handler(response, error); |
||||
[expectSSL fulfill]; |
||||
}]; |
||||
|
||||
[self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil]; |
||||
} |
||||
|
||||
- (void)testFullDuplexRPC { |
||||
__weak XCTestExpectation *expectRemote = [self expectationWithDescription:@"Remote RPC finish"]; |
||||
__weak XCTestExpectation *expectCronetRemote = |
||||
[self expectationWithDescription:@"Remote RPC finish"]; |
||||
__weak XCTestExpectation *expectCleartext = |
||||
[self expectationWithDescription:@"Remote RPC finish"]; |
||||
__weak XCTestExpectation *expectSSL = [self expectationWithDescription:@"Remote RPC finish"]; |
||||
|
||||
NSArray *requestSizes = @[ @100, @101, @102, @103 ]; |
||||
NSArray *responseSizes = @[ @104, @105, @106, @107 ]; |
||||
XCTAssertEqual([requestSizes count], [responseSizes count]); |
||||
NSUInteger kRounds = [requestSizes count]; |
||||
|
||||
NSMutableArray *requests = [NSMutableArray arrayWithCapacity:kRounds]; |
||||
NSMutableArray *responses = [NSMutableArray arrayWithCapacity:kRounds]; |
||||
for (int i = 0; i < kRounds; i++) { |
||||
requests[i] = [RMTStreamingOutputCallRequest messageWithPayloadSize:requestSizes[i] |
||||
requestedResponseSize:responseSizes[i]]; |
||||
responses[i] = [RMTStreamingOutputCallResponse messageWithPayloadSize:responseSizes[i]]; |
||||
} |
||||
|
||||
__block NSMutableArray *steps = [NSMutableArray arrayWithCapacity:4]; |
||||
__block NSMutableArray *requestsBuffers = [NSMutableArray arrayWithCapacity:4]; |
||||
for (int i = 0; i < 4; i++) { |
||||
steps[i] = [NSNumber numberWithUnsignedInteger:0]; |
||||
requestsBuffers[i] = [[GRXBufferedPipe alloc] init]; |
||||
[requestsBuffers[i] writeValue:requests[0]]; |
||||
} |
||||
|
||||
BOOL (^handler)(int, BOOL, RMTStreamingOutputCallResponse *, NSError *) = |
||||
^(int index, BOOL done, RMTStreamingOutputCallResponse *response, NSError *error) { |
||||
XCTAssertNil(error, @"Finished with unexpected error: %@", error); |
||||
XCTAssertTrue(done || response, @"Event handler called without an event."); |
||||
if (response) { |
||||
NSUInteger step = [steps[index] unsignedIntegerValue]; |
||||
XCTAssertLessThan(step, kRounds, @"More than %lu responses received.", |
||||
(unsigned long)kRounds); |
||||
XCTAssertEqualObjects(response, responses[step]); |
||||
step++; |
||||
steps[index] = [NSNumber numberWithUnsignedInteger:step]; |
||||
GRXBufferedPipe *pipe = requestsBuffers[index]; |
||||
if (step < kRounds) { |
||||
[pipe writeValue:requests[step]]; |
||||
} else { |
||||
[pipe writesFinishedWithError:nil]; |
||||
} |
||||
} |
||||
if (done) { |
||||
NSUInteger step = [steps[index] unsignedIntegerValue]; |
||||
XCTAssertEqual(step, kRounds, @"Received %lu responses instead of %lu.", step, kRounds); |
||||
return YES; |
||||
} |
||||
return NO; |
||||
}; |
||||
|
||||
[_remoteService |
||||
fullDuplexCallWithRequestsWriter:requestsBuffers[0] |
||||
eventHandler:^(BOOL done, |
||||
RMTStreamingOutputCallResponse *_Nullable response, |
||||
NSError *_Nullable error) { |
||||
if (handler(0, done, response, error)) { |
||||
[expectRemote fulfill]; |
||||
} |
||||
}]; |
||||
[_remoteCronetService |
||||
fullDuplexCallWithRequestsWriter:requestsBuffers[1] |
||||
eventHandler:^(BOOL done, |
||||
RMTStreamingOutputCallResponse *_Nullable response, |
||||
NSError *_Nullable error) { |
||||
if (handler(1, done, response, error)) { |
||||
[expectCronetRemote fulfill]; |
||||
} |
||||
}]; |
||||
[_localCleartextService |
||||
fullDuplexCallWithRequestsWriter:requestsBuffers[2] |
||||
eventHandler:^(BOOL done, |
||||
RMTStreamingOutputCallResponse *_Nullable response, |
||||
NSError *_Nullable error) { |
||||
if (handler(2, done, response, error)) { |
||||
[expectCleartext fulfill]; |
||||
} |
||||
}]; |
||||
[_localSSLService |
||||
fullDuplexCallWithRequestsWriter:requestsBuffers[3] |
||||
eventHandler:^(BOOL done, |
||||
RMTStreamingOutputCallResponse *_Nullable response, |
||||
NSError *_Nullable error) { |
||||
if (handler(3, done, response, error)) { |
||||
[expectSSL fulfill]; |
||||
} |
||||
}]; |
||||
|
||||
[self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil]; |
||||
} |
||||
|
||||
@end |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,90 @@ |
||||
<?xml version="1.0" encoding="UTF-8"?> |
||||
<Scheme |
||||
LastUpgradeVersion = "1000" |
||||
version = "1.3"> |
||||
<BuildAction |
||||
parallelizeBuildables = "YES" |
||||
buildImplicitDependencies = "YES"> |
||||
<BuildActionEntries> |
||||
<BuildActionEntry |
||||
buildForTesting = "YES" |
||||
buildForRunning = "YES" |
||||
buildForProfiling = "NO" |
||||
buildForArchiving = "NO" |
||||
buildForAnalyzing = "NO"> |
||||
<BuildableReference |
||||
BuildableIdentifier = "primary" |
||||
BlueprintIdentifier = "5E3B95A121CAC6C500C0A151" |
||||
BuildableName = "APIv2Tests.xctest" |
||||
BlueprintName = "APIv2Tests" |
||||
ReferencedContainer = "container:Tests.xcodeproj"> |
||||
</BuildableReference> |
||||
</BuildActionEntry> |
||||
</BuildActionEntries> |
||||
</BuildAction> |
||||
<TestAction |
||||
buildConfiguration = "Test" |
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" |
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" |
||||
shouldUseLaunchSchemeArgsEnv = "YES"> |
||||
<Testables> |
||||
<TestableReference |
||||
skipped = "NO"> |
||||
<BuildableReference |
||||
BuildableIdentifier = "primary" |
||||
BlueprintIdentifier = "5E3B95A121CAC6C500C0A151" |
||||
BuildableName = "APIv2Tests.xctest" |
||||
BlueprintName = "APIv2Tests" |
||||
ReferencedContainer = "container:Tests.xcodeproj"> |
||||
</BuildableReference> |
||||
</TestableReference> |
||||
</Testables> |
||||
<AdditionalOptions> |
||||
</AdditionalOptions> |
||||
</TestAction> |
||||
<LaunchAction |
||||
buildConfiguration = "Test" |
||||
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 = "5E3B95A121CAC6C500C0A151" |
||||
BuildableName = "APIv2Tests.xctest" |
||||
BlueprintName = "APIv2Tests" |
||||
ReferencedContainer = "container:Tests.xcodeproj"> |
||||
</BuildableReference> |
||||
</MacroExpansion> |
||||
<AdditionalOptions> |
||||
</AdditionalOptions> |
||||
</LaunchAction> |
||||
<ProfileAction |
||||
buildConfiguration = "Release" |
||||
shouldUseLaunchSchemeArgsEnv = "YES" |
||||
savedToolIdentifier = "" |
||||
useCustomWorkingDirectory = "NO" |
||||
debugDocumentVersioning = "YES"> |
||||
<MacroExpansion> |
||||
<BuildableReference |
||||
BuildableIdentifier = "primary" |
||||
BlueprintIdentifier = "5E3B95A121CAC6C500C0A151" |
||||
BuildableName = "APIv2Tests.xctest" |
||||
BlueprintName = "APIv2Tests" |
||||
ReferencedContainer = "container:Tests.xcodeproj"> |
||||
</BuildableReference> |
||||
</MacroExpansion> |
||||
</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 = "0930" |
||||
version = "1.3"> |
||||
<BuildAction |
||||
parallelizeBuildables = "YES" |
||||
buildImplicitDependencies = "YES"> |
||||
<BuildActionEntries> |
||||
<BuildActionEntry |
||||
buildForTesting = "YES" |
||||
buildForRunning = "YES" |
||||
buildForProfiling = "NO" |
||||
buildForArchiving = "NO" |
||||
buildForAnalyzing = "NO"> |
||||
<BuildableReference |
||||
BuildableIdentifier = "primary" |
||||
BlueprintIdentifier = "5EB2A2E32107DED300EB4B69" |
||||
BuildableName = "ChannelTests.xctest" |
||||
BlueprintName = "ChannelTests" |
||||
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 = "5EB2A2E32107DED300EB4B69" |
||||
BuildableName = "ChannelTests.xctest" |
||||
BlueprintName = "ChannelTests" |
||||
ReferencedContainer = "container:Tests.xcodeproj"> |
||||
</BuildableReference> |
||||
</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 = "5EB2A2E32107DED300EB4B69" |
||||
BuildableName = "ChannelTests.xctest" |
||||
BlueprintName = "ChannelTests" |
||||
ReferencedContainer = "container:Tests.xcodeproj"> |
||||
</BuildableReference> |
||||
</MacroExpansion> |
||||
<AdditionalOptions> |
||||
</AdditionalOptions> |
||||
</LaunchAction> |
||||
<ProfileAction |
||||
buildConfiguration = "Release" |
||||
shouldUseLaunchSchemeArgsEnv = "YES" |
||||
savedToolIdentifier = "" |
||||
useCustomWorkingDirectory = "NO" |
||||
debugDocumentVersioning = "YES"> |
||||
<MacroExpansion> |
||||
<BuildableReference |
||||
BuildableIdentifier = "primary" |
||||
BlueprintIdentifier = "5EB2A2E32107DED300EB4B69" |
||||
BuildableName = "ChannelTests.xctest" |
||||
BlueprintName = "ChannelTests" |
||||
ReferencedContainer = "container:Tests.xcodeproj"> |
||||
</BuildableReference> |
||||
</MacroExpansion> |
||||
</ProfileAction> |
||||
<AnalyzeAction |
||||
buildConfiguration = "Debug"> |
||||
</AnalyzeAction> |
||||
<ArchiveAction |
||||
buildConfiguration = "Release" |
||||
revealArchiveInOrganizer = "YES"> |
||||
</ArchiveAction> |
||||
</Scheme> |
@ -0,0 +1,56 @@ |
||||
<?xml version="1.0" encoding="UTF-8"?> |
||||
<Scheme |
||||
LastUpgradeVersion = "0930" |
||||
version = "1.3"> |
||||
<BuildAction |
||||
parallelizeBuildables = "YES" |
||||
buildImplicitDependencies = "YES"> |
||||
</BuildAction> |
||||
<TestAction |
||||
buildConfiguration = "Test" |
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" |
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" |
||||
shouldUseLaunchSchemeArgsEnv = "YES"> |
||||
<Testables> |
||||
<TestableReference |
||||
skipped = "NO"> |
||||
<BuildableReference |
||||
BuildableIdentifier = "primary" |
||||
BlueprintIdentifier = "5E7D71B1210B9EC8001EA6BA" |
||||
BuildableName = "InteropTestsCallOptions.xctest" |
||||
BlueprintName = "InteropTestsCallOptions" |
||||
ReferencedContainer = "container:Tests.xcodeproj"> |
||||
</BuildableReference> |
||||
</TestableReference> |
||||
</Testables> |
||||
<AdditionalOptions> |
||||
</AdditionalOptions> |
||||
</TestAction> |
||||
<LaunchAction |
||||
buildConfiguration = "Test" |
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" |
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" |
||||
launchStyle = "0" |
||||
useCustomWorkingDirectory = "NO" |
||||
ignoresPersistentStateOnLaunch = "NO" |
||||
debugDocumentVersioning = "YES" |
||||
debugServiceExtension = "internal" |
||||
allowLocationSimulation = "YES"> |
||||
<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,56 @@ |
||||
<?xml version="1.0" encoding="UTF-8"?> |
||||
<Scheme |
||||
LastUpgradeVersion = "0930" |
||||
version = "1.3"> |
||||
<BuildAction |
||||
parallelizeBuildables = "YES" |
||||
buildImplicitDependencies = "YES"> |
||||
</BuildAction> |
||||
<TestAction |
||||
buildConfiguration = "Cronet" |
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" |
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" |
||||
shouldUseLaunchSchemeArgsEnv = "YES"> |
||||
<Testables> |
||||
<TestableReference |
||||
skipped = "NO"> |
||||
<BuildableReference |
||||
BuildableIdentifier = "primary" |
||||
BlueprintIdentifier = "5EB2A2F42109284500EB4B69" |
||||
BuildableName = "InteropTestsMultipleChannels.xctest" |
||||
BlueprintName = "InteropTestsMultipleChannels" |
||||
ReferencedContainer = "container:Tests.xcodeproj"> |
||||
</BuildableReference> |
||||
</TestableReference> |
||||
</Testables> |
||||
<AdditionalOptions> |
||||
</AdditionalOptions> |
||||
</TestAction> |
||||
<LaunchAction |
||||
buildConfiguration = "Cronet" |
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" |
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" |
||||
launchStyle = "0" |
||||
useCustomWorkingDirectory = "NO" |
||||
ignoresPersistentStateOnLaunch = "NO" |
||||
debugDocumentVersioning = "YES" |
||||
debugServiceExtension = "internal" |
||||
allowLocationSimulation = "YES"> |
||||
<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> |
Loading…
Reference in new issue