mirror of
https://github.com/pion/webrtc.git
synced 2025-10-05 15:16:52 +08:00
378 lines
10 KiB
Go
378 lines
10 KiB
Go
// +build !js
|
|
|
|
package webrtc
|
|
|
|
import (
|
|
"crypto/ecdsa"
|
|
"crypto/elliptic"
|
|
"crypto/rand"
|
|
"crypto/x509"
|
|
"math/big"
|
|
"reflect"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/pion/ice"
|
|
"github.com/pion/transport/test"
|
|
"github.com/pion/webrtc/v2/internal/mux"
|
|
"github.com/pion/webrtc/v2/pkg/rtcerr"
|
|
"github.com/stretchr/testify/assert"
|
|
)
|
|
|
|
// newPair creates two new peer connections (an offerer and an answerer) using
|
|
// the api.
|
|
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 TestNew_Go(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_Go(t *testing.T) {
|
|
// Note: this test includes all SetConfiguration features that are supported
|
|
// by Go but not the WASM bindings, namely: ICEServer.Credential,
|
|
// ICEServer.CredentialType, and Certificates.
|
|
|
|
api := NewAPI()
|
|
|
|
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)
|
|
|
|
for _, test := range []struct {
|
|
name string
|
|
init func() (*PeerConnection, error)
|
|
config Configuration
|
|
wantErr error
|
|
}{
|
|
{
|
|
name: "valid",
|
|
init: func() (*PeerConnection, error) {
|
|
pc, err := api.NewPeerConnection(Configuration{
|
|
PeerIdentity: "unittest",
|
|
Certificates: []Certificate{*certificate1},
|
|
ICECandidatePoolSize: 5,
|
|
})
|
|
if err != nil {
|
|
return pc, 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{*certificate1},
|
|
ICECandidatePoolSize: 5,
|
|
})
|
|
if err != nil {
|
|
return pc, err
|
|
}
|
|
|
|
return pc, nil
|
|
},
|
|
config: Configuration{},
|
|
wantErr: nil,
|
|
},
|
|
{
|
|
name: "update multiple certificates",
|
|
init: func() (*PeerConnection, error) {
|
|
return api.NewPeerConnection(Configuration{})
|
|
},
|
|
config: Configuration{
|
|
Certificates: []Certificate{*certificate1, *certificate2},
|
|
},
|
|
wantErr: &rtcerr.InvalidModificationError{Err: ErrModifyingCertificates},
|
|
},
|
|
{
|
|
name: "update certificate",
|
|
init: func() (*PeerConnection, error) {
|
|
return api.NewPeerConnection(Configuration{})
|
|
},
|
|
config: Configuration{
|
|
Certificates: []Certificate{*certificate1},
|
|
},
|
|
wantErr: &rtcerr.InvalidModificationError{Err: ErrModifyingCertificates},
|
|
},
|
|
{
|
|
name: "update ICEServers, no TURN credentials",
|
|
init: func() (*PeerConnection, error) {
|
|
return NewPeerConnection(Configuration{})
|
|
},
|
|
config: Configuration{
|
|
ICEServers: []ICEServer{
|
|
{
|
|
URLs: []string{
|
|
"stun:stun.l.google.com:19302",
|
|
"turns:google.de?transport=tcp",
|
|
},
|
|
Username: "unittest",
|
|
},
|
|
},
|
|
},
|
|
wantErr: &rtcerr.InvalidAccessError{Err: ErrNoTurnCredencials},
|
|
},
|
|
} {
|
|
pc, err := test.init()
|
|
if err != nil {
|
|
t.Errorf("SetConfiguration %q: init failed: %v", test.name, err)
|
|
}
|
|
|
|
err = pc.SetConfiguration(test.config)
|
|
if got, want := err, test.wantErr; !reflect.DeepEqual(got, want) {
|
|
t.Errorf("SetConfiguration %q: err = %v, want %v", test.name, got, want)
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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)
|
|
// }
|
|
|
|
func TestPeerConnection_EventHandlers_Go(t *testing.T) {
|
|
// Note: When testing the Go event handlers we peer into the state a bit more
|
|
// than what is possible for the environment agnostic (Go or WASM/JavaScript)
|
|
// EventHandlers test.
|
|
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, nil) })
|
|
assert.NotPanics(t, func() { pc.onICEConnectionStateChange(ice.ConnectionStateNew) })
|
|
|
|
pc.OnTrack(func(t *Track, r *RTPReceiver) {
|
|
onTrackCalled <- true
|
|
})
|
|
|
|
pc.OnICEConnectionStateChange(func(cs ICEConnectionState) {
|
|
onICEConnectionStateChangeCalled <- true
|
|
})
|
|
|
|
pc.OnDataChannel(func(dc *DataChannel) {
|
|
onDataChannelCalled <- true
|
|
})
|
|
|
|
// Verify that the handlers deal with nil inputs
|
|
assert.NotPanics(t, func() { pc.onTrack(nil, nil) })
|
|
assert.NotPanics(t, func() { go pc.onDataChannelHandler(nil) })
|
|
|
|
// Verify that the set handlers are called
|
|
assert.NotPanics(t, func() { pc.onTrack(&Track{}, &RTPReceiver{}) })
|
|
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,
|
|
}))
|
|
}
|
|
|
|
// This test asserts that nothing deadlocks we try to shutdown when DTLS is in flight
|
|
// We ensure that DTLS is in flight by removing the mux func for it, so all inbound DTLS is lost
|
|
func TestPeerConnection_ShutdownNoDTLS(t *testing.T) {
|
|
dtlsMatchFunc := mux.MatchDTLS
|
|
defer func() {
|
|
mux.MatchDTLS = dtlsMatchFunc
|
|
}()
|
|
|
|
// Drop all incoming DTLS traffic
|
|
mux.MatchDTLS = func([]byte) bool {
|
|
return false
|
|
}
|
|
|
|
lim := test.TimeOut(time.Second * 10)
|
|
defer lim.Stop()
|
|
|
|
api := NewAPI()
|
|
offerPC, answerPC, err := api.newPair()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if err = signalPair(offerPC, answerPC); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
iceComplete := make(chan interface{})
|
|
answerPC.OnICEConnectionStateChange(func(iceState ICEConnectionState) {
|
|
if iceState == ICEConnectionStateConnected {
|
|
time.Sleep(time.Second) // Give time for DTLS to start
|
|
|
|
select {
|
|
case <-iceComplete:
|
|
default:
|
|
close(iceComplete)
|
|
}
|
|
}
|
|
})
|
|
|
|
<-iceComplete
|
|
if err = offerPC.Close(); err != nil {
|
|
t.Fatal(err)
|
|
} else if err = answerPC.Close(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func TestPeerConnection_PeropertyGetters(t *testing.T) {
|
|
pc := &PeerConnection{
|
|
currentLocalDescription: &SessionDescription{},
|
|
pendingLocalDescription: &SessionDescription{},
|
|
currentRemoteDescription: &SessionDescription{},
|
|
pendingRemoteDescription: &SessionDescription{},
|
|
signalingState: SignalingStateHaveLocalOffer,
|
|
iceGatheringState: ICEGatheringStateGathering,
|
|
iceConnectionState: ICEConnectionStateChecking,
|
|
connectionState: PeerConnectionStateConnecting,
|
|
}
|
|
|
|
assert.Equal(t, pc.currentLocalDescription, pc.CurrentLocalDescription(), "should match")
|
|
assert.Equal(t, pc.pendingLocalDescription, pc.PendingLocalDescription(), "should match")
|
|
assert.Equal(t, pc.currentRemoteDescription, pc.CurrentRemoteDescription(), "should match")
|
|
assert.Equal(t, pc.pendingRemoteDescription, pc.PendingRemoteDescription(), "should match")
|
|
assert.Equal(t, pc.signalingState, pc.SignalingState(), "should match")
|
|
assert.Equal(t, pc.iceGatheringState, pc.ICEGatheringState(), "should match")
|
|
assert.Equal(t, pc.iceConnectionState, pc.ICEConnectionState(), "should match")
|
|
assert.Equal(t, pc.connectionState, pc.ConnectionState(), "should match")
|
|
}
|
|
|
|
func TestPeerConnection_AnswerWithoutOffer(t *testing.T) {
|
|
pc, err := NewPeerConnection(Configuration{})
|
|
if err != nil {
|
|
t.Errorf("New PeerConnection: got error: %v", err)
|
|
}
|
|
_, err = pc.CreateAnswer(nil)
|
|
if !reflect.DeepEqual(&rtcerr.InvalidStateError{Err: ErrNoRemoteDescription}, err) {
|
|
t.Errorf("CreateAnswer without RemoteDescription: got error: %v", err)
|
|
}
|
|
}
|