/* * libjingle * Copyright 2013, Google Inc. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * 3. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #if !defined(__has_feature) || !__has_feature(objc_arc) #error "This file requires ARC support." #endif #import "APPRTCAppClient.h" #import #import "ARDSignalingParams.h" #import "ARDUtilities.h" #import "GAEChannelClient.h" #import "RTCICEServer.h" #import "RTCICEServer+JSON.h" #import "RTCMediaConstraints.h" #import "RTCPair.h" @implementation APPRTCAppClient { dispatch_queue_t _backgroundQueue; GAEChannelClient* _gaeChannel; NSURL* _postMessageURL; BOOL _verboseLogging; __weak id _messageHandler; } - (instancetype)initWithDelegate:(id)delegate messageHandler:(id)handler { if (self = [super init]) { _delegate = delegate; _messageHandler = handler; _backgroundQueue = dispatch_queue_create("RTCBackgroundQueue", DISPATCH_QUEUE_SERIAL); // Uncomment to see Request/Response logging. // _verboseLogging = YES; } return self; } - (void)connectToRoom:(NSURL*)url { NSString *urlString = [[url absoluteString] stringByAppendingString:@"&t=json"]; NSURL *requestURL = [NSURL URLWithString:urlString]; NSURLRequest *request = [NSURLRequest requestWithURL:requestURL]; [NSURLConnection sendAsynchronousRequest:request completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) { NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response; int statusCode = [httpResponse statusCode]; [self logVerbose:[NSString stringWithFormat: @"Response received\nURL\n%@\nStatus [%d]\nHeaders\n%@", [httpResponse URL], statusCode, [httpResponse allHeaderFields]]]; NSAssert(statusCode == 200, @"Invalid response of %d received while connecting to: %@", statusCode, urlString); if (statusCode != 200) { return; } [self handleResponseData:data forRoomRequest:request]; }]; } - (void)sendData:(NSData*)data { NSParameterAssert([data length] > 0); NSString *message = [NSString stringWithUTF8String:[data bytes]]; [self logVerbose:[NSString stringWithFormat:@"Send message:\n%@", message]]; if (!_postMessageURL) { return; } NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:_postMessageURL]; request.HTTPMethod = @"POST"; [request setHTTPBody:data]; [NSURLConnection sendAsynchronousRequest:request completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) { NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response; int status = [httpResponse statusCode]; NSString *responseString = [data length] > 0 ? [NSString stringWithUTF8String:[data bytes]] : nil; NSAssert(status == 200, @"Bad response [%d] to message: %@\n\n%@", status, message, responseString); }]; } #pragma mark - Private - (void)logVerbose:(NSString *)message { if (_verboseLogging) { NSLog(@"%@", message); } } - (void)handleResponseData:(NSData *)responseData forRoomRequest:(NSURLRequest *)request { ARDSignalingParams *params = [ARDSignalingParams paramsFromJSONData:responseData]; if (params.errorMessages.count > 0) { NSMutableString *message = [NSMutableString string]; for (NSString *errorMessage in params.errorMessages) { [message appendFormat:@"%@\n", errorMessage]; } [self.delegate appClient:self didErrorWithMessage:message]; return; } [self requestTURNServerForICEServers:params.iceServers turnServerUrl:[params.turnRequestURL absoluteString]]; NSString *token = params.channelToken; [self logVerbose: [NSString stringWithFormat:@"About to open GAE with token: %@", token]]; _gaeChannel = [[GAEChannelClient alloc] initWithToken:token delegate:_messageHandler]; _params = params; // Generate URL for posting data. NSDictionary *roomJSON = [NSDictionary dictionaryWithJSONData:responseData]; _postMessageURL = [self parsePostMessageURLForRoomJSON:roomJSON request:request]; [self logVerbose:[NSString stringWithFormat:@"POST message URL:\n%@", _postMessageURL]]; } - (NSURL*)parsePostMessageURLForRoomJSON:(NSDictionary*)roomJSON request:(NSURLRequest*)request { NSString* requestUrl = [[request URL] absoluteString]; NSRange queryRange = [requestUrl rangeOfString:@"?"]; NSString* baseUrl = [requestUrl substringToIndex:queryRange.location]; NSString* roomKey = roomJSON[@"room_key"]; NSParameterAssert([roomKey length] > 0); NSString* me = roomJSON[@"me"]; NSParameterAssert([me length] > 0); NSString* postMessageUrl = [NSString stringWithFormat:@"%@/message?r=%@&u=%@", baseUrl, roomKey, me]; return [NSURL URLWithString:postMessageUrl]; } - (void)requestTURNServerWithUrl:(NSString *)turnServerUrl completionHandler: (void (^)(RTCICEServer *turnServer))completionHandler { NSURL *turnServerURL = [NSURL URLWithString:turnServerUrl]; NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:turnServerURL]; [request addValue:@"Mozilla/5.0" forHTTPHeaderField:@"user-agent"]; [request addValue:@"https://apprtc.appspot.com" forHTTPHeaderField:@"origin"]; [NSURLConnection sendAsynchronousRequest:request completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) { if (error) { NSLog(@"Unable to get TURN server."); completionHandler(nil); return; } NSDictionary *json = [NSDictionary dictionaryWithJSONData:data]; RTCICEServer *turnServer = [RTCICEServer serverFromCEODJSONDictionary:json]; completionHandler(turnServer); }]; } - (void)requestTURNServerForICEServers:(NSArray*)iceServers turnServerUrl:(NSString*)turnServerUrl { BOOL isTurnPresent = NO; for (RTCICEServer* iceServer in iceServers) { if ([[iceServer.URI scheme] isEqualToString:@"turn"]) { isTurnPresent = YES; break; } } if (!isTurnPresent) { [self requestTURNServerWithUrl:turnServerUrl completionHandler:^(RTCICEServer* turnServer) { NSArray* servers = iceServers; if (turnServer) { servers = [servers arrayByAddingObject:turnServer]; } NSLog(@"ICE servers:\n%@", servers); [self.delegate appClient:self didReceiveICEServers:servers]; }]; } else { NSLog(@"ICE servers:\n%@", iceServers); dispatch_async(dispatch_get_main_queue(), ^{ [self.delegate appClient:self didReceiveICEServers:iceServers]; }); } } @end