1/*
2 * libjingle
3 * Copyright 2013, Google Inc.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 *
8 *  1. Redistributions of source code must retain the above copyright notice,
9 *     this list of conditions and the following disclaimer.
10 *  2. Redistributions in binary form must reproduce the above copyright notice,
11 *     this list of conditions and the following disclaimer in the documentation
12 *     and/or other materials provided with the distribution.
13 *  3. The name of the author may not be used to endorse or promote products
14 *     derived from this software without specific prior written permission.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
17 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
18 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
19 * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
22 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
23 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
24 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
25 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27
28#if !defined(__has_feature) || !__has_feature(objc_arc)
29#error "This file requires ARC support."
30#endif
31
32#import "APPRTCAppClient.h"
33
34#import <dispatch/dispatch.h>
35
36#import "GAEChannelClient.h"
37#import "RTCICEServer.h"
38#import "RTCMediaConstraints.h"
39#import "RTCPair.h"
40
41@implementation APPRTCAppClient {
42  dispatch_queue_t _backgroundQueue;
43  GAEChannelClient* _gaeChannel;
44  NSURL* _postMessageURL;
45  BOOL _verboseLogging;
46  __weak id<GAEMessageHandler> _messageHandler;
47}
48
49- (instancetype)initWithDelegate:(id<APPRTCAppClientDelegate>)delegate
50                  messageHandler:(id<GAEMessageHandler>)handler {
51  if (self = [super init]) {
52    _delegate = delegate;
53    _messageHandler = handler;
54    _backgroundQueue = dispatch_queue_create("RTCBackgroundQueue",
55                                             DISPATCH_QUEUE_SERIAL);
56    // Uncomment to see Request/Response logging.
57    // _verboseLogging = YES;
58  }
59  return self;
60}
61
62- (void)connectToRoom:(NSURL*)url {
63  NSString* urlString =
64      [[url absoluteString] stringByAppendingString:@"&t=json"];
65  NSURL* requestURL = [NSURL URLWithString:urlString];
66  NSURLRequest* request = [NSURLRequest requestWithURL:requestURL];
67  [self sendURLRequest:request
68      completionHandler:^(NSError* error,
69                          NSHTTPURLResponse* httpResponse,
70                          NSData* responseData) {
71    int statusCode = [httpResponse statusCode];
72    [self logVerbose:[NSString stringWithFormat:
73        @"Response received\nURL\n%@\nStatus [%d]\nHeaders\n%@",
74        [httpResponse URL],
75        statusCode,
76        [httpResponse allHeaderFields]]];
77    NSAssert(statusCode == 200,
78             @"Invalid response of %d received while connecting to: %@",
79             statusCode,
80             urlString);
81    if (statusCode != 200) {
82      return;
83    }
84    [self handleResponseData:responseData
85              forRoomRequest:request];
86  }];
87}
88
89- (void)sendData:(NSData*)data {
90  NSParameterAssert([data length] > 0);
91  NSString* message = [NSString stringWithUTF8String:[data bytes]];
92  [self logVerbose:[NSString stringWithFormat:@"Send message:\n%@", message]];
93  if (!_postMessageURL) {
94    return;
95  }
96  NSMutableURLRequest* request =
97      [NSMutableURLRequest requestWithURL:_postMessageURL];
98  request.HTTPMethod = @"POST";
99  [request setHTTPBody:data];
100  [self sendURLRequest:request
101     completionHandler:^(NSError* error,
102                         NSHTTPURLResponse* httpResponse,
103                         NSData* responseData) {
104    int status = [httpResponse statusCode];
105    NSString* response = [responseData length] > 0 ?
106        [NSString stringWithUTF8String:[responseData bytes]] :
107        nil;
108    NSAssert(status == 200,
109             @"Bad response [%d] to message: %@\n\n%@",
110             status,
111             message,
112             response);
113  }];
114}
115
116#pragma mark - Private
117
118- (void)logVerbose:(NSString*)message {
119  if (_verboseLogging) {
120    NSLog(@"%@", message);
121  }
122}
123
124- (void)handleResponseData:(NSData*)responseData
125            forRoomRequest:(NSURLRequest*)request {
126  NSDictionary* roomJSON = [self parseJSONData:responseData];
127  [self logVerbose:[NSString stringWithFormat:@"Room JSON:\n%@", roomJSON]];
128  NSParameterAssert(roomJSON);
129  if (roomJSON[@"error"]) {
130    NSArray* errorMessages = roomJSON[@"error_messages"];
131    NSMutableString* message = [NSMutableString string];
132    for (NSString* errorMessage in errorMessages) {
133      [message appendFormat:@"%@\n", errorMessage];
134    }
135    [self.delegate appClient:self didErrorWithMessage:message];
136    return;
137  }
138  NSString* pcConfig = roomJSON[@"pc_config"];
139  NSData* pcConfigData = [pcConfig dataUsingEncoding:NSUTF8StringEncoding];
140  NSDictionary* pcConfigJSON = [self parseJSONData:pcConfigData];
141  [self logVerbose:[NSString stringWithFormat:@"PCConfig JSON:\n%@",
142                                              pcConfigJSON]];
143  NSParameterAssert(pcConfigJSON);
144
145  NSArray* iceServers = [self parseICEServersForPCConfigJSON:pcConfigJSON];
146  [self requestTURNServerForICEServers:iceServers
147                         turnServerUrl:roomJSON[@"turn_url"]];
148
149  _initiator = [roomJSON[@"initiator"] boolValue];
150  [self logVerbose:[NSString stringWithFormat:@"Initiator: %d", _initiator]];
151  _postMessageURL = [self parsePostMessageURLForRoomJSON:roomJSON
152                                                 request:request];
153  [self logVerbose:[NSString stringWithFormat:@"POST message URL:\n%@",
154                                              _postMessageURL]];
155  _videoConstraints = [self parseVideoConstraintsForRoomJSON:roomJSON];
156  [self logVerbose:[NSString stringWithFormat:@"Media constraints:\n%@",
157                                              _videoConstraints]];
158  NSString* token = roomJSON[@"token"];
159  [self logVerbose:
160      [NSString stringWithFormat:@"About to open GAE with token:  %@",
161                                 token]];
162  _gaeChannel =
163      [[GAEChannelClient alloc] initWithToken:token
164                                     delegate:_messageHandler];
165}
166
167- (NSDictionary*)parseJSONData:(NSData*)data {
168  NSError* error = nil;
169  NSDictionary* json =
170      [NSJSONSerialization JSONObjectWithData:data options:0 error:&error];
171  NSAssert(!error, @"Unable to parse.  %@", error.localizedDescription);
172  return json;
173}
174
175- (NSArray*)parseICEServersForPCConfigJSON:(NSDictionary*)pcConfigJSON {
176  NSMutableArray* result = [NSMutableArray array];
177  NSArray* iceServers = pcConfigJSON[@"iceServers"];
178  for (NSDictionary* iceServer in iceServers) {
179    NSString* url = iceServer[@"urls"];
180    NSString* username = pcConfigJSON[@"username"];
181    NSString* credential = iceServer[@"credential"];
182    username = username ? username : @"";
183    credential = credential ? credential : @"";
184    [self logVerbose:[NSString stringWithFormat:@"url [%@] - credential [%@]",
185                                                url,
186                                                credential]];
187    RTCICEServer* server =
188        [[RTCICEServer alloc] initWithURI:[NSURL URLWithString:url]
189                                 username:username
190                                 password:credential];
191    [result addObject:server];
192  }
193  return result;
194}
195
196- (NSURL*)parsePostMessageURLForRoomJSON:(NSDictionary*)roomJSON
197                                 request:(NSURLRequest*)request {
198  NSString* requestUrl = [[request URL] absoluteString];
199  NSRange queryRange = [requestUrl rangeOfString:@"?"];
200  NSString* baseUrl = [requestUrl substringToIndex:queryRange.location];
201  NSString* roomKey = roomJSON[@"room_key"];
202  NSParameterAssert([roomKey length] > 0);
203  NSString* me = roomJSON[@"me"];
204  NSParameterAssert([me length] > 0);
205  NSString* postMessageUrl =
206      [NSString stringWithFormat:@"%@/message?r=%@&u=%@", baseUrl, roomKey, me];
207  return [NSURL URLWithString:postMessageUrl];
208}
209
210- (RTCMediaConstraints*)parseVideoConstraintsForRoomJSON:
211    (NSDictionary*)roomJSON {
212  NSString* mediaConstraints = roomJSON[@"media_constraints"];
213  RTCMediaConstraints* constraints = nil;
214  if ([mediaConstraints length] > 0) {
215    NSData* constraintsData =
216        [mediaConstraints dataUsingEncoding:NSUTF8StringEncoding];
217    NSDictionary* constraintsJSON = [self parseJSONData:constraintsData];
218    id video = constraintsJSON[@"video"];
219    if ([video isKindOfClass:[NSDictionary class]]) {
220      NSDictionary* mandatory = video[@"mandatory"];
221      NSMutableArray* mandatoryContraints =
222          [NSMutableArray arrayWithCapacity:[mandatory count]];
223      [mandatory enumerateKeysAndObjectsUsingBlock:^(
224          id key, id obj, BOOL* stop) {
225        [mandatoryContraints addObject:[[RTCPair alloc] initWithKey:key
226                                                              value:obj]];
227      }];
228      // TODO(tkchin): figure out json formats for optional constraints.
229      constraints =
230          [[RTCMediaConstraints alloc]
231              initWithMandatoryConstraints:mandatoryContraints
232                       optionalConstraints:nil];
233    } else if ([video isKindOfClass:[NSNumber class]] && [video boolValue]) {
234      constraints = [[RTCMediaConstraints alloc] init];
235    }
236  }
237  return constraints;
238}
239
240- (void)requestTURNServerWithUrl:(NSString*)turnServerUrl
241               completionHandler:
242    (void (^)(RTCICEServer* turnServer))completionHandler {
243  NSURL* turnServerURL = [NSURL URLWithString:turnServerUrl];
244  NSMutableURLRequest* request =
245      [NSMutableURLRequest requestWithURL:turnServerURL];
246  [request addValue:@"Mozilla/5.0" forHTTPHeaderField:@"user-agent"];
247  [request addValue:@"https://apprtc.appspot.com"
248      forHTTPHeaderField:@"origin"];
249  [self sendURLRequest:request
250     completionHandler:^(NSError* error,
251                         NSHTTPURLResponse* response,
252                         NSData* responseData) {
253    if (error) {
254      NSLog(@"Unable to get TURN server.");
255      completionHandler(nil);
256      return;
257    }
258    NSDictionary* json = [self parseJSONData:responseData];
259    NSString* username = json[@"username"];
260    NSString* password = json[@"password"];
261    NSArray* uris = json[@"uris"];
262    NSParameterAssert([uris count] > 0);
263    RTCICEServer* turnServer =
264        [[RTCICEServer alloc] initWithURI:[NSURL URLWithString:uris[0]]
265                                 username:username
266                                 password:password];
267    completionHandler(turnServer);
268  }];
269}
270
271- (void)requestTURNServerForICEServers:(NSArray*)iceServers
272                         turnServerUrl:(NSString*)turnServerUrl {
273  BOOL isTurnPresent = NO;
274  for (RTCICEServer* iceServer in iceServers) {
275    if ([[iceServer.URI scheme] isEqualToString:@"turn"]) {
276      isTurnPresent = YES;
277      break;
278    }
279  }
280  if (!isTurnPresent) {
281    [self requestTURNServerWithUrl:turnServerUrl
282                 completionHandler:^(RTCICEServer* turnServer) {
283      NSArray* servers = iceServers;
284      if (turnServer) {
285        servers = [servers arrayByAddingObject:turnServer];
286      }
287      NSLog(@"ICE servers:\n%@", servers);
288      [self.delegate appClient:self didReceiveICEServers:servers];
289    }];
290  } else {
291    NSLog(@"ICE servers:\n%@", iceServers);
292    dispatch_async(dispatch_get_main_queue(), ^{
293      [self.delegate appClient:self didReceiveICEServers:iceServers];
294    });
295  }
296}
297
298- (void)sendURLRequest:(NSURLRequest*)request
299     completionHandler:(void (^)(NSError* error,
300                                 NSHTTPURLResponse* httpResponse,
301                                 NSData* responseData))completionHandler {
302  dispatch_async(_backgroundQueue, ^{
303    NSError* error = nil;
304    NSURLResponse* response = nil;
305    NSData* responseData = [NSURLConnection sendSynchronousRequest:request
306                                                 returningResponse:&response
307                                                             error:&error];
308    NSParameterAssert(!response ||
309                      [response isKindOfClass:[NSHTTPURLResponse class]]);
310    if (error) {
311      NSLog(@"Failed URL request for:%@\nError:%@", request, error);
312    }
313    dispatch_async(dispatch_get_main_queue(), ^{
314      NSHTTPURLResponse* httpResponse = (NSHTTPURLResponse*)response;
315      completionHandler(error, httpResponse, responseData);
316    });
317  });
318}
319
320@end
321