Files
webrtc/peerconnection_go_test.go
Alex Browne 5ee8b1a5c5 Add ICE candidate event handlers
Add OnICECandidate and OnICEGatheringStateChange methods to
PeerConnection. The main goal of this change is to improve API
compatibility with the JavaScript/Wasm bindings. It does not actually
add trickle ICE support or change the ICE candidate gathering process,
which is still synchronous in the Go implementation. Rather, it fires
the appropriate events similar to they way they would be fired in a true
trickle ICE process.

Remove unused OnNegotiationNeeded event handler. This handler is not
required for most applications and would be difficult to implement in
Go. This commit removes the handler from the JavaScript/Wasm bindings,
which leads to a more similar API for Go and JavaScript/Wasm.

Add OnICEGatheringStateChange to the JavaScript/Wasm bindings. Also
changes the Go implementation so that the function signatures match.
2019-03-25 14:22:11 -07:00

367 lines
10 KiB
Go

// +build !js
package webrtc
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/x509"
"math/big"
"reflect"
"testing"
"time"
"github.com/pions/transport/test"
"github.com/pions/webrtc/internal/ice"
"github.com/pions/webrtc/internal/mux"
"github.com/pions/webrtc/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")
}