1// +build !js 2 3package webrtc 4 5import ( 6 "crypto/ecdsa" 7 "crypto/elliptic" 8 "crypto/rand" 9 "crypto/x509" 10 "fmt" 11 "math/big" 12 "reflect" 13 "regexp" 14 "testing" 15 "time" 16 17 "github.com/pion/ice" 18 "github.com/pion/transport/test" 19 "github.com/pion/webrtc/v2/pkg/rtcerr" 20 "github.com/stretchr/testify/assert" 21) 22 23// newPair creates two new peer connections (an offerer and an answerer) using 24// the api. 25func (api *API) newPair() (pcOffer *PeerConnection, pcAnswer *PeerConnection, err error) { 26 pca, err := api.NewPeerConnection(Configuration{}) 27 if err != nil { 28 return nil, nil, err 29 } 30 31 pcb, err := api.NewPeerConnection(Configuration{}) 32 if err != nil { 33 return nil, nil, err 34 } 35 36 return pca, pcb, nil 37} 38 39func TestNew_Go(t *testing.T) { 40 report := test.CheckRoutines(t) 41 defer report() 42 43 api := NewAPI() 44 t.Run("Success", func(t *testing.T) { 45 secretKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) 46 assert.Nil(t, err) 47 48 certificate, err := GenerateCertificate(secretKey) 49 assert.Nil(t, err) 50 51 pc, err := api.NewPeerConnection(Configuration{ 52 ICEServers: []ICEServer{ 53 { 54 URLs: []string{ 55 "stun:stun.l.google.com:19302", 56 "turns:google.de?transport=tcp", 57 }, 58 Username: "unittest", 59 Credential: OAuthCredential{ 60 MACKey: "WmtzanB3ZW9peFhtdm42NzUzNG0=", 61 AccessToken: "AAwg3kPHWPfvk9bDFL936wYvkoctMADzQ==", 62 }, 63 CredentialType: ICECredentialTypeOauth, 64 }, 65 }, 66 ICETransportPolicy: ICETransportPolicyRelay, 67 BundlePolicy: BundlePolicyMaxCompat, 68 RTCPMuxPolicy: RTCPMuxPolicyNegotiate, 69 PeerIdentity: "unittest", 70 Certificates: []Certificate{*certificate}, 71 ICECandidatePoolSize: 5, 72 }) 73 assert.Nil(t, err) 74 assert.NotNil(t, pc) 75 assert.NoError(t, pc.Close()) 76 }) 77 t.Run("Failure", func(t *testing.T) { 78 testCases := []struct { 79 initialize func() (*PeerConnection, error) 80 expectedErr error 81 }{ 82 {func() (*PeerConnection, error) { 83 secretKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) 84 assert.Nil(t, err) 85 86 certificate, err := NewCertificate(secretKey, x509.Certificate{ 87 Version: 2, 88 SerialNumber: big.NewInt(1653), 89 NotBefore: time.Now().AddDate(0, -2, 0), 90 NotAfter: time.Now().AddDate(0, -1, 0), 91 }) 92 assert.Nil(t, err) 93 94 return api.NewPeerConnection(Configuration{ 95 Certificates: []Certificate{*certificate}, 96 }) 97 }, &rtcerr.InvalidAccessError{Err: ErrCertificateExpired}}, 98 {func() (*PeerConnection, error) { 99 return api.NewPeerConnection(Configuration{ 100 ICEServers: []ICEServer{ 101 { 102 URLs: []string{ 103 "stun:stun.l.google.com:19302", 104 "turns:google.de?transport=tcp", 105 }, 106 Username: "unittest", 107 }, 108 }, 109 }) 110 }, &rtcerr.InvalidAccessError{Err: ErrNoTurnCredentials}}, 111 } 112 113 for i, testCase := range testCases { 114 pc, err := testCase.initialize() 115 assert.EqualError(t, err, testCase.expectedErr.Error(), 116 "testCase: %d %v", i, testCase, 117 ) 118 if pc != nil { 119 assert.NoError(t, pc.Close()) 120 } 121 } 122 }) 123} 124 125func TestPeerConnection_SetConfiguration_Go(t *testing.T) { 126 // Note: this test includes all SetConfiguration features that are supported 127 // by Go but not the WASM bindings, namely: ICEServer.Credential, 128 // ICEServer.CredentialType, and Certificates. 129 report := test.CheckRoutines(t) 130 defer report() 131 132 api := NewAPI() 133 134 secretKey1, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) 135 assert.Nil(t, err) 136 137 certificate1, err := GenerateCertificate(secretKey1) 138 assert.Nil(t, err) 139 140 secretKey2, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) 141 assert.Nil(t, err) 142 143 certificate2, err := GenerateCertificate(secretKey2) 144 assert.Nil(t, err) 145 146 for _, test := range []struct { 147 name string 148 init func() (*PeerConnection, error) 149 config Configuration 150 wantErr error 151 }{ 152 { 153 name: "valid", 154 init: func() (*PeerConnection, error) { 155 pc, err := api.NewPeerConnection(Configuration{ 156 PeerIdentity: "unittest", 157 Certificates: []Certificate{*certificate1}, 158 ICECandidatePoolSize: 5, 159 }) 160 if err != nil { 161 return pc, err 162 } 163 164 err = pc.SetConfiguration(Configuration{ 165 ICEServers: []ICEServer{ 166 { 167 URLs: []string{ 168 "stun:stun.l.google.com:19302", 169 "turns:google.de?transport=tcp", 170 }, 171 Username: "unittest", 172 Credential: OAuthCredential{ 173 MACKey: "WmtzanB3ZW9peFhtdm42NzUzNG0=", 174 AccessToken: "AAwg3kPHWPfvk9bDFL936wYvkoctMADzQ==", 175 }, 176 CredentialType: ICECredentialTypeOauth, 177 }, 178 }, 179 ICETransportPolicy: ICETransportPolicyAll, 180 BundlePolicy: BundlePolicyBalanced, 181 RTCPMuxPolicy: RTCPMuxPolicyRequire, 182 PeerIdentity: "unittest", 183 Certificates: []Certificate{*certificate1}, 184 ICECandidatePoolSize: 5, 185 }) 186 if err != nil { 187 return pc, err 188 } 189 190 return pc, nil 191 }, 192 config: Configuration{}, 193 wantErr: nil, 194 }, 195 { 196 name: "update multiple certificates", 197 init: func() (*PeerConnection, error) { 198 return api.NewPeerConnection(Configuration{}) 199 }, 200 config: Configuration{ 201 Certificates: []Certificate{*certificate1, *certificate2}, 202 }, 203 wantErr: &rtcerr.InvalidModificationError{Err: ErrModifyingCertificates}, 204 }, 205 { 206 name: "update certificate", 207 init: func() (*PeerConnection, error) { 208 return api.NewPeerConnection(Configuration{}) 209 }, 210 config: Configuration{ 211 Certificates: []Certificate{*certificate1}, 212 }, 213 wantErr: &rtcerr.InvalidModificationError{Err: ErrModifyingCertificates}, 214 }, 215 { 216 name: "update ICEServers, no TURN credentials", 217 init: func() (*PeerConnection, error) { 218 return NewPeerConnection(Configuration{}) 219 }, 220 config: Configuration{ 221 ICEServers: []ICEServer{ 222 { 223 URLs: []string{ 224 "stun:stun.l.google.com:19302", 225 "turns:google.de?transport=tcp", 226 }, 227 Username: "unittest", 228 }, 229 }, 230 }, 231 wantErr: &rtcerr.InvalidAccessError{Err: ErrNoTurnCredentials}, 232 }, 233 } { 234 pc, err := test.init() 235 if err != nil { 236 t.Errorf("SetConfiguration %q: init failed: %v", test.name, err) 237 } 238 239 err = pc.SetConfiguration(test.config) 240 if got, want := err, test.wantErr; !reflect.DeepEqual(got, want) { 241 t.Errorf("SetConfiguration %q: err = %v, want %v", test.name, got, want) 242 } 243 244 assert.NoError(t, pc.Close()) 245 } 246} 247 248// TODO - This unittest needs to be completed when CreateDataChannel is complete 249// func TestPeerConnection_CreateDataChannel(t *testing.T) { 250// pc, err := New(Configuration{}) 251// assert.Nil(t, err) 252// 253// _, err = pc.CreateDataChannel("data", &DataChannelInit{ 254// 255// }) 256// assert.Nil(t, err) 257// } 258 259func TestPeerConnection_EventHandlers_Go(t *testing.T) { 260 lim := test.TimeOut(time.Second * 5) 261 defer lim.Stop() 262 263 report := test.CheckRoutines(t) 264 defer report() 265 266 // Note: When testing the Go event handlers we peer into the state a bit more 267 // than what is possible for the environment agnostic (Go or WASM/JavaScript) 268 // EventHandlers test. 269 api := NewAPI() 270 pc, err := api.NewPeerConnection(Configuration{}) 271 assert.Nil(t, err) 272 273 onTrackCalled := make(chan struct{}) 274 onICEConnectionStateChangeCalled := make(chan struct{}) 275 onDataChannelCalled := make(chan struct{}) 276 277 // Verify that the noop case works 278 assert.NotPanics(t, func() { pc.onTrack(nil, nil) }) 279 assert.NotPanics(t, func() { pc.onICEConnectionStateChange(ice.ConnectionStateNew) }) 280 281 pc.OnTrack(func(t *Track, r *RTPReceiver) { 282 close(onTrackCalled) 283 }) 284 285 pc.OnICEConnectionStateChange(func(cs ICEConnectionState) { 286 close(onICEConnectionStateChangeCalled) 287 }) 288 289 pc.OnDataChannel(func(dc *DataChannel) { 290 // Questions: 291 // (1) How come this callback is made with dc being nil? 292 // (2) How come this callback is made without CreateDataChannel? 293 if dc != nil { 294 close(onDataChannelCalled) 295 } 296 }) 297 298 // Verify that the handlers deal with nil inputs 299 assert.NotPanics(t, func() { pc.onTrack(nil, nil) }) 300 assert.NotPanics(t, func() { go pc.onDataChannelHandler(nil) }) 301 302 // Verify that the set handlers are called 303 assert.NotPanics(t, func() { pc.onTrack(&Track{}, &RTPReceiver{}) }) 304 assert.NotPanics(t, func() { pc.onICEConnectionStateChange(ice.ConnectionStateNew) }) 305 assert.NotPanics(t, func() { go pc.onDataChannelHandler(&DataChannel{api: api}) }) 306 307 <-onTrackCalled 308 <-onICEConnectionStateChangeCalled 309 <-onDataChannelCalled 310 assert.NoError(t, pc.Close()) 311} 312 313// This test asserts that nothing deadlocks we try to shutdown when DTLS is in flight 314// We ensure that DTLS is in flight by removing the mux func for it, so all inbound DTLS is lost 315func TestPeerConnection_ShutdownNoDTLS(t *testing.T) { 316 lim := test.TimeOut(time.Second * 10) 317 defer lim.Stop() 318 319 report := test.CheckRoutines(t) 320 defer report() 321 322 api := NewAPI() 323 offerPC, answerPC, err := api.newPair() 324 if err != nil { 325 t.Fatal(err) 326 } 327 328 // Drop all incoming DTLS traffic 329 dropAllDTLS := func([]byte) bool { 330 return false 331 } 332 offerPC.dtlsTransport.dtlsMatcher = dropAllDTLS 333 answerPC.dtlsTransport.dtlsMatcher = dropAllDTLS 334 335 if err = signalPair(offerPC, answerPC); err != nil { 336 t.Fatal(err) 337 } 338 339 iceComplete := make(chan interface{}) 340 answerPC.OnICEConnectionStateChange(func(iceState ICEConnectionState) { 341 if iceState == ICEConnectionStateConnected { 342 time.Sleep(time.Second) // Give time for DTLS to start 343 344 select { 345 case <-iceComplete: 346 default: 347 close(iceComplete) 348 } 349 } 350 }) 351 352 <-iceComplete 353 assert.NoError(t, offerPC.Close()) 354 assert.NoError(t, answerPC.Close()) 355} 356 357func TestPeerConnection_PropertyGetters(t *testing.T) { 358 pc := &PeerConnection{ 359 currentLocalDescription: &SessionDescription{}, 360 pendingLocalDescription: &SessionDescription{}, 361 currentRemoteDescription: &SessionDescription{}, 362 pendingRemoteDescription: &SessionDescription{}, 363 signalingState: SignalingStateHaveLocalOffer, 364 iceConnectionState: ICEConnectionStateChecking, 365 connectionState: PeerConnectionStateConnecting, 366 } 367 368 assert.Equal(t, pc.currentLocalDescription, pc.CurrentLocalDescription(), "should match") 369 assert.Equal(t, pc.pendingLocalDescription, pc.PendingLocalDescription(), "should match") 370 assert.Equal(t, pc.currentRemoteDescription, pc.CurrentRemoteDescription(), "should match") 371 assert.Equal(t, pc.pendingRemoteDescription, pc.PendingRemoteDescription(), "should match") 372 assert.Equal(t, pc.signalingState, pc.SignalingState(), "should match") 373 assert.Equal(t, pc.iceConnectionState, pc.ICEConnectionState(), "should match") 374 assert.Equal(t, pc.connectionState, pc.ConnectionState(), "should match") 375} 376 377func TestPeerConnection_AnswerWithoutOffer(t *testing.T) { 378 report := test.CheckRoutines(t) 379 defer report() 380 381 pc, err := NewPeerConnection(Configuration{}) 382 if err != nil { 383 t.Errorf("New PeerConnection: got error: %v", err) 384 } 385 _, err = pc.CreateAnswer(nil) 386 if !reflect.DeepEqual(&rtcerr.InvalidStateError{Err: ErrNoRemoteDescription}, err) { 387 t.Errorf("CreateAnswer without RemoteDescription: got error: %v", err) 388 } 389 390 assert.NoError(t, pc.Close()) 391} 392 393func TestPeerConnection_satisfyTypeAndDirection(t *testing.T) { 394 createTransceiver := func(kind RTPCodecType, direction RTPTransceiverDirection) *RTPTransceiver { 395 return &RTPTransceiver{kind: kind, Direction: direction} 396 } 397 398 for _, test := range []struct { 399 name string 400 401 kinds []RTPCodecType 402 directions []RTPTransceiverDirection 403 404 localTransceivers []*RTPTransceiver 405 want []*RTPTransceiver 406 }{ 407 { 408 "Audio and Video Transceivers can not satisfy each other", 409 []RTPCodecType{RTPCodecTypeVideo}, 410 []RTPTransceiverDirection{RTPTransceiverDirectionSendrecv}, 411 []*RTPTransceiver{createTransceiver(RTPCodecTypeAudio, RTPTransceiverDirectionSendrecv)}, 412 []*RTPTransceiver{createTransceiver(RTPCodecTypeVideo, RTPTransceiverDirectionInactive)}, 413 }, 414 { 415 "No local Transceivers, every remote should get an inactive", 416 []RTPCodecType{RTPCodecTypeVideo, RTPCodecTypeAudio, RTPCodecTypeVideo, RTPCodecTypeVideo}, 417 []RTPTransceiverDirection{RTPTransceiverDirectionSendrecv, RTPTransceiverDirectionRecvonly, RTPTransceiverDirectionSendonly, RTPTransceiverDirectionInactive}, 418 419 []*RTPTransceiver{}, 420 421 []*RTPTransceiver{ 422 createTransceiver(RTPCodecTypeVideo, RTPTransceiverDirectionInactive), 423 createTransceiver(RTPCodecTypeAudio, RTPTransceiverDirectionInactive), 424 createTransceiver(RTPCodecTypeVideo, RTPTransceiverDirectionInactive), 425 createTransceiver(RTPCodecTypeVideo, RTPTransceiverDirectionInactive), 426 }, 427 }, 428 { 429 "Local Recv can satisfy remote SendRecv", 430 []RTPCodecType{RTPCodecTypeVideo}, 431 []RTPTransceiverDirection{RTPTransceiverDirectionSendrecv}, 432 433 []*RTPTransceiver{createTransceiver(RTPCodecTypeVideo, RTPTransceiverDirectionRecvonly)}, 434 435 []*RTPTransceiver{createTransceiver(RTPCodecTypeVideo, RTPTransceiverDirectionRecvonly)}, 436 }, 437 { 438 "Don't satisfy a Sendonly with a SendRecv, later SendRecv will be marked as Inactive", 439 []RTPCodecType{RTPCodecTypeVideo, RTPCodecTypeVideo}, 440 []RTPTransceiverDirection{RTPTransceiverDirectionSendonly, RTPTransceiverDirectionSendrecv}, 441 442 []*RTPTransceiver{ 443 createTransceiver(RTPCodecTypeVideo, RTPTransceiverDirectionSendrecv), 444 createTransceiver(RTPCodecTypeVideo, RTPTransceiverDirectionRecvonly), 445 }, 446 447 []*RTPTransceiver{ 448 createTransceiver(RTPCodecTypeVideo, RTPTransceiverDirectionRecvonly), 449 createTransceiver(RTPCodecTypeVideo, RTPTransceiverDirectionSendrecv), 450 }, 451 }, 452 } { 453 if len(test.kinds) != len(test.directions) { 454 t.Fatal("Kinds and Directions must be the same length") 455 } 456 457 got := []*RTPTransceiver{} 458 for i := range test.kinds { 459 res, filteredLocalTransceivers := satisfyTypeAndDirection(test.kinds[i], test.directions[i], test.localTransceivers) 460 461 got = append(got, res) 462 test.localTransceivers = filteredLocalTransceivers 463 } 464 465 if !reflect.DeepEqual(got, test.want) { 466 gotStr := "" 467 for _, t := range got { 468 gotStr += fmt.Sprintf("%+v\n", t) 469 } 470 471 wantStr := "" 472 for _, t := range test.want { 473 wantStr += fmt.Sprintf("%+v\n", t) 474 } 475 t.Errorf("satisfyTypeAndDirection %q: \ngot\n%s \nwant\n%s", test.name, gotStr, wantStr) 476 } 477 } 478} 479 480func TestOneAttrKeyConnectionSetupPerMediaDescriptionInSDP(t *testing.T) { 481 pc, err := NewPeerConnection(Configuration{}) 482 assert.NoError(t, err) 483 484 _, err = pc.AddTransceiver(RTPCodecTypeVideo) 485 assert.NoError(t, err) 486 487 _, err = pc.AddTransceiver(RTPCodecTypeAudio) 488 assert.NoError(t, err) 489 490 _, err = pc.AddTransceiver(RTPCodecTypeAudio) 491 assert.NoError(t, err) 492 493 _, err = pc.AddTransceiver(RTPCodecTypeVideo) 494 assert.NoError(t, err) 495 496 sdp, err := pc.CreateOffer(nil) 497 assert.NoError(t, err) 498 499 re := regexp.MustCompile(`a=setup:[[:alpha:]]+`) 500 501 matches := re.FindAllStringIndex(sdp.SDP, -1) 502 503 // 5 because a datachannel is always added 504 assert.Len(t, matches, 5) 505 assert.NoError(t, pc.Close()) 506} 507 508// Assert that candidates are gathered by calling SetLocalDescription, not SetRemoteDescription 509// When trickle in on by default we can move this to peerconnection_test.go 510func TestGatherOnSetLocalDescription(t *testing.T) { 511 lim := test.TimeOut(time.Second * 30) 512 defer lim.Stop() 513 514 report := test.CheckRoutines(t) 515 defer report() 516 517 pcOfferGathered := make(chan SessionDescription) 518 pcAnswerGathered := make(chan SessionDescription) 519 520 s := SettingEngine{} 521 s.SetTrickle(true) 522 api := NewAPI(WithSettingEngine(s)) 523 524 pcOffer, err := api.NewPeerConnection(Configuration{}) 525 if err != nil { 526 t.Error(err.Error()) 527 } 528 529 // We need to create a data channel in order to trigger ICE 530 if _, err = pcOffer.CreateDataChannel("initial_data_channel", nil); err != nil { 531 t.Error(err.Error()) 532 } 533 534 pcOffer.OnICECandidate(func(i *ICECandidate) { 535 if i == nil { 536 close(pcOfferGathered) 537 } 538 }) 539 540 offer, err := pcOffer.CreateOffer(nil) 541 if err != nil { 542 t.Error(err.Error()) 543 } else if err = pcOffer.SetLocalDescription(offer); err != nil { 544 t.Error(err.Error()) 545 } 546 547 <-pcOfferGathered 548 549 pcAnswer, err := api.NewPeerConnection(Configuration{}) 550 if err != nil { 551 t.Error(err.Error()) 552 } 553 554 pcAnswer.OnICECandidate(func(i *ICECandidate) { 555 if i == nil { 556 close(pcAnswerGathered) 557 } 558 }) 559 560 if err = pcAnswer.SetRemoteDescription(offer); err != nil { 561 t.Error(err.Error()) 562 } 563 564 select { 565 case <-pcAnswerGathered: 566 t.Fatal("pcAnswer started gathering with no SetLocalDescription") 567 // Gathering is async, not sure of a better way to catch this currently 568 case <-time.After(3 * time.Second): 569 } 570 571 answer, err := pcAnswer.CreateAnswer(nil) 572 if err != nil { 573 t.Error(err.Error()) 574 } else if err = pcAnswer.SetLocalDescription(answer); err != nil { 575 t.Error(err.Error()) 576 } 577 <-pcAnswerGathered 578 assert.NoError(t, pcOffer.Close()) 579 assert.NoError(t, pcAnswer.Close()) 580} 581 582func TestPeerConnection_OfferingLite(t *testing.T) { 583 report := test.CheckRoutines(t) 584 defer report() 585 586 s := SettingEngine{} 587 s.SetLite(true) 588 offerPC, err := NewAPI(WithSettingEngine(s)).NewPeerConnection(Configuration{}) 589 if err != nil { 590 t.Fatal(err) 591 } 592 593 answerPC, err := NewAPI().NewPeerConnection(Configuration{}) 594 if err != nil { 595 t.Fatal(err) 596 } 597 598 if err = signalPair(offerPC, answerPC); err != nil { 599 t.Fatal(err) 600 } 601 602 iceComplete := make(chan interface{}) 603 answerPC.OnICEConnectionStateChange(func(iceState ICEConnectionState) { 604 if iceState == ICEConnectionStateConnected { 605 select { 606 case <-iceComplete: 607 default: 608 close(iceComplete) 609 } 610 } 611 }) 612 613 <-iceComplete 614 assert.NoError(t, offerPC.Close()) 615 assert.NoError(t, answerPC.Close()) 616} 617 618func TestPeerConnection_AnsweringLite(t *testing.T) { 619 report := test.CheckRoutines(t) 620 defer report() 621 622 offerPC, err := NewAPI().NewPeerConnection(Configuration{}) 623 if err != nil { 624 t.Fatal(err) 625 } 626 627 s := SettingEngine{} 628 s.SetLite(true) 629 answerPC, err := NewAPI(WithSettingEngine(s)).NewPeerConnection(Configuration{}) 630 if err != nil { 631 t.Fatal(err) 632 } 633 634 if err = signalPair(offerPC, answerPC); err != nil { 635 t.Fatal(err) 636 } 637 638 iceComplete := make(chan interface{}) 639 answerPC.OnICEConnectionStateChange(func(iceState ICEConnectionState) { 640 if iceState == ICEConnectionStateConnected { 641 select { 642 case <-iceComplete: 643 default: 644 close(iceComplete) 645 } 646 } 647 }) 648 649 <-iceComplete 650 assert.NoError(t, offerPC.Close()) 651 assert.NoError(t, answerPC.Close()) 652} 653