From 3a63a3c35d93616606c23d27583b83a198b94a3e Mon Sep 17 00:00:00 2001 From: "tkchin@webrtc.org" Date: Tue, 6 Jan 2015 07:21:34 +0000 Subject: [PATCH] iOS AppRTC: First unit test. Tests basic session ICE connection by stubbing out network components, which have been refactored to faciliate testing. BUG=3994 R=jiayl@webrtc.org, kjellander@webrtc.org, phoglund@webrtc.org Review URL: https://webrtc-codereview.appspot.com/28349004 git-svn-id: http://webrtc.googlecode.com/svn/trunk@8002 4adac7df-926f-26a2-2b94-8c16560cd09d --- .gitignore | 1 + setup_links.py | 1 + talk/build/{ios_tests.gypi => objc_app.gypi} | 39 +- talk/build/{ios_test.plist => objc_app.plist} | 0 .../objc/AppRTCDemo/ARDAppClient+Internal.h | 68 ++++ talk/examples/objc/AppRTCDemo/ARDAppClient.h | 3 + talk/examples/objc/AppRTCDemo/ARDAppClient.m | 373 ++++++++---------- .../objc/AppRTCDemo/ARDAppEngineClient.h | 31 ++ .../objc/AppRTCDemo/ARDAppEngineClient.m | 178 +++++++++ .../objc/AppRTCDemo/ARDCEODTURNClient.h | 35 ++ .../objc/AppRTCDemo/ARDCEODTURNClient.m | 83 ++++ .../AppRTCDemo/ARDMessageResponse+Internal.h | 34 ++ .../objc/AppRTCDemo/ARDMessageResponse.m | 8 +- .../AppRTCDemo/ARDRegisterResponse+Internal.h | 40 ++ .../objc/AppRTCDemo/ARDRegisterResponse.m | 14 +- .../objc/AppRTCDemo/ARDRoomServerClient.h | 50 +++ .../objc/AppRTCDemo/ARDSignalingChannel.h | 69 ++++ talk/examples/objc/AppRTCDemo/ARDTURNClient.h | 37 ++ .../objc/AppRTCDemo/ARDWebSocketChannel.h | 37 +- .../objc/AppRTCDemo/ARDWebSocketChannel.m | 27 +- .../AppRTCDemo/ios/APPRTCViewController.m | 4 + .../AppRTCDemo/mac/APPRTCViewController.m | 4 + .../objc/AppRTCDemo/tests/ARDAppClientTest.mm | 323 +++++++++++++++ talk/libjingle.gyp | 5 + talk/libjingle_examples.gyp | 88 +++-- talk/libjingle_tests.gyp | 58 ++- 26 files changed, 1244 insertions(+), 366 deletions(-) rename talk/build/{ios_tests.gypi => objc_app.gypi} (68%) rename talk/build/{ios_test.plist => objc_app.plist} (100%) create mode 100644 talk/examples/objc/AppRTCDemo/ARDAppClient+Internal.h create mode 100644 talk/examples/objc/AppRTCDemo/ARDAppEngineClient.h create mode 100644 talk/examples/objc/AppRTCDemo/ARDAppEngineClient.m create mode 100644 talk/examples/objc/AppRTCDemo/ARDCEODTURNClient.h create mode 100644 talk/examples/objc/AppRTCDemo/ARDCEODTURNClient.m create mode 100644 talk/examples/objc/AppRTCDemo/ARDMessageResponse+Internal.h create mode 100644 talk/examples/objc/AppRTCDemo/ARDRegisterResponse+Internal.h create mode 100644 talk/examples/objc/AppRTCDemo/ARDRoomServerClient.h create mode 100644 talk/examples/objc/AppRTCDemo/ARDSignalingChannel.h create mode 100644 talk/examples/objc/AppRTCDemo/ARDTURNClient.h create mode 100644 talk/examples/objc/AppRTCDemo/tests/ARDAppClientTest.mm diff --git a/.gitignore b/.gitignore index 1082352c1b..cb684c9718 100644 --- a/.gitignore +++ b/.gitignore @@ -94,6 +94,7 @@ /third_party/modp_b64 /third_party/nss /third_party/oauth2 +/third_party/ocmock /third_party/openmax_dl /third_party/opus /third_party/protobuf diff --git a/setup_links.py b/setup_links.py index 0dd453c948..58912702c8 100755 --- a/setup_links.py +++ b/setup_links.py @@ -55,6 +55,7 @@ DIRECTORIES = [ 'third_party/libyuv', 'third_party/llvm-build', 'third_party/nss', + 'third_party/ocmock', 'third_party/openmax_dl', 'third_party/opus', 'third_party/protobuf', diff --git a/talk/build/ios_tests.gypi b/talk/build/objc_app.gypi similarity index 68% rename from talk/build/ios_tests.gypi rename to talk/build/objc_app.gypi index baf1f100a6..a479802348 100644 --- a/talk/build/ios_tests.gypi +++ b/talk/build/objc_app.gypi @@ -29,27 +29,20 @@ # used as an iOS or OS/X application. { - 'conditions': [ - ['OS=="ios"', { - 'variables': { - 'infoplist_file': './ios_test.plist', - }, - 'mac_bundle': 1, - 'mac_bundle_resources': [ - '<(infoplist_file)', - ], - # The plist is listed above so that it appears in XCode's file list, - # but we don't actually want to bundle it. - 'mac_bundle_resources!': [ - '<(infoplist_file)', - ], - 'xcode_settings': { - 'CLANG_ENABLE_OBJC_ARC': 'YES', - # common.gypi enables this for mac but we want this to be disabled - # like it is for ios. - 'CLANG_WARN_OBJC_MISSING_PROPERTY_SYNTHESIS': 'NO', - 'INFOPLIST_FILE': '<(infoplist_file)', - }, - }], - ], # conditions + 'variables': { + 'infoplist_file': './objc_app.plist', + }, + 'mac_bundle': 1, + 'mac_bundle_resources': [ + '<(infoplist_file)', + ], + # The plist is listed above so that it appears in XCode's file list, + # but we don't actually want to bundle it. + 'mac_bundle_resources!': [ + '<(infoplist_file)', + ], + 'xcode_settings': { + 'CLANG_ENABLE_OBJC_ARC': 'YES', + 'INFOPLIST_FILE': '<(infoplist_file)', + }, } diff --git a/talk/build/ios_test.plist b/talk/build/objc_app.plist similarity index 100% rename from talk/build/ios_test.plist rename to talk/build/objc_app.plist diff --git a/talk/examples/objc/AppRTCDemo/ARDAppClient+Internal.h b/talk/examples/objc/AppRTCDemo/ARDAppClient+Internal.h new file mode 100644 index 0000000000..068c041ca2 --- /dev/null +++ b/talk/examples/objc/AppRTCDemo/ARDAppClient+Internal.h @@ -0,0 +1,68 @@ +/* + * libjingle + * Copyright 2014, 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. + */ + +#import "ARDAppClient.h" + +#import "ARDRoomServerClient.h" +#import "ARDSignalingChannel.h" +#import "ARDTURNClient.h" +#import "RTCPeerConnection.h" +#import "RTCPeerConnectionDelegate.h" +#import "RTCPeerConnectionFactory.h" +#import "RTCSessionDescriptionDelegate.h" + +@interface ARDAppClient () + +@property(nonatomic, strong) id roomServerClient; +@property(nonatomic, strong) id channel; +@property(nonatomic, strong) id turnClient; + +@property(nonatomic, strong) RTCPeerConnection *peerConnection; +@property(nonatomic, strong) RTCPeerConnectionFactory *factory; +@property(nonatomic, strong) NSMutableArray *messageQueue; + +@property(nonatomic, assign) BOOL isTurnComplete; +@property(nonatomic, assign) BOOL hasReceivedSdp; +@property(nonatomic, readonly) BOOL isRegisteredWithRoomServer; + +@property(nonatomic, strong) NSString *roomId; +@property(nonatomic, strong) NSString *clientId; +@property(nonatomic, assign) BOOL isInitiator; +@property(nonatomic, strong) NSMutableArray *iceServers; +@property(nonatomic, strong) NSURL *webSocketURL; +@property(nonatomic, strong) NSURL *webSocketRestURL; + +@property(nonatomic, strong) + RTCMediaConstraints *defaultPeerConnectionConstraints; + +- (instancetype)initWithRoomServerClient:(id)rsClient + signalingChannel:(id)channel + turnClient:(id)turnClient + delegate:(id)delegate; + +@end diff --git a/talk/examples/objc/AppRTCDemo/ARDAppClient.h b/talk/examples/objc/AppRTCDemo/ARDAppClient.h index d742ea3f9a..fa3a634f9f 100644 --- a/talk/examples/objc/AppRTCDemo/ARDAppClient.h +++ b/talk/examples/objc/AppRTCDemo/ARDAppClient.h @@ -44,6 +44,9 @@ typedef NS_ENUM(NSInteger, ARDAppClientState) { - (void)appClient:(ARDAppClient *)client didChangeState:(ARDAppClientState)state; +- (void)appClient:(ARDAppClient *)client + didChangeConnectionState:(RTCICEConnectionState)state; + - (void)appClient:(ARDAppClient *)client didReceiveLocalVideoTrack:(RTCVideoTrack *)localVideoTrack; diff --git a/talk/examples/objc/AppRTCDemo/ARDAppClient.m b/talk/examples/objc/AppRTCDemo/ARDAppClient.m index d72e8bba13..fef77273f2 100644 --- a/talk/examples/objc/AppRTCDemo/ARDAppClient.m +++ b/talk/examples/objc/AppRTCDemo/ARDAppClient.m @@ -25,38 +25,26 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#import "ARDAppClient.h" +#import "ARDAppClient+Internal.h" #import +#import "ARDAppEngineClient.h" +#import "ARDCEODTURNClient.h" #import "ARDMessageResponse.h" #import "ARDRegisterResponse.h" #import "ARDSignalingMessage.h" #import "ARDUtilities.h" #import "ARDWebSocketChannel.h" #import "RTCICECandidate+JSON.h" -#import "RTCICEServer+JSON.h" +#import "RTCICEServer.h" #import "RTCMediaConstraints.h" #import "RTCMediaStream.h" #import "RTCPair.h" -#import "RTCPeerConnection.h" -#import "RTCPeerConnectionDelegate.h" -#import "RTCPeerConnectionFactory.h" #import "RTCSessionDescription+JSON.h" -#import "RTCSessionDescriptionDelegate.h" #import "RTCVideoCapturer.h" #import "RTCVideoTrack.h" -// TODO(tkchin): move these to a configuration object. -static NSString *kARDRoomServerHostUrl = - @"https://apprtc.appspot.com"; -static NSString *kARDRoomServerRegisterFormat = - @"https://apprtc.appspot.com/register/%@"; -static NSString *kARDRoomServerMessageFormat = - @"https://apprtc.appspot.com/message/%@/%@"; -static NSString *kARDRoomServerByeFormat = - @"https://apprtc.appspot.com/bye/%@/%@"; - static NSString *kARDDefaultSTUNServerUrl = @"stun:stun.l.google.com:19302"; // TODO(tkchin): figure out a better username for CEOD statistics. @@ -69,34 +57,16 @@ static NSInteger kARDAppClientErrorUnknown = -1; static NSInteger kARDAppClientErrorRoomFull = -2; static NSInteger kARDAppClientErrorCreateSDP = -3; static NSInteger kARDAppClientErrorSetSDP = -4; -static NSInteger kARDAppClientErrorNetwork = -5; -static NSInteger kARDAppClientErrorInvalidClient = -6; -static NSInteger kARDAppClientErrorInvalidRoom = -7; - -@interface ARDAppClient () -@property(nonatomic, strong) ARDWebSocketChannel *channel; -@property(nonatomic, strong) RTCPeerConnection *peerConnection; -@property(nonatomic, strong) RTCPeerConnectionFactory *factory; -@property(nonatomic, strong) NSMutableArray *messageQueue; - -@property(nonatomic, assign) BOOL isTurnComplete; -@property(nonatomic, assign) BOOL hasReceivedSdp; -@property(nonatomic, readonly) BOOL isRegisteredWithRoomServer; - -@property(nonatomic, strong) NSString *roomId; -@property(nonatomic, strong) NSString *clientId; -@property(nonatomic, assign) BOOL isInitiator; -@property(nonatomic, strong) NSMutableArray *iceServers; -@property(nonatomic, strong) NSURL *webSocketURL; -@property(nonatomic, strong) NSURL *webSocketRestURL; -@end +static NSInteger kARDAppClientErrorInvalidClient = -5; +static NSInteger kARDAppClientErrorInvalidRoom = -6; @implementation ARDAppClient @synthesize delegate = _delegate; @synthesize state = _state; +@synthesize roomServerClient = _roomServerClient; @synthesize channel = _channel; +@synthesize turnClient = _turnClient; @synthesize peerConnection = _peerConnection; @synthesize factory = _factory; @synthesize messageQueue = _messageQueue; @@ -108,17 +78,46 @@ static NSInteger kARDAppClientErrorInvalidRoom = -7; @synthesize iceServers = _iceServers; @synthesize webSocketURL = _websocketURL; @synthesize webSocketRestURL = _websocketRestURL; +@synthesize defaultPeerConnectionConstraints = + _defaultPeerConnectionConstraints; - (instancetype)initWithDelegate:(id)delegate { if (self = [super init]) { + _roomServerClient = [[ARDAppEngineClient alloc] init]; _delegate = delegate; - _factory = [[RTCPeerConnectionFactory alloc] init]; - _messageQueue = [NSMutableArray array]; - _iceServers = [NSMutableArray arrayWithObject:[self defaultSTUNServer]]; + NSURL *turnRequestURL = [NSURL URLWithString:kARDTurnRequestUrl]; + _turnClient = [[ARDCEODTURNClient alloc] initWithURL:turnRequestURL]; + [self configure]; } return self; } +// TODO(tkchin): Provide signaling channel factory interface so we can recreate +// channel if we need to on network failure. Also, make this the default public +// constructor. +- (instancetype)initWithRoomServerClient:(id)rsClient + signalingChannel:(id)channel + turnClient:(id)turnClient + delegate:(id)delegate { + NSParameterAssert(rsClient); + NSParameterAssert(channel); + NSParameterAssert(turnClient); + if (self = [super init]) { + _roomServerClient = rsClient; + _channel = channel; + _turnClient = turnClient; + _delegate = delegate; + [self configure]; + } + return self; +} + +- (void)configure { + _factory = [[RTCPeerConnectionFactory alloc] init]; + _messageQueue = [NSMutableArray array]; + _iceServers = [NSMutableArray arrayWithObject:[self defaultSTUNServer]]; +} + - (void)dealloc { [self disconnect]; } @@ -139,9 +138,11 @@ static NSInteger kARDAppClientErrorInvalidRoom = -7; // Request TURN. __weak ARDAppClient *weakSelf = self; - NSURL *turnRequestURL = [NSURL URLWithString:kARDTurnRequestUrl]; - [self requestTURNServersWithURL:turnRequestURL - completionHandler:^(NSArray *turnServers) { + [_turnClient requestServersWithCompletionHandler:^(NSArray *turnServers, + NSError *error) { + if (error) { + NSLog(@"Error retrieving TURN servers: %@", error); + } ARDAppClient *strongSelf = weakSelf; [strongSelf.iceServers addObjectsFromArray:turnServers]; strongSelf.isTurnComplete = YES; @@ -149,23 +150,21 @@ static NSInteger kARDAppClientErrorInvalidRoom = -7; }]; // Register with room server. - [self registerWithRoomServerForRoomId:roomId - completionHandler:^(ARDRegisterResponse *response) { + [_roomServerClient registerForRoomId:roomId + completionHandler:^(ARDRegisterResponse *response, NSError *error) { ARDAppClient *strongSelf = weakSelf; - if (!response || response.result != kARDRegisterResultTypeSuccess) { - NSLog(@"Failed to register with room server. Result:%d", - (int)response.result); - [strongSelf disconnect]; - NSDictionary *userInfo = @{ - NSLocalizedDescriptionKey: @"Room is full.", - }; - NSError *error = - [[NSError alloc] initWithDomain:kARDAppClientErrorDomain - code:kARDAppClientErrorRoomFull - userInfo:userInfo]; + if (error) { [strongSelf.delegate appClient:strongSelf didError:error]; return; } + NSError *registerError = + [[strongSelf class] errorForRegisterResultType:response.result]; + if (registerError) { + NSLog(@"Failed to register with room server."); + [strongSelf disconnect]; + [strongSelf.delegate appClient:strongSelf didError:registerError]; + return; + } NSLog(@"Registered with room server."); strongSelf.roomId = response.roomId; strongSelf.clientId = response.clientId; @@ -191,14 +190,15 @@ static NSInteger kARDAppClientErrorInvalidRoom = -7; return; } if (self.isRegisteredWithRoomServer) { - [self unregisterWithRoomServer]; + [_roomServerClient deregisterForRoomId:_roomId + clientId:_clientId + completionHandler:nil]; } if (_channel) { - if (_channel.state == kARDWebSocketChannelStateRegistered) { + if (_channel.state == kARDSignalingChannelStateRegistered) { // Tell the other client we're hanging up. ARDByeMessage *byeMessage = [[ARDByeMessage alloc] init]; - NSData *byeData = [byeMessage JSONData]; - [_channel sendData:byeData]; + [_channel sendMessage:byeMessage]; } // Disconnect from collider. _channel = nil; @@ -212,9 +212,9 @@ static NSInteger kARDAppClientErrorInvalidRoom = -7; self.state = kARDAppClientStateDisconnected; } -#pragma mark - ARDWebSocketChannelDelegate +#pragma mark - ARDSignalingChannelDelegate -- (void)channel:(ARDWebSocketChannel *)channel +- (void)channel:(id)channel didReceiveMessage:(ARDSignalingMessage *)message { switch (message.type) { case kARDSignalingMessageTypeOffer: @@ -232,15 +232,15 @@ static NSInteger kARDAppClientErrorInvalidRoom = -7; [self drainMessageQueueIfReady]; } -- (void)channel:(ARDWebSocketChannel *)channel - didChangeState:(ARDWebSocketChannelState)state { +- (void)channel:(id)channel + didChangeState:(ARDSignalingChannelState)state { switch (state) { - case kARDWebSocketChannelStateOpen: + case kARDSignalingChannelStateOpen: break; - case kARDWebSocketChannelStateRegistered: + case kARDSignalingChannelStateRegistered: break; - case kARDWebSocketChannelStateClosed: - case kARDWebSocketChannelStateError: + case kARDSignalingChannelStateClosed: + case kARDSignalingChannelStateError: // TODO(tkchin): reconnection scenarios. Right now we just disconnect // completely if the websocket connection fails. [self disconnect]; @@ -281,6 +281,9 @@ static NSInteger kARDAppClientErrorInvalidRoom = -7; - (void)peerConnection:(RTCPeerConnection *)peerConnection iceConnectionChanged:(RTCICEConnectionState)newState { NSLog(@"ICE state changed: %d", newState); + dispatch_async(dispatch_get_main_queue(), ^{ + [_delegate appClient:self didChangeConnectionState:newState]; + }); } - (void)peerConnection:(RTCPeerConnection *)peerConnection @@ -430,9 +433,26 @@ static NSInteger kARDAppClientErrorInvalidRoom = -7; - (void)sendSignalingMessage:(ARDSignalingMessage *)message { if (_isInitiator) { - [self sendSignalingMessageToRoomServer:message completionHandler:nil]; + __weak ARDAppClient *weakSelf = self; + [_roomServerClient sendMessage:message + forRoomId:_roomId + clientId:_clientId + completionHandler:^(ARDMessageResponse *response, + NSError *error) { + ARDAppClient *strongSelf = weakSelf; + if (error) { + [strongSelf.delegate appClient:strongSelf didError:error]; + return; + } + NSError *messageError = + [[strongSelf class] errorForMessageResultType:response.result]; + if (messageError) { + [strongSelf.delegate appClient:strongSelf didError:messageError]; + return; + } + }]; } else { - [self sendSignalingMessageToCollider:message]; + [_channel sendMessage:message]; } } @@ -473,142 +493,6 @@ static NSInteger kARDAppClientErrorInvalidRoom = -7; return localStream; } -- (void)requestTURNServersWithURL:(NSURL *)requestURL - completionHandler:(void (^)(NSArray *turnServers))completionHandler { - NSParameterAssert([requestURL absoluteString].length); - NSMutableURLRequest *request = - [NSMutableURLRequest requestWithURL:requestURL]; - // We need to set origin because TURN provider whitelists requests based on - // origin. - [request addValue:@"Mozilla/5.0" forHTTPHeaderField:@"user-agent"]; - [request addValue:kARDRoomServerHostUrl forHTTPHeaderField:@"origin"]; - [NSURLConnection sendAsyncRequest:request - completionHandler:^(NSURLResponse *response, - NSData *data, - NSError *error) { - NSArray *turnServers = [NSArray array]; - if (error) { - NSLog(@"Unable to get TURN server."); - completionHandler(turnServers); - return; - } - NSDictionary *dict = [NSDictionary dictionaryWithJSONData:data]; - turnServers = [RTCICEServer serversFromCEODJSONDictionary:dict]; - completionHandler(turnServers); - }]; -} - -#pragma mark - Room server methods - -- (void)registerWithRoomServerForRoomId:(NSString *)roomId - completionHandler:(void (^)(ARDRegisterResponse *))completionHandler { - NSString *urlString = - [NSString stringWithFormat:kARDRoomServerRegisterFormat, roomId]; - NSURL *roomURL = [NSURL URLWithString:urlString]; - NSLog(@"Registering with room server."); - __weak ARDAppClient *weakSelf = self; - [NSURLConnection sendAsyncPostToURL:roomURL - withData:nil - completionHandler:^(BOOL succeeded, NSData *data) { - ARDAppClient *strongSelf = weakSelf; - if (!succeeded) { - NSError *error = [self roomServerNetworkError]; - [strongSelf.delegate appClient:strongSelf didError:error]; - completionHandler(nil); - return; - } - ARDRegisterResponse *response = - [ARDRegisterResponse responseFromJSONData:data]; - completionHandler(response); - }]; -} - -- (void)sendSignalingMessageToRoomServer:(ARDSignalingMessage *)message - completionHandler:(void (^)(ARDMessageResponse *))completionHandler { - NSData *data = [message JSONData]; - NSString *urlString = - [NSString stringWithFormat: - kARDRoomServerMessageFormat, _roomId, _clientId]; - NSURL *url = [NSURL URLWithString:urlString]; - NSLog(@"C->RS POST: %@", message); - __weak ARDAppClient *weakSelf = self; - [NSURLConnection sendAsyncPostToURL:url - withData:data - completionHandler:^(BOOL succeeded, NSData *data) { - ARDAppClient *strongSelf = weakSelf; - if (!succeeded) { - NSError *error = [self roomServerNetworkError]; - [strongSelf.delegate appClient:strongSelf didError:error]; - return; - } - ARDMessageResponse *response = - [ARDMessageResponse responseFromJSONData:data]; - NSError *error = nil; - switch (response.result) { - case kARDMessageResultTypeSuccess: - break; - case kARDMessageResultTypeUnknown: - error = - [[NSError alloc] initWithDomain:kARDAppClientErrorDomain - code:kARDAppClientErrorUnknown - userInfo:@{ - NSLocalizedDescriptionKey: @"Unknown error.", - }]; - case kARDMessageResultTypeInvalidClient: - error = - [[NSError alloc] initWithDomain:kARDAppClientErrorDomain - code:kARDAppClientErrorInvalidClient - userInfo:@{ - NSLocalizedDescriptionKey: @"Invalid client.", - }]; - break; - case kARDMessageResultTypeInvalidRoom: - error = - [[NSError alloc] initWithDomain:kARDAppClientErrorDomain - code:kARDAppClientErrorInvalidRoom - userInfo:@{ - NSLocalizedDescriptionKey: @"Invalid room.", - }]; - break; - }; - if (error) { - [strongSelf.delegate appClient:strongSelf didError:error]; - } - if (completionHandler) { - completionHandler(response); - } - }]; -} - -- (void)unregisterWithRoomServer { - NSString *urlString = - [NSString stringWithFormat:kARDRoomServerByeFormat, _roomId, _clientId]; - NSURL *url = [NSURL URLWithString:urlString]; - NSURLRequest *request = [NSURLRequest requestWithURL:url]; - NSURLResponse *response = nil; - // We want a synchronous request so that we know that we're unregistered from - // room server before we do any further unregistration. - NSLog(@"C->RS: BYE"); - NSError *error = nil; - [NSURLConnection sendSynchronousRequest:request - returningResponse:&response - error:&error]; - if (error) { - NSLog(@"Error unregistering from room server: %@", error); - } - NSLog(@"Unregistered from room server."); -} - -- (NSError *)roomServerNetworkError { - NSError *error = - [[NSError alloc] initWithDomain:kARDAppClientErrorDomain - code:kARDAppClientErrorNetwork - userInfo:@{ - NSLocalizedDescriptionKey: @"Room server network error", - }]; - return error; -} - #pragma mark - Collider methods - (void)registerWithColliderIfReady { @@ -616,18 +500,15 @@ static NSInteger kARDAppClientErrorInvalidRoom = -7; return; } // Open WebSocket connection. - _channel = - [[ARDWebSocketChannel alloc] initWithURL:_websocketURL - restURL:_websocketRestURL - delegate:self]; + if (!_channel) { + _channel = + [[ARDWebSocketChannel alloc] initWithURL:_websocketURL + restURL:_websocketRestURL + delegate:self]; + } [_channel registerForRoomId:_roomId clientId:_clientId]; } -- (void)sendSignalingMessageToCollider:(ARDSignalingMessage *)message { - NSData *data = [message JSONData]; - [_channel sendData:data]; -} - #pragma mark - Defaults - (RTCMediaConstraints *)defaultMediaStreamConstraints { @@ -655,6 +536,9 @@ static NSInteger kARDAppClientErrorInvalidRoom = -7; } - (RTCMediaConstraints *)defaultPeerConnectionConstraints { + if (_defaultPeerConnectionConstraints) { + return _defaultPeerConnectionConstraints; + } NSArray *optionalConstraints = @[ [[RTCPair alloc] initWithKey:@"DtlsSrtpKeyAgreement" value:@"true"] ]; @@ -672,4 +556,61 @@ static NSInteger kARDAppClientErrorInvalidRoom = -7; password:@""]; } +#pragma mark - Errors + ++ (NSError *)errorForRegisterResultType:(ARDRegisterResultType)resultType { + NSError *error = nil; + switch (resultType) { + case kARDRegisterResultTypeSuccess: + break; + case kARDRegisterResultTypeUnknown: { + error = [[NSError alloc] initWithDomain:kARDAppClientErrorDomain + code:kARDAppClientErrorUnknown + userInfo:@{ + NSLocalizedDescriptionKey: @"Unknown error.", + }]; + break; + } + case kARDRegisterResultTypeFull: { + error = [[NSError alloc] initWithDomain:kARDAppClientErrorDomain + code:kARDAppClientErrorRoomFull + userInfo:@{ + NSLocalizedDescriptionKey: @"Room is full.", + }]; + break; + } + } + return error; +} + ++ (NSError *)errorForMessageResultType:(ARDMessageResultType)resultType { + NSError *error = nil; + switch (resultType) { + case kARDMessageResultTypeSuccess: + break; + case kARDMessageResultTypeUnknown: + error = [[NSError alloc] initWithDomain:kARDAppClientErrorDomain + code:kARDAppClientErrorUnknown + userInfo:@{ + NSLocalizedDescriptionKey: @"Unknown error.", + }]; + break; + case kARDMessageResultTypeInvalidClient: + error = [[NSError alloc] initWithDomain:kARDAppClientErrorDomain + code:kARDAppClientErrorInvalidClient + userInfo:@{ + NSLocalizedDescriptionKey: @"Invalid client.", + }]; + break; + case kARDMessageResultTypeInvalidRoom: + error = [[NSError alloc] initWithDomain:kARDAppClientErrorDomain + code:kARDAppClientErrorInvalidRoom + userInfo:@{ + NSLocalizedDescriptionKey: @"Invalid room.", + }]; + break; + } + return error; +} + @end diff --git a/talk/examples/objc/AppRTCDemo/ARDAppEngineClient.h b/talk/examples/objc/AppRTCDemo/ARDAppEngineClient.h new file mode 100644 index 0000000000..2ec2ef8007 --- /dev/null +++ b/talk/examples/objc/AppRTCDemo/ARDAppEngineClient.h @@ -0,0 +1,31 @@ +/* + * libjingle + * Copyright 2014, 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. + */ + +#import "ARDRoomServerClient.h" + +@interface ARDAppEngineClient : NSObject +@end diff --git a/talk/examples/objc/AppRTCDemo/ARDAppEngineClient.m b/talk/examples/objc/AppRTCDemo/ARDAppEngineClient.m new file mode 100644 index 0000000000..44d3801811 --- /dev/null +++ b/talk/examples/objc/AppRTCDemo/ARDAppEngineClient.m @@ -0,0 +1,178 @@ +/* + * libjingle + * Copyright 2014, 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. + */ + +#import "ARDAppEngineClient.h" + +#import "ARDMessageResponse.h" +#import "ARDRegisterResponse.h" +#import "ARDSignalingMessage.h" +#import "ARDUtilities.h" + +// TODO(tkchin): move these to a configuration object. +static NSString *kARDRoomServerHostUrl = + @"https://apprtc.appspot.com"; +static NSString *kARDRoomServerRegisterFormat = + @"https://apprtc.appspot.com/register/%@"; +static NSString *kARDRoomServerMessageFormat = + @"https://apprtc.appspot.com/message/%@/%@"; +static NSString *kARDRoomServerByeFormat = + @"https://apprtc.appspot.com/bye/%@/%@"; + +static NSString *kARDAppEngineClientErrorDomain = @"ARDAppEngineClient"; +static NSInteger kARDAppEngineClientErrorBadResponse = -1; + +@implementation ARDAppEngineClient + +#pragma mark - ARDRoomServerClient + +- (void)registerForRoomId:(NSString *)roomId + completionHandler:(void (^)(ARDRegisterResponse *response, + NSError *error))completionHandler { + NSParameterAssert(roomId.length); + + NSString *urlString = + [NSString stringWithFormat:kARDRoomServerRegisterFormat, roomId]; + NSURL *roomURL = [NSURL URLWithString:urlString]; + NSLog(@"Registering with room server."); + NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:roomURL]; + request.HTTPMethod = @"POST"; + __weak ARDAppEngineClient *weakSelf = self; + [NSURLConnection sendAsyncRequest:request + completionHandler:^(NSURLResponse *response, + NSData *data, + NSError *error) { + ARDAppEngineClient *strongSelf = weakSelf; + if (error) { + if (completionHandler) { + completionHandler(nil, error); + } + return; + } + ARDRegisterResponse *registerResponse = + [ARDRegisterResponse responseFromJSONData:data]; + if (!registerResponse) { + if (completionHandler) { + NSError *error = [[self class] badResponseError]; + completionHandler(nil, error); + } + return; + } + if (completionHandler) { + completionHandler(registerResponse, nil); + } + }]; +} + +- (void)sendMessage:(ARDSignalingMessage *)message + forRoomId:(NSString *)roomId + clientId:(NSString *)clientId + completionHandler:(void (^)(ARDMessageResponse *response, + NSError *error))completionHandler { + NSParameterAssert(message); + NSParameterAssert(roomId.length); + NSParameterAssert(clientId.length); + + NSData *data = [message JSONData]; + NSString *urlString = + [NSString stringWithFormat: + kARDRoomServerMessageFormat, roomId, clientId]; + NSURL *url = [NSURL URLWithString:urlString]; + NSLog(@"C->RS POST: %@", message); + NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; + request.HTTPMethod = @"POST"; + request.HTTPBody = data; + __weak ARDAppEngineClient *weakSelf = self; + [NSURLConnection sendAsyncRequest:request + completionHandler:^(NSURLResponse *response, + NSData *data, + NSError *error) { + ARDAppEngineClient *strongSelf = weakSelf; + if (error) { + if (completionHandler) { + completionHandler(nil, error); + } + return; + } + ARDMessageResponse *messageResponse = + [ARDMessageResponse responseFromJSONData:data]; + if (!messageResponse) { + if (completionHandler) { + NSError *error = [[self class] badResponseError]; + completionHandler(nil, error); + } + return; + } + if (completionHandler) { + completionHandler(messageResponse, nil); + } + }]; +} + +- (void)deregisterForRoomId:(NSString *)roomId + clientId:(NSString *)clientId + completionHandler:(void (^)(NSError *error))completionHandler { + NSParameterAssert(roomId.length); + NSParameterAssert(clientId.length); + + NSString *urlString = + [NSString stringWithFormat:kARDRoomServerByeFormat, roomId, clientId]; + NSURL *url = [NSURL URLWithString:urlString]; + NSURLRequest *request = [NSURLRequest requestWithURL:url]; + NSURLResponse *response = nil; + NSError *error = nil; + // We want a synchronous request so that we know that we're unregistered from + // room server before we do any further unregistration. + NSLog(@"C->RS: BYE"); + [NSURLConnection sendSynchronousRequest:request + returningResponse:&response + error:&error]; + if (error) { + NSLog(@"Error unregistering from room server: %@", error); + if (completionHandler) { + completionHandler(error); + } + return; + } + NSLog(@"Unregistered from room server."); + if (completionHandler) { + completionHandler(nil); + } +} + +#pragma mark - Private + ++ (NSError *)badResponseError { + NSError *error = + [[NSError alloc] initWithDomain:kARDAppEngineClientErrorDomain + code:kARDAppEngineClientErrorBadResponse + userInfo:@{ + NSLocalizedDescriptionKey: @"Error parsing response.", + }]; + return error; +} + +@end diff --git a/talk/examples/objc/AppRTCDemo/ARDCEODTURNClient.h b/talk/examples/objc/AppRTCDemo/ARDCEODTURNClient.h new file mode 100644 index 0000000000..81be9e8629 --- /dev/null +++ b/talk/examples/objc/AppRTCDemo/ARDCEODTURNClient.h @@ -0,0 +1,35 @@ +/* + * libjingle + * Copyright 2014, 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. + */ + +#import "ARDTURNClient.h" + +// Requests TURN server urls from compute engine on demand. +@interface ARDCEODTURNClient : NSObject + +- (instancetype)initWithURL:(NSURL *)url; + +@end diff --git a/talk/examples/objc/AppRTCDemo/ARDCEODTURNClient.m b/talk/examples/objc/AppRTCDemo/ARDCEODTURNClient.m new file mode 100644 index 0000000000..f63a289fad --- /dev/null +++ b/talk/examples/objc/AppRTCDemo/ARDCEODTURNClient.m @@ -0,0 +1,83 @@ +/* + * libjingle + * Copyright 2014, 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. + */ + +#import "ARDCEODTURNClient.h" + +#import "ARDUtilities.h" +#import "RTCICEServer+JSON.h" + +// TODO(tkchin): move this to a configuration object. +static NSString *kTURNOriginURLString = @"https://apprtc.appspot.com"; +static NSString *kARDCEODTURNClientErrorDomain = @"ARDCEODTURNClient"; +static NSInteger kARDCEODTURNClientErrorBadResponse = -1; + +@implementation ARDCEODTURNClient { + NSURL *_url; +} + +- (instancetype)initWithURL:(NSURL *)url { + NSParameterAssert([url absoluteString].length); + if (self = [super init]) { + _url = url; + } + return self; +} + +- (void)requestServersWithCompletionHandler: + (void (^)(NSArray *turnServers, + NSError *error))completionHandler { + NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:_url]; + // We need to set origin because TURN provider whitelists requests based on + // origin. + [request addValue:@"Mozilla/5.0" forHTTPHeaderField:@"user-agent"]; + [request addValue:kTURNOriginURLString forHTTPHeaderField:@"origin"]; + [NSURLConnection sendAsyncRequest:request + completionHandler:^(NSURLResponse *response, + NSData *data, + NSError *error) { + NSArray *turnServers = [NSArray array]; + if (error) { + completionHandler(turnServers, error); + return; + } + NSDictionary *dict = [NSDictionary dictionaryWithJSONData:data]; + turnServers = [RTCICEServer serversFromCEODJSONDictionary:dict]; + if (!turnServers) { + NSError *responseError = + [[NSError alloc] initWithDomain:kARDCEODTURNClientErrorDomain + code:kARDCEODTURNClientErrorBadResponse + userInfo:@{ + NSLocalizedDescriptionKey: @"Bad TURN response.", + }]; + completionHandler(turnServers, responseError); + return; + } + completionHandler(turnServers, nil); + }]; +} + +@end diff --git a/talk/examples/objc/AppRTCDemo/ARDMessageResponse+Internal.h b/talk/examples/objc/AppRTCDemo/ARDMessageResponse+Internal.h new file mode 100644 index 0000000000..e214ff3211 --- /dev/null +++ b/talk/examples/objc/AppRTCDemo/ARDMessageResponse+Internal.h @@ -0,0 +1,34 @@ +/* + * libjingle + * Copyright 2014, 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. + */ + +#import "ARDMessageResponse.h" + +@interface ARDMessageResponse () + +@property(nonatomic, assign) ARDMessageResultType result; + +@end diff --git a/talk/examples/objc/AppRTCDemo/ARDMessageResponse.m b/talk/examples/objc/AppRTCDemo/ARDMessageResponse.m index c6ab1d4414..496a0687f2 100644 --- a/talk/examples/objc/AppRTCDemo/ARDMessageResponse.m +++ b/talk/examples/objc/AppRTCDemo/ARDMessageResponse.m @@ -25,18 +25,12 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#import "ARDMessageResponse.h" +#import "ARDMessageResponse+Internal.h" #import "ARDUtilities.h" static NSString const *kARDMessageResultKey = @"result"; -@interface ARDMessageResponse () - -@property(nonatomic, assign) ARDMessageResultType result; - -@end - @implementation ARDMessageResponse @synthesize result = _result; diff --git a/talk/examples/objc/AppRTCDemo/ARDRegisterResponse+Internal.h b/talk/examples/objc/AppRTCDemo/ARDRegisterResponse+Internal.h new file mode 100644 index 0000000000..eb2b0b39af --- /dev/null +++ b/talk/examples/objc/AppRTCDemo/ARDRegisterResponse+Internal.h @@ -0,0 +1,40 @@ +/* + * libjingle + * Copyright 2014, 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. + */ + +#import "ARDRegisterResponse.h" + +@interface ARDRegisterResponse () + +@property(nonatomic, assign) ARDRegisterResultType result; +@property(nonatomic, assign) BOOL isInitiator; +@property(nonatomic, strong) NSString *roomId; +@property(nonatomic, strong) NSString *clientId; +@property(nonatomic, strong) NSArray *messages; +@property(nonatomic, strong) NSURL *webSocketURL; +@property(nonatomic, strong) NSURL *webSocketRestURL; + +@end diff --git a/talk/examples/objc/AppRTCDemo/ARDRegisterResponse.m b/talk/examples/objc/AppRTCDemo/ARDRegisterResponse.m index 76eb15c671..55c467e876 100644 --- a/talk/examples/objc/AppRTCDemo/ARDRegisterResponse.m +++ b/talk/examples/objc/AppRTCDemo/ARDRegisterResponse.m @@ -25,7 +25,7 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#import "ARDRegisterResponse.h" +#import "ARDRegisterResponse+Internal.h" #import "ARDSignalingMessage.h" #import "ARDUtilities.h" @@ -40,18 +40,6 @@ static NSString const *kARDRegisterMessagesKey = @"messages"; static NSString const *kARDRegisterWebSocketURLKey = @"wss_url"; static NSString const *kARDRegisterWebSocketRestURLKey = @"wss_post_url"; -@interface ARDRegisterResponse () - -@property(nonatomic, assign) ARDRegisterResultType result; -@property(nonatomic, assign) BOOL isInitiator; -@property(nonatomic, strong) NSString *roomId; -@property(nonatomic, strong) NSString *clientId; -@property(nonatomic, strong) NSArray *messages; -@property(nonatomic, strong) NSURL *webSocketURL; -@property(nonatomic, strong) NSURL *webSocketRestURL; - -@end - @implementation ARDRegisterResponse @synthesize result = _result; diff --git a/talk/examples/objc/AppRTCDemo/ARDRoomServerClient.h b/talk/examples/objc/AppRTCDemo/ARDRoomServerClient.h new file mode 100644 index 0000000000..a6501870a7 --- /dev/null +++ b/talk/examples/objc/AppRTCDemo/ARDRoomServerClient.h @@ -0,0 +1,50 @@ +/* + * libjingle + * Copyright 2014, 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. + */ + +#import + +@class ARDMessageResponse; +@class ARDRegisterResponse; +@class ARDSignalingMessage; + +@protocol ARDRoomServerClient + +- (void)registerForRoomId:(NSString *)roomId + completionHandler:(void (^)(ARDRegisterResponse *response, + NSError *error))completionHandler; + +- (void)sendMessage:(ARDSignalingMessage *)message + forRoomId:(NSString *)roomId + clientId:(NSString *)clientId + completionHandler:(void (^)(ARDMessageResponse *response, + NSError *error))completionHandler; + +- (void)deregisterForRoomId:(NSString *)roomId + clientId:(NSString *)clientId + completionHandler:(void (^)(NSError *error))completionHandler; + +@end diff --git a/talk/examples/objc/AppRTCDemo/ARDSignalingChannel.h b/talk/examples/objc/AppRTCDemo/ARDSignalingChannel.h new file mode 100644 index 0000000000..aedbb4293f --- /dev/null +++ b/talk/examples/objc/AppRTCDemo/ARDSignalingChannel.h @@ -0,0 +1,69 @@ +/* + * libjingle + * Copyright 2014, 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. + */ + +#import + +#import "ARDSignalingMessage.h" + +typedef NS_ENUM(NSInteger, ARDSignalingChannelState) { + // State when disconnected. + kARDSignalingChannelStateClosed, + // State when connection is established but not ready for use. + kARDSignalingChannelStateOpen, + // State when connection is established and registered. + kARDSignalingChannelStateRegistered, + // State when connection encounters a fatal error. + kARDSignalingChannelStateError +}; + +@protocol ARDSignalingChannel; +@protocol ARDSignalingChannelDelegate + +- (void)channel:(id)channel + didChangeState:(ARDSignalingChannelState)state; + +- (void)channel:(id)channel + didReceiveMessage:(ARDSignalingMessage *)message; + +@end + +@protocol ARDSignalingChannel + +@property(nonatomic, readonly) NSString *roomId; +@property(nonatomic, readonly) NSString *clientId; +@property(nonatomic, readonly) ARDSignalingChannelState state; +@property(nonatomic, weak) id delegate; + +// Registers the channel for the given room and client id. +- (void)registerForRoomId:(NSString *)roomId + clientId:(NSString *)clientId; + +// Sends signaling message over the channel. +- (void)sendMessage:(ARDSignalingMessage *)message; + +@end + diff --git a/talk/examples/objc/AppRTCDemo/ARDTURNClient.h b/talk/examples/objc/AppRTCDemo/ARDTURNClient.h new file mode 100644 index 0000000000..96856ea65a --- /dev/null +++ b/talk/examples/objc/AppRTCDemo/ARDTURNClient.h @@ -0,0 +1,37 @@ +/* + * libjingle + * Copyright 2014, 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. + */ + +#import + +@protocol ARDTURNClient + +// Returns TURN server urls if successful. +- (void)requestServersWithCompletionHandler: + (void (^)(NSArray *turnServers, + NSError *error))completionHandler; + +@end diff --git a/talk/examples/objc/AppRTCDemo/ARDWebSocketChannel.h b/talk/examples/objc/AppRTCDemo/ARDWebSocketChannel.h index 06c65201b8..c3af1d4455 100644 --- a/talk/examples/objc/AppRTCDemo/ARDWebSocketChannel.h +++ b/talk/examples/objc/AppRTCDemo/ARDWebSocketChannel.h @@ -27,49 +27,22 @@ #import -#import "ARDSignalingMessage.h" - -typedef NS_ENUM(NSInteger, ARDWebSocketChannelState) { - // State when disconnected. - kARDWebSocketChannelStateClosed, - // State when connection is established but not ready for use. - kARDWebSocketChannelStateOpen, - // State when connection is established and registered. - kARDWebSocketChannelStateRegistered, - // State when connection encounters a fatal error. - kARDWebSocketChannelStateError -}; - -@class ARDWebSocketChannel; -@protocol ARDWebSocketChannelDelegate - -- (void)channel:(ARDWebSocketChannel *)channel - didChangeState:(ARDWebSocketChannelState)state; - -- (void)channel:(ARDWebSocketChannel *)channel - didReceiveMessage:(ARDSignalingMessage *)message; - -@end +#import "ARDSignalingChannel.h" // Wraps a WebSocket connection to the AppRTC WebSocket server. -@interface ARDWebSocketChannel : NSObject - -@property(nonatomic, readonly) NSString *roomId; -@property(nonatomic, readonly) NSString *clientId; -@property(nonatomic, readonly) ARDWebSocketChannelState state; -@property(nonatomic, weak) id delegate; +@interface ARDWebSocketChannel : NSObject - (instancetype)initWithURL:(NSURL *)url restURL:(NSURL *)restURL - delegate:(id)delegate; + delegate:(id)delegate; // Registers with the WebSocket server for the given room and client id once // the web socket connection is open. - (void)registerForRoomId:(NSString *)roomId clientId:(NSString *)clientId; -// Sends data over the WebSocket connection if registered, otherwise POSTs to +// Sends message over the WebSocket connection if registered, otherwise POSTs to // the web socket server instead. -- (void)sendData:(NSData *)data; +- (void)sendMessage:(ARDSignalingMessage *)message; @end diff --git a/talk/examples/objc/AppRTCDemo/ARDWebSocketChannel.m b/talk/examples/objc/AppRTCDemo/ARDWebSocketChannel.m index 1707201724..688f66d5fa 100644 --- a/talk/examples/objc/AppRTCDemo/ARDWebSocketChannel.m +++ b/talk/examples/objc/AppRTCDemo/ARDWebSocketChannel.m @@ -50,7 +50,7 @@ static NSString const *kARDWSSMessagePayloadKey = @"msg"; - (instancetype)initWithURL:(NSURL *)url restURL:(NSURL *)restURL - delegate:(id)delegate { + delegate:(id)delegate { if (self = [super init]) { _url = url; _restURL = restURL; @@ -67,7 +67,7 @@ static NSString const *kARDWSSMessagePayloadKey = @"msg"; [self disconnect]; } -- (void)setState:(ARDWebSocketChannelState)state { +- (void)setState:(ARDSignalingChannelState)state { if (_state == state) { return; } @@ -81,15 +81,16 @@ static NSString const *kARDWSSMessagePayloadKey = @"msg"; NSParameterAssert(clientId.length); _roomId = roomId; _clientId = clientId; - if (_state == kARDWebSocketChannelStateOpen) { + if (_state == kARDSignalingChannelStateOpen) { [self registerWithCollider]; } } -- (void)sendData:(NSData *)data { +- (void)sendMessage:(ARDSignalingMessage *)message { NSParameterAssert(_clientId.length); NSParameterAssert(_roomId.length); - if (_state == kARDWebSocketChannelStateRegistered) { + NSData *data = [message JSONData]; + if (_state == kARDSignalingChannelStateRegistered) { NSString *payload = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; NSDictionary *message = @{ @@ -120,8 +121,8 @@ static NSString const *kARDWSSMessagePayloadKey = @"msg"; } - (void)disconnect { - if (_state == kARDWebSocketChannelStateClosed || - _state == kARDWebSocketChannelStateError) { + if (_state == kARDSignalingChannelStateClosed || + _state == kARDSignalingChannelStateError) { return; } [_socket close]; @@ -140,7 +141,7 @@ static NSString const *kARDWSSMessagePayloadKey = @"msg"; - (void)webSocketDidOpen:(SRWebSocket *)webSocket { NSLog(@"WebSocket connection opened."); - self.state = kARDWebSocketChannelStateOpen; + self.state = kARDSignalingChannelStateOpen; if (_roomId.length && _clientId.length) { [self registerWithCollider]; } @@ -171,7 +172,7 @@ static NSString const *kARDWSSMessagePayloadKey = @"msg"; - (void)webSocket:(SRWebSocket *)webSocket didFailWithError:(NSError *)error { NSLog(@"WebSocket error: %@", error); - self.state = kARDWebSocketChannelStateError; + self.state = kARDSignalingChannelStateError; } - (void)webSocket:(SRWebSocket *)webSocket @@ -180,14 +181,14 @@ static NSString const *kARDWSSMessagePayloadKey = @"msg"; wasClean:(BOOL)wasClean { NSLog(@"WebSocket closed with code: %ld reason:%@ wasClean:%d", (long)code, reason, wasClean); - NSParameterAssert(_state != kARDWebSocketChannelStateError); - self.state = kARDWebSocketChannelStateClosed; + NSParameterAssert(_state != kARDSignalingChannelStateError); + self.state = kARDSignalingChannelStateClosed; } #pragma mark - Private - (void)registerWithCollider { - if (_state == kARDWebSocketChannelStateRegistered) { + if (_state == kARDSignalingChannelStateRegistered) { return; } NSParameterAssert(_roomId.length); @@ -207,7 +208,7 @@ static NSString const *kARDWSSMessagePayloadKey = @"msg"; // Registration can fail if server rejects it. For example, if the room is // full. [_socket send:messageString]; - self.state = kARDWebSocketChannelStateRegistered; + self.state = kARDSignalingChannelStateRegistered; } @end diff --git a/talk/examples/objc/AppRTCDemo/ios/APPRTCViewController.m b/talk/examples/objc/AppRTCDemo/ios/APPRTCViewController.m index 3d60ee77f7..e6e19b0f97 100644 --- a/talk/examples/objc/AppRTCDemo/ios/APPRTCViewController.m +++ b/talk/examples/objc/AppRTCDemo/ios/APPRTCViewController.m @@ -107,6 +107,10 @@ static CGFloat const kLocalViewPadding = 20; } } +- (void)appClient:(ARDAppClient *)client + didChangeConnectionState:(RTCICEConnectionState)state { +} + - (void)appClient:(ARDAppClient *)client didReceiveLocalVideoTrack:(RTCVideoTrack *)localVideoTrack { _localVideoTrack = localVideoTrack; diff --git a/talk/examples/objc/AppRTCDemo/mac/APPRTCViewController.m b/talk/examples/objc/AppRTCDemo/mac/APPRTCViewController.m index 40d130724e..41bfd35c72 100644 --- a/talk/examples/objc/AppRTCDemo/mac/APPRTCViewController.m +++ b/talk/examples/objc/AppRTCDemo/mac/APPRTCViewController.m @@ -266,6 +266,10 @@ static NSUInteger const kLogViewHeight = 280; } } +- (void)appClient:(ARDAppClient *)client + didChangeConnectionState:(RTCICEConnectionState)state { +} + - (void)appClient:(ARDAppClient *)client didReceiveLocalVideoTrack:(RTCVideoTrack *)localVideoTrack { _localVideoTrack = localVideoTrack; diff --git a/talk/examples/objc/AppRTCDemo/tests/ARDAppClientTest.mm b/talk/examples/objc/AppRTCDemo/tests/ARDAppClientTest.mm new file mode 100644 index 0000000000..171d24487d --- /dev/null +++ b/talk/examples/objc/AppRTCDemo/tests/ARDAppClientTest.mm @@ -0,0 +1,323 @@ +/* + * libjingle + * Copyright 2014, 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. + */ + + +#import +#import + +#import "ARDAppClient+Internal.h" +#import "ARDRegisterResponse+Internal.h" +#import "ARDMessageResponse+Internal.h" +#import "RTCMediaConstraints.h" +#import "RTCPeerConnectionFactory.h" + +#include "webrtc/base/gunit.h" +#include "webrtc/base/ssladapter.h" + +// These classes mimic XCTest APIs, to make eventual conversion to XCTest +// easier. Conversion will happen once XCTest is supported well on build bots. +@interface ARDTestExpectation : NSObject + +@property(nonatomic, readonly) NSString *description; +@property(nonatomic, readonly) BOOL isFulfilled; + +- (instancetype)initWithDescription:(NSString *)description; +- (void)fulfill; + +@end + +@implementation ARDTestExpectation + +@synthesize description = _description; +@synthesize isFulfilled = _isFulfilled; + +- (instancetype)initWithDescription:(NSString *)description { + if (self = [super init]) { + _description = description; + } + return self; +} + +- (void)fulfill { + _isFulfilled = YES; +} + +@end + +@interface ARDTestCase : NSObject + +- (ARDTestExpectation *)expectationWithDescription:(NSString *)description; +- (void)waitForExpectationsWithTimeout:(NSTimeInterval)timeout + handler:(void (^)(NSError *error))handler; + +@end + +@implementation ARDTestCase { + NSMutableArray *_expectations; +} + +- (instancetype)init { + if (self = [super init]) { + _expectations = [NSMutableArray array]; + } + return self; +} + +- (ARDTestExpectation *)expectationWithDescription:(NSString *)description { + ARDTestExpectation *expectation = + [[ARDTestExpectation alloc] initWithDescription:description]; + [_expectations addObject:expectation]; + return expectation; +} + +- (void)waitForExpectationsWithTimeout:(NSTimeInterval)timeout + handler:(void (^)(NSError *error))handler { + NSDate *startDate = [NSDate date]; + while (![self areExpectationsFulfilled]) { + NSTimeInterval duration = [[NSDate date] timeIntervalSinceDate:startDate]; + if (duration > timeout) { + NSAssert(NO, @"Expectation timed out."); + break; + } + [[NSRunLoop currentRunLoop] + runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1]]; + } + handler(nil); +} + +- (BOOL)areExpectationsFulfilled { + for (ARDTestExpectation *expectation in _expectations) { + if (!expectation.isFulfilled) { + return NO; + } + } + return YES; +} + +@end + +@interface ARDAppClientTest : ARDTestCase +@end + +@implementation ARDAppClientTest + +#pragma mark - Mock helpers + +- (id)mockRoomServerClientForRoomId:(NSString *)roomId + clientId:(NSString *)clientId + isInitiator:(BOOL)isInitiator + messages:(NSArray *)messages + messageHandler: + (void (^)(ARDSignalingMessage *))messageHandler { + id mockRoomServerClient = + [OCMockObject mockForProtocol:@protocol(ARDRoomServerClient)]; + + // Successful register response. + ARDRegisterResponse *registerResponse = [[ARDRegisterResponse alloc] init]; + registerResponse.result = kARDRegisterResultTypeSuccess; + registerResponse.roomId = roomId; + registerResponse.clientId = clientId; + registerResponse.isInitiator = isInitiator; + registerResponse.messages = messages; + + // Successful message response. + ARDMessageResponse *messageResponse = [[ARDMessageResponse alloc] init]; + messageResponse.result = kARDMessageResultTypeSuccess; + + // Return register response from above on register. + [[[mockRoomServerClient stub] andDo:^(NSInvocation *invocation) { + __unsafe_unretained void (^completionHandler)(ARDRegisterResponse *response, + NSError *error); + [invocation getArgument:&completionHandler atIndex:3]; + completionHandler(registerResponse, nil); + }] registerForRoomId:roomId completionHandler:[OCMArg any]]; + + // Return message response from above on register. + [[[mockRoomServerClient stub] andDo:^(NSInvocation *invocation) { + __unsafe_unretained ARDSignalingMessage *message; + __unsafe_unretained void (^completionHandler)(ARDMessageResponse *response, + NSError *error); + [invocation getArgument:&message atIndex:2]; + [invocation getArgument:&completionHandler atIndex:5]; + messageHandler(message); + completionHandler(messageResponse, nil); + }] sendMessage:[OCMArg any] + forRoomId:roomId + clientId:clientId + completionHandler:[OCMArg any]]; + + // Do nothing on deregister. + [[[mockRoomServerClient stub] andDo:^(NSInvocation *invocation) { + __unsafe_unretained void (^completionHandler)(NSError *error); + [invocation getArgument:&completionHandler atIndex:4]; + if (completionHandler) { + completionHandler(nil); + } + }] deregisterForRoomId:roomId + clientId:clientId + completionHandler:[OCMArg any]]; + + return mockRoomServerClient; +} + +- (id)mockSignalingChannelForRoomId:(NSString *)roomId + clientId:(NSString *)clientId + messageHandler: + (void (^)(ARDSignalingMessage *message))messageHandler { + id mockSignalingChannel = + [OCMockObject niceMockForProtocol:@protocol(ARDSignalingChannel)]; + [[mockSignalingChannel stub] registerForRoomId:roomId clientId:clientId]; + [[[mockSignalingChannel stub] andDo:^(NSInvocation *invocation) { + __unsafe_unretained ARDSignalingMessage *message; + [invocation getArgument:&message atIndex:2]; + messageHandler(message); + }] sendMessage:[OCMArg any]]; + return mockSignalingChannel; +} + +- (id)mockTURNClient { + id mockTURNClient = + [OCMockObject mockForProtocol:@protocol(ARDTURNClient)]; + [[[mockTURNClient stub] andDo:^(NSInvocation *invocation) { + // Don't return anything in TURN response. + __unsafe_unretained void (^completionHandler)(NSArray *turnServers, + NSError *error); + [invocation getArgument:&completionHandler atIndex:2]; + completionHandler([NSArray array], nil); + }] requestServersWithCompletionHandler:[OCMArg any]]; + return mockTURNClient; +} + +- (ARDAppClient *)createAppClientForRoomId:(NSString *)roomId + clientId:(NSString *)clientId + isInitiator:(BOOL)isInitiator + messages:(NSArray *)messages + messageHandler: + (void (^)(ARDSignalingMessage *message))messageHandler + connectedHandler:(void (^)(void))connectedHandler { + id turnClient = [self mockTURNClient]; + id signalingChannel = [self mockSignalingChannelForRoomId:roomId + clientId:clientId + messageHandler:messageHandler]; + id roomServerClient = + [self mockRoomServerClientForRoomId:roomId + clientId:clientId + isInitiator:isInitiator + messages:messages + messageHandler:messageHandler]; + id delegate = + [OCMockObject niceMockForProtocol:@protocol(ARDAppClientDelegate)]; + [[[delegate stub] andDo:^(NSInvocation *invocation) { + connectedHandler(); + }] appClient:[OCMArg any] didChangeConnectionState:RTCICEConnectionConnected]; + + return [[ARDAppClient alloc] initWithRoomServerClient:roomServerClient + signalingChannel:signalingChannel + turnClient:turnClient + delegate:delegate]; +} + +// Tests that an ICE connection is established between two ARDAppClient objects +// where one is set up as a caller and the other the answerer. Network +// components are mocked out and messages are relayed directly from object to +// object. It's expected that both clients reach the RTCICEConnectionConnected +// state within a reasonable amount of time. +- (void)testSession { + // Need block arguments here because we're setting up a callbacks before we + // create the clients. + ARDAppClient *caller = nil; + ARDAppClient *answerer = nil; + __block __weak ARDAppClient *weakCaller = nil; + __block __weak ARDAppClient *weakAnswerer = nil; + NSString *roomId = @"testRoom"; + NSString *callerId = @"testCallerId"; + NSString *answererId = @"testAnswererId"; + + ARDTestExpectation *callerConnectionExpectation = + [self expectationWithDescription:@"Caller PC connected."]; + ARDTestExpectation *answererConnectionExpectation = + [self expectationWithDescription:@"Answerer PC connected."]; + + caller = [self createAppClientForRoomId:roomId + clientId:callerId + isInitiator:YES + messages:[NSArray array] + messageHandler:^(ARDSignalingMessage *message) { + ARDAppClient *strongAnswerer = weakAnswerer; + [strongAnswerer channel:strongAnswerer.channel didReceiveMessage:message]; + } connectedHandler:^{ + [callerConnectionExpectation fulfill]; + }]; + // TODO(tkchin): Figure out why DTLS-SRTP constraint causes thread assertion + // crash in Debug. + caller.defaultPeerConnectionConstraints = [[RTCMediaConstraints alloc] init]; + weakCaller = caller; + + answerer = [self createAppClientForRoomId:roomId + clientId:answererId + isInitiator:NO + messages:[NSArray array] + messageHandler:^(ARDSignalingMessage *message) { + ARDAppClient *strongCaller = weakCaller; + [strongCaller channel:strongCaller.channel didReceiveMessage:message]; + } connectedHandler:^{ + [answererConnectionExpectation fulfill]; + }]; + // TODO(tkchin): Figure out why DTLS-SRTP constraint causes thread assertion + // crash in Debug. + answerer.defaultPeerConnectionConstraints = + [[RTCMediaConstraints alloc] init]; + weakAnswerer = answerer; + + // Kick off connection. + [caller connectToRoomWithId:roomId options:nil]; + [answerer connectToRoomWithId:roomId options:nil]; + [self waitForExpectationsWithTimeout:20 handler:^(NSError *error) { + if (error) { + NSLog(@"Expectations error: %@", error); + } + }]; +} + +@end + +class SignalingTest : public ::testing::Test { + protected: + static void SetUpTestCase() { + rtc::InitializeSSL(); + } + static void TearDownTestCase() { + rtc::CleanupSSL(); + } +}; + +TEST_F(SignalingTest, SessionTest) { + @autoreleasepool { + ARDAppClientTest *test = [[ARDAppClientTest alloc] init]; + [test testSession]; + } +} diff --git a/talk/libjingle.gyp b/talk/libjingle.gyp index 56d29af12e..29a23bd087 100755 --- a/talk/libjingle.gyp +++ b/talk/libjingle.gyp @@ -278,6 +278,11 @@ '-lstdc++', ], }, + 'all_dependent_settings': { + 'xcode_settings': { + 'CLANG_ENABLE_OBJC_ARC': 'YES', + }, + }, 'xcode_settings': { 'CLANG_ENABLE_OBJC_ARC': 'YES', # common.gypi enables this for mac but we want this to be disabled diff --git a/talk/libjingle_examples.gyp b/talk/libjingle_examples.gyp index aba386b88b..a94610fa96 100755 --- a/talk/libjingle_examples.gyp +++ b/talk/libjingle_examples.gyp @@ -148,14 +148,70 @@ ['OS=="ios" or (OS=="mac" and target_arch!="ia32" and mac_sdk>="10.8")', { 'targets': [ + { 'target_name': 'apprtc_signaling', + 'type': 'static_library', + 'dependencies': [ + 'libjingle.gyp:libjingle_peerconnection_objc', + 'socketrocket', + ], + 'sources': [ + 'examples/objc/AppRTCDemo/ARDAppClient.h', + 'examples/objc/AppRTCDemo/ARDAppClient.m', + 'examples/objc/AppRTCDemo/ARDAppClient+Internal.h', + 'examples/objc/AppRTCDemo/ARDAppEngineClient.h', + 'examples/objc/AppRTCDemo/ARDAppEngineClient.m', + 'examples/objc/AppRTCDemo/ARDCEODTURNClient.h', + 'examples/objc/AppRTCDemo/ARDCEODTURNClient.m', + 'examples/objc/AppRTCDemo/ARDMessageResponse.h', + 'examples/objc/AppRTCDemo/ARDMessageResponse.m', + 'examples/objc/AppRTCDemo/ARDMessageResponse+Internal.h', + 'examples/objc/AppRTCDemo/ARDRegisterResponse.h', + 'examples/objc/AppRTCDemo/ARDRegisterResponse.m', + 'examples/objc/AppRTCDemo/ARDRegisterResponse+Internal.h', + 'examples/objc/AppRTCDemo/ARDRoomServerClient.h', + 'examples/objc/AppRTCDemo/ARDSignalingChannel.h', + 'examples/objc/AppRTCDemo/ARDSignalingMessage.h', + 'examples/objc/AppRTCDemo/ARDSignalingMessage.m', + 'examples/objc/AppRTCDemo/ARDTURNClient.h', + 'examples/objc/AppRTCDemo/ARDUtilities.h', + 'examples/objc/AppRTCDemo/ARDUtilities.m', + 'examples/objc/AppRTCDemo/ARDWebSocketChannel.h', + 'examples/objc/AppRTCDemo/ARDWebSocketChannel.m', + 'examples/objc/AppRTCDemo/RTCICECandidate+JSON.h', + 'examples/objc/AppRTCDemo/RTCICECandidate+JSON.m', + 'examples/objc/AppRTCDemo/RTCICEServer+JSON.h', + 'examples/objc/AppRTCDemo/RTCICEServer+JSON.m', + 'examples/objc/AppRTCDemo/RTCMediaConstraints+JSON.h', + 'examples/objc/AppRTCDemo/RTCMediaConstraints+JSON.m', + 'examples/objc/AppRTCDemo/RTCSessionDescription+JSON.h', + 'examples/objc/AppRTCDemo/RTCSessionDescription+JSON.m', + ], + 'include_dirs': [ + 'examples/objc/APPRTCDemo', + ], + 'direct_dependent_settings': { + 'include_dirs': [ + 'examples/objc/APPRTCDemo', + ], + }, + 'export_dependent_settings': [ + 'libjingle.gyp:libjingle_peerconnection_objc', + ], + 'conditions': [ + ['OS=="mac"', { + 'xcode_settings': { + 'MACOSX_DEPLOYMENT_TARGET' : '10.8', + }, + }], + ], + }, { 'target_name': 'AppRTCDemo', 'type': 'executable', 'product_name': 'AppRTCDemo', 'mac_bundle': 1, 'dependencies': [ - 'libjingle.gyp:libjingle_peerconnection_objc', - 'socketrocket', + 'apprtc_signaling', ], 'conditions': [ ['OS=="ios"', { @@ -199,34 +255,6 @@ ], }], ], - 'include_dirs': [ - 'examples/objc/APPRTCDemo', - ], - 'sources': [ - 'examples/objc/AppRTCDemo/ARDAppClient.h', - 'examples/objc/AppRTCDemo/ARDAppClient.m', - 'examples/objc/AppRTCDemo/ARDMessageResponse.h', - 'examples/objc/AppRTCDemo/ARDMessageResponse.m', - 'examples/objc/AppRTCDemo/ARDRegisterResponse.h', - 'examples/objc/AppRTCDemo/ARDRegisterResponse.m', - 'examples/objc/AppRTCDemo/ARDSignalingMessage.h', - 'examples/objc/AppRTCDemo/ARDSignalingMessage.m', - 'examples/objc/AppRTCDemo/ARDUtilities.h', - 'examples/objc/AppRTCDemo/ARDUtilities.m', - 'examples/objc/AppRTCDemo/ARDWebSocketChannel.h', - 'examples/objc/AppRTCDemo/ARDWebSocketChannel.m', - 'examples/objc/AppRTCDemo/RTCICECandidate+JSON.h', - 'examples/objc/AppRTCDemo/RTCICECandidate+JSON.m', - 'examples/objc/AppRTCDemo/RTCICEServer+JSON.h', - 'examples/objc/AppRTCDemo/RTCICEServer+JSON.m', - 'examples/objc/AppRTCDemo/RTCMediaConstraints+JSON.h', - 'examples/objc/AppRTCDemo/RTCMediaConstraints+JSON.m', - 'examples/objc/AppRTCDemo/RTCSessionDescription+JSON.h', - 'examples/objc/AppRTCDemo/RTCSessionDescription+JSON.m', - ], - 'xcode_settings': { - 'CLANG_ENABLE_OBJC_ARC': 'YES', - }, }, # target AppRTCDemo { # TODO(tkchin): move this into the real third party location and diff --git a/talk/libjingle_tests.gyp b/talk/libjingle_tests.gyp index c8d78c20b4..ff120f3c23 100755 --- a/talk/libjingle_tests.gyp +++ b/talk/libjingle_tests.gyp @@ -72,7 +72,7 @@ { 'target_name': 'libjingle_unittest', 'type': 'executable', - 'includes': [ 'build/ios_tests.gypi', ], + 'includes': [ 'build/objc_app.gypi', ], 'dependencies': [ '<(webrtc_root)/base/base.gyp:rtc_base', '<(webrtc_root)/base/base_tests.gyp:rtc_base_tests_utils', @@ -341,7 +341,7 @@ { 'target_name': 'libjingle_peerconnection_objc_test', 'type': 'executable', - 'includes': [ 'build/ios_tests.gypi', ], + 'includes': [ 'build/objc_app.gypi' ], 'dependencies': [ '<(webrtc_root)/base/base_tests.gyp:rtc_base_tests_utils', 'libjingle.gyp:libjingle_peerconnection_objc', @@ -356,46 +356,40 @@ # needs a GUI driver. 'app/webrtc/objctests/mac/main.mm', ], - 'FRAMEWORK_SEARCH_PATHS': [ - '$(inherited)', - '$(SDKROOT)/Developer/Library/Frameworks', - '$(DEVELOPER_LIBRARY_DIR)/Frameworks', - ], - - # TODO(fischman): there is duplication here with - # build/ios_tests.gypi, because for historical reasons the - # mac x64 bots expect this unittest to be in a bundle - # directory (.app). Once the bots don't expect this - # anymore, remove this duplication. - 'variables': { - 'infoplist_file': 'build/ios_test.plist', - }, - 'mac_bundle': 1, - 'mac_bundle_resources': [ - '<(infoplist_file)', - ], - # The plist is listed above so that it appears in XCode's file list, - # but we don't actually want to bundle it. - 'mac_bundle_resources!': [ - '<(infoplist_file)', - ], - 'xcode_settings': { - 'CLANG_ENABLE_OBJC_ARC': 'YES', - # common.gypi enables this for mac but we want this to be disabled - # like it is for ios. - 'CLANG_WARN_OBJC_MISSING_PROPERTY_SYNTHESIS': 'NO', - 'INFOPLIST_FILE': '<(infoplist_file)', - }, 'conditions': [ ['OS=="mac"', { 'xcode_settings': { # Need to build against 10.7 framework for full ARC support # on OSX. 'MACOSX_DEPLOYMENT_TARGET' : '10.7', + # common.gypi enables this for mac but we want this to be + # disabled like it is for ios. + 'CLANG_WARN_OBJC_MISSING_PROPERTY_SYNTHESIS': 'NO', }, }], ], }, # target libjingle_peerconnection_objc_test + { + 'target_name': 'apprtc_signaling_gunit_test', + 'type': 'executable', + 'includes': [ 'build/objc_app.gypi' ], + 'dependencies': [ + '<(webrtc_root)/base/base_tests.gyp:rtc_base_tests_utils', + '<(DEPTH)/third_party/ocmock/ocmock.gyp:ocmock', + 'libjingle_examples.gyp:apprtc_signaling', + ], + 'sources': [ + 'app/webrtc/objctests/mac/main.mm', + 'examples/objc/AppRTCDemo/tests/ARDAppClientTest.mm', + ], + 'conditions': [ + ['OS=="mac"', { + 'xcode_settings': { + 'MACOSX_DEPLOYMENT_TARGET' : '10.8', + }, + }], + ], + }, # target apprtc_signaling_gunit_test ], }], ['test_isolation_mode != "noop"', {