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
diff --git a/talk/examples/objc/AppRTCDemo/ARDAppClient.m b/talk/examples/objc/AppRTCDemo/ARDAppClient.m
index d72e8bb..fef7727 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 <AVFoundation/AVFoundation.h>
+#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 kARDAppClientErrorRoomFull = -2;
static NSInteger kARDAppClientErrorCreateSDP = -3;
static NSInteger kARDAppClientErrorSetSDP = -4;
-static NSInteger kARDAppClientErrorNetwork = -5;
-static NSInteger kARDAppClientErrorInvalidClient = -6;
-static NSInteger kARDAppClientErrorInvalidRoom = -7;
-
-@interface ARDAppClient () <ARDWebSocketChannelDelegate,
- RTCPeerConnectionDelegate, RTCSessionDescriptionDelegate>
-@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 @@
@synthesize iceServers = _iceServers;
@synthesize webSocketURL = _websocketURL;
@synthesize webSocketRestURL = _websocketRestURL;
+@synthesize defaultPeerConnectionConstraints =
+ _defaultPeerConnectionConstraints;
- (instancetype)initWithDelegate:(id<ARDAppClientDelegate>)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<ARDRoomServerClient>)rsClient
+ signalingChannel:(id<ARDSignalingChannel>)channel
+ turnClient:(id<ARDTURNClient>)turnClient
+ delegate:(id<ARDAppClientDelegate>)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 @@
// 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 @@
}];
// 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 @@
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 @@
self.state = kARDAppClientStateDisconnected;
}
-#pragma mark - ARDWebSocketChannelDelegate
+#pragma mark - ARDSignalingChannelDelegate
-- (void)channel:(ARDWebSocketChannel *)channel
+- (void)channel:(id<ARDSignalingChannel>)channel
didReceiveMessage:(ARDSignalingMessage *)message {
switch (message.type) {
case kARDSignalingMessageTypeOffer:
@@ -232,15 +232,15 @@
[self drainMessageQueueIfReady];
}
-- (void)channel:(ARDWebSocketChannel *)channel
- didChangeState:(ARDWebSocketChannelState)state {
+- (void)channel:(id<ARDSignalingChannel>)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 @@
- (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 @@
- (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 @@
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 @@
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 @@
}
- (RTCMediaConstraints *)defaultPeerConnectionConstraints {
+ if (_defaultPeerConnectionConstraints) {
+ return _defaultPeerConnectionConstraints;
+ }
NSArray *optionalConstraints = @[
[[RTCPair alloc] initWithKey:@"DtlsSrtpKeyAgreement" value:@"true"]
];
@@ -672,4 +556,61 @@
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