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