github.com/pion/webrtc/v4@v4.0.1/peerconnection_test.go (about)

     1  // SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
     2  // SPDX-License-Identifier: MIT
     3  
     4  package webrtc
     5  
     6  import (
     7  	"reflect"
     8  	"sync"
     9  	"sync/atomic"
    10  	"testing"
    11  	"time"
    12  
    13  	"github.com/pion/sdp/v3"
    14  	"github.com/pion/transport/v3/test"
    15  	"github.com/pion/webrtc/v4/pkg/rtcerr"
    16  	"github.com/stretchr/testify/assert"
    17  )
    18  
    19  // newPair creates two new peer connections (an offerer and an answerer)
    20  // *without* using an api (i.e. using the default settings).
    21  func newPair() (pcOffer *PeerConnection, pcAnswer *PeerConnection, err error) {
    22  	pca, err := NewPeerConnection(Configuration{})
    23  	if err != nil {
    24  		return nil, nil, err
    25  	}
    26  
    27  	pcb, err := NewPeerConnection(Configuration{})
    28  	if err != nil {
    29  		return nil, nil, err
    30  	}
    31  
    32  	return pca, pcb, nil
    33  }
    34  
    35  func signalPairWithModification(pcOffer *PeerConnection, pcAnswer *PeerConnection, modificationFunc func(string) string) error {
    36  	// Note(albrow): We need to create a data channel in order to trigger ICE
    37  	// candidate gathering in the background for the JavaScript/Wasm bindings. If
    38  	// we don't do this, the complete offer including ICE candidates will never be
    39  	// generated.
    40  	if _, err := pcOffer.CreateDataChannel("initial_data_channel", nil); err != nil {
    41  		return err
    42  	}
    43  
    44  	offer, err := pcOffer.CreateOffer(nil)
    45  	if err != nil {
    46  		return err
    47  	}
    48  	offerGatheringComplete := GatheringCompletePromise(pcOffer)
    49  	if err = pcOffer.SetLocalDescription(offer); err != nil {
    50  		return err
    51  	}
    52  	<-offerGatheringComplete
    53  
    54  	offer.SDP = modificationFunc(pcOffer.LocalDescription().SDP)
    55  	if err = pcAnswer.SetRemoteDescription(offer); err != nil {
    56  		return err
    57  	}
    58  
    59  	answer, err := pcAnswer.CreateAnswer(nil)
    60  	if err != nil {
    61  		return err
    62  	}
    63  	answerGatheringComplete := GatheringCompletePromise(pcAnswer)
    64  	if err = pcAnswer.SetLocalDescription(answer); err != nil {
    65  		return err
    66  	}
    67  	<-answerGatheringComplete
    68  	return pcOffer.SetRemoteDescription(*pcAnswer.LocalDescription())
    69  }
    70  
    71  func signalPair(pcOffer *PeerConnection, pcAnswer *PeerConnection) error {
    72  	return signalPairWithModification(pcOffer, pcAnswer, func(sessionDescription string) string { return sessionDescription })
    73  }
    74  
    75  func offerMediaHasDirection(offer SessionDescription, kind RTPCodecType, direction RTPTransceiverDirection) bool {
    76  	parsed := &sdp.SessionDescription{}
    77  	if err := parsed.Unmarshal([]byte(offer.SDP)); err != nil {
    78  		return false
    79  	}
    80  
    81  	for _, media := range parsed.MediaDescriptions {
    82  		if media.MediaName.Media == kind.String() {
    83  			_, exists := media.Attribute(direction.String())
    84  			return exists
    85  		}
    86  	}
    87  	return false
    88  }
    89  
    90  func untilConnectionState(state PeerConnectionState, peers ...*PeerConnection) *sync.WaitGroup {
    91  	var triggered sync.WaitGroup
    92  	triggered.Add(len(peers))
    93  
    94  	for _, p := range peers {
    95  		var done atomic.Value
    96  		done.Store(false)
    97  		hdlr := func(p PeerConnectionState) {
    98  			if val, ok := done.Load().(bool); ok && (!val && p == state) {
    99  				done.Store(true)
   100  				triggered.Done()
   101  			}
   102  		}
   103  
   104  		p.OnConnectionStateChange(hdlr)
   105  	}
   106  	return &triggered
   107  }
   108  
   109  func TestNew(t *testing.T) {
   110  	pc, err := NewPeerConnection(Configuration{
   111  		ICEServers: []ICEServer{
   112  			{
   113  				URLs: []string{
   114  					"stun:stun.l.google.com:19302",
   115  				},
   116  				Username: "unittest",
   117  			},
   118  		},
   119  		ICETransportPolicy:   ICETransportPolicyRelay,
   120  		BundlePolicy:         BundlePolicyMaxCompat,
   121  		RTCPMuxPolicy:        RTCPMuxPolicyNegotiate,
   122  		PeerIdentity:         "unittest",
   123  		ICECandidatePoolSize: 5,
   124  	})
   125  	assert.NoError(t, err)
   126  	assert.NotNil(t, pc)
   127  	assert.NoError(t, pc.Close())
   128  }
   129  
   130  func TestPeerConnection_SetConfiguration(t *testing.T) {
   131  	// Note: These tests don't include ICEServer.Credential,
   132  	// ICEServer.CredentialType, or Certificates because those are not supported
   133  	// in the WASM bindings.
   134  
   135  	for _, test := range []struct {
   136  		name    string
   137  		init    func() (*PeerConnection, error)
   138  		config  Configuration
   139  		wantErr error
   140  	}{
   141  		{
   142  			name: "valid",
   143  			init: func() (*PeerConnection, error) {
   144  				pc, err := NewPeerConnection(Configuration{
   145  					ICECandidatePoolSize: 5,
   146  				})
   147  				if err != nil {
   148  					return pc, err
   149  				}
   150  
   151  				err = pc.SetConfiguration(Configuration{
   152  					ICEServers: []ICEServer{
   153  						{
   154  							URLs: []string{
   155  								"stun:stun.l.google.com:19302",
   156  							},
   157  							Username: "unittest",
   158  						},
   159  					},
   160  					ICETransportPolicy:   ICETransportPolicyAll,
   161  					BundlePolicy:         BundlePolicyBalanced,
   162  					RTCPMuxPolicy:        RTCPMuxPolicyRequire,
   163  					ICECandidatePoolSize: 5,
   164  				})
   165  				if err != nil {
   166  					return pc, err
   167  				}
   168  
   169  				return pc, nil
   170  			},
   171  			config:  Configuration{},
   172  			wantErr: nil,
   173  		},
   174  		{
   175  			name: "closed connection",
   176  			init: func() (*PeerConnection, error) {
   177  				pc, err := NewPeerConnection(Configuration{})
   178  				assert.Nil(t, err)
   179  
   180  				err = pc.Close()
   181  				assert.Nil(t, err)
   182  				return pc, err
   183  			},
   184  			config:  Configuration{},
   185  			wantErr: &rtcerr.InvalidStateError{Err: ErrConnectionClosed},
   186  		},
   187  		{
   188  			name: "update PeerIdentity",
   189  			init: func() (*PeerConnection, error) {
   190  				return NewPeerConnection(Configuration{})
   191  			},
   192  			config: Configuration{
   193  				PeerIdentity: "unittest",
   194  			},
   195  			wantErr: &rtcerr.InvalidModificationError{Err: ErrModifyingPeerIdentity},
   196  		},
   197  		{
   198  			name: "update BundlePolicy",
   199  			init: func() (*PeerConnection, error) {
   200  				return NewPeerConnection(Configuration{})
   201  			},
   202  			config: Configuration{
   203  				BundlePolicy: BundlePolicyMaxCompat,
   204  			},
   205  			wantErr: &rtcerr.InvalidModificationError{Err: ErrModifyingBundlePolicy},
   206  		},
   207  		{
   208  			name: "update RTCPMuxPolicy",
   209  			init: func() (*PeerConnection, error) {
   210  				return NewPeerConnection(Configuration{})
   211  			},
   212  			config: Configuration{
   213  				RTCPMuxPolicy: RTCPMuxPolicyNegotiate,
   214  			},
   215  			wantErr: &rtcerr.InvalidModificationError{Err: ErrModifyingRTCPMuxPolicy},
   216  		},
   217  		{
   218  			name: "update ICECandidatePoolSize",
   219  			init: func() (*PeerConnection, error) {
   220  				pc, err := NewPeerConnection(Configuration{
   221  					ICECandidatePoolSize: 0,
   222  				})
   223  				if err != nil {
   224  					return pc, err
   225  				}
   226  				offer, err := pc.CreateOffer(nil)
   227  				if err != nil {
   228  					return pc, err
   229  				}
   230  				err = pc.SetLocalDescription(offer)
   231  				if err != nil {
   232  					return pc, err
   233  				}
   234  				return pc, nil
   235  			},
   236  			config: Configuration{
   237  				ICECandidatePoolSize: 1,
   238  			},
   239  			wantErr: &rtcerr.InvalidModificationError{Err: ErrModifyingICECandidatePoolSize},
   240  		},
   241  	} {
   242  		pc, err := test.init()
   243  		if err != nil {
   244  			t.Errorf("SetConfiguration %q: init failed: %v", test.name, err)
   245  		}
   246  
   247  		err = pc.SetConfiguration(test.config)
   248  		if got, want := err, test.wantErr; !reflect.DeepEqual(got, want) {
   249  			t.Errorf("SetConfiguration %q: err = %v, want %v", test.name, got, want)
   250  		}
   251  
   252  		assert.NoError(t, pc.Close())
   253  	}
   254  }
   255  
   256  func TestPeerConnection_GetConfiguration(t *testing.T) {
   257  	pc, err := NewPeerConnection(Configuration{})
   258  	assert.NoError(t, err)
   259  
   260  	expected := Configuration{
   261  		ICEServers:           []ICEServer{},
   262  		ICETransportPolicy:   ICETransportPolicyAll,
   263  		BundlePolicy:         BundlePolicyBalanced,
   264  		RTCPMuxPolicy:        RTCPMuxPolicyRequire,
   265  		ICECandidatePoolSize: 0,
   266  	}
   267  	actual := pc.GetConfiguration()
   268  	assert.True(t, &expected != &actual)
   269  	assert.Equal(t, expected.ICEServers, actual.ICEServers)
   270  	assert.Equal(t, expected.ICETransportPolicy, actual.ICETransportPolicy)
   271  	assert.Equal(t, expected.BundlePolicy, actual.BundlePolicy)
   272  	assert.Equal(t, expected.RTCPMuxPolicy, actual.RTCPMuxPolicy)
   273  	// nolint:godox
   274  	// TODO(albrow): Uncomment this after #513 is fixed.
   275  	// See: https://github.com/pion/webrtc/issues/513.
   276  	// assert.Equal(t, len(expected.Certificates), len(actual.Certificates))
   277  	assert.Equal(t, expected.ICECandidatePoolSize, actual.ICECandidatePoolSize)
   278  	assert.NoError(t, pc.Close())
   279  }
   280  
   281  const minimalOffer = `v=0
   282  o=- 4596489990601351948 2 IN IP4 127.0.0.1
   283  s=-
   284  t=0 0
   285  a=msid-semantic: WMS
   286  m=application 47299 DTLS/SCTP 5000
   287  c=IN IP4 192.168.20.129
   288  a=candidate:1966762134 1 udp 2122260223 192.168.20.129 47299 typ host generation 0
   289  a=candidate:1966762134 1 udp 2122262783 2001:db8::1 47199 typ host generation 0
   290  a=candidate:211962667 1 udp 2122194687 10.0.3.1 40864 typ host generation 0
   291  a=candidate:1002017894 1 tcp 1518280447 192.168.20.129 0 typ host tcptype active generation 0
   292  a=candidate:1109506011 1 tcp 1518214911 10.0.3.1 0 typ host tcptype active generation 0
   293  a=ice-ufrag:1/MvHwjAyVf27aLu
   294  a=ice-pwd:3dBU7cFOBl120v33cynDvN1E
   295  a=ice-options:google-ice
   296  a=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
   297  a=setup:actpass
   298  a=mid:data
   299  a=sctpmap:5000 webrtc-datachannel 1024
   300  `
   301  
   302  func TestSetRemoteDescription(t *testing.T) {
   303  	testCases := []struct {
   304  		desc        SessionDescription
   305  		expectError bool
   306  	}{
   307  		{SessionDescription{Type: SDPTypeOffer, SDP: minimalOffer}, false},
   308  		{SessionDescription{Type: 0, SDP: ""}, true},
   309  	}
   310  
   311  	for i, testCase := range testCases {
   312  		peerConn, err := NewPeerConnection(Configuration{})
   313  		if err != nil {
   314  			t.Errorf("Case %d: got error: %v", i, err)
   315  		}
   316  
   317  		if testCase.expectError {
   318  			assert.Error(t, peerConn.SetRemoteDescription(testCase.desc))
   319  		} else {
   320  			assert.NoError(t, peerConn.SetRemoteDescription(testCase.desc))
   321  		}
   322  
   323  		assert.NoError(t, peerConn.Close())
   324  	}
   325  }
   326  
   327  func TestCreateOfferAnswer(t *testing.T) {
   328  	offerPeerConn, err := NewPeerConnection(Configuration{})
   329  	assert.NoError(t, err)
   330  
   331  	answerPeerConn, err := NewPeerConnection(Configuration{})
   332  	assert.NoError(t, err)
   333  
   334  	_, err = offerPeerConn.CreateDataChannel("test-channel", nil)
   335  	assert.NoError(t, err)
   336  
   337  	offer, err := offerPeerConn.CreateOffer(nil)
   338  	assert.NoError(t, err)
   339  	assert.NoError(t, offerPeerConn.SetLocalDescription(offer))
   340  
   341  	assert.NoError(t, answerPeerConn.SetRemoteDescription(offer))
   342  
   343  	answer, err := answerPeerConn.CreateAnswer(nil)
   344  	assert.NoError(t, err)
   345  
   346  	assert.NoError(t, answerPeerConn.SetLocalDescription(answer))
   347  	assert.NoError(t, offerPeerConn.SetRemoteDescription(answer))
   348  
   349  	// after setLocalDescription(answer), signaling state should be stable.
   350  	// so CreateAnswer should return an InvalidStateError
   351  	assert.Equal(t, answerPeerConn.SignalingState(), SignalingStateStable)
   352  	_, err = answerPeerConn.CreateAnswer(nil)
   353  	assert.Error(t, err)
   354  
   355  	closePairNow(t, offerPeerConn, answerPeerConn)
   356  }
   357  
   358  func TestPeerConnection_EventHandlers(t *testing.T) {
   359  	pcOffer, err := NewPeerConnection(Configuration{})
   360  	assert.NoError(t, err)
   361  	pcAnswer, err := NewPeerConnection(Configuration{})
   362  	assert.NoError(t, err)
   363  
   364  	// wasCalled is a list of event handlers that were called.
   365  	wasCalled := []string{}
   366  	wasCalledMut := &sync.Mutex{}
   367  	// wg is used to wait for all event handlers to be called.
   368  	wg := &sync.WaitGroup{}
   369  	wg.Add(6)
   370  
   371  	// Each sync.Once is used to ensure that we call wg.Done once for each event
   372  	// handler and don't add multiple entries to wasCalled. The event handlers can
   373  	// be called more than once in some cases.
   374  	onceOffererOnICEConnectionStateChange := &sync.Once{}
   375  	onceOffererOnConnectionStateChange := &sync.Once{}
   376  	onceOffererOnSignalingStateChange := &sync.Once{}
   377  	onceAnswererOnICEConnectionStateChange := &sync.Once{}
   378  	onceAnswererOnConnectionStateChange := &sync.Once{}
   379  	onceAnswererOnSignalingStateChange := &sync.Once{}
   380  
   381  	// Register all the event handlers.
   382  	pcOffer.OnICEConnectionStateChange(func(ICEConnectionState) {
   383  		onceOffererOnICEConnectionStateChange.Do(func() {
   384  			wasCalledMut.Lock()
   385  			defer wasCalledMut.Unlock()
   386  			wasCalled = append(wasCalled, "offerer OnICEConnectionStateChange")
   387  			wg.Done()
   388  		})
   389  	})
   390  	pcOffer.OnConnectionStateChange(func(PeerConnectionState) {
   391  		onceOffererOnConnectionStateChange.Do(func() {
   392  			wasCalledMut.Lock()
   393  			defer wasCalledMut.Unlock()
   394  			wasCalled = append(wasCalled, "offerer OnConnectionStateChange")
   395  			wg.Done()
   396  		})
   397  	})
   398  	pcOffer.OnSignalingStateChange(func(SignalingState) {
   399  		onceOffererOnSignalingStateChange.Do(func() {
   400  			wasCalledMut.Lock()
   401  			defer wasCalledMut.Unlock()
   402  			wasCalled = append(wasCalled, "offerer OnSignalingStateChange")
   403  			wg.Done()
   404  		})
   405  	})
   406  	pcAnswer.OnICEConnectionStateChange(func(ICEConnectionState) {
   407  		onceAnswererOnICEConnectionStateChange.Do(func() {
   408  			wasCalledMut.Lock()
   409  			defer wasCalledMut.Unlock()
   410  			wasCalled = append(wasCalled, "answerer OnICEConnectionStateChange")
   411  			wg.Done()
   412  		})
   413  	})
   414  	pcAnswer.OnConnectionStateChange(func(PeerConnectionState) {
   415  		onceAnswererOnConnectionStateChange.Do(func() {
   416  			wasCalledMut.Lock()
   417  			defer wasCalledMut.Unlock()
   418  			wasCalled = append(wasCalled, "answerer OnConnectionStateChange")
   419  			wg.Done()
   420  		})
   421  	})
   422  	pcAnswer.OnSignalingStateChange(func(SignalingState) {
   423  		onceAnswererOnSignalingStateChange.Do(func() {
   424  			wasCalledMut.Lock()
   425  			defer wasCalledMut.Unlock()
   426  			wasCalled = append(wasCalled, "answerer OnSignalingStateChange")
   427  			wg.Done()
   428  		})
   429  	})
   430  
   431  	// Use signalPair to establish a connection between pcOffer and pcAnswer. This
   432  	// process should trigger the above event handlers.
   433  	assert.NoError(t, signalPair(pcOffer, pcAnswer))
   434  
   435  	// Wait for all of the event handlers to be triggered.
   436  	done := make(chan struct{})
   437  	go func() {
   438  		wg.Wait()
   439  		done <- struct{}{}
   440  	}()
   441  	timeout := time.After(5 * time.Second)
   442  	select {
   443  	case <-done:
   444  		break
   445  	case <-timeout:
   446  		t.Fatalf("timed out waiting for one or more events handlers to be called (these *were* called: %+v)", wasCalled)
   447  	}
   448  
   449  	closePairNow(t, pcOffer, pcAnswer)
   450  }
   451  
   452  func TestMultipleOfferAnswer(t *testing.T) {
   453  	firstPeerConn, err := NewPeerConnection(Configuration{})
   454  	if err != nil {
   455  		t.Errorf("New PeerConnection: got error: %v", err)
   456  	}
   457  
   458  	if _, err = firstPeerConn.CreateOffer(nil); err != nil {
   459  		t.Errorf("First Offer: got error: %v", err)
   460  	}
   461  	if _, err = firstPeerConn.CreateOffer(nil); err != nil {
   462  		t.Errorf("Second Offer: got error: %v", err)
   463  	}
   464  
   465  	secondPeerConn, err := NewPeerConnection(Configuration{})
   466  	if err != nil {
   467  		t.Errorf("New PeerConnection: got error: %v", err)
   468  	}
   469  	secondPeerConn.OnICECandidate(func(*ICECandidate) {
   470  	})
   471  
   472  	if _, err = secondPeerConn.CreateOffer(nil); err != nil {
   473  		t.Errorf("First Offer: got error: %v", err)
   474  	}
   475  	if _, err = secondPeerConn.CreateOffer(nil); err != nil {
   476  		t.Errorf("Second Offer: got error: %v", err)
   477  	}
   478  
   479  	closePairNow(t, firstPeerConn, secondPeerConn)
   480  }
   481  
   482  func TestNoFingerprintInFirstMediaIfSetRemoteDescription(t *testing.T) {
   483  	const sdpNoFingerprintInFirstMedia = `v=0
   484  o=- 143087887 1561022767 IN IP4 192.168.84.254
   485  s=VideoRoom 404986692241682
   486  t=0 0
   487  a=group:BUNDLE audio
   488  a=msid-semantic: WMS 2867270241552712
   489  m=video 0 UDP/TLS/RTP/SAVPF 0
   490  a=mid:video
   491  c=IN IP4 192.168.84.254
   492  a=inactive
   493  m=audio 9 UDP/TLS/RTP/SAVPF 111
   494  c=IN IP4 192.168.84.254
   495  a=recvonly
   496  a=mid:audio
   497  a=rtcp-mux
   498  a=ice-ufrag:AS/w
   499  a=ice-pwd:9NOgoAOMALYu/LOpA1iqg/
   500  a=ice-options:trickle
   501  a=fingerprint:sha-256 D2:B9:31:8F:DF:24:D8:0E:ED:D2:EF:25:9E:AF:6F:B8:34:AE:53:9C:E6:F3:8F:F2:64:15:FA:E8:7F:53:2D:38
   502  a=setup:active
   503  a=rtpmap:111 opus/48000/2
   504  a=candidate:1 1 udp 2013266431 192.168.84.254 46492 typ host
   505  a=end-of-candidates
   506  `
   507  
   508  	report := test.CheckRoutines(t)
   509  	defer report()
   510  
   511  	pc, err := NewPeerConnection(Configuration{})
   512  	if err != nil {
   513  		t.Error(err.Error())
   514  	}
   515  
   516  	desc := SessionDescription{
   517  		Type: SDPTypeOffer,
   518  		SDP:  sdpNoFingerprintInFirstMedia,
   519  	}
   520  
   521  	if err = pc.SetRemoteDescription(desc); err != nil {
   522  		t.Error(err.Error())
   523  	}
   524  
   525  	assert.NoError(t, pc.Close())
   526  }
   527  
   528  func TestNegotiationNeeded(t *testing.T) {
   529  	lim := test.TimeOut(time.Second * 30)
   530  	defer lim.Stop()
   531  
   532  	report := test.CheckRoutines(t)
   533  	defer report()
   534  
   535  	pc, err := NewPeerConnection(Configuration{})
   536  	if err != nil {
   537  		t.Error(err.Error())
   538  	}
   539  
   540  	var wg sync.WaitGroup
   541  	wg.Add(1)
   542  
   543  	pc.OnNegotiationNeeded(wg.Done)
   544  	_, err = pc.CreateDataChannel("initial_data_channel", nil)
   545  	assert.NoError(t, err)
   546  
   547  	wg.Wait()
   548  
   549  	assert.NoError(t, pc.Close())
   550  }
   551  
   552  func TestMultipleCreateChannel(t *testing.T) {
   553  	var wg sync.WaitGroup
   554  
   555  	report := test.CheckRoutines(t)
   556  	defer report()
   557  
   558  	// Two OnDataChannel
   559  	// One OnNegotiationNeeded
   560  	wg.Add(3)
   561  
   562  	pcOffer, _ := NewPeerConnection(Configuration{})
   563  	pcAnswer, _ := NewPeerConnection(Configuration{})
   564  
   565  	pcAnswer.OnDataChannel(func(*DataChannel) {
   566  		wg.Done()
   567  	})
   568  
   569  	pcOffer.OnNegotiationNeeded(func() {
   570  		offer, err := pcOffer.CreateOffer(nil)
   571  		assert.NoError(t, err)
   572  
   573  		offerGatheringComplete := GatheringCompletePromise(pcOffer)
   574  		if err = pcOffer.SetLocalDescription(offer); err != nil {
   575  			t.Error(err)
   576  		}
   577  		<-offerGatheringComplete
   578  		if err = pcAnswer.SetRemoteDescription(*pcOffer.LocalDescription()); err != nil {
   579  			t.Error(err)
   580  		}
   581  
   582  		answer, err := pcAnswer.CreateAnswer(nil)
   583  		assert.NoError(t, err)
   584  
   585  		answerGatheringComplete := GatheringCompletePromise(pcAnswer)
   586  		if err = pcAnswer.SetLocalDescription(answer); err != nil {
   587  			t.Error(err)
   588  		}
   589  		<-answerGatheringComplete
   590  		if err = pcOffer.SetRemoteDescription(*pcAnswer.LocalDescription()); err != nil {
   591  			t.Error(err)
   592  		}
   593  		wg.Done()
   594  	})
   595  
   596  	if _, err := pcOffer.CreateDataChannel("initial_data_channel_0", nil); err != nil {
   597  		t.Error(err)
   598  	}
   599  
   600  	if _, err := pcOffer.CreateDataChannel("initial_data_channel_1", nil); err != nil {
   601  		t.Error(err)
   602  	}
   603  
   604  	wg.Wait()
   605  
   606  	closePairNow(t, pcOffer, pcAnswer)
   607  }
   608  
   609  // Assert that candidates are gathered by calling SetLocalDescription, not SetRemoteDescription
   610  func TestGatherOnSetLocalDescription(t *testing.T) {
   611  	lim := test.TimeOut(time.Second * 30)
   612  	defer lim.Stop()
   613  
   614  	report := test.CheckRoutines(t)
   615  	defer report()
   616  
   617  	pcOfferGathered := make(chan SessionDescription)
   618  	pcAnswerGathered := make(chan SessionDescription)
   619  
   620  	s := SettingEngine{}
   621  	api := NewAPI(WithSettingEngine(s))
   622  
   623  	pcOffer, err := api.NewPeerConnection(Configuration{})
   624  	if err != nil {
   625  		t.Error(err.Error())
   626  	}
   627  
   628  	// We need to create a data channel in order to trigger ICE
   629  	if _, err = pcOffer.CreateDataChannel("initial_data_channel", nil); err != nil {
   630  		t.Error(err.Error())
   631  	}
   632  
   633  	pcOffer.OnICECandidate(func(i *ICECandidate) {
   634  		if i == nil {
   635  			close(pcOfferGathered)
   636  		}
   637  	})
   638  
   639  	offer, err := pcOffer.CreateOffer(nil)
   640  	if err != nil {
   641  		t.Error(err.Error())
   642  	} else if err = pcOffer.SetLocalDescription(offer); err != nil {
   643  		t.Error(err.Error())
   644  	}
   645  
   646  	<-pcOfferGathered
   647  
   648  	pcAnswer, err := api.NewPeerConnection(Configuration{})
   649  	if err != nil {
   650  		t.Error(err.Error())
   651  	}
   652  
   653  	pcAnswer.OnICECandidate(func(i *ICECandidate) {
   654  		if i == nil {
   655  			close(pcAnswerGathered)
   656  		}
   657  	})
   658  
   659  	if err = pcAnswer.SetRemoteDescription(offer); err != nil {
   660  		t.Error(err.Error())
   661  	}
   662  
   663  	select {
   664  	case <-pcAnswerGathered:
   665  		t.Fatal("pcAnswer started gathering with no SetLocalDescription")
   666  	// Gathering is async, not sure of a better way to catch this currently
   667  	case <-time.After(3 * time.Second):
   668  	}
   669  
   670  	answer, err := pcAnswer.CreateAnswer(nil)
   671  	if err != nil {
   672  		t.Error(err.Error())
   673  	} else if err = pcAnswer.SetLocalDescription(answer); err != nil {
   674  		t.Error(err.Error())
   675  	}
   676  	<-pcAnswerGathered
   677  	closePairNow(t, pcOffer, pcAnswer)
   678  }
   679  
   680  // Assert that SetRemoteDescription handles invalid states
   681  func TestSetRemoteDescriptionInvalid(t *testing.T) {
   682  	t.Run("local-offer+SetRemoteDescription(Offer)", func(t *testing.T) {
   683  		pc, err := NewPeerConnection(Configuration{})
   684  		assert.NoError(t, err)
   685  
   686  		offer, err := pc.CreateOffer(nil)
   687  		assert.NoError(t, err)
   688  
   689  		assert.NoError(t, pc.SetLocalDescription(offer))
   690  		assert.Error(t, pc.SetRemoteDescription(offer))
   691  
   692  		assert.NoError(t, pc.Close())
   693  	})
   694  }
   695  
   696  func TestAddTransceiver(t *testing.T) {
   697  	lim := test.TimeOut(time.Second * 30)
   698  	defer lim.Stop()
   699  
   700  	report := test.CheckRoutines(t)
   701  	defer report()
   702  
   703  	for _, testCase := range []struct {
   704  		expectSender, expectReceiver bool
   705  		direction                    RTPTransceiverDirection
   706  	}{
   707  		{true, true, RTPTransceiverDirectionSendrecv},
   708  		// Go and WASM diverge
   709  		// {true, false, RTPTransceiverDirectionSendonly},
   710  		// {false, true, RTPTransceiverDirectionRecvonly},
   711  	} {
   712  		pc, err := NewPeerConnection(Configuration{})
   713  		assert.NoError(t, err)
   714  
   715  		transceiver, err := pc.AddTransceiverFromKind(RTPCodecTypeVideo, RTPTransceiverInit{
   716  			Direction: testCase.direction,
   717  		})
   718  		assert.NoError(t, err)
   719  
   720  		if testCase.expectReceiver {
   721  			assert.NotNil(t, transceiver.Receiver())
   722  		} else {
   723  			assert.Nil(t, transceiver.Receiver())
   724  		}
   725  
   726  		if testCase.expectSender {
   727  			assert.NotNil(t, transceiver.Sender())
   728  		} else {
   729  			assert.Nil(t, transceiver.Sender())
   730  		}
   731  
   732  		offer, err := pc.CreateOffer(nil)
   733  		assert.NoError(t, err)
   734  
   735  		assert.True(t, offerMediaHasDirection(offer, RTPCodecTypeVideo, testCase.direction))
   736  		assert.NoError(t, pc.Close())
   737  	}
   738  }
   739  
   740  // Assert that SCTPTransport -> DTLSTransport -> ICETransport works after connected
   741  func TestTransportChain(t *testing.T) {
   742  	offer, answer, err := newPair()
   743  	assert.NoError(t, err)
   744  
   745  	peerConnectionsConnected := untilConnectionState(PeerConnectionStateConnected, offer, answer)
   746  	assert.NoError(t, signalPair(offer, answer))
   747  	peerConnectionsConnected.Wait()
   748  
   749  	assert.NotNil(t, offer.SCTP().Transport().ICETransport())
   750  
   751  	closePairNow(t, offer, answer)
   752  }
   753  
   754  // Assert that the PeerConnection closes via DTLS (and not ICE)
   755  func TestDTLSClose(t *testing.T) {
   756  	lim := test.TimeOut(time.Second * 10)
   757  	defer lim.Stop()
   758  
   759  	report := test.CheckRoutines(t)
   760  	defer report()
   761  
   762  	pcOffer, pcAnswer, err := newPair()
   763  	assert.NoError(t, err)
   764  
   765  	_, err = pcOffer.AddTransceiverFromKind(RTPCodecTypeVideo)
   766  	assert.NoError(t, err)
   767  
   768  	peerConnectionsConnected := untilConnectionState(PeerConnectionStateConnected, pcOffer, pcAnswer)
   769  
   770  	offer, err := pcOffer.CreateOffer(nil)
   771  	assert.NoError(t, err)
   772  
   773  	offerGatheringComplete := GatheringCompletePromise(pcOffer)
   774  	assert.NoError(t, pcOffer.SetLocalDescription(offer))
   775  	<-offerGatheringComplete
   776  
   777  	assert.NoError(t, pcAnswer.SetRemoteDescription(*pcOffer.LocalDescription()))
   778  
   779  	answer, err := pcAnswer.CreateAnswer(nil)
   780  	assert.NoError(t, err)
   781  
   782  	answerGatheringComplete := GatheringCompletePromise(pcAnswer)
   783  	assert.NoError(t, pcAnswer.SetLocalDescription(answer))
   784  	<-answerGatheringComplete
   785  
   786  	assert.NoError(t, pcOffer.SetRemoteDescription(*pcAnswer.LocalDescription()))
   787  
   788  	peerConnectionsConnected.Wait()
   789  	assert.NoError(t, pcOffer.Close())
   790  }