Home | History | Annotate | Download | only in private
      1 /*
      2  *
      3  * Copyright 2015 gRPC authors.
      4  *
      5  * Licensed under the Apache License, Version 2.0 (the "License");
      6  * you may not use this file except in compliance with the License.
      7  * You may obtain a copy of the License at
      8  *
      9  *     http://www.apache.org/licenses/LICENSE-2.0
     10  *
     11  * Unless required by applicable law or agreed to in writing, software
     12  * distributed under the License is distributed on an "AS IS" BASIS,
     13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     14  * See the License for the specific language governing permissions and
     15  * limitations under the License.
     16  *
     17  */
     18 
     19 #import "GRPCChannel.h"
     20 
     21 #include <grpc/grpc_security.h>
     22 #ifdef GRPC_COMPILE_WITH_CRONET
     23 #include <grpc/grpc_cronet.h>
     24 #endif
     25 #include <grpc/support/alloc.h>
     26 #include <grpc/support/log.h>
     27 #include <grpc/support/string_util.h>
     28 
     29 #ifdef GRPC_COMPILE_WITH_CRONET
     30 #import <Cronet/Cronet.h>
     31 #import <GRPCClient/GRPCCall+Cronet.h>
     32 #endif
     33 #import "GRPCCompletionQueue.h"
     34 
     35 static void *copy_pointer_arg(void *p) {
     36   // Add ref count to the object when making copy
     37   id obj = (__bridge id)p;
     38   return (__bridge_retained void *)obj;
     39 }
     40 
     41 static void destroy_pointer_arg(void *p) {
     42   // Decrease ref count to the object when destroying
     43   CFRelease((CFTreeRef)p);
     44 }
     45 
     46 static int cmp_pointer_arg(void *p, void *q) { return p == q; }
     47 
     48 static const grpc_arg_pointer_vtable objc_arg_vtable = {copy_pointer_arg, destroy_pointer_arg,
     49                                                         cmp_pointer_arg};
     50 
     51 static void FreeChannelArgs(grpc_channel_args *channel_args) {
     52   for (size_t i = 0; i < channel_args->num_args; ++i) {
     53     grpc_arg *arg = &channel_args->args[i];
     54     gpr_free(arg->key);
     55     if (arg->type == GRPC_ARG_STRING) {
     56       gpr_free(arg->value.string);
     57     }
     58   }
     59   gpr_free(channel_args->args);
     60   gpr_free(channel_args);
     61 }
     62 
     63 /**
     64  * Allocates a @c grpc_channel_args and populates it with the options specified in the
     65  * @c dictionary. Keys must be @c NSString. If the value responds to @c @selector(UTF8String) then
     66  * it will be mapped to @c GRPC_ARG_STRING. If not, it will be mapped to @c GRPC_ARG_INTEGER if the
     67  * value responds to @c @selector(intValue). Otherwise, an exception will be raised. The caller of
     68  * this function is responsible for calling @c freeChannelArgs on a non-NULL returned value.
     69  */
     70 static grpc_channel_args *BuildChannelArgs(NSDictionary *dictionary) {
     71   if (!dictionary) {
     72     return NULL;
     73   }
     74 
     75   NSArray *keys = [dictionary allKeys];
     76   NSUInteger argCount = [keys count];
     77 
     78   grpc_channel_args *channelArgs = gpr_malloc(sizeof(grpc_channel_args));
     79   channelArgs->num_args = argCount;
     80   channelArgs->args = gpr_malloc(argCount * sizeof(grpc_arg));
     81 
     82   // TODO(kriswuollett) Check that keys adhere to GRPC core library requirements
     83 
     84   for (NSUInteger i = 0; i < argCount; ++i) {
     85     grpc_arg *arg = &channelArgs->args[i];
     86     arg->key = gpr_strdup([keys[i] UTF8String]);
     87 
     88     id value = dictionary[keys[i]];
     89     if ([value respondsToSelector:@selector(UTF8String)]) {
     90       arg->type = GRPC_ARG_STRING;
     91       arg->value.string = gpr_strdup([value UTF8String]);
     92     } else if ([value respondsToSelector:@selector(intValue)]) {
     93       arg->type = GRPC_ARG_INTEGER;
     94       arg->value.integer = [value intValue];
     95     } else if (value != nil) {
     96       arg->type = GRPC_ARG_POINTER;
     97       arg->value.pointer.p = (__bridge_retained void *)value;
     98       arg->value.pointer.vtable = &objc_arg_vtable;
     99     } else {
    100       [NSException raise:NSInvalidArgumentException
    101                   format:@"Invalid value type: %@", [value class]];
    102     }
    103   }
    104 
    105   return channelArgs;
    106 }
    107 
    108 @implementation GRPCChannel {
    109   // Retain arguments to channel_create because they may not be used on the thread that invoked
    110   // the channel_create function.
    111   NSString *_host;
    112   grpc_channel_args *_channelArgs;
    113 }
    114 
    115 #ifdef GRPC_COMPILE_WITH_CRONET
    116 - (instancetype)initWithHost:(NSString *)host
    117                 cronetEngine:(stream_engine *)cronetEngine
    118                  channelArgs:(NSDictionary *)channelArgs {
    119   if (!host) {
    120     [NSException raise:NSInvalidArgumentException format:@"host argument missing"];
    121   }
    122 
    123   if (self = [super init]) {
    124     _channelArgs = BuildChannelArgs(channelArgs);
    125     _host = [host copy];
    126     _unmanagedChannel =
    127         grpc_cronet_secure_channel_create(cronetEngine, _host.UTF8String, _channelArgs, NULL);
    128   }
    129 
    130   return self;
    131 }
    132 #endif
    133 
    134 - (instancetype)initWithHost:(NSString *)host
    135                       secure:(BOOL)secure
    136                  credentials:(struct grpc_channel_credentials *)credentials
    137                  channelArgs:(NSDictionary *)channelArgs {
    138   if (!host) {
    139     [NSException raise:NSInvalidArgumentException format:@"host argument missing"];
    140   }
    141 
    142   if (secure && !credentials) {
    143     return nil;
    144   }
    145 
    146   if (self = [super init]) {
    147     _channelArgs = BuildChannelArgs(channelArgs);
    148     _host = [host copy];
    149     if (secure) {
    150       _unmanagedChannel =
    151           grpc_secure_channel_create(credentials, _host.UTF8String, _channelArgs, NULL);
    152     } else {
    153       _unmanagedChannel = grpc_insecure_channel_create(_host.UTF8String, _channelArgs, NULL);
    154     }
    155   }
    156 
    157   return self;
    158 }
    159 
    160 - (void)dealloc {
    161   // TODO(jcanizales): Be sure to add a test with a server that closes the connection prematurely,
    162   // as in the past that made this call to crash.
    163   grpc_channel_destroy(_unmanagedChannel);
    164   FreeChannelArgs(_channelArgs);
    165 }
    166 
    167 #ifdef GRPC_COMPILE_WITH_CRONET
    168 + (GRPCChannel *)secureCronetChannelWithHost:(NSString *)host
    169                                  channelArgs:(NSDictionary *)channelArgs {
    170   stream_engine *engine = [GRPCCall cronetEngine];
    171   if (!engine) {
    172     [NSException raise:NSInvalidArgumentException format:@"cronet_engine is NULL. Set it first."];
    173     return nil;
    174   }
    175   return [[GRPCChannel alloc] initWithHost:host cronetEngine:engine channelArgs:channelArgs];
    176 }
    177 #endif
    178 
    179 + (GRPCChannel *)secureChannelWithHost:(NSString *)host {
    180   return [[GRPCChannel alloc] initWithHost:host secure:YES credentials:NULL channelArgs:NULL];
    181 }
    182 
    183 + (GRPCChannel *)secureChannelWithHost:(NSString *)host
    184                            credentials:(struct grpc_channel_credentials *)credentials
    185                            channelArgs:(NSDictionary *)channelArgs {
    186   return [[GRPCChannel alloc] initWithHost:host
    187                                     secure:YES
    188                                credentials:credentials
    189                                channelArgs:channelArgs];
    190 }
    191 
    192 + (GRPCChannel *)insecureChannelWithHost:(NSString *)host channelArgs:(NSDictionary *)channelArgs {
    193   return [[GRPCChannel alloc] initWithHost:host secure:NO credentials:NULL channelArgs:channelArgs];
    194 }
    195 
    196 - (grpc_call *)unmanagedCallWithPath:(NSString *)path
    197                           serverName:(NSString *)serverName
    198                              timeout:(NSTimeInterval)timeout
    199                      completionQueue:(GRPCCompletionQueue *)queue {
    200   GPR_ASSERT(timeout >= 0);
    201   if (timeout < 0) {
    202     timeout = 0;
    203   }
    204   grpc_slice host_slice = grpc_empty_slice();
    205   if (serverName) {
    206     host_slice = grpc_slice_from_copied_string(serverName.UTF8String);
    207   }
    208   grpc_slice path_slice = grpc_slice_from_copied_string(path.UTF8String);
    209   gpr_timespec deadline_ms =
    210       timeout == 0 ? gpr_inf_future(GPR_CLOCK_REALTIME)
    211                    : gpr_time_add(gpr_now(GPR_CLOCK_MONOTONIC),
    212                                   gpr_time_from_millis((int64_t)(timeout * 1000), GPR_TIMESPAN));
    213   grpc_call *call = grpc_channel_create_call(_unmanagedChannel, NULL, GRPC_PROPAGATE_DEFAULTS,
    214                                              queue.unmanagedQueue, path_slice,
    215                                              serverName ? &host_slice : NULL, deadline_ms, NULL);
    216   if (serverName) {
    217     grpc_slice_unref(host_slice);
    218   }
    219   grpc_slice_unref(path_slice);
    220   return call;
    221 }
    222 
    223 @end
    224