1// +build !js 2 3package webrtc 4 5import ( 6 "bufio" 7 "context" 8 "crypto/ecdsa" 9 "crypto/elliptic" 10 "crypto/rand" 11 "crypto/x509" 12 "fmt" 13 "math/big" 14 "reflect" 15 "regexp" 16 "strings" 17 "sync" 18 "testing" 19 "time" 20 21 "github.com/pion/ice/v2" 22 "github.com/pion/rtp" 23 "github.com/pion/transport/test" 24 "github.com/pion/transport/vnet" 25 "github.com/pion/webrtc/v3/internal/util" 26 "github.com/pion/webrtc/v3/pkg/rtcerr" 27 "github.com/stretchr/testify/assert" 28) 29 30// newPair creates two new peer connections (an offerer and an answerer) using 31// the api. 32func (api *API) newPair(cfg Configuration) (pcOffer *PeerConnection, pcAnswer *PeerConnection, err error) { 33 pca, err := api.NewPeerConnection(cfg) 34 if err != nil { 35 return nil, nil, err 36 } 37 38 pcb, err := api.NewPeerConnection(cfg) 39 if err != nil { 40 return nil, nil, err 41 } 42 43 return pca, pcb, nil 44} 45 46func TestNew_Go(t *testing.T) { 47 report := test.CheckRoutines(t) 48 defer report() 49 50 api := NewAPI() 51 t.Run("Success", func(t *testing.T) { 52 secretKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) 53 assert.Nil(t, err) 54 55 certificate, err := GenerateCertificate(secretKey) 56 assert.Nil(t, err) 57 58 pc, err := api.NewPeerConnection(Configuration{ 59 ICEServers: []ICEServer{ 60 { 61 URLs: []string{ 62 "stun:stun.l.google.com:19302", 63 "turns:google.de?transport=tcp", 64 }, 65 Username: "unittest", 66 Credential: OAuthCredential{ 67 MACKey: "WmtzanB3ZW9peFhtdm42NzUzNG0=", 68 AccessToken: "AAwg3kPHWPfvk9bDFL936wYvkoctMADzQ==", 69 }, 70 CredentialType: ICECredentialTypeOauth, 71 }, 72 }, 73 ICETransportPolicy: ICETransportPolicyRelay, 74 BundlePolicy: BundlePolicyMaxCompat, 75 RTCPMuxPolicy: RTCPMuxPolicyNegotiate, 76 PeerIdentity: "unittest", 77 Certificates: []Certificate{*certificate}, 78 ICECandidatePoolSize: 5, 79 }) 80 assert.Nil(t, err) 81 assert.NotNil(t, pc) 82 assert.NoError(t, pc.Close()) 83 }) 84 t.Run("Failure", func(t *testing.T) { 85 testCases := []struct { 86 initialize func() (*PeerConnection, error) 87 expectedErr error 88 }{ 89 {func() (*PeerConnection, error) { 90 secretKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) 91 assert.Nil(t, err) 92 93 certificate, err := NewCertificate(secretKey, x509.Certificate{ 94 Version: 2, 95 SerialNumber: big.NewInt(1653), 96 NotBefore: time.Now().AddDate(0, -2, 0), 97 NotAfter: time.Now().AddDate(0, -1, 0), 98 }) 99 assert.Nil(t, err) 100 101 return api.NewPeerConnection(Configuration{ 102 Certificates: []Certificate{*certificate}, 103 }) 104 }, &rtcerr.InvalidAccessError{Err: ErrCertificateExpired}}, 105 {func() (*PeerConnection, error) { 106 return api.NewPeerConnection(Configuration{ 107 ICEServers: []ICEServer{ 108 { 109 URLs: []string{ 110 "stun:stun.l.google.com:19302", 111 "turns:google.de?transport=tcp", 112 }, 113 Username: "unittest", 114 }, 115 }, 116 }) 117 }, &rtcerr.InvalidAccessError{Err: ErrNoTurnCredentials}}, 118 } 119 120 for i, testCase := range testCases { 121 pc, err := testCase.initialize() 122 assert.EqualError(t, err, testCase.expectedErr.Error(), 123 "testCase: %d %v", i, testCase, 124 ) 125 if pc != nil { 126 assert.NoError(t, pc.Close()) 127 } 128 } 129 }) 130 t.Run("ICEServers_Copy", func(t *testing.T) { 131 const expectedURL = "stun:stun.l.google.com:19302?foo=bar" 132 const expectedUsername = "username" 133 const expectedPassword = "password" 134 135 cfg := Configuration{ 136 ICEServers: []ICEServer{ 137 { 138 URLs: []string{expectedURL}, 139 Username: expectedUsername, 140 Credential: expectedPassword, 141 }, 142 }, 143 } 144 pc, err := api.NewPeerConnection(cfg) 145 assert.NoError(t, err) 146 assert.NotNil(t, pc) 147 148 pc.configuration.ICEServers[0].Username = util.MathRandAlpha(15) // Tests doesn't need crypto random 149 pc.configuration.ICEServers[0].Credential = util.MathRandAlpha(15) 150 pc.configuration.ICEServers[0].URLs[0] = util.MathRandAlpha(15) 151 152 assert.Equal(t, expectedUsername, cfg.ICEServers[0].Username) 153 assert.Equal(t, expectedPassword, cfg.ICEServers[0].Credential) 154 assert.Equal(t, expectedURL, cfg.ICEServers[0].URLs[0]) 155 156 assert.NoError(t, pc.Close()) 157 }) 158} 159 160func TestPeerConnection_SetConfiguration_Go(t *testing.T) { 161 // Note: this test includes all SetConfiguration features that are supported 162 // by Go but not the WASM bindings, namely: ICEServer.Credential, 163 // ICEServer.CredentialType, and Certificates. 164 report := test.CheckRoutines(t) 165 defer report() 166 167 api := NewAPI() 168 169 secretKey1, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) 170 assert.Nil(t, err) 171 172 certificate1, err := GenerateCertificate(secretKey1) 173 assert.Nil(t, err) 174 175 secretKey2, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) 176 assert.Nil(t, err) 177 178 certificate2, err := GenerateCertificate(secretKey2) 179 assert.Nil(t, err) 180 181 for _, test := range []struct { 182 name string 183 init func() (*PeerConnection, error) 184 config Configuration 185 wantErr error 186 }{ 187 { 188 name: "valid", 189 init: func() (*PeerConnection, error) { 190 pc, err := api.NewPeerConnection(Configuration{ 191 PeerIdentity: "unittest", 192 Certificates: []Certificate{*certificate1}, 193 ICECandidatePoolSize: 5, 194 }) 195 if err != nil { 196 return pc, err 197 } 198 199 err = pc.SetConfiguration(Configuration{ 200 ICEServers: []ICEServer{ 201 { 202 URLs: []string{ 203 "stun:stun.l.google.com:19302", 204 "turns:google.de?transport=tcp", 205 }, 206 Username: "unittest", 207 Credential: OAuthCredential{ 208 MACKey: "WmtzanB3ZW9peFhtdm42NzUzNG0=", 209 AccessToken: "AAwg3kPHWPfvk9bDFL936wYvkoctMADzQ==", 210 }, 211 CredentialType: ICECredentialTypeOauth, 212 }, 213 }, 214 ICETransportPolicy: ICETransportPolicyAll, 215 BundlePolicy: BundlePolicyBalanced, 216 RTCPMuxPolicy: RTCPMuxPolicyRequire, 217 PeerIdentity: "unittest", 218 Certificates: []Certificate{*certificate1}, 219 ICECandidatePoolSize: 5, 220 }) 221 if err != nil { 222 return pc, err 223 } 224 225 return pc, nil 226 }, 227 config: Configuration{}, 228 wantErr: nil, 229 }, 230 { 231 name: "update multiple certificates", 232 init: func() (*PeerConnection, error) { 233 return api.NewPeerConnection(Configuration{}) 234 }, 235 config: Configuration{ 236 Certificates: []Certificate{*certificate1, *certificate2}, 237 }, 238 wantErr: &rtcerr.InvalidModificationError{Err: ErrModifyingCertificates}, 239 }, 240 { 241 name: "update certificate", 242 init: func() (*PeerConnection, error) { 243 return api.NewPeerConnection(Configuration{}) 244 }, 245 config: Configuration{ 246 Certificates: []Certificate{*certificate1}, 247 }, 248 wantErr: &rtcerr.InvalidModificationError{Err: ErrModifyingCertificates}, 249 }, 250 { 251 name: "update ICEServers, no TURN credentials", 252 init: func() (*PeerConnection, error) { 253 return NewPeerConnection(Configuration{}) 254 }, 255 config: Configuration{ 256 ICEServers: []ICEServer{ 257 { 258 URLs: []string{ 259 "stun:stun.l.google.com:19302", 260 "turns:google.de?transport=tcp", 261 }, 262 Username: "unittest", 263 }, 264 }, 265 }, 266 wantErr: &rtcerr.InvalidAccessError{Err: ErrNoTurnCredentials}, 267 }, 268 } { 269 pc, err := test.init() 270 if err != nil { 271 t.Errorf("SetConfiguration %q: init failed: %v", test.name, err) 272 } 273 274 err = pc.SetConfiguration(test.config) 275 if got, want := err, test.wantErr; !reflect.DeepEqual(got, want) { 276 t.Errorf("SetConfiguration %q: err = %v, want %v", test.name, got, want) 277 } 278 279 assert.NoError(t, pc.Close()) 280 } 281} 282 283func TestPeerConnection_EventHandlers_Go(t *testing.T) { 284 lim := test.TimeOut(time.Second * 5) 285 defer lim.Stop() 286 287 report := test.CheckRoutines(t) 288 defer report() 289 290 // Note: When testing the Go event handlers we peer into the state a bit more 291 // than what is possible for the environment agnostic (Go or WASM/JavaScript) 292 // EventHandlers test. 293 api := NewAPI() 294 pc, err := api.NewPeerConnection(Configuration{}) 295 assert.Nil(t, err) 296 297 onTrackCalled := make(chan struct{}) 298 onICEConnectionStateChangeCalled := make(chan struct{}) 299 onDataChannelCalled := make(chan struct{}) 300 301 // Verify that the noop case works 302 assert.NotPanics(t, func() { pc.onTrack(nil, nil) }) 303 assert.NotPanics(t, func() { pc.onICEConnectionStateChange(ice.ConnectionStateNew) }) 304 305 pc.OnTrack(func(t *TrackRemote, r *RTPReceiver) { 306 close(onTrackCalled) 307 }) 308 309 pc.OnICEConnectionStateChange(func(cs ICEConnectionState) { 310 close(onICEConnectionStateChangeCalled) 311 }) 312 313 pc.OnDataChannel(func(dc *DataChannel) { 314 // Questions: 315 // (1) How come this callback is made with dc being nil? 316 // (2) How come this callback is made without CreateDataChannel? 317 if dc != nil { 318 close(onDataChannelCalled) 319 } 320 }) 321 322 // Verify that the handlers deal with nil inputs 323 assert.NotPanics(t, func() { pc.onTrack(nil, nil) }) 324 assert.NotPanics(t, func() { go pc.onDataChannelHandler(nil) }) 325 326 // Verify that the set handlers are called 327 assert.NotPanics(t, func() { pc.onTrack(&TrackRemote{}, &RTPReceiver{}) }) 328 assert.NotPanics(t, func() { pc.onICEConnectionStateChange(ice.ConnectionStateNew) }) 329 assert.NotPanics(t, func() { go pc.onDataChannelHandler(&DataChannel{api: api}) }) 330 331 <-onTrackCalled 332 <-onICEConnectionStateChangeCalled 333 <-onDataChannelCalled 334 assert.NoError(t, pc.Close()) 335} 336 337// This test asserts that nothing deadlocks we try to shutdown when DTLS is in flight 338// We ensure that DTLS is in flight by removing the mux func for it, so all inbound DTLS is lost 339func TestPeerConnection_ShutdownNoDTLS(t *testing.T) { 340 lim := test.TimeOut(time.Second * 10) 341 defer lim.Stop() 342 343 report := test.CheckRoutines(t) 344 defer report() 345 346 api := NewAPI() 347 offerPC, answerPC, err := api.newPair(Configuration{}) 348 if err != nil { 349 t.Fatal(err) 350 } 351 352 // Drop all incoming DTLS traffic 353 dropAllDTLS := func([]byte) bool { 354 return false 355 } 356 offerPC.dtlsTransport.dtlsMatcher = dropAllDTLS 357 answerPC.dtlsTransport.dtlsMatcher = dropAllDTLS 358 359 if err = signalPair(offerPC, answerPC); err != nil { 360 t.Fatal(err) 361 } 362 363 iceComplete := make(chan interface{}) 364 answerPC.OnICEConnectionStateChange(func(iceState ICEConnectionState) { 365 if iceState == ICEConnectionStateConnected { 366 time.Sleep(time.Second) // Give time for DTLS to start 367 368 select { 369 case <-iceComplete: 370 default: 371 close(iceComplete) 372 } 373 } 374 }) 375 376 <-iceComplete 377 closePairNow(t, offerPC, answerPC) 378} 379 380func TestPeerConnection_PropertyGetters(t *testing.T) { 381 pc := &PeerConnection{ 382 currentLocalDescription: &SessionDescription{}, 383 pendingLocalDescription: &SessionDescription{}, 384 currentRemoteDescription: &SessionDescription{}, 385 pendingRemoteDescription: &SessionDescription{}, 386 signalingState: SignalingStateHaveLocalOffer, 387 iceConnectionState: ICEConnectionStateChecking, 388 connectionState: PeerConnectionStateConnecting, 389 } 390 391 assert.Equal(t, pc.currentLocalDescription, pc.CurrentLocalDescription(), "should match") 392 assert.Equal(t, pc.pendingLocalDescription, pc.PendingLocalDescription(), "should match") 393 assert.Equal(t, pc.currentRemoteDescription, pc.CurrentRemoteDescription(), "should match") 394 assert.Equal(t, pc.pendingRemoteDescription, pc.PendingRemoteDescription(), "should match") 395 assert.Equal(t, pc.signalingState, pc.SignalingState(), "should match") 396 assert.Equal(t, pc.iceConnectionState, pc.ICEConnectionState(), "should match") 397 assert.Equal(t, pc.connectionState, pc.ConnectionState(), "should match") 398} 399 400func TestPeerConnection_AnswerWithoutOffer(t *testing.T) { 401 report := test.CheckRoutines(t) 402 defer report() 403 404 pc, err := NewPeerConnection(Configuration{}) 405 if err != nil { 406 t.Errorf("New PeerConnection: got error: %v", err) 407 } 408 _, err = pc.CreateAnswer(nil) 409 if !reflect.DeepEqual(&rtcerr.InvalidStateError{Err: ErrNoRemoteDescription}, err) { 410 t.Errorf("CreateAnswer without RemoteDescription: got error: %v", err) 411 } 412 413 assert.NoError(t, pc.Close()) 414} 415 416func TestPeerConnection_AnswerWithClosedConnection(t *testing.T) { 417 report := test.CheckRoutines(t) 418 defer report() 419 420 offerPeerConn, answerPeerConn, err := newPair() 421 assert.NoError(t, err) 422 423 inChecking, inCheckingCancel := context.WithCancel(context.Background()) 424 answerPeerConn.OnICEConnectionStateChange(func(i ICEConnectionState) { 425 if i == ICEConnectionStateChecking { 426 inCheckingCancel() 427 } 428 }) 429 430 _, err = offerPeerConn.CreateDataChannel("test-channel", nil) 431 assert.NoError(t, err) 432 433 offer, err := offerPeerConn.CreateOffer(nil) 434 assert.NoError(t, err) 435 assert.NoError(t, offerPeerConn.SetLocalDescription(offer)) 436 437 assert.NoError(t, offerPeerConn.Close()) 438 439 assert.NoError(t, answerPeerConn.SetRemoteDescription(offer)) 440 441 <-inChecking.Done() 442 assert.NoError(t, answerPeerConn.Close()) 443 444 _, err = answerPeerConn.CreateAnswer(nil) 445 assert.Error(t, err, &rtcerr.InvalidStateError{Err: ErrConnectionClosed}) 446} 447 448func TestPeerConnection_satisfyTypeAndDirection(t *testing.T) { 449 createTransceiver := func(kind RTPCodecType, direction RTPTransceiverDirection) *RTPTransceiver { 450 r := &RTPTransceiver{kind: kind} 451 r.setDirection(direction) 452 453 return r 454 } 455 456 for _, test := range []struct { 457 name string 458 459 kinds []RTPCodecType 460 directions []RTPTransceiverDirection 461 462 localTransceivers []*RTPTransceiver 463 want []*RTPTransceiver 464 }{ 465 { 466 "Audio and Video Transceivers can not satisfy each other", 467 []RTPCodecType{RTPCodecTypeVideo}, 468 []RTPTransceiverDirection{RTPTransceiverDirectionSendrecv}, 469 []*RTPTransceiver{createTransceiver(RTPCodecTypeAudio, RTPTransceiverDirectionSendrecv)}, 470 []*RTPTransceiver{nil}, 471 }, 472 { 473 "No local Transceivers, every remote should get nil", 474 []RTPCodecType{RTPCodecTypeVideo, RTPCodecTypeAudio, RTPCodecTypeVideo, RTPCodecTypeVideo}, 475 []RTPTransceiverDirection{RTPTransceiverDirectionSendrecv, RTPTransceiverDirectionRecvonly, RTPTransceiverDirectionSendonly, RTPTransceiverDirectionInactive}, 476 477 []*RTPTransceiver{}, 478 479 []*RTPTransceiver{ 480 nil, 481 nil, 482 nil, 483 nil, 484 }, 485 }, 486 { 487 "Local Recv can satisfy remote SendRecv", 488 []RTPCodecType{RTPCodecTypeVideo}, 489 []RTPTransceiverDirection{RTPTransceiverDirectionSendrecv}, 490 491 []*RTPTransceiver{createTransceiver(RTPCodecTypeVideo, RTPTransceiverDirectionRecvonly)}, 492 493 []*RTPTransceiver{createTransceiver(RTPCodecTypeVideo, RTPTransceiverDirectionRecvonly)}, 494 }, 495 { 496 "Don't satisfy a Sendonly with a SendRecv, later SendRecv will be marked as Inactive", 497 []RTPCodecType{RTPCodecTypeVideo, RTPCodecTypeVideo}, 498 []RTPTransceiverDirection{RTPTransceiverDirectionSendonly, RTPTransceiverDirectionSendrecv}, 499 500 []*RTPTransceiver{ 501 createTransceiver(RTPCodecTypeVideo, RTPTransceiverDirectionSendrecv), 502 createTransceiver(RTPCodecTypeVideo, RTPTransceiverDirectionRecvonly), 503 }, 504 505 []*RTPTransceiver{ 506 createTransceiver(RTPCodecTypeVideo, RTPTransceiverDirectionRecvonly), 507 createTransceiver(RTPCodecTypeVideo, RTPTransceiverDirectionSendrecv), 508 }, 509 }, 510 } { 511 if len(test.kinds) != len(test.directions) { 512 t.Fatal("Kinds and Directions must be the same length") 513 } 514 515 got := []*RTPTransceiver{} 516 for i := range test.kinds { 517 res, filteredLocalTransceivers := satisfyTypeAndDirection(test.kinds[i], test.directions[i], test.localTransceivers) 518 519 got = append(got, res) 520 test.localTransceivers = filteredLocalTransceivers 521 } 522 523 if !reflect.DeepEqual(got, test.want) { 524 gotStr := "" 525 for _, t := range got { 526 gotStr += fmt.Sprintf("%+v\n", t) 527 } 528 529 wantStr := "" 530 for _, t := range test.want { 531 wantStr += fmt.Sprintf("%+v\n", t) 532 } 533 t.Errorf("satisfyTypeAndDirection %q: \ngot\n%s \nwant\n%s", test.name, gotStr, wantStr) 534 } 535 } 536} 537 538func TestOneAttrKeyConnectionSetupPerMediaDescriptionInSDP(t *testing.T) { 539 pc, err := NewPeerConnection(Configuration{}) 540 assert.NoError(t, err) 541 542 _, err = pc.AddTransceiverFromKind(RTPCodecTypeVideo) 543 assert.NoError(t, err) 544 545 _, err = pc.AddTransceiverFromKind(RTPCodecTypeAudio) 546 assert.NoError(t, err) 547 548 _, err = pc.AddTransceiverFromKind(RTPCodecTypeAudio) 549 assert.NoError(t, err) 550 551 _, err = pc.AddTransceiverFromKind(RTPCodecTypeVideo) 552 assert.NoError(t, err) 553 554 sdp, err := pc.CreateOffer(nil) 555 assert.NoError(t, err) 556 557 re := regexp.MustCompile(`a=setup:[[:alpha:]]+`) 558 559 matches := re.FindAllStringIndex(sdp.SDP, -1) 560 561 assert.Len(t, matches, 4) 562 assert.NoError(t, pc.Close()) 563} 564 565func TestPeerConnection_OfferingLite(t *testing.T) { 566 report := test.CheckRoutines(t) 567 defer report() 568 569 lim := test.TimeOut(time.Second * 10) 570 defer lim.Stop() 571 572 s := SettingEngine{} 573 s.SetLite(true) 574 offerPC, err := NewAPI(WithSettingEngine(s)).NewPeerConnection(Configuration{}) 575 if err != nil { 576 t.Fatal(err) 577 } 578 579 answerPC, err := NewAPI().NewPeerConnection(Configuration{}) 580 if err != nil { 581 t.Fatal(err) 582 } 583 584 if err = signalPair(offerPC, answerPC); err != nil { 585 t.Fatal(err) 586 } 587 588 iceComplete := make(chan interface{}) 589 answerPC.OnICEConnectionStateChange(func(iceState ICEConnectionState) { 590 if iceState == ICEConnectionStateConnected { 591 select { 592 case <-iceComplete: 593 default: 594 close(iceComplete) 595 } 596 } 597 }) 598 599 <-iceComplete 600 closePairNow(t, offerPC, answerPC) 601} 602 603func TestPeerConnection_AnsweringLite(t *testing.T) { 604 report := test.CheckRoutines(t) 605 defer report() 606 607 lim := test.TimeOut(time.Second * 10) 608 defer lim.Stop() 609 610 offerPC, err := NewAPI().NewPeerConnection(Configuration{}) 611 if err != nil { 612 t.Fatal(err) 613 } 614 615 s := SettingEngine{} 616 s.SetLite(true) 617 answerPC, err := NewAPI(WithSettingEngine(s)).NewPeerConnection(Configuration{}) 618 if err != nil { 619 t.Fatal(err) 620 } 621 622 if err = signalPair(offerPC, answerPC); err != nil { 623 t.Fatal(err) 624 } 625 626 iceComplete := make(chan interface{}) 627 answerPC.OnICEConnectionStateChange(func(iceState ICEConnectionState) { 628 if iceState == ICEConnectionStateConnected { 629 select { 630 case <-iceComplete: 631 default: 632 close(iceComplete) 633 } 634 } 635 }) 636 637 <-iceComplete 638 closePairNow(t, offerPC, answerPC) 639} 640 641func TestOnICEGatheringStateChange(t *testing.T) { 642 seenGathering := &atomicBool{} 643 seenComplete := &atomicBool{} 644 645 seenGatheringAndComplete := make(chan interface{}) 646 seenClosed := make(chan interface{}) 647 648 peerConn, err := NewPeerConnection(Configuration{}) 649 assert.NoError(t, err) 650 651 var onStateChange func(s ICEGathererState) 652 onStateChange = func(s ICEGathererState) { 653 // Access to ICEGatherer in the callback must not cause dead lock. 654 peerConn.OnICEGatheringStateChange(onStateChange) 655 if state := peerConn.iceGatherer.State(); state != s { 656 t.Errorf("State change callback argument (%s) and State() (%s) result differs", 657 s, state, 658 ) 659 } 660 661 switch s { // nolint:exhaustive 662 case ICEGathererStateClosed: 663 close(seenClosed) 664 return 665 case ICEGathererStateGathering: 666 if seenComplete.get() { 667 t.Error("Completed before gathering") 668 } 669 seenGathering.set(true) 670 case ICEGathererStateComplete: 671 seenComplete.set(true) 672 } 673 674 if seenGathering.get() && seenComplete.get() { 675 close(seenGatheringAndComplete) 676 } 677 } 678 peerConn.OnICEGatheringStateChange(onStateChange) 679 680 offer, err := peerConn.CreateOffer(nil) 681 assert.NoError(t, err) 682 assert.NoError(t, peerConn.SetLocalDescription(offer)) 683 684 select { 685 case <-time.After(time.Second * 10): 686 t.Fatal("Gathering and Complete were never seen") 687 case <-seenClosed: 688 t.Fatal("Closed before PeerConnection Close") 689 case <-seenGatheringAndComplete: 690 } 691 692 assert.NoError(t, peerConn.Close()) 693 694 select { 695 case <-time.After(time.Second * 10): 696 t.Fatal("Closed was never seen") 697 case <-seenClosed: 698 } 699} 700 701// Assert Trickle ICE behaviors 702func TestPeerConnectionTrickle(t *testing.T) { 703 offerPC, answerPC, err := newPair() 704 assert.NoError(t, err) 705 706 _, err = offerPC.CreateDataChannel("test-channel", nil) 707 assert.NoError(t, err) 708 709 addOrCacheCandidate := func(pc *PeerConnection, c *ICECandidate, candidateCache []ICECandidateInit) []ICECandidateInit { 710 if c == nil { 711 return candidateCache 712 } 713 714 if pc.RemoteDescription() == nil { 715 return append(candidateCache, c.ToJSON()) 716 } 717 718 assert.NoError(t, pc.AddICECandidate(c.ToJSON())) 719 return candidateCache 720 } 721 722 candidateLock := sync.RWMutex{} 723 var offerCandidateDone, answerCandidateDone bool 724 725 cachedOfferCandidates := []ICECandidateInit{} 726 offerPC.OnICECandidate(func(c *ICECandidate) { 727 if offerCandidateDone { 728 t.Error("Received OnICECandidate after finishing gathering") 729 } 730 if c == nil { 731 offerCandidateDone = true 732 } 733 734 candidateLock.Lock() 735 defer candidateLock.Unlock() 736 737 cachedOfferCandidates = addOrCacheCandidate(answerPC, c, cachedOfferCandidates) 738 }) 739 740 cachedAnswerCandidates := []ICECandidateInit{} 741 answerPC.OnICECandidate(func(c *ICECandidate) { 742 if answerCandidateDone { 743 t.Error("Received OnICECandidate after finishing gathering") 744 } 745 if c == nil { 746 answerCandidateDone = true 747 } 748 749 candidateLock.Lock() 750 defer candidateLock.Unlock() 751 752 cachedAnswerCandidates = addOrCacheCandidate(offerPC, c, cachedAnswerCandidates) 753 }) 754 755 offerPCConnected, offerPCConnectedCancel := context.WithCancel(context.Background()) 756 offerPC.OnICEConnectionStateChange(func(i ICEConnectionState) { 757 if i == ICEConnectionStateConnected { 758 offerPCConnectedCancel() 759 } 760 }) 761 762 answerPCConnected, answerPCConnectedCancel := context.WithCancel(context.Background()) 763 answerPC.OnICEConnectionStateChange(func(i ICEConnectionState) { 764 if i == ICEConnectionStateConnected { 765 answerPCConnectedCancel() 766 } 767 }) 768 769 offer, err := offerPC.CreateOffer(nil) 770 assert.NoError(t, err) 771 772 assert.NoError(t, offerPC.SetLocalDescription(offer)) 773 assert.NoError(t, answerPC.SetRemoteDescription(offer)) 774 775 answer, err := answerPC.CreateAnswer(nil) 776 assert.NoError(t, err) 777 778 assert.NoError(t, answerPC.SetLocalDescription(answer)) 779 assert.NoError(t, offerPC.SetRemoteDescription(answer)) 780 781 candidateLock.Lock() 782 for _, c := range cachedAnswerCandidates { 783 assert.NoError(t, offerPC.AddICECandidate(c)) 784 } 785 for _, c := range cachedOfferCandidates { 786 assert.NoError(t, answerPC.AddICECandidate(c)) 787 } 788 candidateLock.Unlock() 789 790 <-answerPCConnected.Done() 791 <-offerPCConnected.Done() 792 closePairNow(t, offerPC, answerPC) 793} 794 795// Issue #1121, assert populateLocalCandidates doesn't mutate 796func TestPopulateLocalCandidates(t *testing.T) { 797 t.Run("PendingLocalDescription shouldn't add extra mutations", func(t *testing.T) { 798 pc, err := NewPeerConnection(Configuration{}) 799 assert.NoError(t, err) 800 801 offer, err := pc.CreateOffer(nil) 802 assert.NoError(t, err) 803 804 offerGatheringComplete := GatheringCompletePromise(pc) 805 assert.NoError(t, pc.SetLocalDescription(offer)) 806 <-offerGatheringComplete 807 808 assert.Equal(t, pc.PendingLocalDescription(), pc.PendingLocalDescription()) 809 assert.NoError(t, pc.Close()) 810 }) 811 812 t.Run("end-of-candidates only when gathering is complete", func(t *testing.T) { 813 pc, err := NewAPI().NewPeerConnection(Configuration{}) 814 assert.NoError(t, err) 815 816 _, err = pc.CreateDataChannel("test-channel", nil) 817 assert.NoError(t, err) 818 819 offer, err := pc.CreateOffer(nil) 820 assert.NoError(t, err) 821 assert.NotContains(t, offer.SDP, "a=candidate") 822 assert.NotContains(t, offer.SDP, "a=end-of-candidates") 823 824 offerGatheringComplete := GatheringCompletePromise(pc) 825 assert.NoError(t, pc.SetLocalDescription(offer)) 826 <-offerGatheringComplete 827 828 assert.Contains(t, pc.PendingLocalDescription().SDP, "a=candidate") 829 assert.Contains(t, pc.PendingLocalDescription().SDP, "a=end-of-candidates") 830 831 assert.NoError(t, pc.Close()) 832 }) 833} 834 835// Assert that two agents that only generate mDNS candidates can connect 836func TestMulticastDNSCandidates(t *testing.T) { 837 lim := test.TimeOut(time.Second * 30) 838 defer lim.Stop() 839 840 report := test.CheckRoutines(t) 841 defer report() 842 843 s := SettingEngine{} 844 s.SetICEMulticastDNSMode(ice.MulticastDNSModeQueryAndGather) 845 846 pcOffer, pcAnswer, err := NewAPI(WithSettingEngine(s)).newPair(Configuration{}) 847 assert.NoError(t, err) 848 849 assert.NoError(t, signalPair(pcOffer, pcAnswer)) 850 851 onDataChannel, onDataChannelCancel := context.WithCancel(context.Background()) 852 pcAnswer.OnDataChannel(func(d *DataChannel) { 853 onDataChannelCancel() 854 }) 855 <-onDataChannel.Done() 856 857 closePairNow(t, pcOffer, pcAnswer) 858} 859 860func TestICERestart(t *testing.T) { 861 extractCandidates := func(sdp string) (candidates []string) { 862 sc := bufio.NewScanner(strings.NewReader(sdp)) 863 for sc.Scan() { 864 if strings.HasPrefix(sc.Text(), "a=candidate:") { 865 candidates = append(candidates, sc.Text()) 866 } 867 } 868 869 return 870 } 871 872 lim := test.TimeOut(time.Second * 30) 873 defer lim.Stop() 874 875 report := test.CheckRoutines(t) 876 defer report() 877 878 offerPC, answerPC, err := newPair() 879 assert.NoError(t, err) 880 881 var connectedWaitGroup sync.WaitGroup 882 connectedWaitGroup.Add(2) 883 884 offerPC.OnICEConnectionStateChange(func(state ICEConnectionState) { 885 if state == ICEConnectionStateConnected { 886 connectedWaitGroup.Done() 887 } 888 }) 889 answerPC.OnICEConnectionStateChange(func(state ICEConnectionState) { 890 if state == ICEConnectionStateConnected { 891 connectedWaitGroup.Done() 892 } 893 }) 894 895 // Connect two PeerConnections and block until ICEConnectionStateConnected 896 assert.NoError(t, signalPair(offerPC, answerPC)) 897 connectedWaitGroup.Wait() 898 899 // Store candidates from first Offer/Answer, compare later to make sure we re-gathered 900 firstOfferCandidates := extractCandidates(offerPC.LocalDescription().SDP) 901 firstAnswerCandidates := extractCandidates(answerPC.LocalDescription().SDP) 902 903 // Use Trickle ICE for ICE Restart 904 offerPC.OnICECandidate(func(c *ICECandidate) { 905 if c != nil { 906 assert.NoError(t, answerPC.AddICECandidate(c.ToJSON())) 907 } 908 }) 909 910 answerPC.OnICECandidate(func(c *ICECandidate) { 911 if c != nil { 912 assert.NoError(t, offerPC.AddICECandidate(c.ToJSON())) 913 } 914 }) 915 916 // Re-signal with ICE Restart, block until ICEConnectionStateConnected 917 connectedWaitGroup.Add(2) 918 offer, err := offerPC.CreateOffer(&OfferOptions{ICERestart: true}) 919 assert.NoError(t, err) 920 921 assert.NoError(t, offerPC.SetLocalDescription(offer)) 922 assert.NoError(t, answerPC.SetRemoteDescription(offer)) 923 924 answer, err := answerPC.CreateAnswer(nil) 925 assert.NoError(t, err) 926 927 assert.NoError(t, answerPC.SetLocalDescription(answer)) 928 assert.NoError(t, offerPC.SetRemoteDescription(answer)) 929 930 // Block until we have connected again 931 connectedWaitGroup.Wait() 932 933 // Compare ICE Candidates across each run, fail if they haven't changed 934 assert.NotEqual(t, firstOfferCandidates, extractCandidates(offerPC.LocalDescription().SDP)) 935 assert.NotEqual(t, firstAnswerCandidates, extractCandidates(answerPC.LocalDescription().SDP)) 936 closePairNow(t, offerPC, answerPC) 937} 938 939// Assert error handling when an Agent is restart 940func TestICERestart_Error_Handling(t *testing.T) { 941 iceStates := make(chan ICEConnectionState, 100) 942 blockUntilICEState := func(wantedState ICEConnectionState) { 943 stateCount := 0 944 for i := range iceStates { 945 if i == wantedState { 946 stateCount++ 947 } 948 949 if stateCount == 2 { 950 return 951 } 952 } 953 } 954 955 connectWithICERestart := func(offerPeerConnection, answerPeerConnection *PeerConnection) { 956 offer, err := offerPeerConnection.CreateOffer(&OfferOptions{ICERestart: true}) 957 assert.NoError(t, err) 958 959 assert.NoError(t, offerPeerConnection.SetLocalDescription(offer)) 960 assert.NoError(t, answerPeerConnection.SetRemoteDescription(*offerPeerConnection.LocalDescription())) 961 962 answer, err := answerPeerConnection.CreateAnswer(nil) 963 assert.NoError(t, err) 964 965 assert.NoError(t, answerPeerConnection.SetLocalDescription(answer)) 966 assert.NoError(t, offerPeerConnection.SetRemoteDescription(*answerPeerConnection.LocalDescription())) 967 } 968 969 lim := test.TimeOut(time.Second * 30) 970 defer lim.Stop() 971 972 report := test.CheckRoutines(t) 973 defer report() 974 975 offerPeerConnection, answerPeerConnection, wan := createVNetPair(t) 976 977 pushICEState := func(i ICEConnectionState) { iceStates <- i } 978 offerPeerConnection.OnICEConnectionStateChange(pushICEState) 979 answerPeerConnection.OnICEConnectionStateChange(pushICEState) 980 981 keepPackets := &atomicBool{} 982 keepPackets.set(true) 983 984 // Add a filter that monitors the traffic on the router 985 wan.AddChunkFilter(func(c vnet.Chunk) bool { 986 return keepPackets.get() 987 }) 988 989 const testMessage = "testMessage" 990 991 d, err := answerPeerConnection.CreateDataChannel("foo", nil) 992 assert.NoError(t, err) 993 994 dataChannelMessages := make(chan string, 100) 995 d.OnMessage(func(m DataChannelMessage) { 996 dataChannelMessages <- string(m.Data) 997 }) 998 999 dataChannelAnswerer := make(chan *DataChannel) 1000 offerPeerConnection.OnDataChannel(func(d *DataChannel) { 1001 d.OnOpen(func() { 1002 dataChannelAnswerer <- d 1003 }) 1004 }) 1005 1006 // Connect and Assert we have connected 1007 assert.NoError(t, signalPair(offerPeerConnection, answerPeerConnection)) 1008 blockUntilICEState(ICEConnectionStateConnected) 1009 1010 offerPeerConnection.OnICECandidate(func(c *ICECandidate) { 1011 if c != nil { 1012 assert.NoError(t, answerPeerConnection.AddICECandidate(c.ToJSON())) 1013 } 1014 }) 1015 1016 answerPeerConnection.OnICECandidate(func(c *ICECandidate) { 1017 if c != nil { 1018 assert.NoError(t, offerPeerConnection.AddICECandidate(c.ToJSON())) 1019 } 1020 }) 1021 1022 dataChannel := <-dataChannelAnswerer 1023 assert.NoError(t, dataChannel.SendText(testMessage)) 1024 assert.Equal(t, testMessage, <-dataChannelMessages) 1025 1026 // Drop all packets, assert we have disconnected 1027 // and send a DataChannel message when disconnected 1028 keepPackets.set(false) 1029 blockUntilICEState(ICEConnectionStateFailed) 1030 assert.NoError(t, dataChannel.SendText(testMessage)) 1031 1032 // ICE Restart and assert we have reconnected 1033 // block until our DataChannel message is delivered 1034 keepPackets.set(true) 1035 connectWithICERestart(offerPeerConnection, answerPeerConnection) 1036 blockUntilICEState(ICEConnectionStateConnected) 1037 assert.Equal(t, testMessage, <-dataChannelMessages) 1038 1039 assert.NoError(t, wan.Stop()) 1040 closePairNow(t, offerPeerConnection, answerPeerConnection) 1041} 1042 1043type trackRecords struct { 1044 mu sync.Mutex 1045 trackIDs map[string]struct{} 1046 receivedTrackIDs map[string]struct{} 1047} 1048 1049func (r *trackRecords) newTrack() (*TrackLocalStaticRTP, error) { 1050 trackID := fmt.Sprintf("pion-track-%d", len(r.trackIDs)) 1051 track, err := NewTrackLocalStaticRTP(RTPCodecCapability{MimeType: "video/vp8"}, trackID, "pion") 1052 r.trackIDs[trackID] = struct{}{} 1053 return track, err 1054} 1055 1056func (r *trackRecords) handleTrack(t *TrackRemote, _ *RTPReceiver) { 1057 r.mu.Lock() 1058 defer r.mu.Unlock() 1059 tID := t.ID() 1060 if _, exist := r.trackIDs[tID]; exist { 1061 r.receivedTrackIDs[tID] = struct{}{} 1062 } 1063} 1064 1065func (r *trackRecords) remains() int { 1066 r.mu.Lock() 1067 defer r.mu.Unlock() 1068 return len(r.trackIDs) - len(r.receivedTrackIDs) 1069} 1070 1071// This test assure that all track events emits. 1072func TestPeerConnection_MassiveTracks(t *testing.T) { 1073 var ( 1074 api = NewAPI() 1075 tRecs = &trackRecords{ 1076 trackIDs: make(map[string]struct{}), 1077 receivedTrackIDs: make(map[string]struct{}), 1078 } 1079 tracks = []*TrackLocalStaticRTP{} 1080 trackCount = 256 1081 pingInterval = 1 * time.Second 1082 noiseInterval = 100 * time.Microsecond 1083 timeoutDuration = 20 * time.Second 1084 rawPkt = []byte{ 1085 0x90, 0xe0, 0x69, 0x8f, 0xd9, 0xc2, 0x93, 0xda, 0x1c, 0x64, 1086 0x27, 0x82, 0x00, 0x01, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0x98, 0x36, 0xbe, 0x88, 0x9e, 1087 } 1088 samplePkt = &rtp.Packet{ 1089 Header: rtp.Header{ 1090 Marker: true, 1091 Extension: false, 1092 ExtensionProfile: 1, 1093 Version: 2, 1094 PayloadOffset: 20, 1095 SequenceNumber: 27023, 1096 Timestamp: 3653407706, 1097 CSRC: []uint32{}, 1098 }, 1099 Payload: rawPkt[20:], 1100 } 1101 connected = make(chan struct{}) 1102 stopped = make(chan struct{}) 1103 ) 1104 assert.NoError(t, api.mediaEngine.RegisterDefaultCodecs()) 1105 offerPC, answerPC, err := api.newPair(Configuration{}) 1106 assert.NoError(t, err) 1107 // Create massive tracks. 1108 for range make([]struct{}, trackCount) { 1109 track, err := tRecs.newTrack() 1110 assert.NoError(t, err) 1111 _, err = offerPC.AddTrack(track) 1112 assert.NoError(t, err) 1113 tracks = append(tracks, track) 1114 } 1115 answerPC.OnTrack(tRecs.handleTrack) 1116 offerPC.OnICEConnectionStateChange(func(s ICEConnectionState) { 1117 if s == ICEConnectionStateConnected { 1118 close(connected) 1119 } 1120 }) 1121 // A routine to periodically call GetTransceivers. This action might cause 1122 // the deadlock and prevent track event to emit. 1123 go func() { 1124 for { 1125 answerPC.GetTransceivers() 1126 time.Sleep(noiseInterval) 1127 select { 1128 case <-stopped: 1129 return 1130 default: 1131 } 1132 } 1133 }() 1134 assert.NoError(t, signalPair(offerPC, answerPC)) 1135 // Send a RTP packets to each track to trigger track event after connected. 1136 <-connected 1137 time.Sleep(1 * time.Second) 1138 for _, track := range tracks { 1139 assert.NoError(t, track.WriteRTP(samplePkt)) 1140 } 1141 // Ping trackRecords to see if any track event not received yet. 1142 tooLong := time.After(timeoutDuration) 1143 for { 1144 remains := tRecs.remains() 1145 if remains == 0 { 1146 break 1147 } 1148 t.Log("remain tracks", remains) 1149 time.Sleep(pingInterval) 1150 select { 1151 case <-tooLong: 1152 t.Error("unable to receive all track events in time") 1153 default: 1154 } 1155 } 1156 close(stopped) 1157 closePairNow(t, offerPC, answerPC) 1158} 1159 1160func TestEmptyCandidate(t *testing.T) { 1161 testCases := []struct { 1162 ICECandidate ICECandidateInit 1163 expectError bool 1164 }{ 1165 {ICECandidateInit{"", nil, nil, nil}, false}, 1166 {ICECandidateInit{ 1167 "211962667 1 udp 2122194687 10.0.3.1 40864 typ host generation 0", 1168 nil, nil, nil, 1169 }, false}, 1170 {ICECandidateInit{ 1171 "1234567", 1172 nil, nil, nil, 1173 }, true}, 1174 } 1175 1176 for i, testCase := range testCases { 1177 peerConn, err := NewPeerConnection(Configuration{}) 1178 if err != nil { 1179 t.Errorf("Case %d: got error: %v", i, err) 1180 } 1181 1182 err = peerConn.SetRemoteDescription(SessionDescription{Type: SDPTypeOffer, SDP: minimalOffer}) 1183 if err != nil { 1184 t.Errorf("Case %d: got error: %v", i, err) 1185 } 1186 1187 if testCase.expectError { 1188 assert.Error(t, peerConn.AddICECandidate(testCase.ICECandidate)) 1189 } else { 1190 assert.NoError(t, peerConn.AddICECandidate(testCase.ICECandidate)) 1191 } 1192 1193 assert.NoError(t, peerConn.Close()) 1194 } 1195} 1196 1197const liteOffer = `v=0 1198o=- 4596489990601351948 2 IN IP4 127.0.0.1 1199s=- 1200t=0 0 1201a=msid-semantic: WMS 1202a=ice-lite 1203m=application 47299 DTLS/SCTP 5000 1204c=IN IP4 192.168.20.129 1205a=ice-ufrag:1/MvHwjAyVf27aLu 1206a=ice-pwd:3dBU7cFOBl120v33cynDvN1E 1207a=fingerprint:sha-256 75:74:5A:A6:A4:E5:52:F4:A7:67:4C:01:C7:EE:91:3F:21:3D:A2:E3:53:7B:6F:30:86:F2:30:AA:65:FB:04:24 1208a=mid:data 1209` 1210 1211// this test asserts that if an ice-lite offer is received, 1212// pion will take the ICE-CONTROLLING role 1213func TestICELite(t *testing.T) { 1214 peerConnection, err := NewPeerConnection(Configuration{}) 1215 assert.NoError(t, err) 1216 1217 assert.NoError(t, peerConnection.SetRemoteDescription( 1218 SessionDescription{SDP: liteOffer, Type: SDPTypeOffer}, 1219 )) 1220 1221 SDPAnswer, err := peerConnection.CreateAnswer(nil) 1222 assert.NoError(t, err) 1223 1224 assert.NoError(t, peerConnection.SetLocalDescription(SDPAnswer)) 1225 1226 assert.Equal(t, ICERoleControlling, peerConnection.iceTransport.role, 1227 "pion did not set state to ICE-CONTROLLED against ice-light offer") 1228 1229 assert.NoError(t, peerConnection.Close()) 1230} 1231 1232func TestPeerConnection_TransceiverDirection(t *testing.T) { 1233 lim := test.TimeOut(time.Second * 30) 1234 defer lim.Stop() 1235 1236 report := test.CheckRoutines(t) 1237 defer report() 1238 1239 createTransceiver := func(pc *PeerConnection, dir RTPTransceiverDirection) error { 1240 // AddTransceiverFromKind() can't be used with sendonly 1241 if dir == RTPTransceiverDirectionSendonly { 1242 codecs := pc.api.mediaEngine.getCodecsByKind(RTPCodecTypeVideo) 1243 1244 track, err := NewTrackLocalStaticSample(codecs[0].RTPCodecCapability, util.MathRandAlpha(16), util.MathRandAlpha(16)) 1245 if err != nil { 1246 return err 1247 } 1248 1249 _, err = pc.AddTransceiverFromTrack(track, []RtpTransceiverInit{ 1250 {Direction: dir}, 1251 }...) 1252 return err 1253 } 1254 1255 _, err := pc.AddTransceiverFromKind( 1256 RTPCodecTypeVideo, 1257 RtpTransceiverInit{Direction: dir}, 1258 ) 1259 return err 1260 } 1261 1262 for _, test := range []struct { 1263 name string 1264 offerDirection RTPTransceiverDirection 1265 answerStartDirection RTPTransceiverDirection 1266 answerFinalDirections []RTPTransceiverDirection 1267 }{ 1268 { 1269 "offer sendrecv answer sendrecv", 1270 RTPTransceiverDirectionSendrecv, 1271 RTPTransceiverDirectionSendrecv, 1272 []RTPTransceiverDirection{RTPTransceiverDirectionSendrecv}, 1273 }, 1274 { 1275 "offer sendonly answer sendrecv", 1276 RTPTransceiverDirectionSendonly, 1277 RTPTransceiverDirectionSendrecv, 1278 []RTPTransceiverDirection{RTPTransceiverDirectionSendrecv, RTPTransceiverDirectionRecvonly}, 1279 }, 1280 { 1281 "offer recvonly answer sendrecv", 1282 RTPTransceiverDirectionRecvonly, 1283 RTPTransceiverDirectionSendrecv, 1284 []RTPTransceiverDirection{RTPTransceiverDirectionSendonly}, 1285 }, 1286 { 1287 "offer sendrecv answer sendonly", 1288 RTPTransceiverDirectionSendrecv, 1289 RTPTransceiverDirectionSendonly, 1290 []RTPTransceiverDirection{RTPTransceiverDirectionSendonly, RTPTransceiverDirectionRecvonly}, 1291 }, 1292 { 1293 "offer sendonly answer sendonly", 1294 RTPTransceiverDirectionSendonly, 1295 RTPTransceiverDirectionSendonly, 1296 []RTPTransceiverDirection{RTPTransceiverDirectionSendonly, RTPTransceiverDirectionRecvonly}, 1297 }, 1298 { 1299 "offer recvonly answer sendonly", 1300 RTPTransceiverDirectionRecvonly, 1301 RTPTransceiverDirectionSendonly, 1302 []RTPTransceiverDirection{RTPTransceiverDirectionSendonly}, 1303 }, 1304 { 1305 "offer sendrecv answer recvonly", 1306 RTPTransceiverDirectionSendrecv, 1307 RTPTransceiverDirectionRecvonly, 1308 []RTPTransceiverDirection{RTPTransceiverDirectionRecvonly}, 1309 }, 1310 { 1311 "offer sendonly answer recvonly", 1312 RTPTransceiverDirectionSendonly, 1313 RTPTransceiverDirectionRecvonly, 1314 []RTPTransceiverDirection{RTPTransceiverDirectionRecvonly}, 1315 }, 1316 { 1317 "offer recvonly answer recvonly", 1318 RTPTransceiverDirectionRecvonly, 1319 RTPTransceiverDirectionRecvonly, 1320 []RTPTransceiverDirection{RTPTransceiverDirectionRecvonly, RTPTransceiverDirectionSendonly}, 1321 }, 1322 } { 1323 offerDirection := test.offerDirection 1324 answerStartDirection := test.answerStartDirection 1325 answerFinalDirections := test.answerFinalDirections 1326 1327 t.Run(test.name, func(t *testing.T) { 1328 pcOffer, pcAnswer, err := newPair() 1329 assert.NoError(t, err) 1330 1331 err = createTransceiver(pcOffer, offerDirection) 1332 assert.NoError(t, err) 1333 1334 offer, err := pcOffer.CreateOffer(nil) 1335 assert.NoError(t, err) 1336 1337 err = createTransceiver(pcAnswer, answerStartDirection) 1338 assert.NoError(t, err) 1339 1340 assert.NoError(t, pcAnswer.SetRemoteDescription(offer)) 1341 1342 assert.Equal(t, len(answerFinalDirections), len(pcAnswer.GetTransceivers())) 1343 1344 for i, tr := range pcAnswer.GetTransceivers() { 1345 assert.Equal(t, answerFinalDirections[i], tr.Direction()) 1346 } 1347 1348 assert.NoError(t, pcOffer.Close()) 1349 assert.NoError(t, pcAnswer.Close()) 1350 }) 1351 } 1352} 1353