status-go/vendor/github.com/pion/webrtc/v3/datachannel.go

696 lines
19 KiB
Go

// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
//go:build !js
// +build !js
package webrtc
import (
"errors"
"fmt"
"io"
"math"
"sync"
"sync/atomic"
"time"
"github.com/pion/datachannel"
"github.com/pion/logging"
"github.com/pion/webrtc/v3/pkg/rtcerr"
)
const dataChannelBufferSize = math.MaxUint16 // message size limit for Chromium
var errSCTPNotEstablished = errors.New("SCTP not established")
// DataChannel represents a WebRTC DataChannel
// The DataChannel interface represents a network channel
// which can be used for bidirectional peer-to-peer transfers of arbitrary data
type DataChannel struct {
mu sync.RWMutex
statsID string
label string
ordered bool
maxPacketLifeTime *uint16
maxRetransmits *uint16
protocol string
negotiated bool
id *uint16
readyState atomic.Value // DataChannelState
bufferedAmountLowThreshold uint64
detachCalled bool
readLoopActive chan struct{}
isGracefulClosed bool
// The binaryType represents attribute MUST, on getting, return the value to
// which it was last set. On setting, if the new value is either the string
// "blob" or the string "arraybuffer", then set the IDL attribute to this
// new value. Otherwise, throw a SyntaxError. When an DataChannel object
// is created, the binaryType attribute MUST be initialized to the string
// "blob". This attribute controls how binary data is exposed to scripts.
// binaryType string
onMessageHandler func(DataChannelMessage)
openHandlerOnce sync.Once
onOpenHandler func()
dialHandlerOnce sync.Once
onDialHandler func()
onCloseHandler func()
onBufferedAmountLow func()
onErrorHandler func(error)
sctpTransport *SCTPTransport
dataChannel *datachannel.DataChannel
// A reference to the associated api object used by this datachannel
api *API
log logging.LeveledLogger
}
// NewDataChannel creates a new DataChannel.
// This constructor is part of the ORTC API. It is not
// meant to be used together with the basic WebRTC API.
func (api *API) NewDataChannel(transport *SCTPTransport, params *DataChannelParameters) (*DataChannel, error) {
d, err := api.newDataChannel(params, nil, api.settingEngine.LoggerFactory.NewLogger("ortc"))
if err != nil {
return nil, err
}
err = d.open(transport)
if err != nil {
return nil, err
}
return d, nil
}
// newDataChannel is an internal constructor for the data channel used to
// create the DataChannel object before the networking is set up.
func (api *API) newDataChannel(params *DataChannelParameters, sctpTransport *SCTPTransport, log logging.LeveledLogger) (*DataChannel, error) {
// https://w3c.github.io/webrtc-pc/#peer-to-peer-data-api (Step #5)
if len(params.Label) > 65535 {
return nil, &rtcerr.TypeError{Err: ErrStringSizeLimit}
}
d := &DataChannel{
sctpTransport: sctpTransport,
statsID: fmt.Sprintf("DataChannel-%d", time.Now().UnixNano()),
label: params.Label,
protocol: params.Protocol,
negotiated: params.Negotiated,
id: params.ID,
ordered: params.Ordered,
maxPacketLifeTime: params.MaxPacketLifeTime,
maxRetransmits: params.MaxRetransmits,
api: api,
log: log,
}
d.setReadyState(DataChannelStateConnecting)
return d, nil
}
// open opens the datachannel over the sctp transport
func (d *DataChannel) open(sctpTransport *SCTPTransport) error {
association := sctpTransport.association()
if association == nil {
return errSCTPNotEstablished
}
d.mu.Lock()
if d.sctpTransport != nil { // already open
d.mu.Unlock()
return nil
}
d.sctpTransport = sctpTransport
var channelType datachannel.ChannelType
var reliabilityParameter uint32
switch {
case d.maxPacketLifeTime == nil && d.maxRetransmits == nil:
if d.ordered {
channelType = datachannel.ChannelTypeReliable
} else {
channelType = datachannel.ChannelTypeReliableUnordered
}
case d.maxRetransmits != nil:
reliabilityParameter = uint32(*d.maxRetransmits)
if d.ordered {
channelType = datachannel.ChannelTypePartialReliableRexmit
} else {
channelType = datachannel.ChannelTypePartialReliableRexmitUnordered
}
default:
reliabilityParameter = uint32(*d.maxPacketLifeTime)
if d.ordered {
channelType = datachannel.ChannelTypePartialReliableTimed
} else {
channelType = datachannel.ChannelTypePartialReliableTimedUnordered
}
}
cfg := &datachannel.Config{
ChannelType: channelType,
Priority: datachannel.ChannelPriorityNormal,
ReliabilityParameter: reliabilityParameter,
Label: d.label,
Protocol: d.protocol,
Negotiated: d.negotiated,
LoggerFactory: d.api.settingEngine.LoggerFactory,
}
if d.id == nil {
// avoid holding lock when generating ID, since id generation locks
d.mu.Unlock()
var dcID *uint16
err := d.sctpTransport.generateAndSetDataChannelID(d.sctpTransport.dtlsTransport.role(), &dcID)
if err != nil {
return err
}
d.mu.Lock()
d.id = dcID
}
dc, err := datachannel.Dial(association, *d.id, cfg)
if err != nil {
d.mu.Unlock()
return err
}
// bufferedAmountLowThreshold and onBufferedAmountLow might be set earlier
dc.SetBufferedAmountLowThreshold(d.bufferedAmountLowThreshold)
dc.OnBufferedAmountLow(d.onBufferedAmountLow)
d.mu.Unlock()
d.onDial()
d.handleOpen(dc, false, d.negotiated)
return nil
}
// Transport returns the SCTPTransport instance the DataChannel is sending over.
func (d *DataChannel) Transport() *SCTPTransport {
d.mu.RLock()
defer d.mu.RUnlock()
return d.sctpTransport
}
// After onOpen is complete check that the user called detach
// and provide an error message if the call was missed
func (d *DataChannel) checkDetachAfterOpen() {
d.mu.RLock()
defer d.mu.RUnlock()
if d.api.settingEngine.detach.DataChannels && !d.detachCalled {
d.log.Warn("webrtc.DetachDataChannels() enabled but didn't Detach, call Detach from OnOpen")
}
}
// OnOpen sets an event handler which is invoked when
// the underlying data transport has been established (or re-established).
func (d *DataChannel) OnOpen(f func()) {
d.mu.Lock()
d.openHandlerOnce = sync.Once{}
d.onOpenHandler = f
d.mu.Unlock()
if d.ReadyState() == DataChannelStateOpen {
// If the data channel is already open, call the handler immediately.
go d.openHandlerOnce.Do(func() {
f()
d.checkDetachAfterOpen()
})
}
}
func (d *DataChannel) onOpen() {
d.mu.RLock()
handler := d.onOpenHandler
if d.isGracefulClosed {
d.mu.RUnlock()
return
}
d.mu.RUnlock()
if handler != nil {
go d.openHandlerOnce.Do(func() {
handler()
d.checkDetachAfterOpen()
})
}
}
// OnDial sets an event handler which is invoked when the
// peer has been dialed, but before said peer has responsed
func (d *DataChannel) OnDial(f func()) {
d.mu.Lock()
d.dialHandlerOnce = sync.Once{}
d.onDialHandler = f
d.mu.Unlock()
if d.ReadyState() == DataChannelStateOpen {
// If the data channel is already open, call the handler immediately.
go d.dialHandlerOnce.Do(f)
}
}
func (d *DataChannel) onDial() {
d.mu.RLock()
handler := d.onDialHandler
if d.isGracefulClosed {
d.mu.RUnlock()
return
}
d.mu.RUnlock()
if handler != nil {
go d.dialHandlerOnce.Do(handler)
}
}
// OnClose sets an event handler which is invoked when
// the underlying data transport has been closed.
// Note: Due to backwards compatibility, there is a chance that
// OnClose can be called, even if the GracefulClose is used.
// If this is the case for you, you can deregister OnClose
// prior to GracefulClose.
func (d *DataChannel) OnClose(f func()) {
d.mu.Lock()
defer d.mu.Unlock()
d.onCloseHandler = f
}
func (d *DataChannel) onClose() {
d.mu.RLock()
handler := d.onCloseHandler
d.mu.RUnlock()
if handler != nil {
go handler()
}
}
// OnMessage sets an event handler which is invoked on a binary
// message arrival over the sctp transport from a remote peer.
// OnMessage can currently receive messages up to 16384 bytes
// in size. Check out the detach API if you want to use larger
// message sizes. Note that browser support for larger messages
// is also limited.
func (d *DataChannel) OnMessage(f func(msg DataChannelMessage)) {
d.mu.Lock()
defer d.mu.Unlock()
d.onMessageHandler = f
}
func (d *DataChannel) onMessage(msg DataChannelMessage) {
d.mu.RLock()
handler := d.onMessageHandler
if d.isGracefulClosed {
d.mu.RUnlock()
return
}
d.mu.RUnlock()
if handler == nil {
return
}
handler(msg)
}
func (d *DataChannel) handleOpen(dc *datachannel.DataChannel, isRemote, isAlreadyNegotiated bool) {
d.mu.Lock()
if d.isGracefulClosed {
d.mu.Unlock()
return
}
d.dataChannel = dc
bufferedAmountLowThreshold := d.bufferedAmountLowThreshold
onBufferedAmountLow := d.onBufferedAmountLow
d.mu.Unlock()
d.setReadyState(DataChannelStateOpen)
// Fire the OnOpen handler immediately not using pion/datachannel
// * detached datachannels have no read loop, the user needs to read and query themselves
// * remote datachannels should fire OnOpened. This isn't spec compliant, but we can't break behavior yet
// * already negotiated datachannels should fire OnOpened
if d.api.settingEngine.detach.DataChannels || isRemote || isAlreadyNegotiated {
// bufferedAmountLowThreshold and onBufferedAmountLow might be set earlier
d.dataChannel.SetBufferedAmountLowThreshold(bufferedAmountLowThreshold)
d.dataChannel.OnBufferedAmountLow(onBufferedAmountLow)
d.onOpen()
} else {
dc.OnOpen(func() {
d.onOpen()
})
}
d.mu.Lock()
defer d.mu.Unlock()
if d.isGracefulClosed {
return
}
if !d.api.settingEngine.detach.DataChannels {
d.readLoopActive = make(chan struct{})
go d.readLoop()
}
}
// OnError sets an event handler which is invoked when
// the underlying data transport cannot be read.
func (d *DataChannel) OnError(f func(err error)) {
d.mu.Lock()
defer d.mu.Unlock()
d.onErrorHandler = f
}
func (d *DataChannel) onError(err error) {
d.mu.RLock()
handler := d.onErrorHandler
if d.isGracefulClosed {
d.mu.RUnlock()
return
}
d.mu.RUnlock()
if handler != nil {
go handler(err)
}
}
// See https://github.com/pion/webrtc/issues/1516
// nolint:gochecknoglobals
var rlBufPool = sync.Pool{New: func() interface{} {
return make([]byte, dataChannelBufferSize)
}}
func (d *DataChannel) readLoop() {
defer func() {
d.mu.Lock()
readLoopActive := d.readLoopActive
d.mu.Unlock()
defer close(readLoopActive)
}()
for {
buffer := rlBufPool.Get().([]byte) //nolint:forcetypeassert
n, isString, err := d.dataChannel.ReadDataChannel(buffer)
if err != nil {
rlBufPool.Put(buffer) // nolint:staticcheck
d.setReadyState(DataChannelStateClosed)
if !errors.Is(err, io.EOF) {
d.onError(err)
}
d.onClose()
return
}
m := DataChannelMessage{Data: make([]byte, n), IsString: isString}
copy(m.Data, buffer[:n])
// The 'staticcheck' pragma is a false positive on the part of the CI linter.
rlBufPool.Put(buffer) // nolint:staticcheck
// NB: Why was DataChannelMessage not passed as a pointer value?
d.onMessage(m) // nolint:staticcheck
}
}
// Send sends the binary message to the DataChannel peer
func (d *DataChannel) Send(data []byte) error {
err := d.ensureOpen()
if err != nil {
return err
}
_, err = d.dataChannel.WriteDataChannel(data, false)
return err
}
// SendText sends the text message to the DataChannel peer
func (d *DataChannel) SendText(s string) error {
err := d.ensureOpen()
if err != nil {
return err
}
_, err = d.dataChannel.WriteDataChannel([]byte(s), true)
return err
}
func (d *DataChannel) ensureOpen() error {
d.mu.RLock()
defer d.mu.RUnlock()
if d.ReadyState() != DataChannelStateOpen {
return io.ErrClosedPipe
}
return nil
}
// Detach allows you to detach the underlying datachannel. This provides
// an idiomatic API to work with, however it disables the OnMessage callback.
// Before calling Detach you have to enable this behavior by calling
// webrtc.DetachDataChannels(). Combining detached and normal data channels
// is not supported.
// Please refer to the data-channels-detach example and the
// pion/datachannel documentation for the correct way to handle the
// resulting DataChannel object.
func (d *DataChannel) Detach() (datachannel.ReadWriteCloser, error) {
d.mu.Lock()
defer d.mu.Unlock()
if !d.api.settingEngine.detach.DataChannels {
return nil, errDetachNotEnabled
}
if d.dataChannel == nil {
return nil, errDetachBeforeOpened
}
d.detachCalled = true
return d.dataChannel, nil
}
// Close Closes the DataChannel. It may be called regardless of whether
// the DataChannel object was created by this peer or the remote peer.
func (d *DataChannel) Close() error {
return d.close(false)
}
// GracefulClose Closes the DataChannel. It may be called regardless of whether
// the DataChannel object was created by this peer or the remote peer. It also waits
// for any goroutines it started to complete. This is only safe to call outside of
// DataChannel callbacks or if in a callback, in its own goroutine.
func (d *DataChannel) GracefulClose() error {
return d.close(true)
}
// Normally, close only stops writes from happening, so graceful=true
// will wait for reads to be finished based on underlying SCTP association
// closure or a SCTP reset stream from the other side. This is safe to call
// with graceful=true after tearing down a PeerConnection but not
// necessarily before. For example, if you used a vnet and dropped all packets
// right before closing the DataChannel, you'd need never see a reset stream.
func (d *DataChannel) close(shouldGracefullyClose bool) error {
d.mu.Lock()
d.isGracefulClosed = true
readLoopActive := d.readLoopActive
if shouldGracefullyClose && readLoopActive != nil {
defer func() {
<-readLoopActive
}()
}
haveSctpTransport := d.dataChannel != nil
d.mu.Unlock()
if d.ReadyState() == DataChannelStateClosed {
return nil
}
d.setReadyState(DataChannelStateClosing)
if !haveSctpTransport {
return nil
}
return d.dataChannel.Close()
}
// Label represents a label that can be used to distinguish this
// DataChannel object from other DataChannel objects. Scripts are
// allowed to create multiple DataChannel objects with the same label.
func (d *DataChannel) Label() string {
d.mu.RLock()
defer d.mu.RUnlock()
return d.label
}
// Ordered returns true if the DataChannel is ordered, and false if
// out-of-order delivery is allowed.
func (d *DataChannel) Ordered() bool {
d.mu.RLock()
defer d.mu.RUnlock()
return d.ordered
}
// MaxPacketLifeTime represents the length of the time window (msec) during
// which transmissions and retransmissions may occur in unreliable mode.
func (d *DataChannel) MaxPacketLifeTime() *uint16 {
d.mu.RLock()
defer d.mu.RUnlock()
return d.maxPacketLifeTime
}
// MaxRetransmits represents the maximum number of retransmissions that are
// attempted in unreliable mode.
func (d *DataChannel) MaxRetransmits() *uint16 {
d.mu.RLock()
defer d.mu.RUnlock()
return d.maxRetransmits
}
// Protocol represents the name of the sub-protocol used with this
// DataChannel.
func (d *DataChannel) Protocol() string {
d.mu.RLock()
defer d.mu.RUnlock()
return d.protocol
}
// Negotiated represents whether this DataChannel was negotiated by the
// application (true), or not (false).
func (d *DataChannel) Negotiated() bool {
d.mu.RLock()
defer d.mu.RUnlock()
return d.negotiated
}
// ID represents the ID for this DataChannel. The value is initially
// null, which is what will be returned if the ID was not provided at
// channel creation time, and the DTLS role of the SCTP transport has not
// yet been negotiated. Otherwise, it will return the ID that was either
// selected by the script or generated. After the ID is set to a non-null
// value, it will not change.
func (d *DataChannel) ID() *uint16 {
d.mu.RLock()
defer d.mu.RUnlock()
return d.id
}
// ReadyState represents the state of the DataChannel object.
func (d *DataChannel) ReadyState() DataChannelState {
if v, ok := d.readyState.Load().(DataChannelState); ok {
return v
}
return DataChannelState(0)
}
// BufferedAmount represents the number of bytes of application data
// (UTF-8 text and binary data) that have been queued using send(). Even
// though the data transmission can occur in parallel, the returned value
// MUST NOT be decreased before the current task yielded back to the event
// loop to prevent race conditions. The value does not include framing
// overhead incurred by the protocol, or buffering done by the operating
// system or network hardware. The value of BufferedAmount slot will only
// increase with each call to the send() method as long as the ReadyState is
// open; however, BufferedAmount does not reset to zero once the channel
// closes.
func (d *DataChannel) BufferedAmount() uint64 {
d.mu.RLock()
defer d.mu.RUnlock()
if d.dataChannel == nil {
return 0
}
return d.dataChannel.BufferedAmount()
}
// BufferedAmountLowThreshold represents the threshold at which the
// bufferedAmount is considered to be low. When the bufferedAmount decreases
// from above this threshold to equal or below it, the bufferedamountlow
// event fires. BufferedAmountLowThreshold is initially zero on each new
// DataChannel, but the application may change its value at any time.
// The threshold is set to 0 by default.
func (d *DataChannel) BufferedAmountLowThreshold() uint64 {
d.mu.RLock()
defer d.mu.RUnlock()
if d.dataChannel == nil {
return d.bufferedAmountLowThreshold
}
return d.dataChannel.BufferedAmountLowThreshold()
}
// SetBufferedAmountLowThreshold is used to update the threshold.
// See BufferedAmountLowThreshold().
func (d *DataChannel) SetBufferedAmountLowThreshold(th uint64) {
d.mu.Lock()
defer d.mu.Unlock()
d.bufferedAmountLowThreshold = th
if d.dataChannel != nil {
d.dataChannel.SetBufferedAmountLowThreshold(th)
}
}
// OnBufferedAmountLow sets an event handler which is invoked when
// the number of bytes of outgoing data becomes lower than the
// BufferedAmountLowThreshold.
func (d *DataChannel) OnBufferedAmountLow(f func()) {
d.mu.Lock()
defer d.mu.Unlock()
d.onBufferedAmountLow = f
if d.dataChannel != nil {
d.dataChannel.OnBufferedAmountLow(f)
}
}
func (d *DataChannel) getStatsID() string {
d.mu.Lock()
defer d.mu.Unlock()
return d.statsID
}
func (d *DataChannel) collectStats(collector *statsReportCollector) {
collector.Collecting()
d.mu.Lock()
defer d.mu.Unlock()
stats := DataChannelStats{
Timestamp: statsTimestampNow(),
Type: StatsTypeDataChannel,
ID: d.statsID,
Label: d.label,
Protocol: d.protocol,
// TransportID string `json:"transportId"`
State: d.ReadyState(),
}
if d.id != nil {
stats.DataChannelIdentifier = int32(*d.id)
}
if d.dataChannel != nil {
stats.MessagesSent = d.dataChannel.MessagesSent()
stats.BytesSent = d.dataChannel.BytesSent()
stats.MessagesReceived = d.dataChannel.MessagesReceived()
stats.BytesReceived = d.dataChannel.BytesReceived()
}
collector.Collect(stats.ID, stats)
}
func (d *DataChannel) setReadyState(r DataChannelState) {
d.readyState.Store(r)
}