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