Home | History | Annotate | Download | only in objctests
      1 /*
      2  * libjingle
      3  * Copyright 2013, Google Inc.
      4  *
      5  * Redistribution and use in source and binary forms, with or without
      6  * modification, are permitted provided that the following conditions are met:
      7  *
      8  *  1. Redistributions of source code must retain the above copyright notice,
      9  *     this list of conditions and the following disclaimer.
     10  *  2. Redistributions in binary form must reproduce the above copyright notice,
     11  *     this list of conditions and the following disclaimer in the documentation
     12  *     and/or other materials provided with the distribution.
     13  *  3. The name of the author may not be used to endorse or promote products
     14  *     derived from this software without specific prior written permission.
     15  *
     16  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
     17  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
     18  * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
     19  * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
     20  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
     21  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
     22  * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
     23  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
     24  * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
     25  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     26  */
     27 
     28 #import <Foundation/Foundation.h>
     29 
     30 #import "RTCICEServer.h"
     31 #import "RTCMediaConstraints.h"
     32 #import "RTCMediaStream.h"
     33 #import "RTCPair.h"
     34 #import "RTCPeerConnection.h"
     35 #import "RTCPeerConnectionFactory.h"
     36 #import "RTCPeerConnectionSyncObserver.h"
     37 #import "RTCSessionDescription.h"
     38 #import "RTCSessionDescriptionSyncObserver.h"
     39 #import "RTCVideoRenderer.h"
     40 #import "RTCVideoTrack.h"
     41 
     42 #include "webrtc/base/gunit.h"
     43 #include "webrtc/base/ssladapter.h"
     44 
     45 #if !defined(__has_feature) || !__has_feature(objc_arc)
     46 #error "This file requires ARC support."
     47 #endif
     48 
     49 @interface RTCPeerConnectionTest : NSObject
     50 
     51 // Returns whether the two sessions are of the same type.
     52 + (BOOL)isSession:(RTCSessionDescription*)session1
     53     ofSameTypeAsSession:(RTCSessionDescription*)session2;
     54 
     55 // Create and add tracks to pc, with the given source, label, and IDs
     56 - (RTCMediaStream*)addTracksToPeerConnection:(RTCPeerConnection*)pc
     57                                  withFactory:(RTCPeerConnectionFactory*)factory
     58                                  videoSource:(RTCVideoSource*)videoSource
     59                                  streamLabel:(NSString*)streamLabel
     60                                 videoTrackID:(NSString*)videoTrackID
     61                                 audioTrackID:(NSString*)audioTrackID;
     62 
     63 - (void)testCompleteSessionWithFactory:(RTCPeerConnectionFactory*)factory;
     64 
     65 @end
     66 
     67 @implementation RTCPeerConnectionTest
     68 
     69 + (BOOL)isSession:(RTCSessionDescription*)session1
     70     ofSameTypeAsSession:(RTCSessionDescription*)session2 {
     71   return [session1.type isEqual:session2.type];
     72 }
     73 
     74 - (RTCMediaStream*)addTracksToPeerConnection:(RTCPeerConnection*)pc
     75                                  withFactory:(RTCPeerConnectionFactory*)factory
     76                                  videoSource:(RTCVideoSource*)videoSource
     77                                  streamLabel:(NSString*)streamLabel
     78                                 videoTrackID:(NSString*)videoTrackID
     79                                 audioTrackID:(NSString*)audioTrackID {
     80   RTCMediaStream* localMediaStream = [factory mediaStreamWithLabel:streamLabel];
     81   RTCVideoTrack* videoTrack =
     82       [factory videoTrackWithID:videoTrackID source:videoSource];
     83   RTCVideoRenderer* videoRenderer =
     84       [[RTCVideoRenderer alloc] initWithDelegate:nil];
     85   [videoTrack addRenderer:videoRenderer];
     86   [localMediaStream addVideoTrack:videoTrack];
     87   // Test that removal/re-add works.
     88   [localMediaStream removeVideoTrack:videoTrack];
     89   [localMediaStream addVideoTrack:videoTrack];
     90   RTCAudioTrack* audioTrack = [factory audioTrackWithID:audioTrackID];
     91   [localMediaStream addAudioTrack:audioTrack];
     92   RTCMediaConstraints* constraints = [[RTCMediaConstraints alloc] init];
     93   [pc addStream:localMediaStream constraints:constraints];
     94   return localMediaStream;
     95 }
     96 
     97 - (void)testCompleteSessionWithFactory:(RTCPeerConnectionFactory*)factory {
     98   NSArray* mandatory = @[
     99     [[RTCPair alloc] initWithKey:@"DtlsSrtpKeyAgreement" value:@"true"],
    100     [[RTCPair alloc] initWithKey:@"internalSctpDataChannels" value:@"true"],
    101   ];
    102   RTCMediaConstraints* constraints = [[RTCMediaConstraints alloc] init];
    103   RTCMediaConstraints* pcConstraints =
    104       [[RTCMediaConstraints alloc] initWithMandatoryConstraints:mandatory
    105                                             optionalConstraints:nil];
    106 
    107   RTCPeerConnectionSyncObserver* offeringExpectations =
    108       [[RTCPeerConnectionSyncObserver alloc] init];
    109   RTCPeerConnection* pcOffer =
    110       [factory peerConnectionWithICEServers:nil
    111                                 constraints:pcConstraints
    112                                    delegate:offeringExpectations];
    113 
    114   RTCPeerConnectionSyncObserver* answeringExpectations =
    115       [[RTCPeerConnectionSyncObserver alloc] init];
    116 
    117   RTCPeerConnection* pcAnswer =
    118       [factory peerConnectionWithICEServers:nil
    119                                 constraints:pcConstraints
    120                                    delegate:answeringExpectations];
    121   // TODO(hughv): Create video capturer
    122   RTCVideoCapturer* capturer = nil;
    123   RTCVideoSource* videoSource =
    124       [factory videoSourceWithCapturer:capturer constraints:constraints];
    125 
    126   // Here and below, "oLMS" refers to offerer's local media stream, and "aLMS"
    127   // refers to the answerer's local media stream, with suffixes of "a0" and "v0"
    128   // for audio and video tracks, resp.  These mirror chrome historical naming.
    129   RTCMediaStream* oLMSUnused = [self addTracksToPeerConnection:pcOffer
    130                                                    withFactory:factory
    131                                                    videoSource:videoSource
    132                                                    streamLabel:@"oLMS"
    133                                                   videoTrackID:@"oLMSv0"
    134                                                   audioTrackID:@"oLMSa0"];
    135 
    136   RTCDataChannel* offerDC =
    137       [pcOffer createDataChannelWithLabel:@"offerDC"
    138                                    config:[[RTCDataChannelInit alloc] init]];
    139   EXPECT_TRUE([offerDC.label isEqual:@"offerDC"]);
    140   offerDC.delegate = offeringExpectations;
    141   offeringExpectations.dataChannel = offerDC;
    142 
    143   RTCSessionDescriptionSyncObserver* sdpObserver =
    144       [[RTCSessionDescriptionSyncObserver alloc] init];
    145   [pcOffer createOfferWithDelegate:sdpObserver constraints:constraints];
    146   [sdpObserver wait];
    147   EXPECT_TRUE(sdpObserver.success);
    148   RTCSessionDescription* offerSDP = sdpObserver.sessionDescription;
    149   EXPECT_EQ([@"offer" compare:offerSDP.type options:NSCaseInsensitiveSearch],
    150             NSOrderedSame);
    151   EXPECT_GT([offerSDP.description length], 0);
    152 
    153   sdpObserver = [[RTCSessionDescriptionSyncObserver alloc] init];
    154   [answeringExpectations expectSignalingChange:RTCSignalingHaveRemoteOffer];
    155   [answeringExpectations expectAddStream:@"oLMS"];
    156   [pcAnswer setRemoteDescriptionWithDelegate:sdpObserver
    157                           sessionDescription:offerSDP];
    158   [sdpObserver wait];
    159 
    160   RTCMediaStream* aLMSUnused = [self addTracksToPeerConnection:pcAnswer
    161                                                    withFactory:factory
    162                                                    videoSource:videoSource
    163                                                    streamLabel:@"aLMS"
    164                                                   videoTrackID:@"aLMSv0"
    165                                                   audioTrackID:@"aLMSa0"];
    166 
    167   sdpObserver = [[RTCSessionDescriptionSyncObserver alloc] init];
    168   [pcAnswer createAnswerWithDelegate:sdpObserver constraints:constraints];
    169   [sdpObserver wait];
    170   EXPECT_TRUE(sdpObserver.success);
    171   RTCSessionDescription* answerSDP = sdpObserver.sessionDescription;
    172   EXPECT_EQ([@"answer" compare:answerSDP.type options:NSCaseInsensitiveSearch],
    173             NSOrderedSame);
    174   EXPECT_GT([answerSDP.description length], 0);
    175 
    176   [offeringExpectations expectICECandidates:2];
    177   [answeringExpectations expectICECandidates:2];
    178 
    179   sdpObserver = [[RTCSessionDescriptionSyncObserver alloc] init];
    180   [answeringExpectations expectSignalingChange:RTCSignalingStable];
    181   [pcAnswer setLocalDescriptionWithDelegate:sdpObserver
    182                          sessionDescription:answerSDP];
    183   [sdpObserver wait];
    184   EXPECT_TRUE(sdpObserver.sessionDescription == NULL);
    185 
    186   sdpObserver = [[RTCSessionDescriptionSyncObserver alloc] init];
    187   [offeringExpectations expectSignalingChange:RTCSignalingHaveLocalOffer];
    188   [pcOffer setLocalDescriptionWithDelegate:sdpObserver
    189                         sessionDescription:offerSDP];
    190   [sdpObserver wait];
    191   EXPECT_TRUE(sdpObserver.sessionDescription == NULL);
    192 
    193   [offeringExpectations expectICEConnectionChange:RTCICEConnectionChecking];
    194   [offeringExpectations expectICEConnectionChange:RTCICEConnectionConnected];
    195   // TODO(fischman): figure out why this is flaky and re-introduce (and remove
    196   // special-casing from the observer!).
    197   // [offeringExpectations expectICEConnectionChange:RTCICEConnectionCompleted];
    198   [answeringExpectations expectICEConnectionChange:RTCICEConnectionChecking];
    199   [answeringExpectations expectICEConnectionChange:RTCICEConnectionConnected];
    200 
    201   [offeringExpectations expectStateChange:kRTCDataChannelStateOpen];
    202   [answeringExpectations expectDataChannel:@"offerDC"];
    203   [answeringExpectations expectStateChange:kRTCDataChannelStateOpen];
    204 
    205   [offeringExpectations expectICEGatheringChange:RTCICEGatheringComplete];
    206   [answeringExpectations expectICEGatheringChange:RTCICEGatheringComplete];
    207 
    208   sdpObserver = [[RTCSessionDescriptionSyncObserver alloc] init];
    209   [offeringExpectations expectSignalingChange:RTCSignalingStable];
    210   [offeringExpectations expectAddStream:@"aLMS"];
    211   [pcOffer setRemoteDescriptionWithDelegate:sdpObserver
    212                          sessionDescription:answerSDP];
    213   [sdpObserver wait];
    214   EXPECT_TRUE(sdpObserver.sessionDescription == NULL);
    215 
    216   EXPECT_TRUE([offerSDP.type isEqual:pcOffer.localDescription.type]);
    217   EXPECT_TRUE([answerSDP.type isEqual:pcOffer.remoteDescription.type]);
    218   EXPECT_TRUE([offerSDP.type isEqual:pcAnswer.remoteDescription.type]);
    219   EXPECT_TRUE([answerSDP.type isEqual:pcAnswer.localDescription.type]);
    220 
    221   for (RTCICECandidate* candidate in offeringExpectations
    222            .releaseReceivedICECandidates) {
    223     [pcAnswer addICECandidate:candidate];
    224   }
    225   for (RTCICECandidate* candidate in answeringExpectations
    226            .releaseReceivedICECandidates) {
    227     [pcOffer addICECandidate:candidate];
    228   }
    229 
    230   [offeringExpectations waitForAllExpectationsToBeSatisfied];
    231   [answeringExpectations waitForAllExpectationsToBeSatisfied];
    232 
    233   EXPECT_EQ(pcOffer.signalingState, RTCSignalingStable);
    234   EXPECT_EQ(pcAnswer.signalingState, RTCSignalingStable);
    235 
    236   // Test send and receive UTF-8 text
    237   NSString* text = @"";
    238   NSData* textData = [text dataUsingEncoding:NSUTF8StringEncoding];
    239   RTCDataBuffer* buffer =
    240       [[RTCDataBuffer alloc] initWithData:textData isBinary:NO];
    241   [answeringExpectations expectMessage:[textData copy] isBinary:NO];
    242   EXPECT_TRUE([offeringExpectations.dataChannel sendData:buffer]);
    243   [answeringExpectations waitForAllExpectationsToBeSatisfied];
    244 
    245   // Test send and receive binary data
    246   const size_t byteLength = 5;
    247   char bytes[byteLength] = {1, 2, 3, 4, 5};
    248   NSData* byteData = [NSData dataWithBytes:bytes length:byteLength];
    249   buffer = [[RTCDataBuffer alloc] initWithData:byteData isBinary:YES];
    250   [answeringExpectations expectMessage:[byteData copy] isBinary:YES];
    251   EXPECT_TRUE([offeringExpectations.dataChannel sendData:buffer]);
    252   [answeringExpectations waitForAllExpectationsToBeSatisfied];
    253 
    254   [offeringExpectations expectStateChange:kRTCDataChannelStateClosing];
    255   [answeringExpectations expectStateChange:kRTCDataChannelStateClosing];
    256   [offeringExpectations expectStateChange:kRTCDataChannelStateClosed];
    257   [answeringExpectations expectStateChange:kRTCDataChannelStateClosed];
    258 
    259   [answeringExpectations.dataChannel close];
    260   [offeringExpectations.dataChannel close];
    261 
    262   [offeringExpectations waitForAllExpectationsToBeSatisfied];
    263   [answeringExpectations waitForAllExpectationsToBeSatisfied];
    264   // Don't need to listen to further state changes.
    265   // TODO(tkchin): figure out why Closed->Closing without this.
    266   offeringExpectations.dataChannel.delegate = nil;
    267   answeringExpectations.dataChannel.delegate = nil;
    268 
    269   // Let the audio feedback run for 2s to allow human testing and to ensure
    270   // things stabilize.  TODO(fischman): replace seconds with # of video frames,
    271   // when we have video flowing.
    272   [[NSRunLoop currentRunLoop]
    273       runUntilDate:[NSDate dateWithTimeIntervalSinceNow:2]];
    274 
    275   [offeringExpectations expectICEConnectionChange:RTCICEConnectionClosed];
    276   [answeringExpectations expectICEConnectionChange:RTCICEConnectionClosed];
    277   [offeringExpectations expectSignalingChange:RTCSignalingClosed];
    278   [answeringExpectations expectSignalingChange:RTCSignalingClosed];
    279 
    280   [pcOffer close];
    281   [pcAnswer close];
    282 
    283   [offeringExpectations waitForAllExpectationsToBeSatisfied];
    284   [answeringExpectations waitForAllExpectationsToBeSatisfied];
    285 
    286   capturer = nil;
    287   videoSource = nil;
    288   pcOffer = nil;
    289   pcAnswer = nil;
    290   // TODO(fischman): be stricter about shutdown checks; ensure thread
    291   // counts return to where they were before the test kicked off, and
    292   // that all objects have in fact shut down.
    293 }
    294 
    295 @end
    296 
    297 // TODO(fischman): move {Initialize,Cleanup}SSL into alloc/dealloc of
    298 // RTCPeerConnectionTest and avoid the appearance of RTCPeerConnectionTest being
    299 // a TestBase since it's not.
    300 TEST(RTCPeerConnectionTest, SessionTest) {
    301   @autoreleasepool {
    302     rtc::InitializeSSL();
    303     // Since |factory| will own the signaling & worker threads, it's important
    304     // that it outlive the created PeerConnections since they self-delete on the
    305     // signaling thread, and if |factory| is freed first then a last refcount on
    306     // the factory will expire during this teardown, causing the signaling
    307     // thread to try to Join() with itself.  This is a hack to ensure that the
    308     // factory outlives RTCPeerConnection:dealloc.
    309     // See https://code.google.com/p/webrtc/issues/detail?id=3100.
    310     RTCPeerConnectionFactory* factory = [[RTCPeerConnectionFactory alloc] init];
    311     @autoreleasepool {
    312       RTCPeerConnectionTest* pcTest = [[RTCPeerConnectionTest alloc] init];
    313       [pcTest testCompleteSessionWithFactory:factory];
    314     }
    315     rtc::CleanupSSL();
    316   }
    317 }
    318