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