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