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