diff options
Diffstat (limited to 'src/objective-c/GRPCClient/private/GRPCChannel.m')
-rw-r--r-- | src/objective-c/GRPCClient/private/GRPCChannel.m | 168 |
1 files changed, 158 insertions, 10 deletions
diff --git a/src/objective-c/GRPCClient/private/GRPCChannel.m b/src/objective-c/GRPCClient/private/GRPCChannel.m index 4366e63320..7e55a473d7 100644 --- a/src/objective-c/GRPCClient/private/GRPCChannel.m +++ b/src/objective-c/GRPCClient/private/GRPCChannel.m @@ -1,6 +1,6 @@ /* * - * Copyright 2015, Google Inc. + * Copyright 2015-2016, Google Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -33,22 +33,114 @@ #import "GRPCChannel.h" -#include <grpc/grpc.h> +#include <grpc/grpc_security.h> +#include <grpc/support/alloc.h> +#include <grpc/support/log.h> +#include <grpc/support/string_util.h> -@implementation GRPCChannel +/** + * Returns @c grpc_channel_credentials from the specified @c path. If the file at the path could not + * be read then NULL is returned. If NULL is returned, @c errorPtr may not be NULL if there are + * details available describing what went wrong. + */ +static grpc_channel_credentials *CertificatesAtPath(NSString *path, NSError **errorPtr) { + // 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:errorPtr]; + NSData *contentInASCII = [contentInUTF8 dataUsingEncoding:NSASCIIStringEncoding + allowLossyConversion:YES]; + if (!contentInASCII.bytes) { + // Passing NULL to grpc_ssl_credentials_create produces behavior we don't want, so return. + return NULL; + } + return grpc_ssl_credentials_create(contentInASCII.bytes, NULL, NULL); +} + +void freeChannelArgs(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); +} + +/** + * Allocates a @c grpc_channel_args and populates it with the options specified in the + * @c dictionary. Keys must be @c NSString. If the value responds to @c @selector(UTF8String) then + * it will be mapped to @c GRPC_ARG_STRING. If not, it will be mapped to @c GRPC_ARG_INTEGER if the + * value responds to @c @selector(intValue). Otherwise, an exception will be raised. The caller of + * this function is responsible for calling @c freeChannelArgs on a non-NULL returned value. + */ +grpc_channel_args * buildChannelArgs(NSDictionary *dictionary) { + if (!dictionary) { + return NULL; + } -- (instancetype)init { - return [self initWithChannel:NULL]; + NSArray *keys = [dictionary allKeys]; + NSUInteger argCount = [keys count]; + + grpc_channel_args *channelArgs = gpr_malloc(sizeof(grpc_channel_args)); + channelArgs->num_args = argCount; + channelArgs->args = gpr_malloc(argCount * sizeof(grpc_arg)); + + // TODO(kriswuollett) Check that keys adhere to GRPC core library requirements + + for (NSUInteger i = 0; i < argCount; ++i) { + grpc_arg *arg = &channelArgs->args[i]; + 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]); + } else if ([value respondsToSelector:@selector(intValue)]) { + arg->type = GRPC_ARG_INTEGER; + arg->value.integer = [value intValue]; + } else { + [NSException raise:NSInvalidArgumentException + format:@"Invalid value type: %@", [value class]]; + } + } + + return channelArgs; +} + +@implementation GRPCChannel { + // Retain arguments to channel_create because they may not be used on the thread that invoked + // the channel_create function. + NSString *_host; + grpc_channel_args *_channelArgs; } -// Designated initializer -- (instancetype)initWithChannel:(grpc_channel *)unmanagedChannel { - if (!unmanagedChannel) { + +- (instancetype)initWithHost:(NSString *)host + secure:(BOOL)secure + credentials:(struct grpc_channel_credentials *)credentials + channelArgs:(NSDictionary *)channelArgs { + if (!host) { + [NSException raise:NSInvalidArgumentException format:@"host argument missing"]; + } + + if (secure && !credentials) { return nil; } - if ((self = [super init])) { - _unmanagedChannel = unmanagedChannel; + + if (self = [super init]) { + _channelArgs = buildChannelArgs(channelArgs); + _host = [host copy]; + if (secure) { + _unmanagedChannel = grpc_secure_channel_create(credentials, _host.UTF8String, _channelArgs, + NULL); + } else { + _unmanagedChannel = grpc_insecure_channel_create(_host.UTF8String, _channelArgs, NULL); + } } + return self; } @@ -56,5 +148,61 @@ // TODO(jcanizales): Be sure to add a test with a server that closes the connection prematurely, // as in the past that made this call to crash. grpc_channel_destroy(_unmanagedChannel); + freeChannelArgs(_channelArgs); } + ++ (GRPCChannel *)secureChannelWithHost:(NSString *)host { + return [[GRPCChannel alloc] initWithHost:host secure:YES credentials:NULL channelArgs:NULL]; +} + ++ (GRPCChannel *)secureChannelWithHost:(NSString *)host + pathToCertificates:(NSString *)path + channelArgs:(NSDictionary *)channelArgs { + // Load default SSL certificates once. + static grpc_channel_credentials *kDefaultCertificates; + 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; + kDefaultCertificates = CertificatesAtPath(path, &error); + NSAssert(kDefaultCertificates, @"Could not read %@/%@.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: %@", + bundle.bundlePath, defaultPath, error); + }); + + //TODO(jcanizales): Add NSError** parameter to the initializer. + grpc_channel_credentials *certificates = path + ? CertificatesAtPath(path, NULL) + : kDefaultCertificates; + + return [[GRPCChannel alloc] initWithHost:host + secure:YES + credentials:certificates + channelArgs:channelArgs]; +} + + ++ (GRPCChannel *)secureChannelWithHost:(NSString *)host + credentials:(struct grpc_channel_credentials *)credentials + channelArgs:(NSDictionary *)channelArgs { + return [[GRPCChannel alloc] initWithHost:host + secure:YES + credentials:credentials + channelArgs:channelArgs]; + +} + ++ (GRPCChannel *)insecureChannelWithHost:(NSString *)host + channelArgs:(NSDictionary *)channelArgs { + return [[GRPCChannel alloc] initWithHost:host + secure:NO + credentials:NULL + channelArgs:channelArgs]; +} + @end |