package webrtc import ( "crypto/ecdsa" "crypto/elliptic" "crypto/rand" "crypto/x509" "math/big" "testing" "time" "github.com/pions/rtp" "github.com/pions/webrtc/pkg/ice" "github.com/pions/webrtc/pkg/media" "github.com/pions/webrtc/pkg/rtcerr" "github.com/stretchr/testify/assert" ) func (api *API) newPair() (pcOffer *PeerConnection, pcAnswer *PeerConnection, err error) { pca, err := api.NewPeerConnection(Configuration{}) if err != nil { return nil, nil, err } pcb, err := api.NewPeerConnection(Configuration{}) if err != nil { return nil, nil, err } return pca, pcb, nil } func signalPair(pcOffer *PeerConnection, pcAnswer *PeerConnection) error { offer, err := pcOffer.CreateOffer(nil) if err != nil { return err } if err = pcOffer.SetLocalDescription(offer); err != nil { return err } err = pcAnswer.SetRemoteDescription(offer) if err != nil { return err } answer, err := pcAnswer.CreateAnswer(nil) if err != nil { return err } if err = pcAnswer.SetLocalDescription(answer); err != nil { return err } err = pcOffer.SetRemoteDescription(answer) if err != nil { return err } return nil } func TestNew(t *testing.T) { api := NewAPI() t.Run("Success", func(t *testing.T) { secretKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) assert.Nil(t, err) certificate, err := GenerateCertificate(secretKey) assert.Nil(t, err) pc, err := api.NewPeerConnection(Configuration{ ICEServers: []ICEServer{ { URLs: []string{ "stun:stun.l.google.com:19302", "turns:google.de?transport=tcp", }, Username: "unittest", Credential: OAuthCredential{ MacKey: "WmtzanB3ZW9peFhtdm42NzUzNG0=", AccessToken: "AAwg3kPHWPfvk9bDFL936wYvkoctMADzQ==", }, CredentialType: ICECredentialTypeOauth, }, }, ICETransportPolicy: ICETransportPolicyRelay, BundlePolicy: BundlePolicyMaxCompat, RTCPMuxPolicy: RTCPMuxPolicyNegotiate, PeerIdentity: "unittest", Certificates: []Certificate{*certificate}, ICECandidatePoolSize: 5, }) assert.Nil(t, err) assert.NotNil(t, pc) }) t.Run("Failure", func(t *testing.T) { testCases := []struct { initialize func() (*PeerConnection, error) expectedErr error }{ {func() (*PeerConnection, error) { secretKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) assert.Nil(t, err) certificate, err := NewCertificate(secretKey, x509.Certificate{ Version: 2, SerialNumber: big.NewInt(1653), NotBefore: time.Now().AddDate(0, -2, 0), NotAfter: time.Now().AddDate(0, -1, 0), }) assert.Nil(t, err) return api.NewPeerConnection(Configuration{ Certificates: []Certificate{*certificate}, }) }, &rtcerr.InvalidAccessError{Err: ErrCertificateExpired}}, {func() (*PeerConnection, error) { return api.NewPeerConnection(Configuration{ ICEServers: []ICEServer{ { URLs: []string{ "stun:stun.l.google.com:19302", "turns:google.de?transport=tcp", }, Username: "unittest", }, }, }) }, &rtcerr.InvalidAccessError{Err: ErrNoTurnCredencials}}, } for i, testCase := range testCases { _, err := testCase.initialize() assert.EqualError(t, err, testCase.expectedErr.Error(), "testCase: %d %v", i, testCase, ) } }) } func TestPeerConnection_SetConfiguration(t *testing.T) { api := NewAPI() t.Run("Success", func(t *testing.T) { secretKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) assert.Nil(t, err) certificate, err := GenerateCertificate(secretKey) assert.Nil(t, err) pc, err := api.NewPeerConnection(Configuration{ PeerIdentity: "unittest", Certificates: []Certificate{*certificate}, ICECandidatePoolSize: 5, }) assert.Nil(t, err) err = pc.SetConfiguration(Configuration{ ICEServers: []ICEServer{ { URLs: []string{ "stun:stun.l.google.com:19302", "turns:google.de?transport=tcp", }, Username: "unittest", Credential: OAuthCredential{ MacKey: "WmtzanB3ZW9peFhtdm42NzUzNG0=", AccessToken: "AAwg3kPHWPfvk9bDFL936wYvkoctMADzQ==", }, CredentialType: ICECredentialTypeOauth, }, }, ICETransportPolicy: ICETransportPolicyAll, BundlePolicy: BundlePolicyBalanced, RTCPMuxPolicy: RTCPMuxPolicyRequire, PeerIdentity: "unittest", Certificates: []Certificate{*certificate}, ICECandidatePoolSize: 5, }) assert.Nil(t, err) }) t.Run("Failure", func(t *testing.T) { testCases := []struct { initialize func() (*PeerConnection, error) updatingConfig func() Configuration expectedErr error }{ {func() (*PeerConnection, error) { pc, err := api.NewPeerConnection(Configuration{}) assert.Nil(t, err) err = pc.Close() assert.Nil(t, err) return pc, err }, func() Configuration { return Configuration{} }, &rtcerr.InvalidStateError{Err: ErrConnectionClosed}}, {func() (*PeerConnection, error) { return api.NewPeerConnection(Configuration{}) }, func() Configuration { return Configuration{ PeerIdentity: "unittest", } }, &rtcerr.InvalidModificationError{Err: ErrModifyingPeerIdentity}}, {func() (*PeerConnection, error) { return api.NewPeerConnection(Configuration{}) }, func() Configuration { secretKey1, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) assert.Nil(t, err) certificate1, err := GenerateCertificate(secretKey1) assert.Nil(t, err) secretKey2, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) assert.Nil(t, err) certificate2, err := GenerateCertificate(secretKey2) assert.Nil(t, err) return Configuration{ Certificates: []Certificate{*certificate1, *certificate2}, } }, &rtcerr.InvalidModificationError{Err: ErrModifyingCertificates}}, {func() (*PeerConnection, error) { return api.NewPeerConnection(Configuration{}) }, func() Configuration { secretKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) assert.Nil(t, err) certificate, err := GenerateCertificate(secretKey) assert.Nil(t, err) return Configuration{ Certificates: []Certificate{*certificate}, } }, &rtcerr.InvalidModificationError{Err: ErrModifyingCertificates}}, {func() (*PeerConnection, error) { return api.NewPeerConnection(Configuration{}) }, func() Configuration { return Configuration{ BundlePolicy: BundlePolicyMaxCompat, } }, &rtcerr.InvalidModificationError{Err: ErrModifyingBundlePolicy}}, {func() (*PeerConnection, error) { return api.NewPeerConnection(Configuration{}) }, func() Configuration { return Configuration{ RTCPMuxPolicy: RTCPMuxPolicyNegotiate, } }, &rtcerr.InvalidModificationError{Err: ErrModifyingRTCPMuxPolicy}}, // TODO Unittest for ICECandidatePoolSize cannot be done now needs pc.LocalDescription() {func() (*PeerConnection, error) { return api.NewPeerConnection(Configuration{}) }, func() Configuration { return Configuration{ ICEServers: []ICEServer{ { URLs: []string{ "stun:stun.l.google.com:19302", "turns:google.de?transport=tcp", }, Username: "unittest", }, }, } }, &rtcerr.InvalidAccessError{Err: ErrNoTurnCredencials}}, } for i, testCase := range testCases { pc, err := testCase.initialize() assert.Nil(t, err) err = pc.SetConfiguration(testCase.updatingConfig()) assert.EqualError(t, err, testCase.expectedErr.Error(), "testCase: %d %v", i, testCase, ) } }) } func TestPeerConnection_GetConfiguration(t *testing.T) { api := NewAPI() pc, err := api.NewPeerConnection(Configuration{}) assert.Nil(t, err) expected := Configuration{ ICEServers: []ICEServer{}, ICETransportPolicy: ICETransportPolicyAll, BundlePolicy: BundlePolicyBalanced, RTCPMuxPolicy: RTCPMuxPolicyRequire, Certificates: []Certificate{}, ICECandidatePoolSize: 0, } actual := pc.GetConfiguration() assert.True(t, &expected != &actual) assert.Equal(t, expected.ICEServers, actual.ICEServers) assert.Equal(t, expected.ICETransportPolicy, actual.ICETransportPolicy) assert.Equal(t, expected.BundlePolicy, actual.BundlePolicy) assert.Equal(t, expected.RTCPMuxPolicy, actual.RTCPMuxPolicy) assert.NotEqual(t, len(expected.Certificates), len(actual.Certificates)) assert.Equal(t, expected.ICECandidatePoolSize, actual.ICECandidatePoolSize) } // TODO - This unittest needs to be completed when CreateDataChannel is complete // func TestPeerConnection_CreateDataChannel(t *testing.T) { // pc, err := New(Configuration{}) // assert.Nil(t, err) // // _, err = pc.CreateDataChannel("data", &DataChannelInit{ // // }) // assert.Nil(t, err) // } // TODO Fix this test const minimalOffer = `v=0 o=- 7193157174393298413 2 IN IP4 127.0.0.1 s=- t=0 0 a=group:BUNDLE video m=video 43858 UDP/TLS/RTP/SAVPF 96 c=IN IP4 172.17.0.1 a=candidate:3885250869 1 udp 1 127.0.0.1 1 typ host a=ice-ufrag:OgYk a=ice-pwd:G0ka4ts7hRhMLNljuuXzqnOF a=fingerprint:sha-256 D7:06:10:DE:69:66:B1:53:0E:02:33:45:63:F8:AF:78:B2:C7:CE:AF:8E:FD:E5:13:20:50:74:93:CD:B5:C8:69 a=setup:active a=mid:video a=sendrecv a=rtpmap:96 VP8/90000 ` func TestSetRemoteDescription(t *testing.T) { api := NewAPI() testCases := []struct { desc SessionDescription }{ {SessionDescription{Type: SDPTypeOffer, Sdp: minimalOffer}}, } for i, testCase := range testCases { peerConn, err := api.NewPeerConnection(Configuration{}) if err != nil { t.Errorf("Case %d: got error: %v", i, err) } err = peerConn.SetRemoteDescription(testCase.desc) if err != nil { t.Errorf("Case %d: got error: %v", i, err) } } } func TestCreateOfferAnswer(t *testing.T) { api := NewAPI() offerPeerConn, err := api.NewPeerConnection(Configuration{}) if err != nil { t.Errorf("New PeerConnection: got error: %v", err) } offer, err := offerPeerConn.CreateOffer(nil) if err != nil { t.Errorf("Create Offer: got error: %v", err) } if err = offerPeerConn.SetLocalDescription(offer); err != nil { t.Errorf("SetLocalDescription: got error: %v", err) } answerPeerConn, err := api.NewPeerConnection(Configuration{}) if err != nil { t.Errorf("New PeerConnection: got error: %v", err) } err = answerPeerConn.SetRemoteDescription(offer) if err != nil { t.Errorf("SetRemoteDescription: got error: %v", err) } answer, err := answerPeerConn.CreateAnswer(nil) if err != nil { t.Errorf("Create Answer: got error: %v", err) } if err = answerPeerConn.SetLocalDescription(answer); err != nil { t.Errorf("SetLocalDescription: got error: %v", err) } err = offerPeerConn.SetRemoteDescription(answer) if err != nil { t.Errorf("SetRemoteDescription (Originator): got error: %v", err) } } func TestPeerConnection_NewRawRTPTrack(t *testing.T) { api := NewAPI() api.mediaEngine.RegisterDefaultCodecs() pc, err := api.NewPeerConnection(Configuration{}) assert.Nil(t, err) _, err = pc.NewRawRTPTrack(DefaultPayloadTypeH264, 0, "trackId", "trackLabel") assert.NotNil(t, err) track, err := pc.NewRawRTPTrack(DefaultPayloadTypeH264, 123456, "trackId", "trackLabel") assert.Nil(t, err) _, err = pc.AddTrack(track) assert.Nil(t, err) // This channel should not be set up for a RawRTP track assert.Panics(t, func() { track.Samples <- media.Sample{} }) assert.NotPanics(t, func() { track.RawRTP <- &rtp.Packet{} }) } func TestPeerConnection_NewSampleTrack(t *testing.T) { api := NewAPI() api.mediaEngine.RegisterDefaultCodecs() pc, err := api.NewPeerConnection(Configuration{}) assert.Nil(t, err) track, err := pc.NewSampleTrack(DefaultPayloadTypeH264, "trackId", "trackLabel") assert.Nil(t, err) _, err = pc.AddTrack(track) assert.Nil(t, err) // This channel should not be set up for a Sample track assert.Panics(t, func() { track.RawRTP <- &rtp.Packet{} }) assert.NotPanics(t, func() { track.Samples <- media.Sample{} }) } func TestPeerConnection_EventHandlers(t *testing.T) { api := NewAPI() pc, err := api.NewPeerConnection(Configuration{}) assert.Nil(t, err) onTrackCalled := make(chan bool) onICEConnectionStateChangeCalled := make(chan bool) onDataChannelCalled := make(chan bool) // Verify that the noop case works assert.NotPanics(t, func() { pc.onTrack(nil) }) assert.NotPanics(t, func() { pc.onICEConnectionStateChange(ice.ConnectionStateNew) }) pc.OnTrack(func(t *Track) { onTrackCalled <- true }) pc.OnICEConnectionStateChange(func(cs ice.ConnectionState) { onICEConnectionStateChangeCalled <- true }) pc.OnDataChannel(func(dc *DataChannel) { onDataChannelCalled <- true }) // Verify that the handlers deal with nil inputs assert.NotPanics(t, func() { pc.onTrack(nil) }) assert.NotPanics(t, func() { go pc.onDataChannelHandler(nil) }) // Verify that the set handlers are called assert.NotPanics(t, func() { pc.onTrack(&Track{}) }) assert.NotPanics(t, func() { pc.onICEConnectionStateChange(ice.ConnectionStateNew) }) assert.NotPanics(t, func() { go pc.onDataChannelHandler(&DataChannel{api: api}) }) allTrue := func(vals []bool) bool { for _, val := range vals { if !val { return false } } return true } assert.True(t, allTrue([]bool{ <-onTrackCalled, <-onICEConnectionStateChangeCalled, <-onDataChannelCalled, })) }