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 49const NSTimeInterval kRTCPeerConnectionTestTimeout = 20; 50 51@interface RTCFakeRenderer : NSObject <RTCVideoRenderer> 52@end 53 54@implementation RTCFakeRenderer 55 56- (void)setSize:(CGSize)size {} 57- (void)renderFrame:(RTCI420Frame*)frame {} 58 59@end 60 61@interface RTCPeerConnectionTest : NSObject 62 63// Returns whether the two sessions are of the same type. 64+ (BOOL)isSession:(RTCSessionDescription*)session1 65 ofSameTypeAsSession:(RTCSessionDescription*)session2; 66 67// Create and add tracks to pc, with the given source, label, and IDs 68- (RTCMediaStream*)addTracksToPeerConnection:(RTCPeerConnection*)pc 69 withFactory:(RTCPeerConnectionFactory*)factory 70 videoSource:(RTCVideoSource*)videoSource 71 streamLabel:(NSString*)streamLabel 72 videoTrackID:(NSString*)videoTrackID 73 audioTrackID:(NSString*)audioTrackID; 74 75- (void)testCompleteSessionWithFactory:(RTCPeerConnectionFactory*)factory; 76 77@end 78 79@implementation RTCPeerConnectionTest 80 81+ (BOOL)isSession:(RTCSessionDescription*)session1 82 ofSameTypeAsSession:(RTCSessionDescription*)session2 { 83 return [session1.type isEqual:session2.type]; 84} 85 86- (RTCMediaStream*)addTracksToPeerConnection:(RTCPeerConnection*)pc 87 withFactory:(RTCPeerConnectionFactory*)factory 88 videoSource:(RTCVideoSource*)videoSource 89 streamLabel:(NSString*)streamLabel 90 videoTrackID:(NSString*)videoTrackID 91 audioTrackID:(NSString*)audioTrackID { 92 RTCMediaStream* localMediaStream = [factory mediaStreamWithLabel:streamLabel]; 93 RTCVideoTrack* videoTrack = 94 [factory videoTrackWithID:videoTrackID source:videoSource]; 95 RTCFakeRenderer* videoRenderer = [[RTCFakeRenderer alloc] init]; 96 [videoTrack addRenderer:videoRenderer]; 97 [localMediaStream addVideoTrack:videoTrack]; 98 // Test that removal/re-add works. 99 [localMediaStream removeVideoTrack:videoTrack]; 100 [localMediaStream addVideoTrack:videoTrack]; 101 RTCAudioTrack* audioTrack = [factory audioTrackWithID:audioTrackID]; 102 [localMediaStream addAudioTrack:audioTrack]; 103 [pc addStream:localMediaStream]; 104 return localMediaStream; 105} 106 107- (void)testCompleteSessionWithFactory:(RTCPeerConnectionFactory*)factory { 108 NSArray* mandatory = @[ 109 [[RTCPair alloc] initWithKey:@"DtlsSrtpKeyAgreement" value:@"true"], 110 [[RTCPair alloc] initWithKey:@"internalSctpDataChannels" value:@"true"], 111 ]; 112 RTCMediaConstraints* constraints = [[RTCMediaConstraints alloc] init]; 113 RTCMediaConstraints* pcConstraints = 114 [[RTCMediaConstraints alloc] initWithMandatoryConstraints:mandatory 115 optionalConstraints:nil]; 116 117 RTCPeerConnectionSyncObserver* offeringExpectations = 118 [[RTCPeerConnectionSyncObserver alloc] init]; 119 RTCPeerConnection* pcOffer = 120 [factory peerConnectionWithICEServers:nil 121 constraints:pcConstraints 122 delegate:offeringExpectations]; 123 124 RTCPeerConnectionSyncObserver* answeringExpectations = 125 [[RTCPeerConnectionSyncObserver alloc] init]; 126 127 RTCPeerConnection* pcAnswer = 128 [factory peerConnectionWithICEServers:nil 129 constraints:pcConstraints 130 delegate:answeringExpectations]; 131 // TODO(hughv): Create video capturer 132 RTCVideoCapturer* capturer = nil; 133 RTCVideoSource* videoSource = 134 [factory videoSourceWithCapturer:capturer constraints:constraints]; 135 136 // Here and below, "oLMS" refers to offerer's local media stream, and "aLMS" 137 // refers to the answerer's local media stream, with suffixes of "a0" and "v0" 138 // for audio and video tracks, resp. These mirror chrome historical naming. 139 RTCMediaStream* oLMSUnused = [self addTracksToPeerConnection:pcOffer 140 withFactory:factory 141 videoSource:videoSource 142 streamLabel:@"oLMS" 143 videoTrackID:@"oLMSv0" 144 audioTrackID:@"oLMSa0"]; 145 146 RTCDataChannel* offerDC = 147 [pcOffer createDataChannelWithLabel:@"offerDC" 148 config:[[RTCDataChannelInit alloc] init]]; 149 EXPECT_TRUE([offerDC.label isEqual:@"offerDC"]); 150 offerDC.delegate = offeringExpectations; 151 offeringExpectations.dataChannel = offerDC; 152 153 RTCSessionDescriptionSyncObserver* sdpObserver = 154 [[RTCSessionDescriptionSyncObserver alloc] init]; 155 [pcOffer createOfferWithDelegate:sdpObserver constraints:constraints]; 156 [sdpObserver wait]; 157 EXPECT_TRUE(sdpObserver.success); 158 RTCSessionDescription* offerSDP = sdpObserver.sessionDescription; 159 EXPECT_EQ([@"offer" compare:offerSDP.type options:NSCaseInsensitiveSearch], 160 NSOrderedSame); 161 EXPECT_GT([offerSDP.description length], 0); 162 163 sdpObserver = [[RTCSessionDescriptionSyncObserver alloc] init]; 164 [answeringExpectations expectSignalingChange:RTCSignalingHaveRemoteOffer]; 165 [answeringExpectations expectAddStream:@"oLMS"]; 166 [pcAnswer setRemoteDescriptionWithDelegate:sdpObserver 167 sessionDescription:offerSDP]; 168 [sdpObserver wait]; 169 170 RTCMediaStream* aLMSUnused = [self addTracksToPeerConnection:pcAnswer 171 withFactory:factory 172 videoSource:videoSource 173 streamLabel:@"aLMS" 174 videoTrackID:@"aLMSv0" 175 audioTrackID:@"aLMSa0"]; 176 177 sdpObserver = [[RTCSessionDescriptionSyncObserver alloc] init]; 178 [pcAnswer createAnswerWithDelegate:sdpObserver constraints:constraints]; 179 [sdpObserver wait]; 180 EXPECT_TRUE(sdpObserver.success); 181 RTCSessionDescription* answerSDP = sdpObserver.sessionDescription; 182 EXPECT_EQ([@"answer" compare:answerSDP.type options:NSCaseInsensitiveSearch], 183 NSOrderedSame); 184 EXPECT_GT([answerSDP.description length], 0); 185 186 [offeringExpectations expectICECandidates:2]; 187 // It's possible to only have 1 ICE candidate for the answerer, since we use 188 // BUNDLE and rtcp-mux by default, and don't provide any ICE servers in this 189 // test. 190 [answeringExpectations expectICECandidates:1]; 191 192 sdpObserver = [[RTCSessionDescriptionSyncObserver alloc] init]; 193 [answeringExpectations expectSignalingChange:RTCSignalingStable]; 194 [pcAnswer setLocalDescriptionWithDelegate:sdpObserver 195 sessionDescription:answerSDP]; 196 [sdpObserver wait]; 197 EXPECT_TRUE(sdpObserver.sessionDescription == NULL); 198 199 sdpObserver = [[RTCSessionDescriptionSyncObserver alloc] init]; 200 [offeringExpectations expectSignalingChange:RTCSignalingHaveLocalOffer]; 201 [pcOffer setLocalDescriptionWithDelegate:sdpObserver 202 sessionDescription:offerSDP]; 203 [sdpObserver wait]; 204 EXPECT_TRUE(sdpObserver.sessionDescription == NULL); 205 206 [offeringExpectations expectICEConnectionChange:RTCICEConnectionChecking]; 207 [offeringExpectations expectICEConnectionChange:RTCICEConnectionConnected]; 208 // TODO(fischman): figure out why this is flaky and re-introduce (and remove 209 // special-casing from the observer!). 210 // [offeringExpectations expectICEConnectionChange:RTCICEConnectionCompleted]; 211 [answeringExpectations expectICEConnectionChange:RTCICEConnectionChecking]; 212 [answeringExpectations expectICEConnectionChange:RTCICEConnectionConnected]; 213 214 [offeringExpectations expectStateChange:kRTCDataChannelStateOpen]; 215 [answeringExpectations expectDataChannel:@"offerDC"]; 216 [answeringExpectations expectStateChange:kRTCDataChannelStateOpen]; 217 218 [offeringExpectations expectICEGatheringChange:RTCICEGatheringComplete]; 219 [answeringExpectations expectICEGatheringChange:RTCICEGatheringComplete]; 220 221 sdpObserver = [[RTCSessionDescriptionSyncObserver alloc] init]; 222 [offeringExpectations expectSignalingChange:RTCSignalingStable]; 223 [offeringExpectations expectAddStream:@"aLMS"]; 224 [pcOffer setRemoteDescriptionWithDelegate:sdpObserver 225 sessionDescription:answerSDP]; 226 [sdpObserver wait]; 227 EXPECT_TRUE(sdpObserver.sessionDescription == NULL); 228 229 EXPECT_TRUE([offerSDP.type isEqual:pcOffer.localDescription.type]); 230 EXPECT_TRUE([answerSDP.type isEqual:pcOffer.remoteDescription.type]); 231 EXPECT_TRUE([offerSDP.type isEqual:pcAnswer.remoteDescription.type]); 232 EXPECT_TRUE([answerSDP.type isEqual:pcAnswer.localDescription.type]); 233 234 for (RTCICECandidate* candidate in offeringExpectations 235 .releaseReceivedICECandidates) { 236 [pcAnswer addICECandidate:candidate]; 237 } 238 for (RTCICECandidate* candidate in answeringExpectations 239 .releaseReceivedICECandidates) { 240 [pcOffer addICECandidate:candidate]; 241 } 242 243 EXPECT_TRUE( 244 [offeringExpectations waitForAllExpectationsToBeSatisfiedWithTimeout: 245 kRTCPeerConnectionTestTimeout]); 246 EXPECT_TRUE( 247 [answeringExpectations waitForAllExpectationsToBeSatisfiedWithTimeout: 248 kRTCPeerConnectionTestTimeout]); 249 250 EXPECT_EQ(pcOffer.signalingState, RTCSignalingStable); 251 EXPECT_EQ(pcAnswer.signalingState, RTCSignalingStable); 252 253 // Test send and receive UTF-8 text 254 NSString* text = @"你好"; 255 NSData* textData = [text dataUsingEncoding:NSUTF8StringEncoding]; 256 RTCDataBuffer* buffer = 257 [[RTCDataBuffer alloc] initWithData:textData isBinary:NO]; 258 [answeringExpectations expectMessage:[textData copy] isBinary:NO]; 259 EXPECT_TRUE([offeringExpectations.dataChannel sendData:buffer]); 260 EXPECT_TRUE( 261 [answeringExpectations waitForAllExpectationsToBeSatisfiedWithTimeout: 262 kRTCPeerConnectionTestTimeout]); 263 264 // Test send and receive binary data 265 const size_t byteLength = 5; 266 char bytes[byteLength] = {1, 2, 3, 4, 5}; 267 NSData* byteData = [NSData dataWithBytes:bytes length:byteLength]; 268 buffer = [[RTCDataBuffer alloc] initWithData:byteData isBinary:YES]; 269 [answeringExpectations expectMessage:[byteData copy] isBinary:YES]; 270 EXPECT_TRUE([offeringExpectations.dataChannel sendData:buffer]); 271 EXPECT_TRUE( 272 [answeringExpectations waitForAllExpectationsToBeSatisfiedWithTimeout: 273 kRTCPeerConnectionTestTimeout]); 274 275 [offeringExpectations expectStateChange:kRTCDataChannelStateClosing]; 276 [answeringExpectations expectStateChange:kRTCDataChannelStateClosing]; 277 [offeringExpectations expectStateChange:kRTCDataChannelStateClosed]; 278 [answeringExpectations expectStateChange:kRTCDataChannelStateClosed]; 279 280 [answeringExpectations.dataChannel close]; 281 [offeringExpectations.dataChannel close]; 282 283 EXPECT_TRUE( 284 [offeringExpectations waitForAllExpectationsToBeSatisfiedWithTimeout: 285 kRTCPeerConnectionTestTimeout]); 286 EXPECT_TRUE( 287 [answeringExpectations waitForAllExpectationsToBeSatisfiedWithTimeout: 288 kRTCPeerConnectionTestTimeout]); 289 // Don't need to listen to further state changes. 290 // TODO(tkchin): figure out why Closed->Closing without this. 291 offeringExpectations.dataChannel.delegate = nil; 292 answeringExpectations.dataChannel.delegate = nil; 293 294 // Let the audio feedback run for 2s to allow human testing and to ensure 295 // things stabilize. TODO(fischman): replace seconds with # of video frames, 296 // when we have video flowing. 297 [[NSRunLoop currentRunLoop] 298 runUntilDate:[NSDate dateWithTimeIntervalSinceNow:2]]; 299 300 [offeringExpectations expectICEConnectionChange:RTCICEConnectionClosed]; 301 [answeringExpectations expectICEConnectionChange:RTCICEConnectionClosed]; 302 [offeringExpectations expectSignalingChange:RTCSignalingClosed]; 303 [answeringExpectations expectSignalingChange:RTCSignalingClosed]; 304 305 [pcOffer close]; 306 [pcAnswer close]; 307 308 EXPECT_TRUE( 309 [offeringExpectations waitForAllExpectationsToBeSatisfiedWithTimeout: 310 kRTCPeerConnectionTestTimeout]); 311 EXPECT_TRUE( 312 [answeringExpectations waitForAllExpectationsToBeSatisfiedWithTimeout: 313 kRTCPeerConnectionTestTimeout]); 314 315 capturer = nil; 316 videoSource = nil; 317 pcOffer = nil; 318 pcAnswer = nil; 319 // TODO(fischman): be stricter about shutdown checks; ensure thread 320 // counts return to where they were before the test kicked off, and 321 // that all objects have in fact shut down. 322} 323 324@end 325 326// TODO(fischman): move {Initialize,Cleanup}SSL into alloc/dealloc of 327// RTCPeerConnectionTest and avoid the appearance of RTCPeerConnectionTest being 328// a TestBase since it's not. 329TEST(RTCPeerConnectionTest, SessionTest) { 330 @autoreleasepool { 331 rtc::InitializeSSL(); 332 // Since |factory| will own the signaling & worker threads, it's important 333 // that it outlive the created PeerConnections since they self-delete on the 334 // signaling thread, and if |factory| is freed first then a last refcount on 335 // the factory will expire during this teardown, causing the signaling 336 // thread to try to Join() with itself. This is a hack to ensure that the 337 // factory outlives RTCPeerConnection:dealloc. 338 // See https://code.google.com/p/webrtc/issues/detail?id=3100. 339 RTCPeerConnectionFactory* factory = [[RTCPeerConnectionFactory alloc] init]; 340 @autoreleasepool { 341 RTCPeerConnectionTest* pcTest = [[RTCPeerConnectionTest alloc] init]; 342 [pcTest testCompleteSessionWithFactory:factory]; 343 } 344 rtc::CleanupSSL(); 345 } 346} 347