• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
35static 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
41static void destroy_pointer_arg(void *p) {
42  // Decrease ref count to the object when destroying
43  CFRelease((CFTreeRef)p);
44}
45
46static int cmp_pointer_arg(void *p, void *q) { return p == q; }
47
48static const grpc_arg_pointer_vtable objc_arg_vtable = {copy_pointer_arg, destroy_pointer_arg,
49                                                        cmp_pointer_arg};
50
51static 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 */
70static 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