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 "GRPCWrappedCall.h"
20
21#import <Foundation/Foundation.h>
22#include <grpc/byte_buffer.h>
23#include <grpc/grpc.h>
24#include <grpc/support/alloc.h>
25
26#import "GRPCCompletionQueue.h"
27#import "GRPCHost.h"
28#import "NSData+GRPC.h"
29#import "NSDictionary+GRPC.h"
30#import "NSError+GRPC.h"
31
32#import "GRPCOpBatchLog.h"
33
34@implementation GRPCOperation {
35 @protected
36  // Most operation subclasses don't set any flags in the grpc_op, and rely on the flag member being
37  // initialized to zero.
38  grpc_op _op;
39  void (^_handler)(void);
40}
41
42- (void)finish {
43  if (_handler) {
44    void (^handler)(void) = _handler;
45    _handler = nil;
46    handler();
47  }
48}
49@end
50
51@implementation GRPCOpSendMetadata
52
53- (instancetype)init {
54  return [self initWithMetadata:nil flags:0 handler:nil];
55}
56
57- (instancetype)initWithMetadata:(NSDictionary *)metadata handler:(void (^)(void))handler {
58  return [self initWithMetadata:metadata flags:0 handler:handler];
59}
60
61- (instancetype)initWithMetadata:(NSDictionary *)metadata
62                           flags:(uint32_t)flags
63                         handler:(void (^)(void))handler {
64  if (self = [super init]) {
65    _op.op = GRPC_OP_SEND_INITIAL_METADATA;
66    _op.data.send_initial_metadata.count = metadata.count;
67    _op.data.send_initial_metadata.metadata = metadata.grpc_metadataArray;
68    _op.data.send_initial_metadata.maybe_compression_level.is_set = false;
69    _op.data.send_initial_metadata.maybe_compression_level.level = 0;
70    _op.flags = flags;
71    _handler = handler;
72  }
73  return self;
74}
75
76- (void)dealloc {
77  for (int i = 0; i < _op.data.send_initial_metadata.count; i++) {
78    grpc_slice_unref(_op.data.send_initial_metadata.metadata[i].key);
79    grpc_slice_unref(_op.data.send_initial_metadata.metadata[i].value);
80  }
81  gpr_free(_op.data.send_initial_metadata.metadata);
82}
83
84@end
85
86@implementation GRPCOpSendMessage
87
88- (instancetype)init {
89  return [self initWithMessage:nil handler:nil];
90}
91
92- (instancetype)initWithMessage:(NSData *)message handler:(void (^)(void))handler {
93  if (!message) {
94    [NSException raise:NSInvalidArgumentException format:@"message cannot be nil"];
95  }
96  if (self = [super init]) {
97    _op.op = GRPC_OP_SEND_MESSAGE;
98    _op.data.send_message.send_message = message.grpc_byteBuffer;
99    _handler = handler;
100  }
101  return self;
102}
103
104- (void)dealloc {
105  grpc_byte_buffer_destroy(_op.data.send_message.send_message);
106}
107
108@end
109
110@implementation GRPCOpSendClose
111
112- (instancetype)init {
113  return [self initWithHandler:nil];
114}
115
116- (instancetype)initWithHandler:(void (^)(void))handler {
117  if (self = [super init]) {
118    _op.op = GRPC_OP_SEND_CLOSE_FROM_CLIENT;
119    _handler = handler;
120  }
121  return self;
122}
123
124@end
125
126@implementation GRPCOpRecvMetadata {
127  grpc_metadata_array _headers;
128}
129
130- (instancetype)init {
131  return [self initWithHandler:nil];
132}
133
134- (instancetype)initWithHandler:(void (^)(NSDictionary *))handler {
135  if (self = [super init]) {
136    _op.op = GRPC_OP_RECV_INITIAL_METADATA;
137    grpc_metadata_array_init(&_headers);
138    _op.data.recv_initial_metadata.recv_initial_metadata = &_headers;
139    if (handler) {
140      // Prevent reference cycle with _handler
141      __weak typeof(self) weakSelf = self;
142      _handler = ^{
143        __strong typeof(self) strongSelf = weakSelf;
144        NSDictionary *metadata =
145            [NSDictionary grpc_dictionaryFromMetadataArray:strongSelf->_headers];
146        handler(metadata);
147      };
148    }
149  }
150  return self;
151}
152
153- (void)dealloc {
154  grpc_metadata_array_destroy(&_headers);
155}
156
157@end
158
159@implementation GRPCOpRecvMessage {
160  grpc_byte_buffer *_receivedMessage;
161}
162
163- (instancetype)init {
164  return [self initWithHandler:nil];
165}
166
167- (instancetype)initWithHandler:(void (^)(grpc_byte_buffer *))handler {
168  if (self = [super init]) {
169    _op.op = GRPC_OP_RECV_MESSAGE;
170    _op.data.recv_message.recv_message = &_receivedMessage;
171    if (handler) {
172      // Prevent reference cycle with _handler
173      __weak typeof(self) weakSelf = self;
174      _handler = ^{
175        __strong typeof(self) strongSelf = weakSelf;
176        handler(strongSelf->_receivedMessage);
177      };
178    }
179  }
180  return self;
181}
182
183@end
184
185@implementation GRPCOpRecvStatus {
186  grpc_status_code _statusCode;
187  grpc_slice _details;
188  size_t _detailsCapacity;
189  grpc_metadata_array _trailers;
190  const char *_errorString;
191}
192
193- (instancetype)init {
194  return [self initWithHandler:nil];
195}
196
197- (instancetype)initWithHandler:(void (^)(NSError *, NSDictionary *))handler {
198  if (self = [super init]) {
199    _op.op = GRPC_OP_RECV_STATUS_ON_CLIENT;
200    _op.data.recv_status_on_client.status = &_statusCode;
201    _op.data.recv_status_on_client.status_details = &_details;
202    grpc_metadata_array_init(&_trailers);
203    _op.data.recv_status_on_client.trailing_metadata = &_trailers;
204    _op.data.recv_status_on_client.error_string = &_errorString;
205    if (handler) {
206      // Prevent reference cycle with _handler
207      __weak typeof(self) weakSelf = self;
208      _handler = ^{
209        __strong typeof(self) strongSelf = weakSelf;
210        if (strongSelf) {
211          char *details = grpc_slice_to_c_string(strongSelf->_details);
212          NSError *error = [NSError grpc_errorFromStatusCode:strongSelf->_statusCode
213                                                     details:details
214                                                 errorString:strongSelf->_errorString];
215          NSDictionary *trailers =
216              [NSDictionary grpc_dictionaryFromMetadataArray:strongSelf->_trailers];
217          handler(error, trailers);
218          gpr_free(details);
219        }
220      };
221    }
222  }
223  return self;
224}
225
226- (void)dealloc {
227  grpc_metadata_array_destroy(&_trailers);
228  grpc_slice_unref(_details);
229  gpr_free((void *)_errorString);
230}
231
232@end
233
234#pragma mark GRPCWrappedCall
235
236@implementation GRPCWrappedCall {
237  GRPCCompletionQueue *_queue;
238  grpc_call *_call;
239}
240
241- (instancetype)init {
242  return [self initWithHost:nil serverName:nil path:nil timeout:0];
243}
244
245- (instancetype)initWithHost:(NSString *)host
246                  serverName:(NSString *)serverName
247                        path:(NSString *)path
248                     timeout:(NSTimeInterval)timeout {
249  if (!path || !host) {
250    [NSException raise:NSInvalidArgumentException format:@"path and host cannot be nil."];
251  }
252
253  if (self = [super init]) {
254    // Each completion queue consumes one thread. There's a trade to be made between creating and
255    // consuming too many threads and having contention of multiple calls in a single completion
256    // queue. Currently we use a singleton queue.
257    _queue = [GRPCCompletionQueue completionQueue];
258
259    _call = [[GRPCHost hostWithAddress:host] unmanagedCallWithPath:path
260                                                        serverName:serverName
261                                                           timeout:timeout
262                                                   completionQueue:_queue];
263    if (_call == NULL) {
264      return nil;
265    }
266  }
267  return self;
268}
269
270- (void)startBatchWithOperations:(NSArray *)operations {
271  [self startBatchWithOperations:operations errorHandler:nil];
272}
273
274- (void)startBatchWithOperations:(NSArray *)operations errorHandler:(void (^)(void))errorHandler {
275// Keep logs of op batches when we are running tests. Disabled when in production for improved
276// performance.
277#ifdef GRPC_TEST_OBJC
278  [GRPCOpBatchLog addOpBatchToLog:operations];
279#endif
280
281  size_t nops = operations.count;
282  grpc_op *ops_array = gpr_malloc(nops * sizeof(grpc_op));
283  size_t i = 0;
284  for (GRPCOperation *operation in operations) {
285    ops_array[i++] = operation.op;
286  }
287  grpc_call_error error =
288      grpc_call_start_batch(_call, ops_array, nops, (__bridge_retained void *)(^(bool success) {
289                              if (!success) {
290                                if (errorHandler) {
291                                  errorHandler();
292                                } else {
293                                  return;
294                                }
295                              }
296                              for (GRPCOperation *operation in operations) {
297                                [operation finish];
298                              }
299                            }),
300                            NULL);
301  gpr_free(ops_array);
302
303  if (error != GRPC_CALL_OK) {
304    [NSException
305         raise:NSInternalInconsistencyException
306        format:@"A precondition for calling grpc_call_start_batch wasn't met. Error %i", error];
307  }
308}
309
310- (void)cancel {
311  grpc_call_cancel(_call, NULL);
312}
313
314- (void)dealloc {
315  grpc_call_unref(_call);
316}
317
318@end
319