Files
webrtc/settingengine_test.go
cnderrauber 8e2c8682de Add option to disable close by dtls
Close peerconnection on DTLS.CloseNotify
could break ice restart with dtls restart,
when the dtls finger-print changed, the
browser could teardown the old dtlstransport
and establish new one then pion could close
the peerconnection and restart failed. So
browser don't do this and spec also don't
say peerconnection should close when dtls
is closed.
2024-11-29 08:09:21 -05:00

420 lines
12 KiB
Go

// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
//go:build !js
// +build !js
package webrtc
import (
"context"
"net"
"testing"
"time"
"github.com/pion/dtls/v3/pkg/crypto/elliptic"
"github.com/pion/dtls/v3/pkg/protocol/handshake"
"github.com/pion/ice/v4"
"github.com/pion/stun/v3"
"github.com/pion/transport/v3/test"
"github.com/stretchr/testify/assert"
)
func TestSetEphemeralUDPPortRange(t *testing.T) {
s := SettingEngine{}
if s.ephemeralUDP.PortMin != 0 ||
s.ephemeralUDP.PortMax != 0 {
t.Fatalf("SettingEngine defaults aren't as expected.")
}
// set bad ephemeral ports
if err := s.SetEphemeralUDPPortRange(3000, 2999); err == nil {
t.Fatalf("Setting engine should fail bad ephemeral ports.")
}
if err := s.SetEphemeralUDPPortRange(3000, 4000); err != nil {
t.Fatalf("Setting engine failed valid port range: %s", err)
}
if s.ephemeralUDP.PortMin != 3000 ||
s.ephemeralUDP.PortMax != 4000 {
t.Fatalf("Setting engine ports do not reflect expected range")
}
}
func TestSetConnectionTimeout(t *testing.T) {
s := SettingEngine{}
var nilDuration *time.Duration
assert.Equal(t, s.timeout.ICEDisconnectedTimeout, nilDuration)
assert.Equal(t, s.timeout.ICEFailedTimeout, nilDuration)
assert.Equal(t, s.timeout.ICEKeepaliveInterval, nilDuration)
s.SetICETimeouts(1*time.Second, 2*time.Second, 3*time.Second)
assert.Equal(t, *s.timeout.ICEDisconnectedTimeout, 1*time.Second)
assert.Equal(t, *s.timeout.ICEFailedTimeout, 2*time.Second)
assert.Equal(t, *s.timeout.ICEKeepaliveInterval, 3*time.Second)
}
func TestDetachDataChannels(t *testing.T) {
s := SettingEngine{}
if s.detach.DataChannels {
t.Fatalf("SettingEngine defaults aren't as expected.")
}
s.DetachDataChannels()
if !s.detach.DataChannels {
t.Fatalf("Failed to enable detached data channels.")
}
}
func TestSetNAT1To1IPs(t *testing.T) {
s := SettingEngine{}
if s.candidates.NAT1To1IPs != nil {
t.Errorf("Invalid default value")
}
if s.candidates.NAT1To1IPCandidateType != 0 {
t.Errorf("Invalid default value")
}
ips := []string{"1.2.3.4"}
typ := ICECandidateTypeHost
s.SetNAT1To1IPs(ips, typ)
if len(s.candidates.NAT1To1IPs) != 1 || s.candidates.NAT1To1IPs[0] != "1.2.3.4" {
t.Fatalf("Failed to set NAT1To1IPs")
}
if s.candidates.NAT1To1IPCandidateType != typ {
t.Fatalf("Failed to set NAT1To1IPCandidateType")
}
}
func TestSetAnsweringDTLSRole(t *testing.T) {
s := SettingEngine{}
assert.Error(t, s.SetAnsweringDTLSRole(DTLSRoleAuto), "SetAnsweringDTLSRole can only be called with DTLSRoleClient or DTLSRoleServer")
assert.Error(t, s.SetAnsweringDTLSRole(DTLSRole(0)), "SetAnsweringDTLSRole can only be called with DTLSRoleClient or DTLSRoleServer")
}
func TestSetReplayProtection(t *testing.T) {
s := SettingEngine{}
if s.replayProtection.DTLS != nil ||
s.replayProtection.SRTP != nil ||
s.replayProtection.SRTCP != nil {
t.Fatalf("SettingEngine defaults aren't as expected.")
}
s.SetDTLSReplayProtectionWindow(128)
s.SetSRTPReplayProtectionWindow(64)
s.SetSRTCPReplayProtectionWindow(32)
if s.replayProtection.DTLS == nil ||
*s.replayProtection.DTLS != 128 {
t.Errorf("Failed to set DTLS replay protection window")
}
if s.replayProtection.SRTP == nil ||
*s.replayProtection.SRTP != 64 {
t.Errorf("Failed to set SRTP replay protection window")
}
if s.replayProtection.SRTCP == nil ||
*s.replayProtection.SRTCP != 32 {
t.Errorf("Failed to set SRTCP replay protection window")
}
}
func TestSettingEngine_SetICETCP(t *testing.T) {
report := test.CheckRoutines(t)
defer report()
listener, err := net.ListenTCP("tcp", &net.TCPAddr{})
if err != nil {
panic(err)
}
defer func() {
_ = listener.Close()
}()
tcpMux := NewICETCPMux(nil, listener, 8)
defer func() {
_ = tcpMux.Close()
}()
settingEngine := SettingEngine{}
settingEngine.SetICETCPMux(tcpMux)
assert.Equal(t, tcpMux, settingEngine.iceTCPMux)
}
func TestSettingEngine_SetDisableMediaEngineCopy(t *testing.T) {
t.Run("Copy", func(t *testing.T) {
m := &MediaEngine{}
assert.NoError(t, m.RegisterDefaultCodecs())
api := NewAPI(WithMediaEngine(m))
offerer, answerer, err := api.newPair(Configuration{})
assert.NoError(t, err)
_, err = offerer.AddTransceiverFromKind(RTPCodecTypeVideo)
assert.NoError(t, err)
assert.NoError(t, signalPair(offerer, answerer))
// Assert that the MediaEngine the user created isn't modified
assert.False(t, m.negotiatedVideo)
assert.Empty(t, m.negotiatedVideoCodecs)
// Assert that the internal MediaEngine is modified
assert.True(t, offerer.api.mediaEngine.negotiatedVideo)
assert.NotEmpty(t, offerer.api.mediaEngine.negotiatedVideoCodecs)
closePairNow(t, offerer, answerer)
newOfferer, newAnswerer, err := api.newPair(Configuration{})
assert.NoError(t, err)
// Assert that the first internal MediaEngine hasn't been cleared
assert.True(t, offerer.api.mediaEngine.negotiatedVideo)
assert.NotEmpty(t, offerer.api.mediaEngine.negotiatedVideoCodecs)
// Assert that the new internal MediaEngine isn't modified
assert.False(t, newOfferer.api.mediaEngine.negotiatedVideo)
assert.Empty(t, newAnswerer.api.mediaEngine.negotiatedVideoCodecs)
closePairNow(t, newOfferer, newAnswerer)
})
t.Run("No Copy", func(t *testing.T) {
m := &MediaEngine{}
assert.NoError(t, m.RegisterDefaultCodecs())
s := SettingEngine{}
s.DisableMediaEngineCopy(true)
api := NewAPI(WithMediaEngine(m), WithSettingEngine(s))
offerer, answerer, err := api.newPair(Configuration{})
assert.NoError(t, err)
_, err = offerer.AddTransceiverFromKind(RTPCodecTypeVideo)
assert.NoError(t, err)
assert.NoError(t, signalPair(offerer, answerer))
// Assert that the user MediaEngine was modified, so no copy happened
assert.True(t, m.negotiatedVideo)
assert.NotEmpty(t, m.negotiatedVideoCodecs)
closePairNow(t, offerer, answerer)
offerer, answerer, err = api.newPair(Configuration{})
assert.NoError(t, err)
// Assert that the new internal MediaEngine was modified, so no copy happened
assert.True(t, offerer.api.mediaEngine.negotiatedVideo)
assert.NotEmpty(t, offerer.api.mediaEngine.negotiatedVideoCodecs)
closePairNow(t, offerer, answerer)
})
}
func TestSetDTLSRetransmissionInterval(t *testing.T) {
s := SettingEngine{}
if s.dtls.retransmissionInterval != 0 {
t.Fatalf("SettingEngine defaults aren't as expected.")
}
s.SetDTLSRetransmissionInterval(100 * time.Millisecond)
if s.dtls.retransmissionInterval == 0 ||
s.dtls.retransmissionInterval != 100*time.Millisecond {
t.Errorf("Failed to set DTLS retransmission interval")
}
s.SetDTLSRetransmissionInterval(1 * time.Second)
if s.dtls.retransmissionInterval == 0 ||
s.dtls.retransmissionInterval != 1*time.Second {
t.Errorf("Failed to set DTLS retransmission interval")
}
}
func TestSetDTLSEllipticCurves(t *testing.T) {
s := SettingEngine{}
if len(s.dtls.ellipticCurves) != 0 {
t.Fatalf("SettingEngine defaults aren't as expected.")
}
s.SetDTLSEllipticCurves(elliptic.P256)
if len(s.dtls.ellipticCurves) == 0 ||
s.dtls.ellipticCurves[0] != elliptic.P256 {
t.Errorf("Failed to set DTLS elliptic curves")
}
}
func TestSetDTLSHandShakeTimeout(*testing.T) {
s := SettingEngine{}
s.SetDTLSConnectContextMaker(func() (context.Context, func()) {
return context.WithTimeout(context.Background(), 60*time.Second)
})
}
func TestSetSCTPMaxReceiverBufferSize(t *testing.T) {
s := SettingEngine{}
assert.Equal(t, uint32(0), s.sctp.maxReceiveBufferSize)
expSize := uint32(4 * 1024 * 1024)
s.SetSCTPMaxReceiveBufferSize(expSize)
assert.Equal(t, expSize, s.sctp.maxReceiveBufferSize)
}
func TestSetSCTPRTOMax(t *testing.T) {
s := SettingEngine{}
assert.Equal(t, time.Duration(0), s.sctp.rtoMax)
expSize := time.Second
s.SetSCTPRTOMax(expSize)
assert.Equal(t, expSize, s.sctp.rtoMax)
}
func TestSetICEBindingRequestHandler(t *testing.T) {
seenICEControlled, seenICEControlledCancel := context.WithCancel(context.Background())
seenICEControlling, seenICEControllingCancel := context.WithCancel(context.Background())
s := SettingEngine{}
s.SetICEBindingRequestHandler(func(m *stun.Message, _, _ ice.Candidate, _ *ice.CandidatePair) bool {
for _, a := range m.Attributes {
switch a.Type {
case stun.AttrICEControlled:
seenICEControlledCancel()
case stun.AttrICEControlling:
seenICEControllingCancel()
default:
}
}
return false
})
pcOffer, pcAnswer, err := NewAPI(WithSettingEngine(s)).newPair(Configuration{})
assert.NoError(t, err)
assert.NoError(t, signalPair(pcOffer, pcAnswer))
<-seenICEControlled.Done()
<-seenICEControlling.Done()
closePairNow(t, pcOffer, pcAnswer)
}
func TestSetHooks(t *testing.T) {
s := SettingEngine{}
if s.dtls.clientHelloMessageHook != nil ||
s.dtls.serverHelloMessageHook != nil ||
s.dtls.certificateRequestMessageHook != nil {
t.Fatalf("SettingEngine defaults aren't as expected.")
}
s.SetDTLSClientHelloMessageHook(func(msg handshake.MessageClientHello) handshake.Message {
return &msg
})
s.SetDTLSServerHelloMessageHook(func(msg handshake.MessageServerHello) handshake.Message {
return &msg
})
s.SetDTLSCertificateRequestMessageHook(func(msg handshake.MessageCertificateRequest) handshake.Message {
return &msg
})
if s.dtls.clientHelloMessageHook == nil {
t.Errorf("Failed to set DTLS Client Hello Hook")
}
if s.dtls.serverHelloMessageHook == nil {
t.Errorf("Failed to set DTLS Server Hello Hook")
}
if s.dtls.certificateRequestMessageHook == nil {
t.Errorf("Failed to set DTLS Certificate Request Hook")
}
}
func TestSetFireOnTrackBeforeFirstRTP(t *testing.T) {
lim := test.TimeOut(time.Second * 30)
defer lim.Stop()
report := test.CheckRoutines(t)
defer report()
s := SettingEngine{}
s.SetFireOnTrackBeforeFirstRTP(true)
mediaEngineOne := &MediaEngine{}
assert.NoError(t, mediaEngineOne.RegisterCodec(RTPCodecParameters{
RTPCodecCapability: RTPCodecCapability{MimeType: "video/VP8", ClockRate: 90000, Channels: 0, SDPFmtpLine: "", RTCPFeedback: nil},
PayloadType: 100,
}, RTPCodecTypeVideo))
mediaEngineTwo := &MediaEngine{}
assert.NoError(t, mediaEngineTwo.RegisterCodec(RTPCodecParameters{
RTPCodecCapability: RTPCodecCapability{MimeType: "video/VP8", ClockRate: 90000, Channels: 0, SDPFmtpLine: "", RTCPFeedback: nil},
PayloadType: 200,
}, RTPCodecTypeVideo))
offerer, err := NewAPI(WithMediaEngine(mediaEngineOne), WithSettingEngine(s)).NewPeerConnection(Configuration{})
assert.NoError(t, err)
answerer, err := NewAPI(WithMediaEngine(mediaEngineTwo)).NewPeerConnection(Configuration{})
assert.NoError(t, err)
track, err := NewTrackLocalStaticSample(RTPCodecCapability{MimeType: MimeTypeVP8}, "video", "pion")
assert.NoError(t, err)
_, err = offerer.AddTransceiverFromKind(RTPCodecTypeVideo)
assert.NoError(t, err)
_, err = answerer.AddTrack(track)
assert.NoError(t, err)
onTrackFired, onTrackFiredFunc := context.WithCancel(context.Background())
offerer.OnTrack(func(track *TrackRemote, _ *RTPReceiver) {
_, _, err = track.Read(make([]byte, 1500))
assert.NoError(t, err)
assert.Equal(t, track.PayloadType(), PayloadType(100))
assert.Equal(t, track.Codec().RTPCodecCapability.MimeType, "video/VP8")
onTrackFiredFunc()
})
assert.NoError(t, signalPair(offerer, answerer))
sendVideoUntilDone(onTrackFired.Done(), t, []*TrackLocalStaticSample{track})
closePairNow(t, offerer, answerer)
}
func TestDisableCloseByDTLS(t *testing.T) {
lim := test.TimeOut(time.Second * 30)
defer lim.Stop()
report := test.CheckRoutines(t)
defer report()
s := SettingEngine{}
s.DisableCloseByDTLS(true)
offer, answer, err := NewAPI(WithSettingEngine(s)).newPair(Configuration{})
assert.NoError(t, err)
assert.NoError(t, signalPair(offer, answer))
untilConnectionState(PeerConnectionStateConnected, offer, answer).Wait()
assert.NoError(t, answer.Close())
time.Sleep(time.Second)
assert.True(t, offer.ConnectionState() == PeerConnectionStateConnected)
assert.NoError(t, offer.Close())
}