diff options
Diffstat (limited to 'vendor/github.com/pion')
599 files changed, 63956 insertions, 0 deletions
diff --git a/vendor/github.com/pion/datachannel/.gitignore b/vendor/github.com/pion/datachannel/.gitignore new file mode 100644 index 0000000..23501a3 --- /dev/null +++ b/vendor/github.com/pion/datachannel/.gitignore @@ -0,0 +1,2 @@ +# vim temporary files +*.sw[poe] diff --git a/vendor/github.com/pion/datachannel/.golangci.yml b/vendor/github.com/pion/datachannel/.golangci.yml new file mode 100644 index 0000000..570f17b --- /dev/null +++ b/vendor/github.com/pion/datachannel/.golangci.yml @@ -0,0 +1,8 @@ +linters-settings: + govet: + check-shadowing: true + misspell: + locale: US + +run: + skip-dirs-use-default: false diff --git a/vendor/github.com/pion/datachannel/DESIGN.md b/vendor/github.com/pion/datachannel/DESIGN.md new file mode 100644 index 0000000..55d6c8f --- /dev/null +++ b/vendor/github.com/pion/datachannel/DESIGN.md @@ -0,0 +1,20 @@ +<h1 align="center"> + Design +</h1> + +### Portable +Pion Data Channels is written in Go and extremely portable. Anywhere Golang runs, Pion Data Channels should work as well! Instead of dealing with complicated +cross-compiling of multiple libraries, you now can run anywhere with one `go build` + +### Simple API +The API is based on an io.ReadWriteCloser. + +### Readable +If code comes from an RFC we try to make sure everything is commented with a link to the spec. +This makes learning and debugging easier, this library was written to also serve as a guide for others. + +### Tested +Every commit is tested via travis-ci Go provides fantastic facilities for testing, and more will be added as time goes on. + +### Shared libraries +Every pion product is built using shared libraries, allowing others to review and reuse our libraries. diff --git a/vendor/github.com/pion/datachannel/LICENSE b/vendor/github.com/pion/datachannel/LICENSE new file mode 100644 index 0000000..ab60297 --- /dev/null +++ b/vendor/github.com/pion/datachannel/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/pion/datachannel/README.md b/vendor/github.com/pion/datachannel/README.md new file mode 100644 index 0000000..1132f62 --- /dev/null +++ b/vendor/github.com/pion/datachannel/README.md @@ -0,0 +1,45 @@ +<h1 align="center"> + <br> + Pion Data Channels + <br> +</h1> +<h4 align="center">A Go implementation of WebRTC Data Channels</h4> +<p align="center"> + <a href="https://pion.ly"><img src="https://img.shields.io/badge/pion-datachannel-gray.svg?longCache=true&colorB=brightgreen" alt="Pion Data Channels"></a> + <!--<a href="https://sourcegraph.com/github.com/pion/webrtc?badge"><img src="https://sourcegraph.com/github.com/pion/webrtc/-/badge.svg" alt="Sourcegraph Widget"></a>--> + <a href="https://pion.ly/slack"><img src="https://img.shields.io/badge/join-us%20on%20slack-gray.svg?longCache=true&logo=slack&colorB=brightgreen" alt="Slack Widget"></a> + <br> + <a href="https://travis-ci.org/pion/datachannel"><img src="https://travis-ci.org/pion/datachannel.svg?branch=master" alt="Build Status"></a> + <a href="https://pkg.go.dev/github.com/pion/datachannel"><img src="https://godoc.org/github.com/pion/datachannel?status.svg" alt="GoDoc"></a> + <a href="https://codecov.io/gh/pion/datachannel"><img src="https://codecov.io/gh/pion/datachannel/branch/master/graph/badge.svg" alt="Coverage Status"></a> + <a href="https://goreportcard.com/report/github.com/pion/datachannel"><img src="https://goreportcard.com/badge/github.com/pion/datachannel" alt="Go Report Card"></a> + <!--<a href="https://www.codacy.com/app/Sean-Der/webrtc"><img src="https://api.codacy.com/project/badge/Grade/18f4aec384894e6aac0b94effe51961d" alt="Codacy Badge"></a>--> + <a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="License: MIT"></a> +</p> +<br> + +See [DESIGN.md](DESIGN.md) for an overview of features and future goals. + +### Roadmap +The library is used as a part of our WebRTC implementation. Please refer to that [roadmap](https://github.com/pion/webrtc/issues/9) to track our major milestones. + +### Community +Pion has an active community on the [Golang Slack](https://invite.slack.golangbridge.org/). Sign up and join the **#pion** channel for discussions and support. You can also use [Pion mailing list](https://groups.google.com/forum/#!forum/pion). + +We are always looking to support **your projects**. Please reach out if you have something to build! + +If you need commercial support or don't want to use public methods you can contact us at [team@pion.ly](mailto:team@pion.ly) + +### Contributing +Check out the **[contributing wiki](https://github.com/pion/webrtc/wiki/Contributing)** to join the group of amazing people making this project possible: + +* [John Bradley](https://github.com/kc5nra) - *Original Author* +* [Sean DuBois](https://github.com/Sean-Der) - *Original Author* +* [Michiel De Backker](https://github.com/backkem) - *Public API* +* [Yutaka Takeda](https://github.com/enobufs) - *PR-SCTP* +* [Hugo Arregui](https://github.com/hugoArregui) +* [Atsushi Watanabe](https://github.com/at-wat) +* [Norman Rasmussen](https://github.com/normanr) - *Fix Empty DataChannel messages* + +### License +MIT License - see [LICENSE](LICENSE) for full text diff --git a/vendor/github.com/pion/datachannel/codecov.yml b/vendor/github.com/pion/datachannel/codecov.yml new file mode 100644 index 0000000..085200a --- /dev/null +++ b/vendor/github.com/pion/datachannel/codecov.yml @@ -0,0 +1,20 @@ +# +# DO NOT EDIT THIS FILE +# +# It is automatically copied from https://github.com/pion/.goassets repository. +# + +coverage: + status: + project: + default: + # Allow decreasing 2% of total coverage to avoid noise. + threshold: 2% + patch: + default: + target: 70% + only_pulls: true + +ignore: + - "examples/*" + - "examples/**/*" diff --git a/vendor/github.com/pion/datachannel/datachannel.go b/vendor/github.com/pion/datachannel/datachannel.go new file mode 100644 index 0000000..237ad72 --- /dev/null +++ b/vendor/github.com/pion/datachannel/datachannel.go @@ -0,0 +1,378 @@ +// Package datachannel implements WebRTC Data Channels +package datachannel + +import ( + "fmt" + "io" + "sync/atomic" + + "github.com/pion/logging" + "github.com/pion/sctp" + "github.com/pkg/errors" +) + +const receiveMTU = 8192 + +// Reader is an extended io.Reader +// that also returns if the message is text. +type Reader interface { + ReadDataChannel([]byte) (int, bool, error) +} + +// Writer is an extended io.Writer +// that also allows indicating if a message is text. +type Writer interface { + WriteDataChannel([]byte, bool) (int, error) +} + +// ReadWriteCloser is an extended io.ReadWriteCloser +// that also implements our Reader and Writer. +type ReadWriteCloser interface { + io.Reader + io.Writer + Reader + Writer + io.Closer +} + +// DataChannel represents a data channel +type DataChannel struct { + Config + + // stats + messagesSent uint32 + messagesReceived uint32 + bytesSent uint64 + bytesReceived uint64 + + stream *sctp.Stream + log logging.LeveledLogger +} + +// Config is used to configure the data channel. +type Config struct { + ChannelType ChannelType + Negotiated bool + Priority uint16 + ReliabilityParameter uint32 + Label string + Protocol string + LoggerFactory logging.LoggerFactory +} + +func newDataChannel(stream *sctp.Stream, config *Config) (*DataChannel, error) { + return &DataChannel{ + Config: *config, + stream: stream, + log: config.LoggerFactory.NewLogger("datachannel"), + }, nil +} + +// Dial opens a data channels over SCTP +func Dial(a *sctp.Association, id uint16, config *Config) (*DataChannel, error) { + stream, err := a.OpenStream(id, sctp.PayloadTypeWebRTCBinary) + if err != nil { + return nil, err + } + + dc, err := Client(stream, config) + if err != nil { + return nil, err + } + + return dc, nil +} + +// Client opens a data channel over an SCTP stream +func Client(stream *sctp.Stream, config *Config) (*DataChannel, error) { + msg := &channelOpen{ + ChannelType: config.ChannelType, + Priority: config.Priority, + ReliabilityParameter: config.ReliabilityParameter, + + Label: []byte(config.Label), + Protocol: []byte(config.Protocol), + } + + if !config.Negotiated { + rawMsg, err := msg.Marshal() + if err != nil { + return nil, fmt.Errorf("failed to marshal ChannelOpen %v", err) + } + + if _, err = stream.WriteSCTP(rawMsg, sctp.PayloadTypeWebRTCDCEP); err != nil { + return nil, fmt.Errorf("failed to send ChannelOpen %v", err) + } + } + return newDataChannel(stream, config) +} + +// Accept is used to accept incoming data channels over SCTP +func Accept(a *sctp.Association, config *Config) (*DataChannel, error) { + stream, err := a.AcceptStream() + if err != nil { + return nil, err + } + + stream.SetDefaultPayloadType(sctp.PayloadTypeWebRTCBinary) + + dc, err := Server(stream, config) + if err != nil { + return nil, err + } + + return dc, nil +} + +// Server accepts a data channel over an SCTP stream +func Server(stream *sctp.Stream, config *Config) (*DataChannel, error) { + buffer := make([]byte, receiveMTU) // TODO: Can probably be smaller + n, ppi, err := stream.ReadSCTP(buffer) + if err != nil { + return nil, err + } + + if ppi != sctp.PayloadTypeWebRTCDCEP { + return nil, fmt.Errorf("unexpected packet type: %s", ppi) + } + + openMsg, err := parseExpectDataChannelOpen(buffer[:n]) + if err != nil { + return nil, errors.Wrap(err, "failed to parse DataChannelOpen packet") + } + + config.ChannelType = openMsg.ChannelType + config.Priority = openMsg.Priority + config.ReliabilityParameter = openMsg.ReliabilityParameter + config.Label = string(openMsg.Label) + config.Protocol = string(openMsg.Protocol) + + dataChannel, err := newDataChannel(stream, config) + if err != nil { + return nil, err + } + + err = dataChannel.writeDataChannelAck() + if err != nil { + return nil, err + } + + err = dataChannel.commitReliabilityParams() + if err != nil { + return nil, err + } + return dataChannel, nil +} + +// Read reads a packet of len(p) bytes as binary data +func (c *DataChannel) Read(p []byte) (int, error) { + n, _, err := c.ReadDataChannel(p) + return n, err +} + +// ReadDataChannel reads a packet of len(p) bytes +func (c *DataChannel) ReadDataChannel(p []byte) (int, bool, error) { + for { + n, ppi, err := c.stream.ReadSCTP(p) + if err == io.EOF { + // When the peer sees that an incoming stream was + // reset, it also resets its corresponding outgoing stream. + closeErr := c.stream.Close() + if closeErr != nil { + return 0, false, closeErr + } + } + if err != nil { + return 0, false, err + } + + var isString bool + switch ppi { + case sctp.PayloadTypeWebRTCDCEP: + err = c.handleDCEP(p[:n]) + if err != nil { + c.log.Errorf("Failed to handle DCEP: %s", err.Error()) + continue + } + continue + case sctp.PayloadTypeWebRTCString, sctp.PayloadTypeWebRTCStringEmpty: + isString = true + } + switch ppi { + case sctp.PayloadTypeWebRTCBinaryEmpty, sctp.PayloadTypeWebRTCStringEmpty: + n = 0 + } + + atomic.AddUint32(&c.messagesReceived, 1) + atomic.AddUint64(&c.bytesReceived, uint64(n)) + + return n, isString, err + } +} + +// MessagesSent returns the number of messages sent +func (c *DataChannel) MessagesSent() uint32 { + return atomic.LoadUint32(&c.messagesSent) +} + +// MessagesReceived returns the number of messages received +func (c *DataChannel) MessagesReceived() uint32 { + return atomic.LoadUint32(&c.messagesReceived) +} + +// BytesSent returns the number of bytes sent +func (c *DataChannel) BytesSent() uint64 { + return atomic.LoadUint64(&c.bytesSent) +} + +// BytesReceived returns the number of bytes received +func (c *DataChannel) BytesReceived() uint64 { + return atomic.LoadUint64(&c.bytesReceived) +} + +// StreamIdentifier returns the Stream identifier associated to the stream. +func (c *DataChannel) StreamIdentifier() uint16 { + return c.stream.StreamIdentifier() +} + +func (c *DataChannel) handleDCEP(data []byte) error { + msg, err := parse(data) + if err != nil { + return errors.Wrap(err, "Failed to parse DataChannel packet") + } + + switch msg := msg.(type) { + case *channelOpen: + c.log.Debug("Received DATA_CHANNEL_OPEN") + err = c.writeDataChannelAck() + if err != nil { + return fmt.Errorf("failed to ACK channel open: %v", err) + } + // Note: DATA_CHANNEL_OPEN message is handled inside Server() method. + // Therefore, the message will not reach here. + + case *channelAck: + c.log.Debug("Received DATA_CHANNEL_ACK") + err = c.commitReliabilityParams() + if err != nil { + return err + } + // TODO: handle ChannelAck (https://tools.ietf.org/html/draft-ietf-rtcweb-data-protocol-09#section-5.2) + + default: + return fmt.Errorf("unhandled DataChannel message %v", msg) + } + + return nil +} + +// Write writes len(p) bytes from p as binary data +func (c *DataChannel) Write(p []byte) (n int, err error) { + return c.WriteDataChannel(p, false) +} + +// WriteDataChannel writes len(p) bytes from p +func (c *DataChannel) WriteDataChannel(p []byte, isString bool) (n int, err error) { + // https://tools.ietf.org/html/draft-ietf-rtcweb-data-channel-12#section-6.6 + // SCTP does not support the sending of empty user messages. Therefore, + // if an empty message has to be sent, the appropriate PPID (WebRTC + // String Empty or WebRTC Binary Empty) is used and the SCTP user + // message of one zero byte is sent. When receiving an SCTP user + // message with one of these PPIDs, the receiver MUST ignore the SCTP + // user message and process it as an empty message. + var ppi sctp.PayloadProtocolIdentifier + switch { + case !isString && len(p) > 0: + ppi = sctp.PayloadTypeWebRTCBinary + case !isString && len(p) == 0: + ppi = sctp.PayloadTypeWebRTCBinaryEmpty + case isString && len(p) > 0: + ppi = sctp.PayloadTypeWebRTCString + case isString && len(p) == 0: + ppi = sctp.PayloadTypeWebRTCStringEmpty + } + + atomic.AddUint32(&c.messagesSent, 1) + atomic.AddUint64(&c.bytesSent, uint64(len(p))) + + if len(p) == 0 { + _, err := c.stream.WriteSCTP([]byte{0}, ppi) + return 0, err + } + return c.stream.WriteSCTP(p, ppi) +} + +func (c *DataChannel) writeDataChannelAck() error { + ack := channelAck{} + ackMsg, err := ack.Marshal() + if err != nil { + return fmt.Errorf("failed to marshal ChannelOpen ACK: %v", err) + } + + _, err = c.stream.WriteSCTP(ackMsg, sctp.PayloadTypeWebRTCDCEP) + if err != nil { + return fmt.Errorf("failed to send ChannelOpen ACK: %v", err) + } + + return err +} + +// Close closes the DataChannel and the underlying SCTP stream. +func (c *DataChannel) Close() error { + // https://tools.ietf.org/html/draft-ietf-rtcweb-data-channel-13#section-6.7 + // Closing of a data channel MUST be signaled by resetting the + // corresponding outgoing streams [RFC6525]. This means that if one + // side decides to close the data channel, it resets the corresponding + // outgoing stream. When the peer sees that an incoming stream was + // reset, it also resets its corresponding outgoing stream. Once this + // is completed, the data channel is closed. Resetting a stream sets + // the Stream Sequence Numbers (SSNs) of the stream back to 'zero' with + // a corresponding notification to the application layer that the reset + // has been performed. Streams are available for reuse after a reset + // has been performed. + return c.stream.Close() +} + +// BufferedAmount returns the number of bytes of data currently queued to be +// sent over this stream. +func (c *DataChannel) BufferedAmount() uint64 { + return c.stream.BufferedAmount() +} + +// BufferedAmountLowThreshold returns the number of bytes of buffered outgoing +// data that is considered "low." Defaults to 0. +func (c *DataChannel) BufferedAmountLowThreshold() uint64 { + return c.stream.BufferedAmountLowThreshold() +} + +// SetBufferedAmountLowThreshold is used to update the threshold. +// See BufferedAmountLowThreshold(). +func (c *DataChannel) SetBufferedAmountLowThreshold(th uint64) { + c.stream.SetBufferedAmountLowThreshold(th) +} + +// OnBufferedAmountLow sets the callback handler which would be called when the +// number of bytes of outgoing data buffered is lower than the threshold. +func (c *DataChannel) OnBufferedAmountLow(f func()) { + c.stream.OnBufferedAmountLow(f) +} + +func (c *DataChannel) commitReliabilityParams() error { + switch c.Config.ChannelType { + case ChannelTypeReliable: + c.stream.SetReliabilityParams(false, sctp.ReliabilityTypeReliable, c.Config.ReliabilityParameter) + case ChannelTypeReliableUnordered: + c.stream.SetReliabilityParams(true, sctp.ReliabilityTypeReliable, c.Config.ReliabilityParameter) + case ChannelTypePartialReliableRexmit: + c.stream.SetReliabilityParams(false, sctp.ReliabilityTypeRexmit, c.Config.ReliabilityParameter) + case ChannelTypePartialReliableRexmitUnordered: + c.stream.SetReliabilityParams(true, sctp.ReliabilityTypeRexmit, c.Config.ReliabilityParameter) + case ChannelTypePartialReliableTimed: + c.stream.SetReliabilityParams(false, sctp.ReliabilityTypeTimed, c.Config.ReliabilityParameter) + case ChannelTypePartialReliableTimedUnordered: + c.stream.SetReliabilityParams(true, sctp.ReliabilityTypeTimed, c.Config.ReliabilityParameter) + default: + return fmt.Errorf("invalid ChannelType: %v ", c.Config.ChannelType) + } + return nil +} diff --git a/vendor/github.com/pion/datachannel/go.mod b/vendor/github.com/pion/datachannel/go.mod new file mode 100644 index 0000000..e635596 --- /dev/null +++ b/vendor/github.com/pion/datachannel/go.mod @@ -0,0 +1,11 @@ +module github.com/pion/datachannel + +require ( + github.com/pion/logging v0.2.2 + github.com/pion/sctp v1.7.10 + github.com/pion/transport v0.10.1 + github.com/pkg/errors v0.9.1 + github.com/stretchr/testify v1.6.1 +) + +go 1.13 diff --git a/vendor/github.com/pion/datachannel/go.sum b/vendor/github.com/pion/datachannel/go.sum new file mode 100644 index 0000000..f9ede08 --- /dev/null +++ b/vendor/github.com/pion/datachannel/go.sum @@ -0,0 +1,38 @@ +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY= +github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms= +github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA= +github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8= +github.com/pion/sctp v1.7.10 h1:o3p3/hZB5Cx12RMGyWmItevJtZ6o2cpuxaw6GOS4x+8= +github.com/pion/sctp v1.7.10/go.mod h1:EhpTUQu1/lcK3xI+eriS6/96fWetHGCvBi9MSsnaBN0= +github.com/pion/transport v0.10.1 h1:2W+yJT+0mOQ160ThZYUx5Zp2skzshiNgxrNE9GUfhJM= +github.com/pion/transport v0.10.1/go.mod h1:PBis1stIILMiis0PewDw91WJeLJkyIMcEk+DwKOzf4A= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20200625001655-4c5254603344 h1:vGXIOMxbNfDTk/aXCmfdLgkrSV+Z2tcbze+pEc3v5W4= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/vendor/github.com/pion/datachannel/message.go b/vendor/github.com/pion/datachannel/message.go new file mode 100644 index 0000000..84665cb --- /dev/null +++ b/vendor/github.com/pion/datachannel/message.go @@ -0,0 +1,94 @@ +package datachannel + +import ( + "fmt" + + "github.com/pkg/errors" +) + +// message is a parsed DataChannel message +type message interface { + Marshal() ([]byte, error) + Unmarshal([]byte) error +} + +// messageType is the first byte in a DataChannel message that specifies type +type messageType byte + +// DataChannel Message Types +const ( + dataChannelAck messageType = 0x02 + dataChannelOpen messageType = 0x03 +) + +func (t messageType) String() string { + switch t { + case dataChannelAck: + return "DataChannelAck" + case dataChannelOpen: + return "DataChannelOpen" + default: + return fmt.Sprintf("Unknown MessageType: %d", t) + } +} + +// parse accepts raw input and returns a DataChannel message +func parse(raw []byte) (message, error) { + if len(raw) == 0 { + return nil, errors.Errorf("DataChannel message is not long enough to determine type ") + } + + var msg message + switch messageType(raw[0]) { + case dataChannelOpen: + msg = &channelOpen{} + case dataChannelAck: + msg = &channelAck{} + default: + return nil, errors.Errorf("Unknown MessageType %v", messageType(raw[0])) + } + + if err := msg.Unmarshal(raw); err != nil { + return nil, err + } + + return msg, nil +} + +// parseExpectDataChannelOpen parses a DataChannelOpen message +// or throws an error +func parseExpectDataChannelOpen(raw []byte) (*channelOpen, error) { + if len(raw) == 0 { + return nil, errors.Errorf("the DataChannel message is not long enough to determine type") + } + + if actualTyp := messageType(raw[0]); actualTyp != dataChannelOpen { + return nil, errors.Errorf("expected DataChannelOpen but got %s", actualTyp) + } + + msg := &channelOpen{} + if err := msg.Unmarshal(raw); err != nil { + return nil, err + } + + return msg, nil +} + +// parseExpectDataChannelAck parses a DataChannelAck message +// or throws an error +// func parseExpectDataChannelAck(raw []byte) (*channelAck, error) { +// if len(raw) == 0 { +// return nil, errors.Errorf("the DataChannel message is not long enough to determine type") +// } +// +// if actualTyp := messageType(raw[0]); actualTyp != dataChannelAck { +// return nil, errors.Errorf("expected DataChannelAck but got %s", actualTyp) +// } +// +// msg := &channelAck{} +// if err := msg.Unmarshal(raw); err != nil { +// return nil, err +// } +// +// return msg, nil +// } diff --git a/vendor/github.com/pion/datachannel/message_channel_ack.go b/vendor/github.com/pion/datachannel/message_channel_ack.go new file mode 100644 index 0000000..fd20757 --- /dev/null +++ b/vendor/github.com/pion/datachannel/message_channel_ack.go @@ -0,0 +1,22 @@ +package datachannel + +// channelAck is used to ACK a DataChannel open +type channelAck struct{} + +const ( + channelOpenAckLength = 4 +) + +// Marshal returns raw bytes for the given message +func (c *channelAck) Marshal() ([]byte, error) { + raw := make([]byte, channelOpenAckLength) + raw[0] = uint8(dataChannelAck) + + return raw, nil +} + +// Unmarshal populates the struct with the given raw data +func (c *channelAck) Unmarshal(raw []byte) error { + // Message type already checked in Parse and there is no further data + return nil +} diff --git a/vendor/github.com/pion/datachannel/message_channel_open.go b/vendor/github.com/pion/datachannel/message_channel_open.go new file mode 100644 index 0000000..9dce036 --- /dev/null +++ b/vendor/github.com/pion/datachannel/message_channel_open.go @@ -0,0 +1,123 @@ +package datachannel + +import ( + "encoding/binary" + + "github.com/pkg/errors" +) + +/* +channelOpen represents a DATA_CHANNEL_OPEN Message + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| Message Type | Channel Type | Priority | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| Reliability Parameter | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| Label Length | Protocol Length | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| | +| Label | +| | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| | +| Protocol | +| | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +*/ +type channelOpen struct { + ChannelType ChannelType + Priority uint16 + ReliabilityParameter uint32 + + Label []byte + Protocol []byte +} + +const ( + channelOpenHeaderLength = 12 +) + +// ChannelType determines the reliability of the WebRTC DataChannel +type ChannelType byte + +// ChannelType enums +const ( + // ChannelTypeReliable determines the Data Channel provides a + // reliable in-order bi-directional communication. + ChannelTypeReliable ChannelType = 0x00 + // ChannelTypeReliableUnordered determines the Data Channel + // provides a reliable unordered bi-directional communication. + ChannelTypeReliableUnordered ChannelType = 0x80 + // ChannelTypePartialReliableRexmit determines the Data Channel + // provides a partially-reliable in-order bi-directional communication. + // User messages will not be retransmitted more times than specified in the Reliability Parameter. + ChannelTypePartialReliableRexmit ChannelType = 0x01 + // ChannelTypePartialReliableRexmitUnordered determines + // the Data Channel provides a partial reliable unordered bi-directional communication. + // User messages will not be retransmitted more times than specified in the Reliability Parameter. + ChannelTypePartialReliableRexmitUnordered ChannelType = 0x81 + // ChannelTypePartialReliableTimed determines the Data Channel + // provides a partial reliable in-order bi-directional communication. + // User messages might not be transmitted or retransmitted after + // a specified life-time given in milli- seconds in the Reliability Parameter. + // This life-time starts when providing the user message to the protocol stack. + ChannelTypePartialReliableTimed ChannelType = 0x02 + // The Data Channel provides a partial reliable unordered bi-directional + // communication. User messages might not be transmitted or retransmitted + // after a specified life-time given in milli- seconds in the Reliability Parameter. + // This life-time starts when providing the user message to the protocol stack. + ChannelTypePartialReliableTimedUnordered ChannelType = 0x82 +) + +// ChannelPriority enums +const ( + ChannelPriorityBelowNormal uint16 = 128 + ChannelPriorityNormal uint16 = 256 + ChannelPriorityHigh uint16 = 512 + ChannelPriorityExtraHigh uint16 = 1024 +) + +// Marshal returns raw bytes for the given message +func (c *channelOpen) Marshal() ([]byte, error) { + labelLength := len(c.Label) + protocolLength := len(c.Protocol) + + totalLen := channelOpenHeaderLength + labelLength + protocolLength + raw := make([]byte, totalLen) + + raw[0] = uint8(dataChannelOpen) + raw[1] = byte(c.ChannelType) + binary.BigEndian.PutUint16(raw[2:], c.Priority) + binary.BigEndian.PutUint32(raw[4:], c.ReliabilityParameter) + binary.BigEndian.PutUint16(raw[8:], uint16(labelLength)) + binary.BigEndian.PutUint16(raw[10:], uint16(protocolLength)) + endLabel := channelOpenHeaderLength + labelLength + copy(raw[channelOpenHeaderLength:endLabel], c.Label) + copy(raw[endLabel:endLabel+protocolLength], c.Protocol) + + return raw, nil +} + +// Unmarshal populates the struct with the given raw data +func (c *channelOpen) Unmarshal(raw []byte) error { + if len(raw) < channelOpenHeaderLength { + return errors.Errorf("Length of input is not long enough to satisfy header %d", len(raw)) + } + c.ChannelType = ChannelType(raw[1]) + c.Priority = binary.BigEndian.Uint16(raw[2:]) + c.ReliabilityParameter = binary.BigEndian.Uint32(raw[4:]) + + labelLength := binary.BigEndian.Uint16(raw[8:]) + protocolLength := binary.BigEndian.Uint16(raw[10:]) + + if len(raw) != int(channelOpenHeaderLength+labelLength+protocolLength) { + return errors.Errorf("Label + Protocol length don't match full packet length") + } + + c.Label = raw[channelOpenHeaderLength : channelOpenHeaderLength+labelLength] + c.Protocol = raw[channelOpenHeaderLength+labelLength : channelOpenHeaderLength+labelLength+protocolLength] + return nil +} diff --git a/vendor/github.com/pion/datachannel/renovate.json b/vendor/github.com/pion/datachannel/renovate.json new file mode 100644 index 0000000..4400fd9 --- /dev/null +++ b/vendor/github.com/pion/datachannel/renovate.json @@ -0,0 +1,15 @@ +{ + "extends": [ + "config:base" + ], + "postUpdateOptions": [ + "gomodTidy" + ], + "commitBody": "Generated by renovateBot", + "packageRules": [ + { + "packagePatterns": ["^golang.org/x/"], + "schedule": ["on the first day of the month"] + } + ] +} diff --git a/vendor/github.com/pion/dtls/v2/.editorconfig b/vendor/github.com/pion/dtls/v2/.editorconfig new file mode 100644 index 0000000..d2b3206 --- /dev/null +++ b/vendor/github.com/pion/dtls/v2/.editorconfig @@ -0,0 +1,21 @@ +# http://editorconfig.org/ + +root = true + +[*] +charset = utf-8 +insert_final_newline = true +trim_trailing_whitespace = true +end_of_line = lf + +[*.go] +indent_style = tab +indent_size = 4 + +[{*.yml,*.yaml}] +indent_style = space +indent_size = 2 + +# Makefiles always use tabs for indentation +[Makefile] +indent_style = tab diff --git a/vendor/github.com/pion/dtls/v2/.gitignore b/vendor/github.com/pion/dtls/v2/.gitignore new file mode 100644 index 0000000..83db74b --- /dev/null +++ b/vendor/github.com/pion/dtls/v2/.gitignore @@ -0,0 +1,24 @@ +### JetBrains IDE ### +##################### +.idea/ + +### Emacs Temporary Files ### +############################# +*~ + +### Folders ### +############### +bin/ +vendor/ +node_modules/ + +### Files ### +############# +*.ivf +*.ogg +tags +cover.out +*.sw[poe] +*.wasm +examples/sfu-ws/cert.pem +examples/sfu-ws/key.pem diff --git a/vendor/github.com/pion/dtls/v2/.golangci.yml b/vendor/github.com/pion/dtls/v2/.golangci.yml new file mode 100644 index 0000000..d6162c9 --- /dev/null +++ b/vendor/github.com/pion/dtls/v2/.golangci.yml @@ -0,0 +1,89 @@ +linters-settings: + govet: + check-shadowing: true + misspell: + locale: US + exhaustive: + default-signifies-exhaustive: true + gomodguard: + blocked: + modules: + - github.com/pkg/errors: + recommendations: + - errors + +linters: + enable: + - asciicheck # Simple linter to check that your code does not contain non-ASCII identifiers + - bodyclose # checks whether HTTP response body is closed successfully + - deadcode # Finds unused code + - depguard # Go linter that checks if package imports are in a list of acceptable packages + - dogsled # Checks assignments with too many blank identifiers (e.g. x, _, _, _, := f()) + - dupl # Tool for code clone detection + - errcheck # Errcheck is a program for checking for unchecked errors in go programs. These unchecked errors can be critical bugs in some cases + - exhaustive # check exhaustiveness of enum switch statements + - exportloopref # checks for pointers to enclosing loop variables + - gci # Gci control golang package import order and make it always deterministic. + - gochecknoglobals # Checks that no globals are present in Go code + - gochecknoinits # Checks that no init functions are present in Go code + - gocognit # Computes and checks the cognitive complexity of functions + - goconst # Finds repeated strings that could be replaced by a constant + - gocritic # The most opinionated Go source code linter + - godox # Tool for detection of FIXME, TODO and other comment keywords + - goerr113 # Golang linter to check the errors handling expressions + - gofmt # Gofmt checks whether code was gofmt-ed. By default this tool runs with -s option to check for code simplification + - gofumpt # Gofumpt checks whether code was gofumpt-ed. + - goheader # Checks is file header matches to pattern + - goimports # Goimports does everything that gofmt does. Additionally it checks unused imports + - golint # Golint differs from gofmt. Gofmt reformats Go source code, whereas golint prints out style mistakes + - gomodguard # Allow and block list linter for direct Go module dependencies. This is different from depguard where there are different block types for example version constraints and module recommendations. + - goprintffuncname # Checks that printf-like functions are named with `f` at the end + - gosec # Inspects source code for security problems + - gosimple # Linter for Go source code that specializes in simplifying a code + - govet # Vet examines Go source code and reports suspicious constructs, such as Printf calls whose arguments do not align with the format string + - ineffassign # Detects when assignments to existing variables are not used + - misspell # Finds commonly misspelled English words in comments + - nakedret # Finds naked returns in functions greater than a specified function length + - noctx # noctx finds sending http request without context.Context + - scopelint # Scopelint checks for unpinned variables in go programs + - staticcheck # Staticcheck is a go vet on steroids, applying a ton of static analysis checks + - structcheck # Finds unused struct fields + - stylecheck # Stylecheck is a replacement for golint + - typecheck # Like the front-end of a Go compiler, parses and type-checks Go code + - unconvert # Remove unnecessary type conversions + - unparam # Reports unused function parameters + - unused # Checks Go code for unused constants, variables, functions and types + - varcheck # Finds unused global variables and constants + - whitespace # Tool for detection of leading and trailing whitespace + disable: + - funlen # Tool for detection of long functions + - gocyclo # Computes and checks the cyclomatic complexity of functions + - godot # Check if comments end in a period + - gomnd # An analyzer to detect magic numbers. + - lll # Reports long lines + - maligned # Tool to detect Go structs that would take less memory if their fields were sorted + - nestif # Reports deeply nested if statements + - nlreturn # nlreturn checks for a new line before return and branch statements to increase code clarity + - nolintlint # Reports ill-formed or insufficient nolint directives + - prealloc # Finds slice declarations that could potentially be preallocated + - rowserrcheck # checks whether Err of rows is checked successfully + - sqlclosecheck # Checks that sql.Rows and sql.Stmt are closed. + - testpackage # linter that makes you use a separate _test package + - wsl # Whitespace Linter - Forces you to use empty lines! + +issues: + exclude-use-default: false + exclude-rules: + # Allow complex tests, better to be self contained + - path: _test\.go + linters: + - gocognit + + # Allow complex main function in examples + - path: examples + text: "of func `main` is high" + linters: + - gocognit + +run: + skip-dirs-use-default: false diff --git a/vendor/github.com/pion/dtls/v2/LICENSE b/vendor/github.com/pion/dtls/v2/LICENSE new file mode 100644 index 0000000..ab60297 --- /dev/null +++ b/vendor/github.com/pion/dtls/v2/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/pion/dtls/v2/Makefile b/vendor/github.com/pion/dtls/v2/Makefile new file mode 100644 index 0000000..1df38b2 --- /dev/null +++ b/vendor/github.com/pion/dtls/v2/Makefile @@ -0,0 +1,6 @@ +fuzz-build-record-layer: fuzz-prepare + go-fuzz-build -tags gofuzz -func FuzzRecordLayer +fuzz-run-record-layer: + go-fuzz -bin dtls-fuzz.zip -workdir fuzz +fuzz-prepare: + @GO111MODULE=on go mod vendor diff --git a/vendor/github.com/pion/dtls/v2/README.md b/vendor/github.com/pion/dtls/v2/README.md new file mode 100644 index 0000000..62cac7c --- /dev/null +++ b/vendor/github.com/pion/dtls/v2/README.md @@ -0,0 +1,155 @@ +<h1 align="center"> + <br> + Pion DTLS + <br> +</h1> +<h4 align="center">A Go implementation of DTLS</h4> +<p align="center"> + <a href="https://pion.ly"><img src="https://img.shields.io/badge/pion-dtls-gray.svg?longCache=true&colorB=brightgreen" alt="Pion DTLS"></a> + <a href="https://sourcegraph.com/github.com/pion/dtls"><img src="https://sourcegraph.com/github.com/pion/dtls/-/badge.svg" alt="Sourcegraph Widget"></a> + <a href="https://pion.ly/slack"><img src="https://img.shields.io/badge/join-us%20on%20slack-gray.svg?longCache=true&logo=slack&colorB=brightgreen" alt="Slack Widget"></a> + <br> + <a href="https://travis-ci.org/pion/dtls"><img src="https://travis-ci.org/pion/dtls.svg?branch=master" alt="Build Status"></a> + <a href="https://pkg.go.dev/github.com/pion/dtls"><img src="https://godoc.org/github.com/pion/dtls?status.svg" alt="GoDoc"></a> + <a href="https://codecov.io/gh/pion/dtls"><img src="https://codecov.io/gh/pion/dtls/branch/master/graph/badge.svg" alt="Coverage Status"></a> + <a href="https://goreportcard.com/report/github.com/pion/dtls"><img src="https://goreportcard.com/badge/github.com/pion/dtls" alt="Go Report Card"></a> + <a href="https://www.codacy.com/app/Sean-Der/dtls"><img src="https://api.codacy.com/project/badge/Grade/18f4aec384894e6aac0b94effe51961d" alt="Codacy Badge"></a> + <a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="License: MIT"></a> +</p> +<br> + +Native [DTLS 1.2][rfc6347] implementation in the Go programming language. + +A long term goal is a professional security review, and maye inclusion in stdlib. + +[rfc6347]: https://tools.ietf.org/html/rfc6347 + +### Goals/Progress +This will only be targeting DTLS 1.2, and the most modern/common cipher suites. +We would love contributes that fall under the 'Planned Features' and fixing any bugs! + +#### Current features +* DTLS 1.2 Client/Server +* Key Exchange via ECDHE(curve25519, nistp256, nistp384) and PSK +* Packet loss and re-ordering is handled during handshaking +* Key export ([RFC 5705][rfc5705]) +* Serialization and Resumption of sessions +* Extended Master Secret extension ([RFC 7627][rfc7627]) + +[rfc5705]: https://tools.ietf.org/html/rfc5705 +[rfc7627]: https://tools.ietf.org/html/rfc7627 + +#### Supported ciphers + +##### ECDHE +* TLS_ECDHE_ECDSA_WITH_AES_128_CCM ([RFC 6655][rfc6655]) +* TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8 ([RFC 6655][rfc6655]) +* TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 ([RFC 5289][rfc5289]) +* TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 ([RFC 5289][rfc5289]) +* TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA ([RFC 8422][rfc8422]) +* TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA ([RFC 8422][rfc8422]) + +##### PSK +* TLS_PSK_WITH_AES_128_CCM ([RFC 6655][rfc6655]) +* TLS_PSK_WITH_AES_128_CCM_8 ([RFC 6655][rfc6655]) +* TLS_PSK_WITH_AES_128_GCM_SHA256 ([RFC 5487][rfc5487]) +* TLS_PSK_WITH_AES_128_CBC_SHA256 ([RFC 5487][rfc5487]) + +[rfc5289]: https://tools.ietf.org/html/rfc5289 +[rfc8422]: https://tools.ietf.org/html/rfc8422 +[rfc6655]: https://tools.ietf.org/html/rfc6655 +[rfc5487]: https://tools.ietf.org/html/rfc5487 + +#### Planned Features +* Chacha20Poly1305 + +#### Excluded Features +* DTLS 1.0 +* Renegotiation +* Compression + +### Using + +This library needs at least Go 1.13, and you should have [Go modules +enabled](https://github.com/golang/go/wiki/Modules). + +#### Pion DTLS +For a DTLS 1.2 Server that listens on 127.0.0.1:4444 +```sh +go run examples/listen/selfsign/main.go +``` + +For a DTLS 1.2 Client that connects to 127.0.0.1:4444 +```sh +go run examples/dial/selfsign/main.go +``` + +#### OpenSSL +Pion DTLS can connect to itself and OpenSSL. +``` + // Generate a certificate + openssl ecparam -out key.pem -name prime256v1 -genkey + openssl req -new -sha256 -key key.pem -out server.csr + openssl x509 -req -sha256 -days 365 -in server.csr -signkey key.pem -out cert.pem + + // Use with examples/dial/selfsign/main.go + openssl s_server -dtls1_2 -cert cert.pem -key key.pem -accept 4444 + + // Use with examples/listen/selfsign/main.go + openssl s_client -dtls1_2 -connect 127.0.0.1:4444 -debug -cert cert.pem -key key.pem +``` + +### Using with PSK +Pion DTLS also comes with examples that do key exchange via PSK + + +#### Pion DTLS +```sh +go run examples/listen/psk/main.go +``` + +```sh +go run examples/dial/psk/main.go +``` + +#### OpenSSL +``` + // Use with examples/dial/psk/main.go + openssl s_server -dtls1_2 -accept 4444 -nocert -psk abc123 -cipher PSK-AES128-CCM8 + + // Use with examples/listen/psk/main.go + openssl s_client -dtls1_2 -connect 127.0.0.1:4444 -psk abc123 -cipher PSK-AES128-CCM8 +``` + +### Contributing +Check out the **[contributing wiki](https://github.com/pion/webrtc/wiki/Contributing)** to join the group of amazing people making this project possible: + +* [Sean DuBois](https://github.com/Sean-Der) - *Original Author* +* [Michiel De Backker](https://github.com/backkem) - *Public API* +* [Chris Hiszpanski](https://github.com/thinkski) - *Support Signature Algorithms Extension* +* [Iñigo Garcia Olaizola](https://github.com/igolaizola) - *Serialization & resumption, cert verification, E2E* +* [Daniele Sluijters](https://github.com/daenney) - *AES-CCM support* +* [Jin Lei](https://github.com/jinleileiking) - *Logging* +* [Hugo Arregui](https://github.com/hugoArregui) +* [Lander Noterman](https://github.com/LanderN) +* [Aleksandr Razumov](https://github.com/ernado) - *Fuzzing* +* [Ryan Gordon](https://github.com/ryangordon) +* [Stefan Tatschner](https://rumpelsepp.org/contact.html) +* [Hayden James](https://github.com/hjames9) +* [Jozef Kralik](https://github.com/jkralik) +* [Robert Eperjesi](https://github.com/epes) +* [Atsushi Watanabe](https://github.com/at-wat) +* [Julien Salleyron](https://github.com/juliens) - *Server Name Indication* +* [Jeroen de Bruijn](https://github.com/vidavidorra) +* [bjdgyc](https://github.com/bjdgyc) +* [Jeffrey Stoke (Jeff Ctor)](https://github.com/jeffreystoke) - *Fragmentbuffer Fix* +* [Frank Olbricht](https://github.com/folbricht) +* [ZHENK](https://github.com/scorpionknifes) +* [Carson Hoffman](https://github.com/CarsonHoffman) +* [Vadim Filimonov](https://github.com/fffilimonov) +* [Jim Wert](https://github.com/bocajim) +* [Alvaro Viebrantz](https://github.com/alvarowolfx) +* [Kegan Dougal](https://github.com/Kegsay) + +### License +MIT License - see [LICENSE](LICENSE) for full text diff --git a/vendor/github.com/pion/dtls/v2/certificate.go b/vendor/github.com/pion/dtls/v2/certificate.go new file mode 100644 index 0000000..c99e1c9 --- /dev/null +++ b/vendor/github.com/pion/dtls/v2/certificate.go @@ -0,0 +1,67 @@ +package dtls + +import ( + "crypto/tls" + "crypto/x509" + "strings" +) + +func (c *handshakeConfig) getCertificate(serverName string) (*tls.Certificate, error) { + c.mu.Lock() + defer c.mu.Unlock() + + if c.nameToCertificate == nil { + nameToCertificate := make(map[string]*tls.Certificate) + for i := range c.localCertificates { + cert := &c.localCertificates[i] + x509Cert := cert.Leaf + if x509Cert == nil { + var parseErr error + x509Cert, parseErr = x509.ParseCertificate(cert.Certificate[0]) + if parseErr != nil { + continue + } + } + if len(x509Cert.Subject.CommonName) > 0 { + nameToCertificate[strings.ToLower(x509Cert.Subject.CommonName)] = cert + } + for _, san := range x509Cert.DNSNames { + nameToCertificate[strings.ToLower(san)] = cert + } + } + c.nameToCertificate = nameToCertificate + } + + if len(c.localCertificates) == 0 { + return nil, errNoCertificates + } + + if len(c.localCertificates) == 1 { + // There's only one choice, so no point doing any work. + return &c.localCertificates[0], nil + } + + if len(serverName) == 0 { + return &c.localCertificates[0], nil + } + + name := strings.TrimRight(strings.ToLower(serverName), ".") + + if cert, ok := c.nameToCertificate[name]; ok { + return cert, nil + } + + // try replacing labels in the name with wildcards until we get a + // match. + labels := strings.Split(name, ".") + for i := range labels { + labels[i] = "*" + candidate := strings.Join(labels, ".") + if cert, ok := c.nameToCertificate[candidate]; ok { + return cert, nil + } + } + + // If nothing matches, return the first certificate. + return &c.localCertificates[0], nil +} diff --git a/vendor/github.com/pion/dtls/v2/cipher_suite.go b/vendor/github.com/pion/dtls/v2/cipher_suite.go new file mode 100644 index 0000000..ed10609 --- /dev/null +++ b/vendor/github.com/pion/dtls/v2/cipher_suite.go @@ -0,0 +1,213 @@ +package dtls + +import ( + "fmt" + "hash" + + "github.com/pion/dtls/v2/internal/ciphersuite" + "github.com/pion/dtls/v2/pkg/crypto/clientcertificate" + "github.com/pion/dtls/v2/pkg/protocol/recordlayer" +) + +// CipherSuiteID is an ID for our supported CipherSuites +type CipherSuiteID = ciphersuite.ID + +// Supported Cipher Suites +const ( + // AES-128-CCM + TLS_ECDHE_ECDSA_WITH_AES_128_CCM CipherSuiteID = ciphersuite.TLS_ECDHE_ECDSA_WITH_AES_128_CCM //nolint:golint,stylecheck + TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8 CipherSuiteID = ciphersuite.TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8 //nolint:golint,stylecheck + + // AES-128-GCM-SHA256 + TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 CipherSuiteID = ciphersuite.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 //nolint:golint,stylecheck + TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 CipherSuiteID = ciphersuite.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 //nolint:golint,stylecheck + + // AES-256-CBC-SHA + TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA CipherSuiteID = ciphersuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA //nolint:golint,stylecheck + TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA CipherSuiteID = ciphersuite.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA //nolint:golint,stylecheck + + TLS_PSK_WITH_AES_128_CCM CipherSuiteID = ciphersuite.TLS_PSK_WITH_AES_128_CCM //nolint:golint,stylecheck + TLS_PSK_WITH_AES_128_CCM_8 CipherSuiteID = ciphersuite.TLS_PSK_WITH_AES_128_CCM_8 //nolint:golint,stylecheck + TLS_PSK_WITH_AES_128_GCM_SHA256 CipherSuiteID = ciphersuite.TLS_PSK_WITH_AES_128_GCM_SHA256 //nolint:golint,stylecheck + TLS_PSK_WITH_AES_128_CBC_SHA256 CipherSuiteID = ciphersuite.TLS_PSK_WITH_AES_128_CBC_SHA256 //nolint:golint,stylecheck +) + +// CipherSuiteAuthenticationType controls what authentication method is using during the handshake for a CipherSuite +type CipherSuiteAuthenticationType = ciphersuite.AuthenticationType + +// AuthenticationType Enums +const ( + CipherSuiteAuthenticationTypeCertificate CipherSuiteAuthenticationType = ciphersuite.AuthenticationTypeCertificate + CipherSuiteAuthenticationTypePreSharedKey CipherSuiteAuthenticationType = ciphersuite.AuthenticationTypePreSharedKey + CipherSuiteAuthenticationTypeAnonymous CipherSuiteAuthenticationType = ciphersuite.AuthenticationTypeAnonymous +) + +var _ = allCipherSuites() // Necessary until this function isn't only used by Go 1.14 + +// CipherSuite is an interface that all DTLS CipherSuites must satisfy +type CipherSuite interface { + // String of CipherSuite, only used for logging + String() string + + // ID of CipherSuite. + ID() CipherSuiteID + + // What type of Certificate does this CipherSuite use + CertificateType() clientcertificate.Type + + // What Hash function is used during verification + HashFunc() func() hash.Hash + + // AuthenticationType controls what authentication method is using during the handshake + AuthenticationType() CipherSuiteAuthenticationType + + // Called when keying material has been generated, should initialize the internal cipher + Init(masterSecret, clientRandom, serverRandom []byte, isClient bool) error + IsInitialized() bool + + Encrypt(pkt *recordlayer.RecordLayer, raw []byte) ([]byte, error) + Decrypt(in []byte) ([]byte, error) +} + +// CipherSuiteName provides the same functionality as tls.CipherSuiteName +// that appeared first in Go 1.14. +// +// Our implementation differs slightly in that it takes in a CiperSuiteID, +// like the rest of our library, instead of a uint16 like crypto/tls. +func CipherSuiteName(id CipherSuiteID) string { + suite := cipherSuiteForID(id, nil) + if suite != nil { + return suite.String() + } + return fmt.Sprintf("0x%04X", uint16(id)) +} + +// Taken from https://www.iana.org/assignments/tls-parameters/tls-parameters.xml +// A cipherSuite is a specific combination of key agreement, cipher and MAC +// function. +func cipherSuiteForID(id CipherSuiteID, customCiphers func() []CipherSuite) CipherSuite { + switch id { //nolint:exhaustive + case TLS_ECDHE_ECDSA_WITH_AES_128_CCM: + return ciphersuite.NewTLSEcdheEcdsaWithAes128Ccm() + case TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8: + return ciphersuite.NewTLSEcdheEcdsaWithAes128Ccm8() + case TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256: + return &ciphersuite.TLSEcdheEcdsaWithAes128GcmSha256{} + case TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256: + return &ciphersuite.TLSEcdheRsaWithAes128GcmSha256{} + case TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA: + return &ciphersuite.TLSEcdheEcdsaWithAes256CbcSha{} + case TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA: + return &ciphersuite.TLSEcdheRsaWithAes256CbcSha{} + case TLS_PSK_WITH_AES_128_CCM: + return ciphersuite.NewTLSPskWithAes128Ccm() + case TLS_PSK_WITH_AES_128_CCM_8: + return ciphersuite.NewTLSPskWithAes128Ccm8() + case TLS_PSK_WITH_AES_128_GCM_SHA256: + return &ciphersuite.TLSPskWithAes128GcmSha256{} + case TLS_PSK_WITH_AES_128_CBC_SHA256: + return &ciphersuite.TLSPskWithAes128CbcSha256{} + } + + if customCiphers != nil { + for _, c := range customCiphers() { + if c.ID() == id { + return c + } + } + } + + return nil +} + +// CipherSuites we support in order of preference +func defaultCipherSuites() []CipherSuite { + return []CipherSuite{ + &ciphersuite.TLSEcdheEcdsaWithAes128GcmSha256{}, + &ciphersuite.TLSEcdheRsaWithAes128GcmSha256{}, + &ciphersuite.TLSEcdheEcdsaWithAes256CbcSha{}, + &ciphersuite.TLSEcdheRsaWithAes256CbcSha{}, + } +} + +func allCipherSuites() []CipherSuite { + return []CipherSuite{ + ciphersuite.NewTLSEcdheEcdsaWithAes128Ccm(), + ciphersuite.NewTLSEcdheEcdsaWithAes128Ccm8(), + &ciphersuite.TLSEcdheEcdsaWithAes128GcmSha256{}, + &ciphersuite.TLSEcdheRsaWithAes128GcmSha256{}, + &ciphersuite.TLSEcdheEcdsaWithAes256CbcSha{}, + &ciphersuite.TLSEcdheRsaWithAes256CbcSha{}, + ciphersuite.NewTLSPskWithAes128Ccm(), + ciphersuite.NewTLSPskWithAes128Ccm8(), + &ciphersuite.TLSPskWithAes128GcmSha256{}, + } +} + +func cipherSuiteIDs(cipherSuites []CipherSuite) []uint16 { + rtrn := []uint16{} + for _, c := range cipherSuites { + rtrn = append(rtrn, uint16(c.ID())) + } + return rtrn +} + +func parseCipherSuites(userSelectedSuites []CipherSuiteID, customCipherSuites func() []CipherSuite, includeCertificateSuites, includePSKSuites bool) ([]CipherSuite, error) { + cipherSuitesForIDs := func(ids []CipherSuiteID) ([]CipherSuite, error) { + cipherSuites := []CipherSuite{} + for _, id := range ids { + c := cipherSuiteForID(id, nil) + if c == nil { + return nil, &invalidCipherSuite{id} + } + cipherSuites = append(cipherSuites, c) + } + return cipherSuites, nil + } + + var ( + cipherSuites []CipherSuite + err error + i int + ) + if userSelectedSuites != nil { + cipherSuites, err = cipherSuitesForIDs(userSelectedSuites) + if err != nil { + return nil, err + } + } else { + cipherSuites = defaultCipherSuites() + } + + // Put CustomCipherSuites before ID selected suites + if customCipherSuites != nil { + cipherSuites = append(customCipherSuites(), cipherSuites...) + } + + var foundCertificateSuite, foundPSKSuite, foundAnonymousSuite bool + for _, c := range cipherSuites { + switch { + case includeCertificateSuites && c.AuthenticationType() == CipherSuiteAuthenticationTypeCertificate: + foundCertificateSuite = true + case includePSKSuites && c.AuthenticationType() == CipherSuiteAuthenticationTypePreSharedKey: + foundPSKSuite = true + case c.AuthenticationType() == CipherSuiteAuthenticationTypeAnonymous: + foundAnonymousSuite = true + default: + continue + } + cipherSuites[i] = c + i++ + } + + switch { + case includeCertificateSuites && !foundCertificateSuite && !foundAnonymousSuite: + return nil, errNoAvailableCertificateCipherSuite + case includePSKSuites && !foundPSKSuite: + return nil, errNoAvailablePSKCipherSuite + case i == 0: + return nil, errNoAvailableCipherSuites + } + + return cipherSuites[:i], nil +} diff --git a/vendor/github.com/pion/dtls/v2/cipher_suite_go114.go b/vendor/github.com/pion/dtls/v2/cipher_suite_go114.go new file mode 100644 index 0000000..7bba16e --- /dev/null +++ b/vendor/github.com/pion/dtls/v2/cipher_suite_go114.go @@ -0,0 +1,40 @@ +// +build go1.14 + +package dtls + +import ( + "crypto/tls" +) + +// VersionDTLS12 is the DTLS version in the same style as +// VersionTLSXX from crypto/tls +const VersionDTLS12 = 0xfefd + +// Convert from our cipherSuite interface to a tls.CipherSuite struct +func toTLSCipherSuite(c CipherSuite) *tls.CipherSuite { + return &tls.CipherSuite{ + ID: uint16(c.ID()), + Name: c.String(), + SupportedVersions: []uint16{VersionDTLS12}, + Insecure: false, + } +} + +// CipherSuites returns a list of cipher suites currently implemented by this +// package, excluding those with security issues, which are returned by +// InsecureCipherSuites. +func CipherSuites() []*tls.CipherSuite { + suites := allCipherSuites() + res := make([]*tls.CipherSuite, len(suites)) + for i, c := range suites { + res[i] = toTLSCipherSuite(c) + } + return res +} + +// InsecureCipherSuites returns a list of cipher suites currently implemented by +// this package and which have security issues. +func InsecureCipherSuites() []*tls.CipherSuite { + var res []*tls.CipherSuite + return res +} diff --git a/vendor/github.com/pion/dtls/v2/codecov.yml b/vendor/github.com/pion/dtls/v2/codecov.yml new file mode 100644 index 0000000..085200a --- /dev/null +++ b/vendor/github.com/pion/dtls/v2/codecov.yml @@ -0,0 +1,20 @@ +# +# DO NOT EDIT THIS FILE +# +# It is automatically copied from https://github.com/pion/.goassets repository. +# + +coverage: + status: + project: + default: + # Allow decreasing 2% of total coverage to avoid noise. + threshold: 2% + patch: + default: + target: 70% + only_pulls: true + +ignore: + - "examples/*" + - "examples/**/*" diff --git a/vendor/github.com/pion/dtls/v2/compression_method.go b/vendor/github.com/pion/dtls/v2/compression_method.go new file mode 100644 index 0000000..693eb7a --- /dev/null +++ b/vendor/github.com/pion/dtls/v2/compression_method.go @@ -0,0 +1,9 @@ +package dtls + +import "github.com/pion/dtls/v2/pkg/protocol" + +func defaultCompressionMethods() []*protocol.CompressionMethod { + return []*protocol.CompressionMethod{ + {}, + } +} diff --git a/vendor/github.com/pion/dtls/v2/config.go b/vendor/github.com/pion/dtls/v2/config.go new file mode 100644 index 0000000..5ad42da --- /dev/null +++ b/vendor/github.com/pion/dtls/v2/config.go @@ -0,0 +1,193 @@ +package dtls + +import ( + "context" + "crypto/ecdsa" + "crypto/ed25519" + "crypto/tls" + "crypto/x509" + "io" + "time" + + "github.com/pion/logging" +) + +const keyLogLabelTLS12 = "CLIENT_RANDOM" + +// Config is used to configure a DTLS client or server. +// After a Config is passed to a DTLS function it must not be modified. +type Config struct { + // Certificates contains certificate chain to present to the other side of the connection. + // Server MUST set this if PSK is non-nil + // client SHOULD sets this so CertificateRequests can be handled if PSK is non-nil + Certificates []tls.Certificate + + // CipherSuites is a list of supported cipher suites. + // If CipherSuites is nil, a default list is used + CipherSuites []CipherSuiteID + + // CustomCipherSuites is a list of CipherSuites that can be + // provided by the user. This allow users to user Ciphers that are reserved + // for private usage. + CustomCipherSuites func() []CipherSuite + + // SignatureSchemes contains the signature and hash schemes that the peer requests to verify. + SignatureSchemes []tls.SignatureScheme + + // SRTPProtectionProfiles are the supported protection profiles + // Clients will send this via use_srtp and assert that the server properly responds + // Servers will assert that clients send one of these profiles and will respond as needed + SRTPProtectionProfiles []SRTPProtectionProfile + + // ClientAuth determines the server's policy for + // TLS Client Authentication. The default is NoClientCert. + ClientAuth ClientAuthType + + // RequireExtendedMasterSecret determines if the "Extended Master Secret" extension + // should be disabled, requested, or required (default requested). + ExtendedMasterSecret ExtendedMasterSecretType + + // FlightInterval controls how often we send outbound handshake messages + // defaults to time.Second + FlightInterval time.Duration + + // PSK sets the pre-shared key used by this DTLS connection + // If PSK is non-nil only PSK CipherSuites will be used + PSK PSKCallback + PSKIdentityHint []byte + + // InsecureSkipVerify controls whether a client verifies the + // server's certificate chain and host name. + // If InsecureSkipVerify is true, TLS accepts any certificate + // presented by the server and any host name in that certificate. + // In this mode, TLS is susceptible to man-in-the-middle attacks. + // This should be used only for testing. + InsecureSkipVerify bool + + // InsecureHashes allows the use of hashing algorithms that are known + // to be vulnerable. + InsecureHashes bool + + // VerifyPeerCertificate, if not nil, is called after normal + // certificate verification by either a client or server. It + // receives the certificate provided by the peer and also a flag + // that tells if normal verification has succeedded. If it returns a + // non-nil error, the handshake is aborted and that error results. + // + // If normal verification fails then the handshake will abort before + // considering this callback. If normal verification is disabled by + // setting InsecureSkipVerify, or (for a server) when ClientAuth is + // RequestClientCert or RequireAnyClientCert, then this callback will + // be considered but the verifiedChains will always be nil. + VerifyPeerCertificate func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error + + // RootCAs defines the set of root certificate authorities + // that one peer uses when verifying the other peer's certificates. + // If RootCAs is nil, TLS uses the host's root CA set. + RootCAs *x509.CertPool + + // ClientCAs defines the set of root certificate authorities + // that servers use if required to verify a client certificate + // by the policy in ClientAuth. + ClientCAs *x509.CertPool + + // ServerName is used to verify the hostname on the returned + // certificates unless InsecureSkipVerify is given. + ServerName string + + LoggerFactory logging.LoggerFactory + + // ConnectContextMaker is a function to make a context used in Dial(), + // Client(), Server(), and Accept(). If nil, the default ConnectContextMaker + // is used. It can be implemented as following. + // + // func ConnectContextMaker() (context.Context, func()) { + // return context.WithTimeout(context.Background(), 30*time.Second) + // } + ConnectContextMaker func() (context.Context, func()) + + // MTU is the length at which handshake messages will be fragmented to + // fit within the maximum transmission unit (default is 1200 bytes) + MTU int + + // ReplayProtectionWindow is the size of the replay attack protection window. + // Duplication of the sequence number is checked in this window size. + // Packet with sequence number older than this value compared to the latest + // accepted packet will be discarded. (default is 64) + ReplayProtectionWindow int + + // KeyLogWriter optionally specifies a destination for TLS master secrets + // in NSS key log format that can be used to allow external programs + // such as Wireshark to decrypt TLS connections. + // See https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/Key_Log_Format. + // Use of KeyLogWriter compromises security and should only be + // used for debugging. + KeyLogWriter io.Writer +} + +func defaultConnectContextMaker() (context.Context, func()) { + return context.WithTimeout(context.Background(), 30*time.Second) +} + +func (c *Config) connectContextMaker() (context.Context, func()) { + if c.ConnectContextMaker == nil { + return defaultConnectContextMaker() + } + return c.ConnectContextMaker() +} + +const defaultMTU = 1200 // bytes + +// PSKCallback is called once we have the remote's PSKIdentityHint. +// If the remote provided none it will be nil +type PSKCallback func([]byte) ([]byte, error) + +// ClientAuthType declares the policy the server will follow for +// TLS Client Authentication. +type ClientAuthType int + +// ClientAuthType enums +const ( + NoClientCert ClientAuthType = iota + RequestClientCert + RequireAnyClientCert + VerifyClientCertIfGiven + RequireAndVerifyClientCert +) + +// ExtendedMasterSecretType declares the policy the client and server +// will follow for the Extended Master Secret extension +type ExtendedMasterSecretType int + +// ExtendedMasterSecretType enums +const ( + RequestExtendedMasterSecret ExtendedMasterSecretType = iota + RequireExtendedMasterSecret + DisableExtendedMasterSecret +) + +func validateConfig(config *Config) error { + switch { + case config == nil: + return errNoConfigProvided + case config.PSKIdentityHint != nil && config.PSK == nil: + return errIdentityNoPSK + } + + for _, cert := range config.Certificates { + if cert.Certificate == nil { + return errInvalidCertificate + } + if cert.PrivateKey != nil { + switch cert.PrivateKey.(type) { + case ed25519.PrivateKey: + case *ecdsa.PrivateKey: + default: + return errInvalidPrivateKey + } + } + } + + _, err := parseCipherSuites(config.CipherSuites, config.CustomCipherSuites, config.PSK == nil || len(config.Certificates) > 0, config.PSK != nil) + return err +} diff --git a/vendor/github.com/pion/dtls/v2/conn.go b/vendor/github.com/pion/dtls/v2/conn.go new file mode 100644 index 0000000..42d732f --- /dev/null +++ b/vendor/github.com/pion/dtls/v2/conn.go @@ -0,0 +1,978 @@ +package dtls + +import ( + "context" + "errors" + "fmt" + "io" + "net" + "sync" + "sync/atomic" + "time" + + "github.com/pion/dtls/v2/internal/closer" + "github.com/pion/dtls/v2/pkg/crypto/elliptic" + "github.com/pion/dtls/v2/pkg/crypto/signaturehash" + "github.com/pion/dtls/v2/pkg/protocol" + "github.com/pion/dtls/v2/pkg/protocol/alert" + "github.com/pion/dtls/v2/pkg/protocol/handshake" + "github.com/pion/dtls/v2/pkg/protocol/recordlayer" + "github.com/pion/logging" + "github.com/pion/transport/connctx" + "github.com/pion/transport/deadline" + "github.com/pion/transport/replaydetector" +) + +const ( + initialTickerInterval = time.Second + cookieLength = 20 + defaultNamedCurve = elliptic.X25519 + inboundBufferSize = 8192 + // Default replay protection window is specified by RFC 6347 Section 4.1.2.6 + defaultReplayProtectionWindow = 64 +) + +func invalidKeyingLabels() map[string]bool { + return map[string]bool{ + "client finished": true, + "server finished": true, + "master secret": true, + "key expansion": true, + } +} + +// Conn represents a DTLS connection +type Conn struct { + lock sync.RWMutex // Internal lock (must not be public) + nextConn connctx.ConnCtx // Embedded Conn, typically a udpconn we read/write from + fragmentBuffer *fragmentBuffer // out-of-order and missing fragment handling + handshakeCache *handshakeCache // caching of handshake messages for verifyData generation + decrypted chan interface{} // Decrypted Application Data or error, pull by calling `Read` + + state State // Internal state + + maximumTransmissionUnit int + + handshakeCompletedSuccessfully atomic.Value + + encryptedPackets [][]byte + + connectionClosedByUser bool + closeLock sync.Mutex + closed *closer.Closer + handshakeLoopsFinished sync.WaitGroup + + readDeadline *deadline.Deadline + writeDeadline *deadline.Deadline + + log logging.LeveledLogger + + reading chan struct{} + handshakeRecv chan chan struct{} + cancelHandshaker func() + cancelHandshakeReader func() + + fsm *handshakeFSM + + replayProtectionWindow uint +} + +func createConn(ctx context.Context, nextConn net.Conn, config *Config, isClient bool, initialState *State) (*Conn, error) { + err := validateConfig(config) + if err != nil { + return nil, err + } + + if nextConn == nil { + return nil, errNilNextConn + } + + cipherSuites, err := parseCipherSuites(config.CipherSuites, config.CustomCipherSuites, config.PSK == nil || len(config.Certificates) > 0, config.PSK != nil) + if err != nil { + return nil, err + } + + signatureSchemes, err := signaturehash.ParseSignatureSchemes(config.SignatureSchemes, config.InsecureHashes) + if err != nil { + return nil, err + } + + workerInterval := initialTickerInterval + if config.FlightInterval != 0 { + workerInterval = config.FlightInterval + } + + loggerFactory := config.LoggerFactory + if loggerFactory == nil { + loggerFactory = logging.NewDefaultLoggerFactory() + } + + logger := loggerFactory.NewLogger("dtls") + + mtu := config.MTU + if mtu <= 0 { + mtu = defaultMTU + } + + replayProtectionWindow := config.ReplayProtectionWindow + if replayProtectionWindow <= 0 { + replayProtectionWindow = defaultReplayProtectionWindow + } + + c := &Conn{ + nextConn: connctx.New(nextConn), + fragmentBuffer: newFragmentBuffer(), + handshakeCache: newHandshakeCache(), + maximumTransmissionUnit: mtu, + + decrypted: make(chan interface{}, 1), + log: logger, + + readDeadline: deadline.New(), + writeDeadline: deadline.New(), + + reading: make(chan struct{}, 1), + handshakeRecv: make(chan chan struct{}), + closed: closer.NewCloser(), + cancelHandshaker: func() {}, + + replayProtectionWindow: uint(replayProtectionWindow), + + state: State{ + isClient: isClient, + }, + } + + c.setRemoteEpoch(0) + c.setLocalEpoch(0) + + serverName := config.ServerName + // Use host from conn address when serverName is not provided + if isClient && serverName == "" && nextConn.RemoteAddr() != nil { + remoteAddr := nextConn.RemoteAddr().String() + var host string + host, _, err = net.SplitHostPort(remoteAddr) + if err != nil { + serverName = remoteAddr + } else { + serverName = host + } + } + + hsCfg := &handshakeConfig{ + localPSKCallback: config.PSK, + localPSKIdentityHint: config.PSKIdentityHint, + localCipherSuites: cipherSuites, + localSignatureSchemes: signatureSchemes, + extendedMasterSecret: config.ExtendedMasterSecret, + localSRTPProtectionProfiles: config.SRTPProtectionProfiles, + serverName: serverName, + clientAuth: config.ClientAuth, + localCertificates: config.Certificates, + insecureSkipVerify: config.InsecureSkipVerify, + verifyPeerCertificate: config.VerifyPeerCertificate, + rootCAs: config.RootCAs, + clientCAs: config.ClientCAs, + customCipherSuites: config.CustomCipherSuites, + retransmitInterval: workerInterval, + log: logger, + initialEpoch: 0, + keyLogWriter: config.KeyLogWriter, + } + + var initialFlight flightVal + var initialFSMState handshakeState + + if initialState != nil { + if c.state.isClient { + initialFlight = flight5 + } else { + initialFlight = flight6 + } + initialFSMState = handshakeFinished + + c.state = *initialState + } else { + if c.state.isClient { + initialFlight = flight1 + } else { + initialFlight = flight0 + } + initialFSMState = handshakePreparing + } + // Do handshake + if err := c.handshake(ctx, hsCfg, initialFlight, initialFSMState); err != nil { + return nil, err + } + + c.log.Trace("Handshake Completed") + + return c, nil +} + +// Dial connects to the given network address and establishes a DTLS connection on top. +// Connection handshake will timeout using ConnectContextMaker in the Config. +// If you want to specify the timeout duration, use DialWithContext() instead. +func Dial(network string, raddr *net.UDPAddr, config *Config) (*Conn, error) { + ctx, cancel := config.connectContextMaker() + defer cancel() + + return DialWithContext(ctx, network, raddr, config) +} + +// Client establishes a DTLS connection over an existing connection. +// Connection handshake will timeout using ConnectContextMaker in the Config. +// If you want to specify the timeout duration, use ClientWithContext() instead. +func Client(conn net.Conn, config *Config) (*Conn, error) { + ctx, cancel := config.connectContextMaker() + defer cancel() + + return ClientWithContext(ctx, conn, config) +} + +// Server listens for incoming DTLS connections. +// Connection handshake will timeout using ConnectContextMaker in the Config. +// If you want to specify the timeout duration, use ServerWithContext() instead. +func Server(conn net.Conn, config *Config) (*Conn, error) { + ctx, cancel := config.connectContextMaker() + defer cancel() + + return ServerWithContext(ctx, conn, config) +} + +// DialWithContext connects to the given network address and establishes a DTLS connection on top. +func DialWithContext(ctx context.Context, network string, raddr *net.UDPAddr, config *Config) (*Conn, error) { + pConn, err := net.DialUDP(network, nil, raddr) + if err != nil { + return nil, err + } + return ClientWithContext(ctx, pConn, config) +} + +// ClientWithContext establishes a DTLS connection over an existing connection. +func ClientWithContext(ctx context.Context, conn net.Conn, config *Config) (*Conn, error) { + switch { + case config == nil: + return nil, errNoConfigProvided + case config.PSK != nil && config.PSKIdentityHint == nil: + return nil, errPSKAndIdentityMustBeSetForClient + } + + return createConn(ctx, conn, config, true, nil) +} + +// ServerWithContext listens for incoming DTLS connections. +func ServerWithContext(ctx context.Context, conn net.Conn, config *Config) (*Conn, error) { + if config == nil { + return nil, errNoConfigProvided + } + + return createConn(ctx, conn, config, false, nil) +} + +// Read reads data from the connection. +func (c *Conn) Read(p []byte) (n int, err error) { + if !c.isHandshakeCompletedSuccessfully() { + return 0, errHandshakeInProgress + } + + select { + case <-c.readDeadline.Done(): + return 0, errDeadlineExceeded + default: + } + + for { + select { + case <-c.readDeadline.Done(): + return 0, errDeadlineExceeded + case out, ok := <-c.decrypted: + if !ok { + return 0, io.EOF + } + switch val := out.(type) { + case ([]byte): + if len(p) < len(val) { + return 0, errBufferTooSmall + } + copy(p, val) + return len(val), nil + case (error): + return 0, val + } + } + } +} + +// Write writes len(p) bytes from p to the DTLS connection +func (c *Conn) Write(p []byte) (int, error) { + if c.isConnectionClosed() { + return 0, ErrConnClosed + } + + select { + case <-c.writeDeadline.Done(): + return 0, errDeadlineExceeded + default: + } + + if !c.isHandshakeCompletedSuccessfully() { + return 0, errHandshakeInProgress + } + + return len(p), c.writePackets(c.writeDeadline, []*packet{ + { + record: &recordlayer.RecordLayer{ + Header: recordlayer.Header{ + Epoch: c.getLocalEpoch(), + Version: protocol.Version1_2, + }, + Content: &protocol.ApplicationData{ + Data: p, + }, + }, + shouldEncrypt: true, + }, + }) +} + +// Close closes the connection. +func (c *Conn) Close() error { + err := c.close(true) + c.handshakeLoopsFinished.Wait() + return err +} + +// ConnectionState returns basic DTLS details about the connection. +// Note that this replaced the `Export` function of v1. +func (c *Conn) ConnectionState() State { + c.lock.RLock() + defer c.lock.RUnlock() + return *c.state.clone() +} + +// SelectedSRTPProtectionProfile returns the selected SRTPProtectionProfile +func (c *Conn) SelectedSRTPProtectionProfile() (SRTPProtectionProfile, bool) { + c.lock.RLock() + defer c.lock.RUnlock() + + if c.state.srtpProtectionProfile == 0 { + return 0, false + } + + return c.state.srtpProtectionProfile, true +} + +func (c *Conn) writePackets(ctx context.Context, pkts []*packet) error { + c.lock.Lock() + defer c.lock.Unlock() + + var rawPackets [][]byte + + for _, p := range pkts { + if h, ok := p.record.Content.(*handshake.Handshake); ok { + handshakeRaw, err := p.record.Marshal() + if err != nil { + return err + } + + c.log.Tracef("[handshake:%v] -> %s (epoch: %d, seq: %d)", + srvCliStr(c.state.isClient), h.Header.Type.String(), + p.record.Header.Epoch, h.Header.MessageSequence) + c.handshakeCache.push(handshakeRaw[recordlayer.HeaderSize:], p.record.Header.Epoch, h.Header.MessageSequence, h.Header.Type, c.state.isClient) + + rawHandshakePackets, err := c.processHandshakePacket(p, h) + if err != nil { + return err + } + rawPackets = append(rawPackets, rawHandshakePackets...) + } else { + rawPacket, err := c.processPacket(p) + if err != nil { + return err + } + rawPackets = append(rawPackets, rawPacket) + } + } + if len(rawPackets) == 0 { + return nil + } + compactedRawPackets := c.compactRawPackets(rawPackets) + + for _, compactedRawPackets := range compactedRawPackets { + if _, err := c.nextConn.WriteContext(ctx, compactedRawPackets); err != nil { + return netError(err) + } + } + + return nil +} + +func (c *Conn) compactRawPackets(rawPackets [][]byte) [][]byte { + combinedRawPackets := make([][]byte, 0) + currentCombinedRawPacket := make([]byte, 0) + + for _, rawPacket := range rawPackets { + if len(currentCombinedRawPacket) > 0 && len(currentCombinedRawPacket)+len(rawPacket) >= c.maximumTransmissionUnit { + combinedRawPackets = append(combinedRawPackets, currentCombinedRawPacket) + currentCombinedRawPacket = []byte{} + } + currentCombinedRawPacket = append(currentCombinedRawPacket, rawPacket...) + } + + combinedRawPackets = append(combinedRawPackets, currentCombinedRawPacket) + + return combinedRawPackets +} + +func (c *Conn) processPacket(p *packet) ([]byte, error) { + epoch := p.record.Header.Epoch + for len(c.state.localSequenceNumber) <= int(epoch) { + c.state.localSequenceNumber = append(c.state.localSequenceNumber, uint64(0)) + } + seq := atomic.AddUint64(&c.state.localSequenceNumber[epoch], 1) - 1 + if seq > recordlayer.MaxSequenceNumber { + // RFC 6347 Section 4.1.0 + // The implementation must either abandon an association or rehandshake + // prior to allowing the sequence number to wrap. + return nil, errSequenceNumberOverflow + } + p.record.Header.SequenceNumber = seq + + rawPacket, err := p.record.Marshal() + if err != nil { + return nil, err + } + + if p.shouldEncrypt { + var err error + rawPacket, err = c.state.cipherSuite.Encrypt(p.record, rawPacket) + if err != nil { + return nil, err + } + } + + return rawPacket, nil +} + +func (c *Conn) processHandshakePacket(p *packet, h *handshake.Handshake) ([][]byte, error) { + rawPackets := make([][]byte, 0) + + handshakeFragments, err := c.fragmentHandshake(h) + if err != nil { + return nil, err + } + epoch := p.record.Header.Epoch + for len(c.state.localSequenceNumber) <= int(epoch) { + c.state.localSequenceNumber = append(c.state.localSequenceNumber, uint64(0)) + } + + for _, handshakeFragment := range handshakeFragments { + seq := atomic.AddUint64(&c.state.localSequenceNumber[epoch], 1) - 1 + if seq > recordlayer.MaxSequenceNumber { + return nil, errSequenceNumberOverflow + } + + recordlayerHeader := &recordlayer.Header{ + Version: p.record.Header.Version, + ContentType: p.record.Header.ContentType, + ContentLen: uint16(len(handshakeFragment)), + Epoch: p.record.Header.Epoch, + SequenceNumber: seq, + } + + recordlayerHeaderBytes, err := recordlayerHeader.Marshal() + if err != nil { + return nil, err + } + + p.record.Header = *recordlayerHeader + + rawPacket := append(recordlayerHeaderBytes, handshakeFragment...) + if p.shouldEncrypt { + var err error + rawPacket, err = c.state.cipherSuite.Encrypt(p.record, rawPacket) + if err != nil { + return nil, err + } + } + + rawPackets = append(rawPackets, rawPacket) + } + + return rawPackets, nil +} + +func (c *Conn) fragmentHandshake(h *handshake.Handshake) ([][]byte, error) { + content, err := h.Message.Marshal() + if err != nil { + return nil, err + } + + fragmentedHandshakes := make([][]byte, 0) + + contentFragments := splitBytes(content, c.maximumTransmissionUnit) + if len(contentFragments) == 0 { + contentFragments = [][]byte{ + {}, + } + } + + offset := 0 + for _, contentFragment := range contentFragments { + contentFragmentLen := len(contentFragment) + + headerFragment := &handshake.Header{ + Type: h.Header.Type, + Length: h.Header.Length, + MessageSequence: h.Header.MessageSequence, + FragmentOffset: uint32(offset), + FragmentLength: uint32(contentFragmentLen), + } + + offset += contentFragmentLen + + headerFragmentRaw, err := headerFragment.Marshal() + if err != nil { + return nil, err + } + + fragmentedHandshake := append(headerFragmentRaw, contentFragment...) + fragmentedHandshakes = append(fragmentedHandshakes, fragmentedHandshake) + } + + return fragmentedHandshakes, nil +} + +var poolReadBuffer = sync.Pool{ //nolint:gochecknoglobals + New: func() interface{} { + b := make([]byte, inboundBufferSize) + return &b + }, +} + +func (c *Conn) readAndBuffer(ctx context.Context) error { + bufptr := poolReadBuffer.Get().(*[]byte) + defer poolReadBuffer.Put(bufptr) + + b := *bufptr + i, err := c.nextConn.ReadContext(ctx, b) + if err != nil { + return netError(err) + } + + pkts, err := recordlayer.UnpackDatagram(b[:i]) + if err != nil { + return err + } + + var hasHandshake bool + for _, p := range pkts { + hs, alert, err := c.handleIncomingPacket(p, true) + if alert != nil { + if alertErr := c.notify(ctx, alert.Level, alert.Description); alertErr != nil { + if err == nil { + err = alertErr + } + } + } + if hs { + hasHandshake = true + } + switch e := err.(type) { + case nil: + case *errAlert: + if e.IsFatalOrCloseNotify() { + return e + } + default: + return e + } + } + if hasHandshake { + done := make(chan struct{}) + select { + case c.handshakeRecv <- done: + // If the other party may retransmit the flight, + // we should respond even if it not a new message. + <-done + case <-c.fsm.Done(): + } + } + return nil +} + +func (c *Conn) handleQueuedPackets(ctx context.Context) error { + pkts := c.encryptedPackets + c.encryptedPackets = nil + + for _, p := range pkts { + _, alert, err := c.handleIncomingPacket(p, false) // don't re-enqueue + if alert != nil { + if alertErr := c.notify(ctx, alert.Level, alert.Description); alertErr != nil { + if err == nil { + err = alertErr + } + } + } + switch e := err.(type) { + case nil: + case *errAlert: + if e.IsFatalOrCloseNotify() { + return e + } + default: + return e + } + } + return nil +} + +func (c *Conn) handleIncomingPacket(buf []byte, enqueue bool) (bool, *alert.Alert, error) { //nolint:gocognit + h := &recordlayer.Header{} + if err := h.Unmarshal(buf); err != nil { + // Decode error must be silently discarded + // [RFC6347 Section-4.1.2.7] + c.log.Debugf("discarded broken packet: %v", err) + return false, nil, nil + } + + // Validate epoch + remoteEpoch := c.getRemoteEpoch() + if h.Epoch > remoteEpoch { + if h.Epoch > remoteEpoch+1 { + c.log.Debugf("discarded future packet (epoch: %d, seq: %d)", + h.Epoch, h.SequenceNumber, + ) + return false, nil, nil + } + if enqueue { + c.log.Debug("received packet of next epoch, queuing packet") + c.encryptedPackets = append(c.encryptedPackets, buf) + } + return false, nil, nil + } + + // Anti-replay protection + for len(c.state.replayDetector) <= int(h.Epoch) { + c.state.replayDetector = append(c.state.replayDetector, + replaydetector.New(c.replayProtectionWindow, recordlayer.MaxSequenceNumber), + ) + } + markPacketAsValid, ok := c.state.replayDetector[int(h.Epoch)].Check(h.SequenceNumber) + if !ok { + c.log.Debugf("discarded duplicated packet (epoch: %d, seq: %d)", + h.Epoch, h.SequenceNumber, + ) + return false, nil, nil + } + + // Decrypt + if h.Epoch != 0 { + if c.state.cipherSuite == nil || !c.state.cipherSuite.IsInitialized() { + if enqueue { + c.encryptedPackets = append(c.encryptedPackets, buf) + c.log.Debug("handshake not finished, queuing packet") + } + return false, nil, nil + } + + var err error + buf, err = c.state.cipherSuite.Decrypt(buf) + if err != nil { + c.log.Debugf("%s: decrypt failed: %s", srvCliStr(c.state.isClient), err) + return false, nil, nil + } + } + + isHandshake, err := c.fragmentBuffer.push(append([]byte{}, buf...)) + if err != nil { + // Decode error must be silently discarded + // [RFC6347 Section-4.1.2.7] + c.log.Debugf("defragment failed: %s", err) + return false, nil, nil + } else if isHandshake { + markPacketAsValid() + for out, epoch := c.fragmentBuffer.pop(); out != nil; out, epoch = c.fragmentBuffer.pop() { + rawHandshake := &handshake.Handshake{} + if err := rawHandshake.Unmarshal(out); err != nil { + c.log.Debugf("%s: handshake parse failed: %s", srvCliStr(c.state.isClient), err) + continue + } + + _ = c.handshakeCache.push(out, epoch, rawHandshake.Header.MessageSequence, rawHandshake.Header.Type, !c.state.isClient) + } + + return true, nil, nil + } + + r := &recordlayer.RecordLayer{} + if err := r.Unmarshal(buf); err != nil { + return false, &alert.Alert{Level: alert.Fatal, Description: alert.DecodeError}, err + } + + switch content := r.Content.(type) { + case *alert.Alert: + c.log.Tracef("%s: <- %s", srvCliStr(c.state.isClient), content.String()) + var a *alert.Alert + if content.Description == alert.CloseNotify { + // Respond with a close_notify [RFC5246 Section 7.2.1] + a = &alert.Alert{Level: alert.Warning, Description: alert.CloseNotify} + } + markPacketAsValid() + return false, a, &errAlert{content} + case *protocol.ChangeCipherSpec: + if c.state.cipherSuite == nil || !c.state.cipherSuite.IsInitialized() { + if enqueue { + c.encryptedPackets = append(c.encryptedPackets, buf) + c.log.Debugf("CipherSuite not initialized, queuing packet") + } + return false, nil, nil + } + + newRemoteEpoch := h.Epoch + 1 + c.log.Tracef("%s: <- ChangeCipherSpec (epoch: %d)", srvCliStr(c.state.isClient), newRemoteEpoch) + + if c.getRemoteEpoch()+1 == newRemoteEpoch { + c.setRemoteEpoch(newRemoteEpoch) + markPacketAsValid() + } + case *protocol.ApplicationData: + if h.Epoch == 0 { + return false, &alert.Alert{Level: alert.Fatal, Description: alert.UnexpectedMessage}, errApplicationDataEpochZero + } + + markPacketAsValid() + + select { + case c.decrypted <- content.Data: + case <-c.closed.Done(): + } + + default: + return false, &alert.Alert{Level: alert.Fatal, Description: alert.UnexpectedMessage}, fmt.Errorf("%w: %d", errUnhandledContextType, content.ContentType()) + } + return false, nil, nil +} + +func (c *Conn) recvHandshake() <-chan chan struct{} { + return c.handshakeRecv +} + +func (c *Conn) notify(ctx context.Context, level alert.Level, desc alert.Description) error { + return c.writePackets(ctx, []*packet{ + { + record: &recordlayer.RecordLayer{ + Header: recordlayer.Header{ + Epoch: c.getLocalEpoch(), + Version: protocol.Version1_2, + }, + Content: &alert.Alert{ + Level: level, + Description: desc, + }, + }, + shouldEncrypt: c.isHandshakeCompletedSuccessfully(), + }, + }) +} + +func (c *Conn) setHandshakeCompletedSuccessfully() { + c.handshakeCompletedSuccessfully.Store(struct{ bool }{true}) +} + +func (c *Conn) isHandshakeCompletedSuccessfully() bool { + boolean, _ := c.handshakeCompletedSuccessfully.Load().(struct{ bool }) + return boolean.bool +} + +func (c *Conn) handshake(ctx context.Context, cfg *handshakeConfig, initialFlight flightVal, initialState handshakeState) error { //nolint:gocognit + c.fsm = newHandshakeFSM(&c.state, c.handshakeCache, cfg, initialFlight) + + done := make(chan struct{}) + ctxRead, cancelRead := context.WithCancel(context.Background()) + c.cancelHandshakeReader = cancelRead + cfg.onFlightState = func(f flightVal, s handshakeState) { + if s == handshakeFinished && !c.isHandshakeCompletedSuccessfully() { + c.setHandshakeCompletedSuccessfully() + close(done) + } + } + + ctxHs, cancel := context.WithCancel(context.Background()) + c.cancelHandshaker = cancel + + firstErr := make(chan error, 1) + + c.handshakeLoopsFinished.Add(2) + + // Handshake routine should be live until close. + // The other party may request retransmission of the last flight to cope with packet drop. + go func() { + defer c.handshakeLoopsFinished.Done() + err := c.fsm.Run(ctxHs, c, initialState) + if !errors.Is(err, context.Canceled) { + select { + case firstErr <- err: + default: + } + } + }() + go func() { + defer func() { + // Escaping read loop. + // It's safe to close decrypted channnel now. + close(c.decrypted) + + // Force stop handshaker when the underlying connection is closed. + cancel() + }() + defer c.handshakeLoopsFinished.Done() + for { + if err := c.readAndBuffer(ctxRead); err != nil { + switch e := err.(type) { + case *errAlert: + if !e.IsFatalOrCloseNotify() { + if c.isHandshakeCompletedSuccessfully() { + // Pass the error to Read() + select { + case c.decrypted <- err: + case <-c.closed.Done(): + } + } + continue // non-fatal alert must not stop read loop + } + case error: + switch err { + case context.DeadlineExceeded, context.Canceled, io.EOF: + default: + if c.isHandshakeCompletedSuccessfully() { + // Keep read loop and pass the read error to Read() + select { + case c.decrypted <- err: + case <-c.closed.Done(): + } + continue // non-fatal alert must not stop read loop + } + } + } + select { + case firstErr <- err: + default: + } + + if e, ok := err.(*errAlert); ok { + if e.IsFatalOrCloseNotify() { + _ = c.close(false) + } + } + return + } + } + }() + + select { + case err := <-firstErr: + cancelRead() + cancel() + return c.translateHandshakeCtxError(err) + case <-ctx.Done(): + cancelRead() + cancel() + return c.translateHandshakeCtxError(ctx.Err()) + case <-done: + return nil + } +} + +func (c *Conn) translateHandshakeCtxError(err error) error { + if err == nil { + return nil + } + if errors.Is(err, context.Canceled) && c.isHandshakeCompletedSuccessfully() { + return nil + } + return &HandshakeError{Err: err} +} + +func (c *Conn) close(byUser bool) error { + c.cancelHandshaker() + c.cancelHandshakeReader() + + if c.isHandshakeCompletedSuccessfully() && byUser { + // Discard error from notify() to return non-error on the first user call of Close() + // even if the underlying connection is already closed. + _ = c.notify(context.Background(), alert.Warning, alert.CloseNotify) + } + + c.closeLock.Lock() + // Don't return ErrConnClosed at the first time of the call from user. + closedByUser := c.connectionClosedByUser + if byUser { + c.connectionClosedByUser = true + } + c.closed.Close() + c.closeLock.Unlock() + + if closedByUser { + return ErrConnClosed + } + + return c.nextConn.Close() +} + +func (c *Conn) isConnectionClosed() bool { + select { + case <-c.closed.Done(): + return true + default: + return false + } +} + +func (c *Conn) setLocalEpoch(epoch uint16) { + c.state.localEpoch.Store(epoch) +} + +func (c *Conn) getLocalEpoch() uint16 { + return c.state.localEpoch.Load().(uint16) +} + +func (c *Conn) setRemoteEpoch(epoch uint16) { + c.state.remoteEpoch.Store(epoch) +} + +func (c *Conn) getRemoteEpoch() uint16 { + return c.state.remoteEpoch.Load().(uint16) +} + +// LocalAddr implements net.Conn.LocalAddr +func (c *Conn) LocalAddr() net.Addr { + return c.nextConn.LocalAddr() +} + +// RemoteAddr implements net.Conn.RemoteAddr +func (c *Conn) RemoteAddr() net.Addr { + return c.nextConn.RemoteAddr() +} + +// SetDeadline implements net.Conn.SetDeadline +func (c *Conn) SetDeadline(t time.Time) error { + c.readDeadline.Set(t) + return c.SetWriteDeadline(t) +} + +// SetReadDeadline implements net.Conn.SetReadDeadline +func (c *Conn) SetReadDeadline(t time.Time) error { + c.readDeadline.Set(t) + // Read deadline is fully managed by this layer. + // Don't set read deadline to underlying connection. + return nil +} + +// SetWriteDeadline implements net.Conn.SetWriteDeadline +func (c *Conn) SetWriteDeadline(t time.Time) error { + c.writeDeadline.Set(t) + // Write deadline is also fully managed by this layer. + return nil +} diff --git a/vendor/github.com/pion/dtls/v2/crypto.go b/vendor/github.com/pion/dtls/v2/crypto.go new file mode 100644 index 0000000..768ee47 --- /dev/null +++ b/vendor/github.com/pion/dtls/v2/crypto.go @@ -0,0 +1,221 @@ +package dtls + +import ( + "crypto" + "crypto/ecdsa" + "crypto/ed25519" + "crypto/rand" + "crypto/rsa" + "crypto/sha256" + "crypto/x509" + "encoding/asn1" + "encoding/binary" + "math/big" + "time" + + "github.com/pion/dtls/v2/pkg/crypto/elliptic" + "github.com/pion/dtls/v2/pkg/crypto/hash" +) + +type ecdsaSignature struct { + R, S *big.Int +} + +func valueKeyMessage(clientRandom, serverRandom, publicKey []byte, namedCurve elliptic.Curve) []byte { + serverECDHParams := make([]byte, 4) + serverECDHParams[0] = 3 // named curve + binary.BigEndian.PutUint16(serverECDHParams[1:], uint16(namedCurve)) + serverECDHParams[3] = byte(len(publicKey)) + + plaintext := []byte{} + plaintext = append(plaintext, clientRandom...) + plaintext = append(plaintext, serverRandom...) + plaintext = append(plaintext, serverECDHParams...) + plaintext = append(plaintext, publicKey...) + + return plaintext +} + +// If the client provided a "signature_algorithms" extension, then all +// certificates provided by the server MUST be signed by a +// hash/signature algorithm pair that appears in that extension +// +// https://tools.ietf.org/html/rfc5246#section-7.4.2 +func generateKeySignature(clientRandom, serverRandom, publicKey []byte, namedCurve elliptic.Curve, privateKey crypto.PrivateKey, hashAlgorithm hash.Algorithm) ([]byte, error) { + msg := valueKeyMessage(clientRandom, serverRandom, publicKey, namedCurve) + switch p := privateKey.(type) { + case ed25519.PrivateKey: + // https://crypto.stackexchange.com/a/55483 + return p.Sign(rand.Reader, msg, crypto.Hash(0)) + case *ecdsa.PrivateKey: + hashed := hashAlgorithm.Digest(msg) + return p.Sign(rand.Reader, hashed, hashAlgorithm.CryptoHash()) + case *rsa.PrivateKey: + hashed := hashAlgorithm.Digest(msg) + return p.Sign(rand.Reader, hashed, hashAlgorithm.CryptoHash()) + } + + return nil, errKeySignatureGenerateUnimplemented +} + +func verifyKeySignature(message, remoteKeySignature []byte, hashAlgorithm hash.Algorithm, rawCertificates [][]byte) error { //nolint:dupl + if len(rawCertificates) == 0 { + return errLengthMismatch + } + certificate, err := x509.ParseCertificate(rawCertificates[0]) + if err != nil { + return err + } + + switch p := certificate.PublicKey.(type) { + case ed25519.PublicKey: + if ok := ed25519.Verify(p, message, remoteKeySignature); !ok { + return errKeySignatureMismatch + } + return nil + case *ecdsa.PublicKey: + ecdsaSig := &ecdsaSignature{} + if _, err := asn1.Unmarshal(remoteKeySignature, ecdsaSig); err != nil { + return err + } + if ecdsaSig.R.Sign() <= 0 || ecdsaSig.S.Sign() <= 0 { + return errInvalidECDSASignature + } + hashed := hashAlgorithm.Digest(message) + if !ecdsa.Verify(p, hashed, ecdsaSig.R, ecdsaSig.S) { + return errKeySignatureMismatch + } + return nil + case *rsa.PublicKey: + switch certificate.SignatureAlgorithm { + case x509.SHA1WithRSA, x509.SHA256WithRSA, x509.SHA384WithRSA, x509.SHA512WithRSA: + hashed := hashAlgorithm.Digest(message) + return rsa.VerifyPKCS1v15(p, hashAlgorithm.CryptoHash(), hashed, remoteKeySignature) + default: + return errKeySignatureVerifyUnimplemented + } + } + + return errKeySignatureVerifyUnimplemented +} + +// If the server has sent a CertificateRequest message, the client MUST send the Certificate +// message. The ClientKeyExchange message is now sent, and the content +// of that message will depend on the public key algorithm selected +// between the ClientHello and the ServerHello. If the client has sent +// a certificate with signing ability, a digitally-signed +// CertificateVerify message is sent to explicitly verify possession of +// the private key in the certificate. +// https://tools.ietf.org/html/rfc5246#section-7.3 +func generateCertificateVerify(handshakeBodies []byte, privateKey crypto.PrivateKey, hashAlgorithm hash.Algorithm) ([]byte, error) { + h := sha256.New() + if _, err := h.Write(handshakeBodies); err != nil { + return nil, err + } + hashed := h.Sum(nil) + + switch p := privateKey.(type) { + case ed25519.PrivateKey: + // https://crypto.stackexchange.com/a/55483 + return p.Sign(rand.Reader, hashed, crypto.Hash(0)) + case *ecdsa.PrivateKey: + return p.Sign(rand.Reader, hashed, hashAlgorithm.CryptoHash()) + case *rsa.PrivateKey: + return p.Sign(rand.Reader, hashed, hashAlgorithm.CryptoHash()) + } + + return nil, errInvalidSignatureAlgorithm +} + +func verifyCertificateVerify(handshakeBodies []byte, hashAlgorithm hash.Algorithm, remoteKeySignature []byte, rawCertificates [][]byte) error { //nolint:dupl + if len(rawCertificates) == 0 { + return errLengthMismatch + } + certificate, err := x509.ParseCertificate(rawCertificates[0]) + if err != nil { + return err + } + + switch p := certificate.PublicKey.(type) { + case ed25519.PublicKey: + if ok := ed25519.Verify(p, handshakeBodies, remoteKeySignature); !ok { + return errKeySignatureMismatch + } + return nil + case *ecdsa.PublicKey: + ecdsaSig := &ecdsaSignature{} + if _, err := asn1.Unmarshal(remoteKeySignature, ecdsaSig); err != nil { + return err + } + if ecdsaSig.R.Sign() <= 0 || ecdsaSig.S.Sign() <= 0 { + return errInvalidECDSASignature + } + hash := hashAlgorithm.Digest(handshakeBodies) + if !ecdsa.Verify(p, hash, ecdsaSig.R, ecdsaSig.S) { + return errKeySignatureMismatch + } + return nil + case *rsa.PublicKey: + switch certificate.SignatureAlgorithm { + case x509.SHA1WithRSA, x509.SHA256WithRSA, x509.SHA384WithRSA, x509.SHA512WithRSA: + hash := hashAlgorithm.Digest(handshakeBodies) + return rsa.VerifyPKCS1v15(p, hashAlgorithm.CryptoHash(), hash, remoteKeySignature) + default: + return errKeySignatureVerifyUnimplemented + } + } + + return errKeySignatureVerifyUnimplemented +} + +func loadCerts(rawCertificates [][]byte) ([]*x509.Certificate, error) { + if len(rawCertificates) == 0 { + return nil, errLengthMismatch + } + + certs := make([]*x509.Certificate, 0, len(rawCertificates)) + for _, rawCert := range rawCertificates { + cert, err := x509.ParseCertificate(rawCert) + if err != nil { + return nil, err + } + certs = append(certs, cert) + } + return certs, nil +} + +func verifyClientCert(rawCertificates [][]byte, roots *x509.CertPool) (chains [][]*x509.Certificate, err error) { + certificate, err := loadCerts(rawCertificates) + if err != nil { + return nil, err + } + intermediateCAPool := x509.NewCertPool() + for _, cert := range certificate[1:] { + intermediateCAPool.AddCert(cert) + } + opts := x509.VerifyOptions{ + Roots: roots, + CurrentTime: time.Now(), + Intermediates: intermediateCAPool, + KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, + } + return certificate[0].Verify(opts) +} + +func verifyServerCert(rawCertificates [][]byte, roots *x509.CertPool, serverName string) (chains [][]*x509.Certificate, err error) { + certificate, err := loadCerts(rawCertificates) + if err != nil { + return nil, err + } + intermediateCAPool := x509.NewCertPool() + for _, cert := range certificate[1:] { + intermediateCAPool.AddCert(cert) + } + opts := x509.VerifyOptions{ + Roots: roots, + CurrentTime: time.Now(), + DNSName: serverName, + Intermediates: intermediateCAPool, + } + return certificate[0].Verify(opts) +} diff --git a/vendor/github.com/pion/dtls/v2/dtls.go b/vendor/github.com/pion/dtls/v2/dtls.go new file mode 100644 index 0000000..125b904 --- /dev/null +++ b/vendor/github.com/pion/dtls/v2/dtls.go @@ -0,0 +1,2 @@ +// Package dtls implements Datagram Transport Layer Security (DTLS) 1.2 +package dtls diff --git a/vendor/github.com/pion/dtls/v2/errors.go b/vendor/github.com/pion/dtls/v2/errors.go new file mode 100644 index 0000000..2e16388 --- /dev/null +++ b/vendor/github.com/pion/dtls/v2/errors.go @@ -0,0 +1,141 @@ +package dtls + +import ( + "context" + "errors" + "fmt" + "io" + "net" + "os" + + "github.com/pion/dtls/v2/pkg/protocol" + "github.com/pion/dtls/v2/pkg/protocol/alert" + "golang.org/x/xerrors" +) + +// Typed errors +var ( + ErrConnClosed = &FatalError{Err: errors.New("conn is closed")} //nolint:goerr113 + + errDeadlineExceeded = &TimeoutError{Err: xerrors.Errorf("read/write timeout: %w", context.DeadlineExceeded)} + errInvalidContentType = &TemporaryError{Err: errors.New("invalid content type")} //nolint:goerr113 + + errBufferTooSmall = &TemporaryError{Err: errors.New("buffer is too small")} //nolint:goerr113 + errContextUnsupported = &TemporaryError{Err: errors.New("context is not supported for ExportKeyingMaterial")} //nolint:goerr113 + errHandshakeInProgress = &TemporaryError{Err: errors.New("handshake is in progress")} //nolint:goerr113 + errReservedExportKeyingMaterial = &TemporaryError{Err: errors.New("ExportKeyingMaterial can not be used with a reserved label")} //nolint:goerr113 + errApplicationDataEpochZero = &TemporaryError{Err: errors.New("ApplicationData with epoch of 0")} //nolint:goerr113 + errUnhandledContextType = &TemporaryError{Err: errors.New("unhandled contentType")} //nolint:goerr113 + + errCertificateVerifyNoCertificate = &FatalError{Err: errors.New("client sent certificate verify but we have no certificate to verify")} //nolint:goerr113 + errCipherSuiteNoIntersection = &FatalError{Err: errors.New("client+server do not support any shared cipher suites")} //nolint:goerr113 + errClientCertificateNotVerified = &FatalError{Err: errors.New("client sent certificate but did not verify it")} //nolint:goerr113 + errClientCertificateRequired = &FatalError{Err: errors.New("server required client verification, but got none")} //nolint:goerr113 + errClientNoMatchingSRTPProfile = &FatalError{Err: errors.New("server responded with SRTP Profile we do not support")} //nolint:goerr113 + errClientRequiredButNoServerEMS = &FatalError{Err: errors.New("client required Extended Master Secret extension, but server does not support it")} //nolint:goerr113 + errCookieMismatch = &FatalError{Err: errors.New("client+server cookie does not match")} //nolint:goerr113 + errIdentityNoPSK = &FatalError{Err: errors.New("PSK Identity Hint provided but PSK is nil")} //nolint:goerr113 + errInvalidCertificate = &FatalError{Err: errors.New("no certificate provided")} //nolint:goerr113 + errInvalidCipherSuite = &FatalError{Err: errors.New("invalid or unknown cipher suite")} //nolint:goerr113 + errInvalidECDSASignature = &FatalError{Err: errors.New("ECDSA signature contained zero or negative values")} //nolint:goerr113 + errInvalidPrivateKey = &FatalError{Err: errors.New("invalid private key type")} //nolint:goerr113 + errInvalidSignatureAlgorithm = &FatalError{Err: errors.New("invalid signature algorithm")} //nolint:goerr113 + errKeySignatureMismatch = &FatalError{Err: errors.New("expected and actual key signature do not match")} //nolint:goerr113 + errNilNextConn = &FatalError{Err: errors.New("Conn can not be created with a nil nextConn")} //nolint:goerr113 + errNoAvailableCipherSuites = &FatalError{Err: errors.New("connection can not be created, no CipherSuites satisfy this Config")} //nolint:goerr113 + errNoAvailablePSKCipherSuite = &FatalError{Err: errors.New("connection can not be created, pre-shared key present but no compatible CipherSuite")} //nolint:goerr113 + errNoAvailableCertificateCipherSuite = &FatalError{Err: errors.New("connection can not be created, certificate present but no compatible CipherSuite")} //nolint:goerr113 + errNoAvailableSignatureSchemes = &FatalError{Err: errors.New("connection can not be created, no SignatureScheme satisfy this Config")} //nolint:goerr113 + errNoCertificates = &FatalError{Err: errors.New("no certificates configured")} //nolint:goerr113 + errNoConfigProvided = &FatalError{Err: errors.New("no config provided")} //nolint:goerr113 + errNoSupportedEllipticCurves = &FatalError{Err: errors.New("client requested zero or more elliptic curves that are not supported by the server")} //nolint:goerr113 + errUnsupportedProtocolVersion = &FatalError{Err: errors.New("unsupported protocol version")} //nolint:goerr113 + errPSKAndIdentityMustBeSetForClient = &FatalError{Err: errors.New("PSK and PSK Identity Hint must both be set for client")} //nolint:goerr113 + errRequestedButNoSRTPExtension = &FatalError{Err: errors.New("SRTP support was requested but server did not respond with use_srtp extension")} //nolint:goerr113 + errServerNoMatchingSRTPProfile = &FatalError{Err: errors.New("client requested SRTP but we have no matching profiles")} //nolint:goerr113 + errServerRequiredButNoClientEMS = &FatalError{Err: errors.New("server requires the Extended Master Secret extension, but the client does not support it")} //nolint:goerr113 + errVerifyDataMismatch = &FatalError{Err: errors.New("expected and actual verify data does not match")} //nolint:goerr113 + + errInvalidFlight = &InternalError{Err: errors.New("invalid flight number")} //nolint:goerr113 + errKeySignatureGenerateUnimplemented = &InternalError{Err: errors.New("unable to generate key signature, unimplemented")} //nolint:goerr113 + errKeySignatureVerifyUnimplemented = &InternalError{Err: errors.New("unable to verify key signature, unimplemented")} //nolint:goerr113 + errLengthMismatch = &InternalError{Err: errors.New("data length and declared length do not match")} //nolint:goerr113 + errSequenceNumberOverflow = &InternalError{Err: errors.New("sequence number overflow")} //nolint:goerr113 + errInvalidFSMTransition = &InternalError{Err: errors.New("invalid state machine transition")} //nolint:goerr113 +) + +// FatalError indicates that the DTLS connection is no longer available. +// It is mainly caused by wrong configuration of server or client. +type FatalError = protocol.FatalError + +// InternalError indicates and internal error caused by the implementation, and the DTLS connection is no longer available. +// It is mainly caused by bugs or tried to use unimplemented features. +type InternalError = protocol.InternalError + +// TemporaryError indicates that the DTLS connection is still available, but the request was failed temporary. +type TemporaryError = protocol.TemporaryError + +// TimeoutError indicates that the request was timed out. +type TimeoutError = protocol.TimeoutError + +// HandshakeError indicates that the handshake failed. +type HandshakeError = protocol.HandshakeError + +// invalidCipherSuite indicates an attempt at using an unsupported cipher suite. +type invalidCipherSuite struct { + id CipherSuiteID +} + +func (e *invalidCipherSuite) Error() string { + return fmt.Sprintf("CipherSuite with id(%d) is not valid", e.id) +} + +func (e *invalidCipherSuite) Is(err error) bool { + if other, ok := err.(*invalidCipherSuite); ok { + return e.id == other.id + } + return false +} + +// errAlert wraps DTLS alert notification as an error +type errAlert struct { + *alert.Alert +} + +func (e *errAlert) Error() string { + return fmt.Sprintf("alert: %s", e.Alert.String()) +} + +func (e *errAlert) IsFatalOrCloseNotify() bool { + return e.Level == alert.Fatal || e.Description == alert.CloseNotify +} + +func (e *errAlert) Is(err error) bool { + if other, ok := err.(*errAlert); ok { + return e.Level == other.Level && e.Description == other.Description + } + return false +} + +// netError translates an error from underlying Conn to corresponding net.Error. +func netError(err error) error { + switch err { + case io.EOF, context.Canceled, context.DeadlineExceeded: + // Return io.EOF and context errors as is. + return err + } + switch e := err.(type) { + case (*net.OpError): + if se, ok := e.Err.(*os.SyscallError); ok { + if se.Timeout() { + return &TimeoutError{Err: err} + } + if isOpErrorTemporary(se) { + return &TemporaryError{Err: err} + } + } + case (net.Error): + return err + } + return &FatalError{Err: err} +} diff --git a/vendor/github.com/pion/dtls/v2/errors_errno.go b/vendor/github.com/pion/dtls/v2/errors_errno.go new file mode 100644 index 0000000..a9a439b --- /dev/null +++ b/vendor/github.com/pion/dtls/v2/errors_errno.go @@ -0,0 +1,25 @@ +// +build aix darwin dragonfly freebsd linux nacl nacljs netbsd openbsd solaris windows + +// For systems having syscall.Errno. +// Update build targets by following command: +// $ grep -R ECONN $(go env GOROOT)/src/syscall/zerrors_*.go \ +// | tr "." "_" | cut -d"_" -f"2" | sort | uniq + +package dtls + +import ( + "os" + "syscall" +) + +func isOpErrorTemporary(err *os.SyscallError) bool { + if ne, ok := err.Err.(syscall.Errno); ok { + switch ne { + case syscall.ECONNREFUSED: + return true + default: + return false + } + } + return false +} diff --git a/vendor/github.com/pion/dtls/v2/errors_noerrno.go b/vendor/github.com/pion/dtls/v2/errors_noerrno.go new file mode 100644 index 0000000..fcc37ce --- /dev/null +++ b/vendor/github.com/pion/dtls/v2/errors_noerrno.go @@ -0,0 +1,14 @@ +// +build !aix,!darwin,!dragonfly,!freebsd,!linux,!nacl,!nacljs,!netbsd,!openbsd,!solaris,!windows + +// For systems without syscall.Errno. +// Build targets must be inverse of errors_errno.go + +package dtls + +import ( + "os" +) + +func isOpErrorTemporary(err *os.SyscallError) bool { + return false +} diff --git a/vendor/github.com/pion/dtls/v2/flight.go b/vendor/github.com/pion/dtls/v2/flight.go new file mode 100644 index 0000000..580ee48 --- /dev/null +++ b/vendor/github.com/pion/dtls/v2/flight.go @@ -0,0 +1,75 @@ +package dtls + +/* + DTLS messages are grouped into a series of message flights, according + to the diagrams below. Although each flight of messages may consist + of a number of messages, they should be viewed as monolithic for the + purpose of timeout and retransmission. + https://tools.ietf.org/html/rfc4347#section-4.2.4 + Client Server + ------ ------ + Waiting Flight 0 + + ClientHello --------> Flight 1 + + <------- HelloVerifyRequest Flight 2 + + ClientHello --------> Flight 3 + + ServerHello \ + Certificate* \ + ServerKeyExchange* Flight 4 + CertificateRequest* / + <-------- ServerHelloDone / + + Certificate* \ + ClientKeyExchange \ + CertificateVerify* Flight 5 + [ChangeCipherSpec] / + Finished --------> / + + [ChangeCipherSpec] \ Flight 6 + <-------- Finished / + +*/ + +type flightVal uint8 + +const ( + flight0 flightVal = iota + 1 + flight1 + flight2 + flight3 + flight4 + flight5 + flight6 +) + +func (f flightVal) String() string { + switch f { + case flight0: + return "Flight 0" + case flight1: + return "Flight 1" + case flight2: + return "Flight 2" + case flight3: + return "Flight 3" + case flight4: + return "Flight 4" + case flight5: + return "Flight 5" + case flight6: + return "Flight 6" + default: + return "Invalid Flight" + } +} + +func (f flightVal) isLastSendFlight() bool { + return f == flight6 +} + +func (f flightVal) isLastRecvFlight() bool { + return f == flight5 +} diff --git a/vendor/github.com/pion/dtls/v2/flight0handler.go b/vendor/github.com/pion/dtls/v2/flight0handler.go new file mode 100644 index 0000000..949d7c0 --- /dev/null +++ b/vendor/github.com/pion/dtls/v2/flight0handler.go @@ -0,0 +1,102 @@ +package dtls + +import ( + "context" + "crypto/rand" + + "github.com/pion/dtls/v2/pkg/crypto/elliptic" + "github.com/pion/dtls/v2/pkg/protocol" + "github.com/pion/dtls/v2/pkg/protocol/alert" + "github.com/pion/dtls/v2/pkg/protocol/extension" + "github.com/pion/dtls/v2/pkg/protocol/handshake" +) + +func flight0Parse(ctx context.Context, c flightConn, state *State, cache *handshakeCache, cfg *handshakeConfig) (flightVal, *alert.Alert, error) { + seq, msgs, ok := cache.fullPullMap(0, + handshakeCachePullRule{handshake.TypeClientHello, cfg.initialEpoch, true, false}, + ) + if !ok { + // No valid message received. Keep reading + return 0, nil, nil + } + state.handshakeRecvSequence = seq + + var clientHello *handshake.MessageClientHello + + // Validate type + if clientHello, ok = msgs[handshake.TypeClientHello].(*handshake.MessageClientHello); !ok { + return 0, &alert.Alert{Level: alert.Fatal, Description: alert.InternalError}, nil + } + + if !clientHello.Version.Equal(protocol.Version1_2) { + return 0, &alert.Alert{Level: alert.Fatal, Description: alert.ProtocolVersion}, errUnsupportedProtocolVersion + } + + state.remoteRandom = clientHello.Random + + cipherSuites := []CipherSuite{} + for _, id := range clientHello.CipherSuiteIDs { + if c := cipherSuiteForID(CipherSuiteID(id), cfg.customCipherSuites); c != nil { + cipherSuites = append(cipherSuites, c) + } + } + + if state.cipherSuite, ok = findMatchingCipherSuite(cipherSuites, cfg.localCipherSuites); !ok { + return 0, &alert.Alert{Level: alert.Fatal, Description: alert.InsufficientSecurity}, errCipherSuiteNoIntersection + } + + for _, val := range clientHello.Extensions { + switch e := val.(type) { + case *extension.SupportedEllipticCurves: + if len(e.EllipticCurves) == 0 { + return 0, &alert.Alert{Level: alert.Fatal, Description: alert.InsufficientSecurity}, errNoSupportedEllipticCurves + } + state.namedCurve = e.EllipticCurves[0] + case *extension.UseSRTP: + profile, ok := findMatchingSRTPProfile(e.ProtectionProfiles, cfg.localSRTPProtectionProfiles) + if !ok { + return 0, &alert.Alert{Level: alert.Fatal, Description: alert.InsufficientSecurity}, errServerNoMatchingSRTPProfile + } + state.srtpProtectionProfile = profile + case *extension.UseExtendedMasterSecret: + if cfg.extendedMasterSecret != DisableExtendedMasterSecret { + state.extendedMasterSecret = true + } + case *extension.ServerName: + state.serverName = e.ServerName // remote server name + } + } + + if cfg.extendedMasterSecret == RequireExtendedMasterSecret && !state.extendedMasterSecret { + return 0, &alert.Alert{Level: alert.Fatal, Description: alert.InsufficientSecurity}, errServerRequiredButNoClientEMS + } + + if state.localKeypair == nil { + var err error + state.localKeypair, err = elliptic.GenerateKeypair(state.namedCurve) + if err != nil { + return 0, &alert.Alert{Level: alert.Fatal, Description: alert.IllegalParameter}, err + } + } + + return flight2, nil, nil +} + +func flight0Generate(c flightConn, state *State, cache *handshakeCache, cfg *handshakeConfig) ([]*packet, *alert.Alert, error) { + // Initialize + state.cookie = make([]byte, cookieLength) + if _, err := rand.Read(state.cookie); err != nil { + return nil, nil, err + } + + var zeroEpoch uint16 + state.localEpoch.Store(zeroEpoch) + state.remoteEpoch.Store(zeroEpoch) + state.namedCurve = defaultNamedCurve + + if err := state.localRandom.Populate(); err != nil { + return nil, nil, err + } + + return nil, nil, nil +} diff --git a/vendor/github.com/pion/dtls/v2/flight1handler.go b/vendor/github.com/pion/dtls/v2/flight1handler.go new file mode 100644 index 0000000..9229292 --- /dev/null +++ b/vendor/github.com/pion/dtls/v2/flight1handler.go @@ -0,0 +1,112 @@ +package dtls + +import ( + "context" + + "github.com/pion/dtls/v2/pkg/crypto/elliptic" + "github.com/pion/dtls/v2/pkg/protocol" + "github.com/pion/dtls/v2/pkg/protocol/alert" + "github.com/pion/dtls/v2/pkg/protocol/extension" + "github.com/pion/dtls/v2/pkg/protocol/handshake" + "github.com/pion/dtls/v2/pkg/protocol/recordlayer" +) + +func flight1Parse(ctx context.Context, c flightConn, state *State, cache *handshakeCache, cfg *handshakeConfig) (flightVal, *alert.Alert, error) { + // HelloVerifyRequest can be skipped by the server, + // so allow ServerHello during flight1 also + seq, msgs, ok := cache.fullPullMap(state.handshakeRecvSequence, + handshakeCachePullRule{handshake.TypeHelloVerifyRequest, cfg.initialEpoch, false, true}, + handshakeCachePullRule{handshake.TypeServerHello, cfg.initialEpoch, false, true}, + ) + if !ok { + // No valid message received. Keep reading + return 0, nil, nil + } + + if _, ok := msgs[handshake.TypeServerHello]; ok { + // Flight1 and flight2 were skipped. + // Parse as flight3. + return flight3Parse(ctx, c, state, cache, cfg) + } + + if h, ok := msgs[handshake.TypeHelloVerifyRequest].(*handshake.MessageHelloVerifyRequest); ok { + // DTLS 1.2 clients must not assume that the server will use the protocol version + // specified in HelloVerifyRequest message. RFC 6347 Section 4.2.1 + if !h.Version.Equal(protocol.Version1_0) && !h.Version.Equal(protocol.Version1_2) { + return 0, &alert.Alert{Level: alert.Fatal, Description: alert.ProtocolVersion}, errUnsupportedProtocolVersion + } + state.cookie = append([]byte{}, h.Cookie...) + state.handshakeRecvSequence = seq + return flight3, nil, nil + } + + return 0, &alert.Alert{Level: alert.Fatal, Description: alert.InternalError}, nil +} + +func flight1Generate(c flightConn, state *State, cache *handshakeCache, cfg *handshakeConfig) ([]*packet, *alert.Alert, error) { + var zeroEpoch uint16 + state.localEpoch.Store(zeroEpoch) + state.remoteEpoch.Store(zeroEpoch) + state.namedCurve = defaultNamedCurve + state.cookie = nil + + if err := state.localRandom.Populate(); err != nil { + return nil, nil, err + } + + extensions := []extension.Extension{ + &extension.SupportedSignatureAlgorithms{ + SignatureHashAlgorithms: cfg.localSignatureSchemes, + }, + &extension.RenegotiationInfo{ + RenegotiatedConnection: 0, + }, + } + if cfg.localPSKCallback == nil { + extensions = append(extensions, []extension.Extension{ + &extension.SupportedEllipticCurves{ + EllipticCurves: []elliptic.Curve{elliptic.X25519, elliptic.P256, elliptic.P384}, + }, + &extension.SupportedPointFormats{ + PointFormats: []elliptic.CurvePointFormat{elliptic.CurvePointFormatUncompressed}, + }, + }...) + } + + if len(cfg.localSRTPProtectionProfiles) > 0 { + extensions = append(extensions, &extension.UseSRTP{ + ProtectionProfiles: cfg.localSRTPProtectionProfiles, + }) + } + + if cfg.extendedMasterSecret == RequestExtendedMasterSecret || + cfg.extendedMasterSecret == RequireExtendedMasterSecret { + extensions = append(extensions, &extension.UseExtendedMasterSecret{ + Supported: true, + }) + } + + if len(cfg.serverName) > 0 { + extensions = append(extensions, &extension.ServerName{ServerName: cfg.serverName}) + } + + return []*packet{ + { + record: &recordlayer.RecordLayer{ + Header: recordlayer.Header{ + Version: protocol.Version1_2, + }, + Content: &handshake.Handshake{ + Message: &handshake.MessageClientHello{ + Version: protocol.Version1_2, + Cookie: state.cookie, + Random: state.localRandom, + CipherSuiteIDs: cipherSuiteIDs(cfg.localCipherSuites), + CompressionMethods: defaultCompressionMethods(), + Extensions: extensions, + }, + }, + }, + }, + }, nil, nil +} diff --git a/vendor/github.com/pion/dtls/v2/flight2handler.go b/vendor/github.com/pion/dtls/v2/flight2handler.go new file mode 100644 index 0000000..bb8e91d --- /dev/null +++ b/vendor/github.com/pion/dtls/v2/flight2handler.go @@ -0,0 +1,61 @@ +package dtls + +import ( + "bytes" + "context" + + "github.com/pion/dtls/v2/pkg/protocol" + "github.com/pion/dtls/v2/pkg/protocol/alert" + "github.com/pion/dtls/v2/pkg/protocol/handshake" + "github.com/pion/dtls/v2/pkg/protocol/recordlayer" +) + +func flight2Parse(ctx context.Context, c flightConn, state *State, cache *handshakeCache, cfg *handshakeConfig) (flightVal, *alert.Alert, error) { + seq, msgs, ok := cache.fullPullMap(state.handshakeRecvSequence, + handshakeCachePullRule{handshake.TypeClientHello, cfg.initialEpoch, true, false}, + ) + if !ok { + // Client may retransmit the first ClientHello when HelloVerifyRequest is dropped. + // Parse as flight 0 in this case. + return flight0Parse(ctx, c, state, cache, cfg) + } + state.handshakeRecvSequence = seq + + var clientHello *handshake.MessageClientHello + + // Validate type + if clientHello, ok = msgs[handshake.TypeClientHello].(*handshake.MessageClientHello); !ok { + return 0, &alert.Alert{Level: alert.Fatal, Description: alert.InternalError}, nil + } + + if !clientHello.Version.Equal(protocol.Version1_2) { + return 0, &alert.Alert{Level: alert.Fatal, Description: alert.ProtocolVersion}, errUnsupportedProtocolVersion + } + + if len(clientHello.Cookie) == 0 { + return 0, nil, nil + } + if !bytes.Equal(state.cookie, clientHello.Cookie) { + return 0, &alert.Alert{Level: alert.Fatal, Description: alert.AccessDenied}, errCookieMismatch + } + return flight4, nil, nil +} + +func flight2Generate(c flightConn, state *State, cache *handshakeCache, cfg *handshakeConfig) ([]*packet, *alert.Alert, error) { + state.handshakeSendSequence = 0 + return []*packet{ + { + record: &recordlayer.RecordLayer{ + Header: recordlayer.Header{ + Version: protocol.Version1_2, + }, + Content: &handshake.Handshake{ + Message: &handshake.MessageHelloVerifyRequest{ + Version: protocol.Version1_2, + Cookie: state.cookie, + }, + }, + }, + }, + }, nil, nil +} diff --git a/vendor/github.com/pion/dtls/v2/flight3handler.go b/vendor/github.com/pion/dtls/v2/flight3handler.go new file mode 100644 index 0000000..f953be8 --- /dev/null +++ b/vendor/github.com/pion/dtls/v2/flight3handler.go @@ -0,0 +1,194 @@ +package dtls + +import ( + "context" + + "github.com/pion/dtls/v2/pkg/crypto/elliptic" + "github.com/pion/dtls/v2/pkg/crypto/prf" + "github.com/pion/dtls/v2/pkg/protocol" + "github.com/pion/dtls/v2/pkg/protocol/alert" + "github.com/pion/dtls/v2/pkg/protocol/extension" + "github.com/pion/dtls/v2/pkg/protocol/handshake" + "github.com/pion/dtls/v2/pkg/protocol/recordlayer" +) + +func flight3Parse(ctx context.Context, c flightConn, state *State, cache *handshakeCache, cfg *handshakeConfig) (flightVal, *alert.Alert, error) { //nolint:gocognit + // Clients may receive multiple HelloVerifyRequest messages with different cookies. + // Clients SHOULD handle this by sending a new ClientHello with a cookie in response + // to the new HelloVerifyRequest. RFC 6347 Section 4.2.1 + seq, msgs, ok := cache.fullPullMap(state.handshakeRecvSequence, + handshakeCachePullRule{handshake.TypeHelloVerifyRequest, cfg.initialEpoch, false, true}, + ) + if ok { + if h, msgOk := msgs[handshake.TypeHelloVerifyRequest].(*handshake.MessageHelloVerifyRequest); msgOk { + // DTLS 1.2 clients must not assume that the server will use the protocol version + // specified in HelloVerifyRequest message. RFC 6347 Section 4.2.1 + if !h.Version.Equal(protocol.Version1_0) && !h.Version.Equal(protocol.Version1_2) { + return 0, &alert.Alert{Level: alert.Fatal, Description: alert.ProtocolVersion}, errUnsupportedProtocolVersion + } + state.cookie = append([]byte{}, h.Cookie...) + state.handshakeRecvSequence = seq + return flight3, nil, nil + } + } + + if cfg.localPSKCallback != nil { + seq, msgs, ok = cache.fullPullMap(state.handshakeRecvSequence, + handshakeCachePullRule{handshake.TypeServerHello, cfg.initialEpoch, false, false}, + handshakeCachePullRule{handshake.TypeServerKeyExchange, cfg.initialEpoch, false, true}, + handshakeCachePullRule{handshake.TypeServerHelloDone, cfg.initialEpoch, false, false}, + ) + } else { + seq, msgs, ok = cache.fullPullMap(state.handshakeRecvSequence, + handshakeCachePullRule{handshake.TypeServerHello, cfg.initialEpoch, false, false}, + handshakeCachePullRule{handshake.TypeCertificate, cfg.initialEpoch, false, true}, + handshakeCachePullRule{handshake.TypeServerKeyExchange, cfg.initialEpoch, false, false}, + handshakeCachePullRule{handshake.TypeCertificateRequest, cfg.initialEpoch, false, true}, + handshakeCachePullRule{handshake.TypeServerHelloDone, cfg.initialEpoch, false, false}, + ) + } + if !ok { + // Don't have enough messages. Keep reading + return 0, nil, nil + } + state.handshakeRecvSequence = seq + + if h, ok := msgs[handshake.TypeServerHello].(*handshake.MessageServerHello); ok { + if !h.Version.Equal(protocol.Version1_2) { + return 0, &alert.Alert{Level: alert.Fatal, Description: alert.ProtocolVersion}, errUnsupportedProtocolVersion + } + for _, v := range h.Extensions { + switch e := v.(type) { + case *extension.UseSRTP: + profile, ok := findMatchingSRTPProfile(e.ProtectionProfiles, cfg.localSRTPProtectionProfiles) + if !ok { + return 0, &alert.Alert{Level: alert.Fatal, Description: alert.IllegalParameter}, errClientNoMatchingSRTPProfile + } + state.srtpProtectionProfile = profile + case *extension.UseExtendedMasterSecret: + if cfg.extendedMasterSecret != DisableExtendedMasterSecret { + state.extendedMasterSecret = true + } + } + } + if cfg.extendedMasterSecret == RequireExtendedMasterSecret && !state.extendedMasterSecret { + return 0, &alert.Alert{Level: alert.Fatal, Description: alert.InsufficientSecurity}, errClientRequiredButNoServerEMS + } + if len(cfg.localSRTPProtectionProfiles) > 0 && state.srtpProtectionProfile == 0 { + return 0, &alert.Alert{Level: alert.Fatal, Description: alert.InsufficientSecurity}, errRequestedButNoSRTPExtension + } + + remoteCipherSuite := cipherSuiteForID(CipherSuiteID(*h.CipherSuiteID), cfg.customCipherSuites) + if remoteCipherSuite == nil { + return 0, &alert.Alert{Level: alert.Fatal, Description: alert.InsufficientSecurity}, errCipherSuiteNoIntersection + } + + selectedCipherSuite, ok := findMatchingCipherSuite([]CipherSuite{remoteCipherSuite}, cfg.localCipherSuites) + if !ok { + return 0, &alert.Alert{Level: alert.Fatal, Description: alert.InsufficientSecurity}, errInvalidCipherSuite + } + + state.cipherSuite = selectedCipherSuite + state.remoteRandom = h.Random + cfg.log.Tracef("[handshake] use cipher suite: %s", selectedCipherSuite.String()) + } + + if h, ok := msgs[handshake.TypeCertificate].(*handshake.MessageCertificate); ok { + state.PeerCertificates = h.Certificate + } else if state.cipherSuite.AuthenticationType() == CipherSuiteAuthenticationTypeCertificate { + return 0, &alert.Alert{Level: alert.Fatal, Description: alert.NoCertificate}, errInvalidCertificate + } + + if h, ok := msgs[handshake.TypeServerKeyExchange].(*handshake.MessageServerKeyExchange); ok { + alertPtr, err := handleServerKeyExchange(c, state, cfg, h) + if err != nil { + return 0, alertPtr, err + } + } + + if _, ok := msgs[handshake.TypeCertificateRequest].(*handshake.MessageCertificateRequest); ok { + state.remoteRequestedCertificate = true + } + + return flight5, nil, nil +} + +func handleServerKeyExchange(_ flightConn, state *State, cfg *handshakeConfig, h *handshake.MessageServerKeyExchange) (*alert.Alert, error) { + var err error + if cfg.localPSKCallback != nil { + var psk []byte + if psk, err = cfg.localPSKCallback(h.IdentityHint); err != nil { + return &alert.Alert{Level: alert.Fatal, Description: alert.InternalError}, err + } + state.IdentityHint = h.IdentityHint + state.preMasterSecret = prf.PSKPreMasterSecret(psk) + } else { + if state.localKeypair, err = elliptic.GenerateKeypair(h.NamedCurve); err != nil { + return &alert.Alert{Level: alert.Fatal, Description: alert.InternalError}, err + } + + if state.preMasterSecret, err = prf.PreMasterSecret(h.PublicKey, state.localKeypair.PrivateKey, state.localKeypair.Curve); err != nil { + return &alert.Alert{Level: alert.Fatal, Description: alert.InternalError}, err + } + } + + return nil, nil +} + +func flight3Generate(c flightConn, state *State, cache *handshakeCache, cfg *handshakeConfig) ([]*packet, *alert.Alert, error) { + extensions := []extension.Extension{ + &extension.SupportedSignatureAlgorithms{ + SignatureHashAlgorithms: cfg.localSignatureSchemes, + }, + &extension.RenegotiationInfo{ + RenegotiatedConnection: 0, + }, + } + if cfg.localPSKCallback == nil { + extensions = append(extensions, []extension.Extension{ + &extension.SupportedEllipticCurves{ + EllipticCurves: []elliptic.Curve{elliptic.X25519, elliptic.P256, elliptic.P384}, + }, + &extension.SupportedPointFormats{ + PointFormats: []elliptic.CurvePointFormat{elliptic.CurvePointFormatUncompressed}, + }, + }...) + } + + if len(cfg.localSRTPProtectionProfiles) > 0 { + extensions = append(extensions, &extension.UseSRTP{ + ProtectionProfiles: cfg.localSRTPProtectionProfiles, + }) + } + + if cfg.extendedMasterSecret == RequestExtendedMasterSecret || + cfg.extendedMasterSecret == RequireExtendedMasterSecret { + extensions = append(extensions, &extension.UseExtendedMasterSecret{ + Supported: true, + }) + } + + if len(cfg.serverName) > 0 { + extensions = append(extensions, &extension.ServerName{ServerName: cfg.serverName}) + } + + return []*packet{ + { + record: &recordlayer.RecordLayer{ + Header: recordlayer.Header{ + Version: protocol.Version1_2, + }, + Content: &handshake.Handshake{ + Message: &handshake.MessageClientHello{ + Version: protocol.Version1_2, + Cookie: state.cookie, + Random: state.localRandom, + CipherSuiteIDs: cipherSuiteIDs(cfg.localCipherSuites), + CompressionMethods: defaultCompressionMethods(), + Extensions: extensions, + }, + }, + }, + }, + }, nil, nil +} diff --git a/vendor/github.com/pion/dtls/v2/flight4handler.go b/vendor/github.com/pion/dtls/v2/flight4handler.go new file mode 100644 index 0000000..d791c9d --- /dev/null +++ b/vendor/github.com/pion/dtls/v2/flight4handler.go @@ -0,0 +1,337 @@ +package dtls + +import ( + "context" + "crypto/x509" + + "github.com/pion/dtls/v2/pkg/crypto/clientcertificate" + "github.com/pion/dtls/v2/pkg/crypto/elliptic" + "github.com/pion/dtls/v2/pkg/crypto/prf" + "github.com/pion/dtls/v2/pkg/crypto/signaturehash" + "github.com/pion/dtls/v2/pkg/protocol" + "github.com/pion/dtls/v2/pkg/protocol/alert" + "github.com/pion/dtls/v2/pkg/protocol/extension" + "github.com/pion/dtls/v2/pkg/protocol/handshake" + "github.com/pion/dtls/v2/pkg/protocol/recordlayer" +) + +func flight4Parse(ctx context.Context, c flightConn, state *State, cache *handshakeCache, cfg *handshakeConfig) (flightVal, *alert.Alert, error) { //nolint:gocognit + seq, msgs, ok := cache.fullPullMap(state.handshakeRecvSequence, + handshakeCachePullRule{handshake.TypeCertificate, cfg.initialEpoch, true, true}, + handshakeCachePullRule{handshake.TypeClientKeyExchange, cfg.initialEpoch, true, false}, + handshakeCachePullRule{handshake.TypeCertificateVerify, cfg.initialEpoch, true, true}, + ) + if !ok { + // No valid message received. Keep reading + return 0, nil, nil + } + + // Validate type + var clientKeyExchange *handshake.MessageClientKeyExchange + if clientKeyExchange, ok = msgs[handshake.TypeClientKeyExchange].(*handshake.MessageClientKeyExchange); !ok { + return 0, &alert.Alert{Level: alert.Fatal, Description: alert.InternalError}, nil + } + + if h, hasCert := msgs[handshake.TypeCertificate].(*handshake.MessageCertificate); hasCert { + state.PeerCertificates = h.Certificate + } + + if h, hasCertVerify := msgs[handshake.TypeCertificateVerify].(*handshake.MessageCertificateVerify); hasCertVerify { + if state.PeerCertificates == nil { + return 0, &alert.Alert{Level: alert.Fatal, Description: alert.NoCertificate}, errCertificateVerifyNoCertificate + } + + plainText := cache.pullAndMerge( + handshakeCachePullRule{handshake.TypeClientHello, cfg.initialEpoch, true, false}, + handshakeCachePullRule{handshake.TypeServerHello, cfg.initialEpoch, false, false}, + handshakeCachePullRule{handshake.TypeCertificate, cfg.initialEpoch, false, false}, + handshakeCachePullRule{handshake.TypeServerKeyExchange, cfg.initialEpoch, false, false}, + handshakeCachePullRule{handshake.TypeCertificateRequest, cfg.initialEpoch, false, false}, + handshakeCachePullRule{handshake.TypeServerHelloDone, cfg.initialEpoch, false, false}, + handshakeCachePullRule{handshake.TypeCertificate, cfg.initialEpoch, true, false}, + handshakeCachePullRule{handshake.TypeClientKeyExchange, cfg.initialEpoch, true, false}, + ) + + // Verify that the pair of hash algorithm and signiture is listed. + var validSignatureScheme bool + for _, ss := range cfg.localSignatureSchemes { + if ss.Hash == h.HashAlgorithm && ss.Signature == h.SignatureAlgorithm { + validSignatureScheme = true + break + } + } + if !validSignatureScheme { + return 0, &alert.Alert{Level: alert.Fatal, Description: alert.InsufficientSecurity}, errNoAvailableSignatureSchemes + } + + if err := verifyCertificateVerify(plainText, h.HashAlgorithm, h.Signature, state.PeerCertificates); err != nil { + return 0, &alert.Alert{Level: alert.Fatal, Description: alert.BadCertificate}, err + } + var chains [][]*x509.Certificate + var err error + var verified bool + if cfg.clientAuth >= VerifyClientCertIfGiven { + if chains, err = verifyClientCert(state.PeerCertificates, cfg.clientCAs); err != nil { + return 0, &alert.Alert{Level: alert.Fatal, Description: alert.BadCertificate}, err + } + verified = true + } + if cfg.verifyPeerCertificate != nil { + if err := cfg.verifyPeerCertificate(state.PeerCertificates, chains); err != nil { + return 0, &alert.Alert{Level: alert.Fatal, Description: alert.BadCertificate}, err + } + } + state.peerCertificatesVerified = verified + } + + if !state.cipherSuite.IsInitialized() { + serverRandom := state.localRandom.MarshalFixed() + clientRandom := state.remoteRandom.MarshalFixed() + + var err error + var preMasterSecret []byte + if state.cipherSuite.AuthenticationType() == CipherSuiteAuthenticationTypePreSharedKey { + var psk []byte + if psk, err = cfg.localPSKCallback(clientKeyExchange.IdentityHint); err != nil { + return 0, &alert.Alert{Level: alert.Fatal, Description: alert.InternalError}, err + } + state.IdentityHint = clientKeyExchange.IdentityHint + preMasterSecret = prf.PSKPreMasterSecret(psk) + } else { + preMasterSecret, err = prf.PreMasterSecret(clientKeyExchange.PublicKey, state.localKeypair.PrivateKey, state.localKeypair.Curve) + if err != nil { + return 0, &alert.Alert{Level: alert.Fatal, Description: alert.IllegalParameter}, err + } + } + + if state.extendedMasterSecret { + var sessionHash []byte + sessionHash, err = cache.sessionHash(state.cipherSuite.HashFunc(), cfg.initialEpoch) + if err != nil { + return 0, &alert.Alert{Level: alert.Fatal, Description: alert.InternalError}, err + } + + state.masterSecret, err = prf.ExtendedMasterSecret(preMasterSecret, sessionHash, state.cipherSuite.HashFunc()) + if err != nil { + return 0, &alert.Alert{Level: alert.Fatal, Description: alert.InternalError}, err + } + } else { + state.masterSecret, err = prf.MasterSecret(preMasterSecret, clientRandom[:], serverRandom[:], state.cipherSuite.HashFunc()) + if err != nil { + return 0, &alert.Alert{Level: alert.Fatal, Description: alert.InternalError}, err + } + } + + if err := state.cipherSuite.Init(state.masterSecret, clientRandom[:], serverRandom[:], false); err != nil { + return 0, &alert.Alert{Level: alert.Fatal, Description: alert.InternalError}, err + } + cfg.writeKeyLog(keyLogLabelTLS12, clientRandom[:], state.masterSecret) + } + + // Now, encrypted packets can be handled + if err := c.handleQueuedPackets(ctx); err != nil { + return 0, &alert.Alert{Level: alert.Fatal, Description: alert.InternalError}, err + } + + seq, msgs, ok = cache.fullPullMap(seq, + handshakeCachePullRule{handshake.TypeFinished, cfg.initialEpoch + 1, true, false}, + ) + if !ok { + // No valid message received. Keep reading + return 0, nil, nil + } + state.handshakeRecvSequence = seq + + if _, ok = msgs[handshake.TypeFinished].(*handshake.MessageFinished); !ok { + return 0, &alert.Alert{Level: alert.Fatal, Description: alert.InternalError}, nil + } + + if state.cipherSuite.AuthenticationType() == CipherSuiteAuthenticationTypeAnonymous { + return flight6, nil, nil + } + + switch cfg.clientAuth { + case RequireAnyClientCert: + if state.PeerCertificates == nil { + return 0, &alert.Alert{Level: alert.Fatal, Description: alert.NoCertificate}, errClientCertificateRequired + } + case VerifyClientCertIfGiven: + if state.PeerCertificates != nil && !state.peerCertificatesVerified { + return 0, &alert.Alert{Level: alert.Fatal, Description: alert.BadCertificate}, errClientCertificateNotVerified + } + case RequireAndVerifyClientCert: + if state.PeerCertificates == nil { + return 0, &alert.Alert{Level: alert.Fatal, Description: alert.NoCertificate}, errClientCertificateRequired + } + if !state.peerCertificatesVerified { + return 0, &alert.Alert{Level: alert.Fatal, Description: alert.BadCertificate}, errClientCertificateNotVerified + } + case NoClientCert, RequestClientCert: + return flight6, nil, nil + } + + return flight6, nil, nil +} + +func flight4Generate(c flightConn, state *State, cache *handshakeCache, cfg *handshakeConfig) ([]*packet, *alert.Alert, error) { + extensions := []extension.Extension{&extension.RenegotiationInfo{ + RenegotiatedConnection: 0, + }} + if (cfg.extendedMasterSecret == RequestExtendedMasterSecret || + cfg.extendedMasterSecret == RequireExtendedMasterSecret) && state.extendedMasterSecret { + extensions = append(extensions, &extension.UseExtendedMasterSecret{ + Supported: true, + }) + } + if state.srtpProtectionProfile != 0 { + extensions = append(extensions, &extension.UseSRTP{ + ProtectionProfiles: []SRTPProtectionProfile{state.srtpProtectionProfile}, + }) + } + if state.cipherSuite.AuthenticationType() == CipherSuiteAuthenticationTypeCertificate { + extensions = append(extensions, []extension.Extension{ + &extension.SupportedEllipticCurves{ + EllipticCurves: []elliptic.Curve{elliptic.X25519, elliptic.P256, elliptic.P384}, + }, + &extension.SupportedPointFormats{ + PointFormats: []elliptic.CurvePointFormat{elliptic.CurvePointFormatUncompressed}, + }, + }...) + } + + var pkts []*packet + cipherSuiteID := uint16(state.cipherSuite.ID()) + + pkts = append(pkts, &packet{ + record: &recordlayer.RecordLayer{ + Header: recordlayer.Header{ + Version: protocol.Version1_2, + }, + Content: &handshake.Handshake{ + Message: &handshake.MessageServerHello{ + Version: protocol.Version1_2, + Random: state.localRandom, + CipherSuiteID: &cipherSuiteID, + CompressionMethod: defaultCompressionMethods()[0], + Extensions: extensions, + }, + }, + }, + }) + + switch { + case state.cipherSuite.AuthenticationType() == CipherSuiteAuthenticationTypeCertificate: + certificate, err := cfg.getCertificate(cfg.serverName) + if err != nil { + return nil, &alert.Alert{Level: alert.Fatal, Description: alert.HandshakeFailure}, err + } + + pkts = append(pkts, &packet{ + record: &recordlayer.RecordLayer{ + Header: recordlayer.Header{ + Version: protocol.Version1_2, + }, + Content: &handshake.Handshake{ + Message: &handshake.MessageCertificate{ + Certificate: certificate.Certificate, + }, + }, + }, + }) + + serverRandom := state.localRandom.MarshalFixed() + clientRandom := state.remoteRandom.MarshalFixed() + + // Find compatible signature scheme + signatureHashAlgo, err := signaturehash.SelectSignatureScheme(cfg.localSignatureSchemes, certificate.PrivateKey) + if err != nil { + return nil, &alert.Alert{Level: alert.Fatal, Description: alert.InsufficientSecurity}, err + } + + signature, err := generateKeySignature(clientRandom[:], serverRandom[:], state.localKeypair.PublicKey, state.namedCurve, certificate.PrivateKey, signatureHashAlgo.Hash) + if err != nil { + return nil, &alert.Alert{Level: alert.Fatal, Description: alert.InternalError}, err + } + state.localKeySignature = signature + + pkts = append(pkts, &packet{ + record: &recordlayer.RecordLayer{ + Header: recordlayer.Header{ + Version: protocol.Version1_2, + }, + Content: &handshake.Handshake{ + Message: &handshake.MessageServerKeyExchange{ + EllipticCurveType: elliptic.CurveTypeNamedCurve, + NamedCurve: state.namedCurve, + PublicKey: state.localKeypair.PublicKey, + HashAlgorithm: signatureHashAlgo.Hash, + SignatureAlgorithm: signatureHashAlgo.Signature, + Signature: state.localKeySignature, + }, + }, + }, + }) + + if cfg.clientAuth > NoClientCert { + pkts = append(pkts, &packet{ + record: &recordlayer.RecordLayer{ + Header: recordlayer.Header{ + Version: protocol.Version1_2, + }, + Content: &handshake.Handshake{ + Message: &handshake.MessageCertificateRequest{ + CertificateTypes: []clientcertificate.Type{clientcertificate.RSASign, clientcertificate.ECDSASign}, + SignatureHashAlgorithms: cfg.localSignatureSchemes, + }, + }, + }, + }) + } + case cfg.localPSKIdentityHint != nil: + // To help the client in selecting which identity to use, the server + // can provide a "PSK identity hint" in the ServerKeyExchange message. + // If no hint is provided, the ServerKeyExchange message is omitted. + // + // https://tools.ietf.org/html/rfc4279#section-2 + pkts = append(pkts, &packet{ + record: &recordlayer.RecordLayer{ + Header: recordlayer.Header{ + Version: protocol.Version1_2, + }, + Content: &handshake.Handshake{ + Message: &handshake.MessageServerKeyExchange{ + IdentityHint: cfg.localPSKIdentityHint, + }, + }, + }, + }) + case state.cipherSuite.AuthenticationType() == CipherSuiteAuthenticationTypeAnonymous: + pkts = append(pkts, &packet{ + record: &recordlayer.RecordLayer{ + Header: recordlayer.Header{ + Version: protocol.Version1_2, + }, + Content: &handshake.Handshake{ + Message: &handshake.MessageServerKeyExchange{ + EllipticCurveType: elliptic.CurveTypeNamedCurve, + NamedCurve: state.namedCurve, + PublicKey: state.localKeypair.PublicKey, + }, + }, + }, + }) + } + + pkts = append(pkts, &packet{ + record: &recordlayer.RecordLayer{ + Header: recordlayer.Header{ + Version: protocol.Version1_2, + }, + Content: &handshake.Handshake{ + Message: &handshake.MessageServerHelloDone{}, + }, + }, + }) + + return pkts, nil, nil +} diff --git a/vendor/github.com/pion/dtls/v2/flight5handler.go b/vendor/github.com/pion/dtls/v2/flight5handler.go new file mode 100644 index 0000000..baa1d5c --- /dev/null +++ b/vendor/github.com/pion/dtls/v2/flight5handler.go @@ -0,0 +1,323 @@ +package dtls + +import ( + "bytes" + "context" + "crypto" + "crypto/x509" + + "github.com/pion/dtls/v2/pkg/crypto/prf" + "github.com/pion/dtls/v2/pkg/crypto/signaturehash" + "github.com/pion/dtls/v2/pkg/protocol" + "github.com/pion/dtls/v2/pkg/protocol/alert" + "github.com/pion/dtls/v2/pkg/protocol/handshake" + "github.com/pion/dtls/v2/pkg/protocol/recordlayer" +) + +func flight5Parse(ctx context.Context, c flightConn, state *State, cache *handshakeCache, cfg *handshakeConfig) (flightVal, *alert.Alert, error) { + _, msgs, ok := cache.fullPullMap(state.handshakeRecvSequence, + handshakeCachePullRule{handshake.TypeFinished, cfg.initialEpoch + 1, false, false}, + ) + if !ok { + // No valid message received. Keep reading + return 0, nil, nil + } + + var finished *handshake.MessageFinished + if finished, ok = msgs[handshake.TypeFinished].(*handshake.MessageFinished); !ok { + return 0, &alert.Alert{Level: alert.Fatal, Description: alert.InternalError}, nil + } + plainText := cache.pullAndMerge( + handshakeCachePullRule{handshake.TypeClientHello, cfg.initialEpoch, true, false}, + handshakeCachePullRule{handshake.TypeServerHello, cfg.initialEpoch, false, false}, + handshakeCachePullRule{handshake.TypeCertificate, cfg.initialEpoch, false, false}, + handshakeCachePullRule{handshake.TypeServerKeyExchange, cfg.initialEpoch, false, false}, + handshakeCachePullRule{handshake.TypeCertificateRequest, cfg.initialEpoch, false, false}, + handshakeCachePullRule{handshake.TypeServerHelloDone, cfg.initialEpoch, false, false}, + handshakeCachePullRule{handshake.TypeCertificate, cfg.initialEpoch, true, false}, + handshakeCachePullRule{handshake.TypeClientKeyExchange, cfg.initialEpoch, true, false}, + handshakeCachePullRule{handshake.TypeCertificateVerify, cfg.initialEpoch, true, false}, + handshakeCachePullRule{handshake.TypeFinished, cfg.initialEpoch + 1, true, false}, + ) + + expectedVerifyData, err := prf.VerifyDataServer(state.masterSecret, plainText, state.cipherSuite.HashFunc()) + if err != nil { + return 0, &alert.Alert{Level: alert.Fatal, Description: alert.InternalError}, err + } + if !bytes.Equal(expectedVerifyData, finished.VerifyData) { + return 0, &alert.Alert{Level: alert.Fatal, Description: alert.HandshakeFailure}, errVerifyDataMismatch + } + + return flight5, nil, nil +} + +func flight5Generate(c flightConn, state *State, cache *handshakeCache, cfg *handshakeConfig) ([]*packet, *alert.Alert, error) { //nolint:gocognit + var certBytes [][]byte + var privateKey crypto.PrivateKey + if len(cfg.localCertificates) > 0 { + certificate, err := cfg.getCertificate(cfg.serverName) + if err != nil { + return nil, &alert.Alert{Level: alert.Fatal, Description: alert.HandshakeFailure}, err + } + certBytes = certificate.Certificate + privateKey = certificate.PrivateKey + } + + var pkts []*packet + + if state.remoteRequestedCertificate { + pkts = append(pkts, + &packet{ + record: &recordlayer.RecordLayer{ + Header: recordlayer.Header{ + Version: protocol.Version1_2, + }, + Content: &handshake.Handshake{ + Message: &handshake.MessageCertificate{ + Certificate: certBytes, + }, + }, + }, + }) + } + + clientKeyExchange := &handshake.MessageClientKeyExchange{} + if cfg.localPSKCallback == nil { + clientKeyExchange.PublicKey = state.localKeypair.PublicKey + } else { + clientKeyExchange.IdentityHint = cfg.localPSKIdentityHint + } + + pkts = append(pkts, + &packet{ + record: &recordlayer.RecordLayer{ + Header: recordlayer.Header{ + Version: protocol.Version1_2, + }, + Content: &handshake.Handshake{ + Message: clientKeyExchange, + }, + }, + }) + + serverKeyExchangeData := cache.pullAndMerge( + handshakeCachePullRule{handshake.TypeServerKeyExchange, cfg.initialEpoch, false, false}, + ) + + serverKeyExchange := &handshake.MessageServerKeyExchange{} + + // handshakeMessageServerKeyExchange is optional for PSK + if len(serverKeyExchangeData) == 0 { + alertPtr, err := handleServerKeyExchange(c, state, cfg, &handshake.MessageServerKeyExchange{}) + if err != nil { + return nil, alertPtr, err + } + } else { + rawHandshake := &handshake.Handshake{} + err := rawHandshake.Unmarshal(serverKeyExchangeData) + if err != nil { + return nil, &alert.Alert{Level: alert.Fatal, Description: alert.UnexpectedMessage}, err + } + + switch h := rawHandshake.Message.(type) { + case *handshake.MessageServerKeyExchange: + serverKeyExchange = h + default: + return nil, &alert.Alert{Level: alert.Fatal, Description: alert.UnexpectedMessage}, errInvalidContentType + } + } + + // Append not-yet-sent packets + merged := []byte{} + seqPred := uint16(state.handshakeSendSequence) + for _, p := range pkts { + h, ok := p.record.Content.(*handshake.Handshake) + if !ok { + return nil, &alert.Alert{Level: alert.Fatal, Description: alert.InternalError}, errInvalidContentType + } + h.Header.MessageSequence = seqPred + seqPred++ + raw, err := h.Marshal() + if err != nil { + return nil, &alert.Alert{Level: alert.Fatal, Description: alert.InternalError}, err + } + merged = append(merged, raw...) + } + + if alertPtr, err := initalizeCipherSuite(state, cache, cfg, serverKeyExchange, merged); err != nil { + return nil, alertPtr, err + } + + // If the client has sent a certificate with signing ability, a digitally-signed + // CertificateVerify message is sent to explicitly verify possession of the + // private key in the certificate. + if state.remoteRequestedCertificate && len(cfg.localCertificates) > 0 { + plainText := append(cache.pullAndMerge( + handshakeCachePullRule{handshake.TypeClientHello, cfg.initialEpoch, true, false}, + handshakeCachePullRule{handshake.TypeServerHello, cfg.initialEpoch, false, false}, + handshakeCachePullRule{handshake.TypeCertificate, cfg.initialEpoch, false, false}, + handshakeCachePullRule{handshake.TypeServerKeyExchange, cfg.initialEpoch, false, false}, + handshakeCachePullRule{handshake.TypeCertificateRequest, cfg.initialEpoch, false, false}, + handshakeCachePullRule{handshake.TypeServerHelloDone, cfg.initialEpoch, false, false}, + handshakeCachePullRule{handshake.TypeCertificate, cfg.initialEpoch, true, false}, + handshakeCachePullRule{handshake.TypeClientKeyExchange, cfg.initialEpoch, true, false}, + ), merged...) + + // Find compatible signature scheme + signatureHashAlgo, err := signaturehash.SelectSignatureScheme(cfg.localSignatureSchemes, privateKey) + if err != nil { + return nil, &alert.Alert{Level: alert.Fatal, Description: alert.InsufficientSecurity}, err + } + + certVerify, err := generateCertificateVerify(plainText, privateKey, signatureHashAlgo.Hash) + if err != nil { + return nil, &alert.Alert{Level: alert.Fatal, Description: alert.InternalError}, err + } + state.localCertificatesVerify = certVerify + + p := &packet{ + record: &recordlayer.RecordLayer{ + Header: recordlayer.Header{ + Version: protocol.Version1_2, + }, + Content: &handshake.Handshake{ + Message: &handshake.MessageCertificateVerify{ + HashAlgorithm: signatureHashAlgo.Hash, + SignatureAlgorithm: signatureHashAlgo.Signature, + Signature: state.localCertificatesVerify, + }, + }, + }, + } + pkts = append(pkts, p) + + h, ok := p.record.Content.(*handshake.Handshake) + if !ok { + return nil, &alert.Alert{Level: alert.Fatal, Description: alert.InternalError}, errInvalidContentType + } + h.Header.MessageSequence = seqPred + // seqPred++ // this is the last use of seqPred + raw, err := h.Marshal() + if err != nil { + return nil, &alert.Alert{Level: alert.Fatal, Description: alert.InternalError}, err + } + merged = append(merged, raw...) + } + + pkts = append(pkts, + &packet{ + record: &recordlayer.RecordLayer{ + Header: recordlayer.Header{ + Version: protocol.Version1_2, + }, + Content: &protocol.ChangeCipherSpec{}, + }, + }) + + if len(state.localVerifyData) == 0 { + plainText := cache.pullAndMerge( + handshakeCachePullRule{handshake.TypeClientHello, cfg.initialEpoch, true, false}, + handshakeCachePullRule{handshake.TypeServerHello, cfg.initialEpoch, false, false}, + handshakeCachePullRule{handshake.TypeCertificate, cfg.initialEpoch, false, false}, + handshakeCachePullRule{handshake.TypeServerKeyExchange, cfg.initialEpoch, false, false}, + handshakeCachePullRule{handshake.TypeCertificateRequest, cfg.initialEpoch, false, false}, + handshakeCachePullRule{handshake.TypeServerHelloDone, cfg.initialEpoch, false, false}, + handshakeCachePullRule{handshake.TypeCertificate, cfg.initialEpoch, true, false}, + handshakeCachePullRule{handshake.TypeClientKeyExchange, cfg.initialEpoch, true, false}, + handshakeCachePullRule{handshake.TypeCertificateVerify, cfg.initialEpoch, true, false}, + handshakeCachePullRule{handshake.TypeFinished, cfg.initialEpoch + 1, true, false}, + ) + + var err error + state.localVerifyData, err = prf.VerifyDataClient(state.masterSecret, append(plainText, merged...), state.cipherSuite.HashFunc()) + if err != nil { + return nil, &alert.Alert{Level: alert.Fatal, Description: alert.InternalError}, err + } + } + + pkts = append(pkts, + &packet{ + record: &recordlayer.RecordLayer{ + Header: recordlayer.Header{ + Version: protocol.Version1_2, + Epoch: 1, + }, + Content: &handshake.Handshake{ + Message: &handshake.MessageFinished{ + VerifyData: state.localVerifyData, + }, + }, + }, + shouldEncrypt: true, + resetLocalSequenceNumber: true, + }) + + return pkts, nil, nil +} + +func initalizeCipherSuite(state *State, cache *handshakeCache, cfg *handshakeConfig, h *handshake.MessageServerKeyExchange, sendingPlainText []byte) (*alert.Alert, error) { //nolint:gocognit + if state.cipherSuite.IsInitialized() { + return nil, nil + } + + clientRandom := state.localRandom.MarshalFixed() + serverRandom := state.remoteRandom.MarshalFixed() + + var err error + + if state.extendedMasterSecret { + var sessionHash []byte + sessionHash, err = cache.sessionHash(state.cipherSuite.HashFunc(), cfg.initialEpoch, sendingPlainText) + if err != nil { + return &alert.Alert{Level: alert.Fatal, Description: alert.InternalError}, err + } + + state.masterSecret, err = prf.ExtendedMasterSecret(state.preMasterSecret, sessionHash, state.cipherSuite.HashFunc()) + if err != nil { + return &alert.Alert{Level: alert.Fatal, Description: alert.IllegalParameter}, err + } + } else { + state.masterSecret, err = prf.MasterSecret(state.preMasterSecret, clientRandom[:], serverRandom[:], state.cipherSuite.HashFunc()) + if err != nil { + return &alert.Alert{Level: alert.Fatal, Description: alert.InternalError}, err + } + } + + if state.cipherSuite.AuthenticationType() == CipherSuiteAuthenticationTypeCertificate { + // Verify that the pair of hash algorithm and signiture is listed. + var validSignatureScheme bool + for _, ss := range cfg.localSignatureSchemes { + if ss.Hash == h.HashAlgorithm && ss.Signature == h.SignatureAlgorithm { + validSignatureScheme = true + break + } + } + if !validSignatureScheme { + return &alert.Alert{Level: alert.Fatal, Description: alert.InsufficientSecurity}, errNoAvailableSignatureSchemes + } + + expectedMsg := valueKeyMessage(clientRandom[:], serverRandom[:], h.PublicKey, h.NamedCurve) + if err = verifyKeySignature(expectedMsg, h.Signature, h.HashAlgorithm, state.PeerCertificates); err != nil { + return &alert.Alert{Level: alert.Fatal, Description: alert.BadCertificate}, err + } + var chains [][]*x509.Certificate + if !cfg.insecureSkipVerify { + if chains, err = verifyServerCert(state.PeerCertificates, cfg.rootCAs, cfg.serverName); err != nil { + return &alert.Alert{Level: alert.Fatal, Description: alert.BadCertificate}, err + } + } + if cfg.verifyPeerCertificate != nil { + if err = cfg.verifyPeerCertificate(state.PeerCertificates, chains); err != nil { + return &alert.Alert{Level: alert.Fatal, Description: alert.BadCertificate}, err + } + } + } + + if err = state.cipherSuite.Init(state.masterSecret, clientRandom[:], serverRandom[:], true); err != nil { + return &alert.Alert{Level: alert.Fatal, Description: alert.InternalError}, err + } + + cfg.writeKeyLog(keyLogLabelTLS12, clientRandom[:], state.masterSecret) + + return nil, nil +} diff --git a/vendor/github.com/pion/dtls/v2/flight6handler.go b/vendor/github.com/pion/dtls/v2/flight6handler.go new file mode 100644 index 0000000..10de5ad --- /dev/null +++ b/vendor/github.com/pion/dtls/v2/flight6handler.go @@ -0,0 +1,82 @@ +package dtls + +import ( + "context" + + "github.com/pion/dtls/v2/pkg/crypto/prf" + "github.com/pion/dtls/v2/pkg/protocol" + "github.com/pion/dtls/v2/pkg/protocol/alert" + "github.com/pion/dtls/v2/pkg/protocol/handshake" + "github.com/pion/dtls/v2/pkg/protocol/recordlayer" +) + +func flight6Parse(ctx context.Context, c flightConn, state *State, cache *handshakeCache, cfg *handshakeConfig) (flightVal, *alert.Alert, error) { + _, msgs, ok := cache.fullPullMap(state.handshakeRecvSequence-1, + handshakeCachePullRule{handshake.TypeFinished, cfg.initialEpoch + 1, true, false}, + ) + if !ok { + // No valid message received. Keep reading + return 0, nil, nil + } + + if _, ok = msgs[handshake.TypeFinished].(*handshake.MessageFinished); !ok { + return 0, &alert.Alert{Level: alert.Fatal, Description: alert.InternalError}, nil + } + + // Other party retransmitted the last flight. + return flight6, nil, nil +} + +func flight6Generate(c flightConn, state *State, cache *handshakeCache, cfg *handshakeConfig) ([]*packet, *alert.Alert, error) { + var pkts []*packet + + pkts = append(pkts, + &packet{ + record: &recordlayer.RecordLayer{ + Header: recordlayer.Header{ + Version: protocol.Version1_2, + }, + Content: &protocol.ChangeCipherSpec{}, + }, + }) + + if len(state.localVerifyData) == 0 { + plainText := cache.pullAndMerge( + handshakeCachePullRule{handshake.TypeClientHello, cfg.initialEpoch, true, false}, + handshakeCachePullRule{handshake.TypeServerHello, cfg.initialEpoch, false, false}, + handshakeCachePullRule{handshake.TypeCertificate, cfg.initialEpoch, false, false}, + handshakeCachePullRule{handshake.TypeServerKeyExchange, cfg.initialEpoch, false, false}, + handshakeCachePullRule{handshake.TypeCertificateRequest, cfg.initialEpoch, false, false}, + handshakeCachePullRule{handshake.TypeServerHelloDone, cfg.initialEpoch, false, false}, + handshakeCachePullRule{handshake.TypeCertificate, cfg.initialEpoch, true, false}, + handshakeCachePullRule{handshake.TypeClientKeyExchange, cfg.initialEpoch, true, false}, + handshakeCachePullRule{handshake.TypeCertificateVerify, cfg.initialEpoch, true, false}, + handshakeCachePullRule{handshake.TypeFinished, cfg.initialEpoch + 1, true, false}, + ) + + var err error + state.localVerifyData, err = prf.VerifyDataServer(state.masterSecret, plainText, state.cipherSuite.HashFunc()) + if err != nil { + return nil, &alert.Alert{Level: alert.Fatal, Description: alert.InternalError}, err + } + } + + pkts = append(pkts, + &packet{ + record: &recordlayer.RecordLayer{ + Header: recordlayer.Header{ + Version: protocol.Version1_2, + Epoch: 1, + }, + Content: &handshake.Handshake{ + Message: &handshake.MessageFinished{ + VerifyData: state.localVerifyData, + }, + }, + }, + shouldEncrypt: true, + resetLocalSequenceNumber: true, + }, + ) + return pkts, nil, nil +} diff --git a/vendor/github.com/pion/dtls/v2/flighthandler.go b/vendor/github.com/pion/dtls/v2/flighthandler.go new file mode 100644 index 0000000..b364c09 --- /dev/null +++ b/vendor/github.com/pion/dtls/v2/flighthandler.go @@ -0,0 +1,57 @@ +package dtls + +import ( + "context" + + "github.com/pion/dtls/v2/pkg/protocol/alert" +) + +// Parse received handshakes and return next flightVal +type flightParser func(context.Context, flightConn, *State, *handshakeCache, *handshakeConfig) (flightVal, *alert.Alert, error) + +// Generate flights +type flightGenerator func(flightConn, *State, *handshakeCache, *handshakeConfig) ([]*packet, *alert.Alert, error) + +func (f flightVal) getFlightParser() (flightParser, error) { + switch f { + case flight0: + return flight0Parse, nil + case flight1: + return flight1Parse, nil + case flight2: + return flight2Parse, nil + case flight3: + return flight3Parse, nil + case flight4: + return flight4Parse, nil + case flight5: + return flight5Parse, nil + case flight6: + return flight6Parse, nil + default: + return nil, errInvalidFlight + } +} + +func (f flightVal) getFlightGenerator() (gen flightGenerator, retransmit bool, err error) { + switch f { + case flight0: + return flight0Generate, true, nil + case flight1: + return flight1Generate, true, nil + case flight2: + // https://tools.ietf.org/html/rfc6347#section-3.2.1 + // HelloVerifyRequests must not be retransmitted. + return flight2Generate, false, nil + case flight3: + return flight3Generate, true, nil + case flight4: + return flight4Generate, true, nil + case flight5: + return flight5Generate, true, nil + case flight6: + return flight6Generate, true, nil + default: + return nil, false, errInvalidFlight + } +} diff --git a/vendor/github.com/pion/dtls/v2/fragment_buffer.go b/vendor/github.com/pion/dtls/v2/fragment_buffer.go new file mode 100644 index 0000000..0274993 --- /dev/null +++ b/vendor/github.com/pion/dtls/v2/fragment_buffer.go @@ -0,0 +1,111 @@ +package dtls + +import ( + "github.com/pion/dtls/v2/pkg/protocol" + "github.com/pion/dtls/v2/pkg/protocol/handshake" + "github.com/pion/dtls/v2/pkg/protocol/recordlayer" +) + +type fragment struct { + recordLayerHeader recordlayer.Header + handshakeHeader handshake.Header + data []byte +} + +type fragmentBuffer struct { + // map of MessageSequenceNumbers that hold slices of fragments + cache map[uint16][]*fragment + + currentMessageSequenceNumber uint16 +} + +func newFragmentBuffer() *fragmentBuffer { + return &fragmentBuffer{cache: map[uint16][]*fragment{}} +} + +// Attempts to push a DTLS packet to the fragmentBuffer +// when it returns true it means the fragmentBuffer has inserted and the buffer shouldn't be handled +// when an error returns it is fatal, and the DTLS connection should be stopped +func (f *fragmentBuffer) push(buf []byte) (bool, error) { + frag := new(fragment) + if err := frag.recordLayerHeader.Unmarshal(buf); err != nil { + return false, err + } + + // fragment isn't a handshake, we don't need to handle it + if frag.recordLayerHeader.ContentType != protocol.ContentTypeHandshake { + return false, nil + } + + for buf = buf[recordlayer.HeaderSize:]; len(buf) != 0; frag = new(fragment) { + if err := frag.handshakeHeader.Unmarshal(buf); err != nil { + return false, err + } + + if _, ok := f.cache[frag.handshakeHeader.MessageSequence]; !ok { + f.cache[frag.handshakeHeader.MessageSequence] = []*fragment{} + } + + // end index should be the length of handshake header but if the handshake + // was fragmented, we should keep them all + end := int(handshake.HeaderLength + frag.handshakeHeader.Length) + if size := len(buf); end > size { + end = size + } + + // Discard all headers, when rebuilding the packet we will re-build + frag.data = append([]byte{}, buf[handshake.HeaderLength:end]...) + f.cache[frag.handshakeHeader.MessageSequence] = append(f.cache[frag.handshakeHeader.MessageSequence], frag) + buf = buf[end:] + } + + return true, nil +} + +func (f *fragmentBuffer) pop() (content []byte, epoch uint16) { + frags, ok := f.cache[f.currentMessageSequenceNumber] + if !ok { + return nil, 0 + } + + // Go doesn't support recursive lambdas + var appendMessage func(targetOffset uint32) bool + + rawMessage := []byte{} + appendMessage = func(targetOffset uint32) bool { + for _, f := range frags { + if f.handshakeHeader.FragmentOffset == targetOffset { + fragmentEnd := (f.handshakeHeader.FragmentOffset + f.handshakeHeader.FragmentLength) + if fragmentEnd != f.handshakeHeader.Length { + if !appendMessage(fragmentEnd) { + return false + } + } + + rawMessage = append(f.data, rawMessage...) + return true + } + } + return false + } + + // Recursively collect up + if !appendMessage(0) { + return nil, 0 + } + + firstHeader := frags[0].handshakeHeader + firstHeader.FragmentOffset = 0 + firstHeader.FragmentLength = firstHeader.Length + + rawHeader, err := firstHeader.Marshal() + if err != nil { + return nil, 0 + } + + messageEpoch := frags[0].recordLayerHeader.Epoch + + delete(f.cache, f.currentMessageSequenceNumber) + f.currentMessageSequenceNumber++ + return append(rawHeader, rawMessage...), messageEpoch +} diff --git a/vendor/github.com/pion/dtls/v2/fuzz.go b/vendor/github.com/pion/dtls/v2/fuzz.go new file mode 100644 index 0000000..56c1bf2 --- /dev/null +++ b/vendor/github.com/pion/dtls/v2/fuzz.go @@ -0,0 +1,38 @@ +// +build gofuzz + +package dtls + +import "fmt" + +func partialHeaderMismatch(a, b recordlayer.Header) bool { + // Ignoring content length for now. + a.contentLen = b.contentLen + return a != b +} + +func FuzzRecordLayer(data []byte) int { + var r recordLayer + if err := r.Unmarshal(data); err != nil { + return 0 + } + buf, err := r.Marshal() + if err != nil { + return 1 + } + if len(buf) == 0 { + panic("zero buff") // nolint + } + var nr recordLayer + if err = nr.Unmarshal(data); err != nil { + panic(err) // nolint + } + if partialHeaderMismatch(nr.recordlayer.Header, r.recordlayer.Header) { + panic( // nolint + fmt.Sprintf("header mismatch: %+v != %+v", + nr.recordlayer.Header, r.recordlayer.Header, + ), + ) + } + + return 1 +} diff --git a/vendor/github.com/pion/dtls/v2/go.mod b/vendor/github.com/pion/dtls/v2/go.mod new file mode 100644 index 0000000..33b60e0 --- /dev/null +++ b/vendor/github.com/pion/dtls/v2/go.mod @@ -0,0 +1,12 @@ +module github.com/pion/dtls/v2 + +require ( + github.com/pion/logging v0.2.2 + github.com/pion/transport v0.12.2 + github.com/pion/udp v0.1.0 + golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad + golang.org/x/net v0.0.0-20210119194325-5f4716e94777 + golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 +) + +go 1.13 diff --git a/vendor/github.com/pion/dtls/v2/go.sum b/vendor/github.com/pion/dtls/v2/go.sum new file mode 100644 index 0000000..fa7c410 --- /dev/null +++ b/vendor/github.com/pion/dtls/v2/go.sum @@ -0,0 +1,48 @@ +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY= +github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms= +github.com/pion/transport v0.10.0 h1:9M12BSneJm6ggGhJyWpDveFOstJsTiQjkLf4M44rm80= +github.com/pion/transport v0.10.0/go.mod h1:BnHnUipd0rZQyTVB2SBGojFHT9CBt5C5TcsJSQGkvSE= +github.com/pion/transport v0.12.2 h1:WYEjhloRHt1R86LhUKjC5y+P52Y11/QqEUalvtzVoys= +github.com/pion/transport v0.12.2/go.mod h1:N3+vZQD9HlDP5GWkZ85LohxNsDcNgofQmyL6ojX5d8Q= +github.com/pion/udp v0.1.0 h1:uGxQsNyrqG3GLINv36Ff60covYmfrLoxzwnCsIYspXI= +github.com/pion/udp v0.1.0/go.mod h1:BPELIjbwE9PRbd/zxI/KYBnbo7B6+oA6YuEaNE8lths= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad h1:DN0cp81fZ3njFcrLCytUHRSUkqBjfTo4Tx9RJTWs0EY= +golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201201195509-5d6afe98e0b7 h1:3uJsdck53FDIpWwLeAXlia9p4C8j0BO2xZrqzKpL0D8= +golang.org/x/net v0.0.0-20201201195509-5d6afe98e0b7/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210119194325-5f4716e94777 h1:003p0dJM77cxMSyCPFphvZf/Y5/NXf5fzg6ufd1/Oew= +golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/vendor/github.com/pion/dtls/v2/handshake_cache.go b/vendor/github.com/pion/dtls/v2/handshake_cache.go new file mode 100644 index 0000000..063a858 --- /dev/null +++ b/vendor/github.com/pion/dtls/v2/handshake_cache.go @@ -0,0 +1,171 @@ +package dtls + +import ( + "sync" + + "github.com/pion/dtls/v2/pkg/crypto/prf" + "github.com/pion/dtls/v2/pkg/protocol/handshake" +) + +type handshakeCacheItem struct { + typ handshake.Type + isClient bool + epoch uint16 + messageSequence uint16 + data []byte +} + +type handshakeCachePullRule struct { + typ handshake.Type + epoch uint16 + isClient bool + optional bool +} + +type handshakeCache struct { + cache []*handshakeCacheItem + mu sync.Mutex +} + +func newHandshakeCache() *handshakeCache { + return &handshakeCache{} +} + +func (h *handshakeCache) push(data []byte, epoch, messageSequence uint16, typ handshake.Type, isClient bool) bool { //nolint + h.mu.Lock() + defer h.mu.Unlock() + + for _, i := range h.cache { + if i.messageSequence == messageSequence && + i.isClient == isClient { + return false + } + } + + h.cache = append(h.cache, &handshakeCacheItem{ + data: append([]byte{}, data...), + epoch: epoch, + messageSequence: messageSequence, + typ: typ, + isClient: isClient, + }) + return true +} + +// returns a list handshakes that match the requested rules +// the list will contain null entries for rules that can't be satisfied +// multiple entries may match a rule, but only the last match is returned (ie ClientHello with cookies) +func (h *handshakeCache) pull(rules ...handshakeCachePullRule) []*handshakeCacheItem { + h.mu.Lock() + defer h.mu.Unlock() + + out := make([]*handshakeCacheItem, len(rules)) + for i, r := range rules { + for _, c := range h.cache { + if c.typ == r.typ && c.isClient == r.isClient && c.epoch == r.epoch { + switch { + case out[i] == nil: + out[i] = c + case out[i].messageSequence < c.messageSequence: + out[i] = c + } + } + } + } + + return out +} + +// fullPullMap pulls all handshakes between rules[0] to rules[len(rules)-1] as map. +func (h *handshakeCache) fullPullMap(startSeq int, rules ...handshakeCachePullRule) (int, map[handshake.Type]handshake.Message, bool) { + h.mu.Lock() + defer h.mu.Unlock() + + ci := make(map[handshake.Type]*handshakeCacheItem) + for _, r := range rules { + var item *handshakeCacheItem + for _, c := range h.cache { + if c.typ == r.typ && c.isClient == r.isClient && c.epoch == r.epoch { + switch { + case item == nil: + item = c + case item.messageSequence < c.messageSequence: + item = c + } + } + } + if !r.optional && item == nil { + // Missing mandatory message. + return startSeq, nil, false + } + ci[r.typ] = item + } + out := make(map[handshake.Type]handshake.Message) + seq := startSeq + for _, r := range rules { + t := r.typ + i := ci[t] + if i == nil { + continue + } + rawHandshake := &handshake.Handshake{} + if err := rawHandshake.Unmarshal(i.data); err != nil { + return startSeq, nil, false + } + if uint16(seq) != rawHandshake.Header.MessageSequence { + // There is a gap. Some messages are not arrived. + return startSeq, nil, false + } + seq++ + out[t] = rawHandshake.Message + } + return seq, out, true +} + +// pullAndMerge calls pull and then merges the results, ignoring any null entries +func (h *handshakeCache) pullAndMerge(rules ...handshakeCachePullRule) []byte { + merged := []byte{} + + for _, p := range h.pull(rules...) { + if p != nil { + merged = append(merged, p.data...) + } + } + return merged +} + +// sessionHash returns the session hash for Extended Master Secret support +// https://tools.ietf.org/html/draft-ietf-tls-session-hash-06#section-4 +func (h *handshakeCache) sessionHash(hf prf.HashFunc, epoch uint16, additional ...[]byte) ([]byte, error) { + merged := []byte{} + + // Order defined by https://tools.ietf.org/html/rfc5246#section-7.3 + handshakeBuffer := h.pull( + handshakeCachePullRule{handshake.TypeClientHello, epoch, true, false}, + handshakeCachePullRule{handshake.TypeServerHello, epoch, false, false}, + handshakeCachePullRule{handshake.TypeCertificate, epoch, false, false}, + handshakeCachePullRule{handshake.TypeServerKeyExchange, epoch, false, false}, + handshakeCachePullRule{handshake.TypeCertificateRequest, epoch, false, false}, + handshakeCachePullRule{handshake.TypeServerHelloDone, epoch, false, false}, + handshakeCachePullRule{handshake.TypeCertificate, epoch, true, false}, + handshakeCachePullRule{handshake.TypeClientKeyExchange, epoch, true, false}, + ) + + for _, p := range handshakeBuffer { + if p == nil { + continue + } + + merged = append(merged, p.data...) + } + for _, a := range additional { + merged = append(merged, a...) + } + + hash := hf() + if _, err := hash.Write(merged); err != nil { + return []byte{}, err + } + + return hash.Sum(nil), nil +} diff --git a/vendor/github.com/pion/dtls/v2/handshaker.go b/vendor/github.com/pion/dtls/v2/handshaker.go new file mode 100644 index 0000000..0f5077e --- /dev/null +++ b/vendor/github.com/pion/dtls/v2/handshaker.go @@ -0,0 +1,334 @@ +package dtls + +import ( + "context" + "crypto/tls" + "crypto/x509" + "fmt" + "io" + "sync" + "time" + + "github.com/pion/dtls/v2/pkg/crypto/signaturehash" + "github.com/pion/dtls/v2/pkg/protocol/alert" + "github.com/pion/dtls/v2/pkg/protocol/handshake" + "github.com/pion/logging" +) + +// [RFC6347 Section-4.2.4] +// +-----------+ +// +---> | PREPARING | <--------------------+ +// | +-----------+ | +// | | | +// | | Buffer next flight | +// | | | +// | \|/ | +// | +-----------+ | +// | | SENDING |<------------------+ | Send +// | +-----------+ | | HelloRequest +// Receive | | | | +// next | | Send flight | | or +// flight | +--------+ | | +// | | | Set retransmit timer | | Receive +// | | \|/ | | HelloRequest +// | | +-----------+ | | Send +// +--)--| WAITING |-------------------+ | ClientHello +// | | +-----------+ Timer expires | | +// | | | | | +// | | +------------------------+ | +// Receive | | Send Read retransmit | +// last | | last | +// flight | | flight | +// | | | +// \|/\|/ | +// +-----------+ | +// | FINISHED | -------------------------------+ +// +-----------+ +// | /|\ +// | | +// +---+ +// Read retransmit +// Retransmit last flight + +type handshakeState uint8 + +const ( + handshakeErrored handshakeState = iota + handshakePreparing + handshakeSending + handshakeWaiting + handshakeFinished +) + +func (s handshakeState) String() string { + switch s { + case handshakeErrored: + return "Errored" + case handshakePreparing: + return "Preparing" + case handshakeSending: + return "Sending" + case handshakeWaiting: + return "Waiting" + case handshakeFinished: + return "Finished" + default: + return "Unknown" + } +} + +type handshakeFSM struct { + currentFlight flightVal + flights []*packet + retransmit bool + state *State + cache *handshakeCache + cfg *handshakeConfig + closed chan struct{} +} + +type handshakeConfig struct { + localPSKCallback PSKCallback + localPSKIdentityHint []byte + localCipherSuites []CipherSuite // Available CipherSuites + localSignatureSchemes []signaturehash.Algorithm // Available signature schemes + extendedMasterSecret ExtendedMasterSecretType // Policy for the Extended Master Support extension + localSRTPProtectionProfiles []SRTPProtectionProfile // Available SRTPProtectionProfiles, if empty no SRTP support + serverName string + clientAuth ClientAuthType // If we are a client should we request a client certificate + localCertificates []tls.Certificate + nameToCertificate map[string]*tls.Certificate + insecureSkipVerify bool + verifyPeerCertificate func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error + rootCAs *x509.CertPool + clientCAs *x509.CertPool + retransmitInterval time.Duration + customCipherSuites func() []CipherSuite + + onFlightState func(flightVal, handshakeState) + log logging.LeveledLogger + keyLogWriter io.Writer + + initialEpoch uint16 + + mu sync.Mutex +} + +type flightConn interface { + notify(ctx context.Context, level alert.Level, desc alert.Description) error + writePackets(context.Context, []*packet) error + recvHandshake() <-chan chan struct{} + setLocalEpoch(epoch uint16) + handleQueuedPackets(context.Context) error +} + +func (c *handshakeConfig) writeKeyLog(label string, clientRandom, secret []byte) { + if c.keyLogWriter == nil { + return + } + c.mu.Lock() + defer c.mu.Unlock() + _, err := c.keyLogWriter.Write([]byte(fmt.Sprintf("%s %x %x\n", label, clientRandom, secret))) + if err != nil { + c.log.Debugf("failed to write key log file: %s", err) + } +} + +func srvCliStr(isClient bool) string { + if isClient { + return "client" + } + return "server" +} + +func newHandshakeFSM( + s *State, cache *handshakeCache, cfg *handshakeConfig, + initialFlight flightVal, +) *handshakeFSM { + return &handshakeFSM{ + currentFlight: initialFlight, + state: s, + cache: cache, + cfg: cfg, + closed: make(chan struct{}), + } +} + +func (s *handshakeFSM) Run(ctx context.Context, c flightConn, initialState handshakeState) error { + state := initialState + defer func() { + close(s.closed) + }() + for { + s.cfg.log.Tracef("[handshake:%s] %s: %s", srvCliStr(s.state.isClient), s.currentFlight.String(), state.String()) + if s.cfg.onFlightState != nil { + s.cfg.onFlightState(s.currentFlight, state) + } + var err error + switch state { + case handshakePreparing: + state, err = s.prepare(ctx, c) + case handshakeSending: + state, err = s.send(ctx, c) + case handshakeWaiting: + state, err = s.wait(ctx, c) + case handshakeFinished: + state, err = s.finish(ctx, c) + default: + return errInvalidFSMTransition + } + if err != nil { + return err + } + } +} + +func (s *handshakeFSM) Done() <-chan struct{} { + return s.closed +} + +func (s *handshakeFSM) prepare(ctx context.Context, c flightConn) (handshakeState, error) { + s.flights = nil + // Prepare flights + var ( + a *alert.Alert + err error + pkts []*packet + ) + gen, retransmit, errFlight := s.currentFlight.getFlightGenerator() + if errFlight != nil { + err = errFlight + a = &alert.Alert{Level: alert.Fatal, Description: alert.InternalError} + } else { + pkts, a, err = gen(c, s.state, s.cache, s.cfg) + s.retransmit = retransmit + } + if a != nil { + if alertErr := c.notify(ctx, a.Level, a.Description); alertErr != nil { + if err != nil { + err = alertErr + } + } + } + if err != nil { + return handshakeErrored, err + } + + s.flights = pkts + epoch := s.cfg.initialEpoch + nextEpoch := epoch + for _, p := range s.flights { + p.record.Header.Epoch += epoch + if p.record.Header.Epoch > nextEpoch { + nextEpoch = p.record.Header.Epoch + } + if h, ok := p.record.Content.(*handshake.Handshake); ok { + h.Header.MessageSequence = uint16(s.state.handshakeSendSequence) + s.state.handshakeSendSequence++ + } + } + if epoch != nextEpoch { + s.cfg.log.Tracef("[handshake:%s] -> changeCipherSpec (epoch: %d)", srvCliStr(s.state.isClient), nextEpoch) + c.setLocalEpoch(nextEpoch) + } + return handshakeSending, nil +} + +func (s *handshakeFSM) send(ctx context.Context, c flightConn) (handshakeState, error) { + // Send flights + if err := c.writePackets(ctx, s.flights); err != nil { + return handshakeErrored, err + } + + if s.currentFlight.isLastSendFlight() { + return handshakeFinished, nil + } + return handshakeWaiting, nil +} + +func (s *handshakeFSM) wait(ctx context.Context, c flightConn) (handshakeState, error) { //nolint:gocognit + parse, errFlight := s.currentFlight.getFlightParser() + if errFlight != nil { + if alertErr := c.notify(ctx, alert.Fatal, alert.InternalError); alertErr != nil { + if errFlight != nil { + return handshakeErrored, alertErr + } + } + return handshakeErrored, errFlight + } + + retransmitTimer := time.NewTimer(s.cfg.retransmitInterval) + for { + select { + case done := <-c.recvHandshake(): + nextFlight, alert, err := parse(ctx, c, s.state, s.cache, s.cfg) + close(done) + if alert != nil { + if alertErr := c.notify(ctx, alert.Level, alert.Description); alertErr != nil { + if err != nil { + err = alertErr + } + } + } + if err != nil { + return handshakeErrored, err + } + if nextFlight == 0 { + break + } + s.cfg.log.Tracef("[handshake:%s] %s -> %s", srvCliStr(s.state.isClient), s.currentFlight.String(), nextFlight.String()) + if nextFlight.isLastRecvFlight() && s.currentFlight == nextFlight { + return handshakeFinished, nil + } + s.currentFlight = nextFlight + return handshakePreparing, nil + + case <-retransmitTimer.C: + if !s.retransmit { + return handshakeWaiting, nil + } + return handshakeSending, nil + case <-ctx.Done(): + return handshakeErrored, ctx.Err() + } + } +} + +func (s *handshakeFSM) finish(ctx context.Context, c flightConn) (handshakeState, error) { + parse, errFlight := s.currentFlight.getFlightParser() + if errFlight != nil { + if alertErr := c.notify(ctx, alert.Fatal, alert.InternalError); alertErr != nil { + if errFlight != nil { + return handshakeErrored, alertErr + } + } + return handshakeErrored, errFlight + } + + retransmitTimer := time.NewTimer(s.cfg.retransmitInterval) + select { + case done := <-c.recvHandshake(): + nextFlight, alert, err := parse(ctx, c, s.state, s.cache, s.cfg) + close(done) + if alert != nil { + if alertErr := c.notify(ctx, alert.Level, alert.Description); alertErr != nil { + if err != nil { + err = alertErr + } + } + } + if err != nil { + return handshakeErrored, err + } + if nextFlight == 0 { + break + } + <-retransmitTimer.C + // Retransmit last flight + return handshakeSending, nil + + case <-ctx.Done(): + return handshakeErrored, ctx.Err() + } + return handshakeFinished, nil +} diff --git a/vendor/github.com/pion/dtls/v2/internal/ciphersuite/aes_128_ccm.go b/vendor/github.com/pion/dtls/v2/internal/ciphersuite/aes_128_ccm.go new file mode 100644 index 0000000..dcc5379 --- /dev/null +++ b/vendor/github.com/pion/dtls/v2/internal/ciphersuite/aes_128_ccm.go @@ -0,0 +1,108 @@ +package ciphersuite + +import ( + "crypto/sha256" + "fmt" + "hash" + "sync/atomic" + + "github.com/pion/dtls/v2/pkg/crypto/ciphersuite" + "github.com/pion/dtls/v2/pkg/crypto/clientcertificate" + "github.com/pion/dtls/v2/pkg/crypto/prf" + "github.com/pion/dtls/v2/pkg/protocol/recordlayer" +) + +// Aes128Ccm is a base class used by multiple AES-CCM Ciphers +type Aes128Ccm struct { + ccm atomic.Value // *cryptoCCM + clientCertificateType clientcertificate.Type + id ID + psk bool + cryptoCCMTagLen ciphersuite.CCMTagLen +} + +func newAes128Ccm(clientCertificateType clientcertificate.Type, id ID, psk bool, cryptoCCMTagLen ciphersuite.CCMTagLen) *Aes128Ccm { + return &Aes128Ccm{ + clientCertificateType: clientCertificateType, + id: id, + psk: psk, + cryptoCCMTagLen: cryptoCCMTagLen, + } +} + +// CertificateType returns what type of certificate this CipherSuite exchanges +func (c *Aes128Ccm) CertificateType() clientcertificate.Type { + return c.clientCertificateType +} + +// ID returns the ID of the CipherSuite +func (c *Aes128Ccm) ID() ID { + return c.id +} + +func (c *Aes128Ccm) String() string { + return c.id.String() +} + +// HashFunc returns the hashing func for this CipherSuite +func (c *Aes128Ccm) HashFunc() func() hash.Hash { + return sha256.New +} + +// AuthenticationType controls what authentication method is using during the handshake +func (c *Aes128Ccm) AuthenticationType() AuthenticationType { + if c.psk { + return AuthenticationTypePreSharedKey + } + return AuthenticationTypeCertificate +} + +// IsInitialized returns if the CipherSuite has keying material and can +// encrypt/decrypt packets +func (c *Aes128Ccm) IsInitialized() bool { + return c.ccm.Load() != nil +} + +// Init initializes the internal Cipher with keying material +func (c *Aes128Ccm) Init(masterSecret, clientRandom, serverRandom []byte, isClient bool) error { + const ( + prfMacLen = 0 + prfKeyLen = 16 + prfIvLen = 4 + ) + + keys, err := prf.GenerateEncryptionKeys(masterSecret, clientRandom, serverRandom, prfMacLen, prfKeyLen, prfIvLen, c.HashFunc()) + if err != nil { + return err + } + + var ccm *ciphersuite.CCM + if isClient { + ccm, err = ciphersuite.NewCCM(c.cryptoCCMTagLen, keys.ClientWriteKey, keys.ClientWriteIV, keys.ServerWriteKey, keys.ServerWriteIV) + } else { + ccm, err = ciphersuite.NewCCM(c.cryptoCCMTagLen, keys.ServerWriteKey, keys.ServerWriteIV, keys.ClientWriteKey, keys.ClientWriteIV) + } + c.ccm.Store(ccm) + + return err +} + +// Encrypt encrypts a single TLS RecordLayer +func (c *Aes128Ccm) Encrypt(pkt *recordlayer.RecordLayer, raw []byte) ([]byte, error) { + ccm := c.ccm.Load() + if ccm == nil { + return nil, fmt.Errorf("%w, unable to encrypt", errCipherSuiteNotInit) + } + + return ccm.(*ciphersuite.CCM).Encrypt(pkt, raw) +} + +// Decrypt decrypts a single TLS RecordLayer +func (c *Aes128Ccm) Decrypt(raw []byte) ([]byte, error) { + ccm := c.ccm.Load() + if ccm == nil { + return nil, fmt.Errorf("%w, unable to decrypt", errCipherSuiteNotInit) + } + + return ccm.(*ciphersuite.CCM).Decrypt(raw) +} diff --git a/vendor/github.com/pion/dtls/v2/internal/ciphersuite/ciphersuite.go b/vendor/github.com/pion/dtls/v2/internal/ciphersuite/ciphersuite.go new file mode 100644 index 0000000..3a4fb43 --- /dev/null +++ b/vendor/github.com/pion/dtls/v2/internal/ciphersuite/ciphersuite.go @@ -0,0 +1,71 @@ +// Package ciphersuite provides TLS Ciphers as registered with the IANA https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-parameters-4 +package ciphersuite + +import ( + "errors" + "fmt" + + "github.com/pion/dtls/v2/pkg/protocol" +) + +var errCipherSuiteNotInit = &protocol.TemporaryError{Err: errors.New("CipherSuite has not been initialized")} //nolint:goerr113 + +// ID is an ID for our supported CipherSuites +type ID uint16 + +func (i ID) String() string { + switch i { + case TLS_ECDHE_ECDSA_WITH_AES_128_CCM: + return "TLS_ECDHE_ECDSA_WITH_AES_128_CCM" + case TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8: + return "TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8" + case TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256: + return "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256" + case TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256: + return "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256" + case TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA: + return "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA" + case TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA: + return "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA" + case TLS_PSK_WITH_AES_128_CCM: + return "TLS_PSK_WITH_AES_128_CCM" + case TLS_PSK_WITH_AES_128_CCM_8: + return "TLS_PSK_WITH_AES_128_CCM_8" + case TLS_PSK_WITH_AES_128_GCM_SHA256: + return "TLS_PSK_WITH_AES_128_GCM_SHA256" + case TLS_PSK_WITH_AES_128_CBC_SHA256: + return "TLS_PSK_WITH_AES_128_CBC_SHA256" + default: + return fmt.Sprintf("unknown(%v)", uint16(i)) + } +} + +// Supported Cipher Suites +const ( + // AES-128-CCM + TLS_ECDHE_ECDSA_WITH_AES_128_CCM ID = 0xc0ac //nolint:golint,stylecheck + TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8 ID = 0xc0ae //nolint:golint,stylecheck + + // AES-128-GCM-SHA256 + TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 ID = 0xc02b //nolint:golint,stylecheck + TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 ID = 0xc02f //nolint:golint,stylecheck + + // AES-256-CBC-SHA + TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA ID = 0xc00a //nolint:golint,stylecheck + TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA ID = 0xc014 //nolint:golint,stylecheck + + TLS_PSK_WITH_AES_128_CCM ID = 0xc0a4 //nolint:golint,stylecheck + TLS_PSK_WITH_AES_128_CCM_8 ID = 0xc0a8 //nolint:golint,stylecheck + TLS_PSK_WITH_AES_128_GCM_SHA256 ID = 0x00a8 //nolint:golint,stylecheck + TLS_PSK_WITH_AES_128_CBC_SHA256 ID = 0x00ae //nolint:golint,stylecheck +) + +// AuthenticationType controls what authentication method is using during the handshake +type AuthenticationType int + +// AuthenticationType Enums +const ( + AuthenticationTypeCertificate AuthenticationType = iota + 1 + AuthenticationTypePreSharedKey + AuthenticationTypeAnonymous +) diff --git a/vendor/github.com/pion/dtls/v2/internal/ciphersuite/tls_ecdhe_ecdsa_with_aes_128_ccm.go b/vendor/github.com/pion/dtls/v2/internal/ciphersuite/tls_ecdhe_ecdsa_with_aes_128_ccm.go new file mode 100644 index 0000000..ac73556 --- /dev/null +++ b/vendor/github.com/pion/dtls/v2/internal/ciphersuite/tls_ecdhe_ecdsa_with_aes_128_ccm.go @@ -0,0 +1,11 @@ +package ciphersuite + +import ( + "github.com/pion/dtls/v2/pkg/crypto/ciphersuite" + "github.com/pion/dtls/v2/pkg/crypto/clientcertificate" +) + +// NewTLSEcdheEcdsaWithAes128Ccm constructs a TLS_ECDHE_ECDSA_WITH_AES_128_CCM Cipher +func NewTLSEcdheEcdsaWithAes128Ccm() *Aes128Ccm { + return newAes128Ccm(clientcertificate.ECDSASign, TLS_ECDHE_ECDSA_WITH_AES_128_CCM, false, ciphersuite.CCMTagLength) +} diff --git a/vendor/github.com/pion/dtls/v2/internal/ciphersuite/tls_ecdhe_ecdsa_with_aes_128_ccm8.go b/vendor/github.com/pion/dtls/v2/internal/ciphersuite/tls_ecdhe_ecdsa_with_aes_128_ccm8.go new file mode 100644 index 0000000..49b1a83 --- /dev/null +++ b/vendor/github.com/pion/dtls/v2/internal/ciphersuite/tls_ecdhe_ecdsa_with_aes_128_ccm8.go @@ -0,0 +1,11 @@ +package ciphersuite + +import ( + "github.com/pion/dtls/v2/pkg/crypto/ciphersuite" + "github.com/pion/dtls/v2/pkg/crypto/clientcertificate" +) + +// NewTLSEcdheEcdsaWithAes128Ccm8 creates a new TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8 CipherSuite +func NewTLSEcdheEcdsaWithAes128Ccm8() *Aes128Ccm { + return newAes128Ccm(clientcertificate.ECDSASign, TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8, false, ciphersuite.CCMTagLength8) +} diff --git a/vendor/github.com/pion/dtls/v2/internal/ciphersuite/tls_ecdhe_ecdsa_with_aes_128_gcm_sha256.go b/vendor/github.com/pion/dtls/v2/internal/ciphersuite/tls_ecdhe_ecdsa_with_aes_128_gcm_sha256.go new file mode 100644 index 0000000..b491320 --- /dev/null +++ b/vendor/github.com/pion/dtls/v2/internal/ciphersuite/tls_ecdhe_ecdsa_with_aes_128_gcm_sha256.go @@ -0,0 +1,92 @@ +package ciphersuite + +import ( + "crypto/sha256" + "fmt" + "hash" + "sync/atomic" + + "github.com/pion/dtls/v2/pkg/crypto/ciphersuite" + "github.com/pion/dtls/v2/pkg/crypto/clientcertificate" + "github.com/pion/dtls/v2/pkg/crypto/prf" + "github.com/pion/dtls/v2/pkg/protocol/recordlayer" +) + +// TLSEcdheEcdsaWithAes128GcmSha256 represents a TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 CipherSuite +type TLSEcdheEcdsaWithAes128GcmSha256 struct { + gcm atomic.Value // *cryptoGCM +} + +// CertificateType returns what type of certficate this CipherSuite exchanges +func (c *TLSEcdheEcdsaWithAes128GcmSha256) CertificateType() clientcertificate.Type { + return clientcertificate.ECDSASign +} + +// ID returns the ID of the CipherSuite +func (c *TLSEcdheEcdsaWithAes128GcmSha256) ID() ID { + return TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 +} + +func (c *TLSEcdheEcdsaWithAes128GcmSha256) String() string { + return "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256" +} + +// HashFunc returns the hashing func for this CipherSuite +func (c *TLSEcdheEcdsaWithAes128GcmSha256) HashFunc() func() hash.Hash { + return sha256.New +} + +// AuthenticationType controls what authentication method is using during the handshake +func (c *TLSEcdheEcdsaWithAes128GcmSha256) AuthenticationType() AuthenticationType { + return AuthenticationTypeCertificate +} + +// IsInitialized returns if the CipherSuite has keying material and can +// encrypt/decrypt packets +func (c *TLSEcdheEcdsaWithAes128GcmSha256) IsInitialized() bool { + return c.gcm.Load() != nil +} + +// Init initializes the internal Cipher with keying material +func (c *TLSEcdheEcdsaWithAes128GcmSha256) Init(masterSecret, clientRandom, serverRandom []byte, isClient bool) error { + const ( + prfMacLen = 0 + prfKeyLen = 16 + prfIvLen = 4 + ) + + keys, err := prf.GenerateEncryptionKeys(masterSecret, clientRandom, serverRandom, prfMacLen, prfKeyLen, prfIvLen, c.HashFunc()) + if err != nil { + return err + } + + var gcm *ciphersuite.GCM + if isClient { + gcm, err = ciphersuite.NewGCM(keys.ClientWriteKey, keys.ClientWriteIV, keys.ServerWriteKey, keys.ServerWriteIV) + } else { + gcm, err = ciphersuite.NewGCM(keys.ServerWriteKey, keys.ServerWriteIV, keys.ClientWriteKey, keys.ClientWriteIV) + } + c.gcm.Store(gcm) + + return err +} + +// Encrypt encrypts a single TLS RecordLayer +func (c *TLSEcdheEcdsaWithAes128GcmSha256) Encrypt(pkt *recordlayer.RecordLayer, raw []byte) ([]byte, error) { + gcm := c.gcm.Load() + if gcm == nil { + return nil, fmt.Errorf("%w, unable to encrypt", errCipherSuiteNotInit) + } + + return gcm.(*ciphersuite.GCM).Encrypt(pkt, raw) +} + +// Decrypt decrypts a single TLS RecordLayer +func (c *TLSEcdheEcdsaWithAes128GcmSha256) Decrypt(raw []byte) ([]byte, error) { + gcm := c.gcm.Load() + if gcm == nil { + return nil, fmt.Errorf("%w, unable to decrypt", errCipherSuiteNotInit) + } + + return gcm.(*ciphersuite.GCM).Decrypt(raw) +} diff --git a/vendor/github.com/pion/dtls/v2/internal/ciphersuite/tls_ecdhe_ecdsa_with_aes_256_cbc_sha.go b/vendor/github.com/pion/dtls/v2/internal/ciphersuite/tls_ecdhe_ecdsa_with_aes_256_cbc_sha.go new file mode 100644 index 0000000..f7a33ad --- /dev/null +++ b/vendor/github.com/pion/dtls/v2/internal/ciphersuite/tls_ecdhe_ecdsa_with_aes_256_cbc_sha.go @@ -0,0 +1,101 @@ +package ciphersuite + +import ( + "crypto/sha1" //nolint: gosec,gci + "crypto/sha256" + "fmt" + "hash" + "sync/atomic" + + "github.com/pion/dtls/v2/pkg/crypto/ciphersuite" + "github.com/pion/dtls/v2/pkg/crypto/clientcertificate" + "github.com/pion/dtls/v2/pkg/crypto/prf" + "github.com/pion/dtls/v2/pkg/protocol/recordlayer" +) + +// TLSEcdheEcdsaWithAes256CbcSha represents a TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA CipherSuite +type TLSEcdheEcdsaWithAes256CbcSha struct { + cbc atomic.Value // *cryptoCBC +} + +// CertificateType returns what type of certficate this CipherSuite exchanges +func (c *TLSEcdheEcdsaWithAes256CbcSha) CertificateType() clientcertificate.Type { + return clientcertificate.ECDSASign +} + +// ID returns the ID of the CipherSuite +func (c *TLSEcdheEcdsaWithAes256CbcSha) ID() ID { + return TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA +} + +func (c *TLSEcdheEcdsaWithAes256CbcSha) String() string { + return "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA" +} + +// HashFunc returns the hashing func for this CipherSuite +func (c *TLSEcdheEcdsaWithAes256CbcSha) HashFunc() func() hash.Hash { + return sha256.New +} + +// AuthenticationType controls what authentication method is using during the handshake +func (c *TLSEcdheEcdsaWithAes256CbcSha) AuthenticationType() AuthenticationType { + return AuthenticationTypeCertificate +} + +// IsInitialized returns if the CipherSuite has keying material and can +// encrypt/decrypt packets +func (c *TLSEcdheEcdsaWithAes256CbcSha) IsInitialized() bool { + return c.cbc.Load() != nil +} + +// Init initializes the internal Cipher with keying material +func (c *TLSEcdheEcdsaWithAes256CbcSha) Init(masterSecret, clientRandom, serverRandom []byte, isClient bool) error { + const ( + prfMacLen = 20 + prfKeyLen = 32 + prfIvLen = 16 + ) + + keys, err := prf.GenerateEncryptionKeys(masterSecret, clientRandom, serverRandom, prfMacLen, prfKeyLen, prfIvLen, c.HashFunc()) + if err != nil { + return err + } + + var cbc *ciphersuite.CBC + if isClient { + cbc, err = ciphersuite.NewCBC( + keys.ClientWriteKey, keys.ClientWriteIV, keys.ClientMACKey, + keys.ServerWriteKey, keys.ServerWriteIV, keys.ServerMACKey, + sha1.New, + ) + } else { + cbc, err = ciphersuite.NewCBC( + keys.ServerWriteKey, keys.ServerWriteIV, keys.ServerMACKey, + keys.ClientWriteKey, keys.ClientWriteIV, keys.ClientMACKey, + sha1.New, + ) + } + c.cbc.Store(cbc) + + return err +} + +// Encrypt encrypts a single TLS RecordLayer +func (c *TLSEcdheEcdsaWithAes256CbcSha) Encrypt(pkt *recordlayer.RecordLayer, raw []byte) ([]byte, error) { + cbc := c.cbc.Load() + if cbc == nil { // !c.isInitialized() + return nil, fmt.Errorf("%w, unable to encrypt", errCipherSuiteNotInit) + } + + return cbc.(*ciphersuite.CBC).Encrypt(pkt, raw) +} + +// Decrypt decrypts a single TLS RecordLayer +func (c *TLSEcdheEcdsaWithAes256CbcSha) Decrypt(raw []byte) ([]byte, error) { + cbc := c.cbc.Load() + if cbc == nil { // !c.isInitialized() + return nil, fmt.Errorf("%w, unable to decrypt", errCipherSuiteNotInit) + } + + return cbc.(*ciphersuite.CBC).Decrypt(raw) +} diff --git a/vendor/github.com/pion/dtls/v2/internal/ciphersuite/tls_ecdhe_rsa_with_aes_128_gcm_sha256.go b/vendor/github.com/pion/dtls/v2/internal/ciphersuite/tls_ecdhe_rsa_with_aes_128_gcm_sha256.go new file mode 100644 index 0000000..70400c3 --- /dev/null +++ b/vendor/github.com/pion/dtls/v2/internal/ciphersuite/tls_ecdhe_rsa_with_aes_128_gcm_sha256.go @@ -0,0 +1,22 @@ +package ciphersuite + +import "github.com/pion/dtls/v2/pkg/crypto/clientcertificate" + +// TLSEcdheRsaWithAes128GcmSha256 implements the TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 CipherSuite +type TLSEcdheRsaWithAes128GcmSha256 struct { + TLSEcdheEcdsaWithAes128GcmSha256 +} + +// CertificateType returns what type of certificate this CipherSuite exchanges +func (c *TLSEcdheRsaWithAes128GcmSha256) CertificateType() clientcertificate.Type { + return clientcertificate.RSASign +} + +// ID returns the ID of the CipherSuite +func (c *TLSEcdheRsaWithAes128GcmSha256) ID() ID { + return TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 +} + +func (c *TLSEcdheRsaWithAes128GcmSha256) String() string { + return "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256" +} diff --git a/vendor/github.com/pion/dtls/v2/internal/ciphersuite/tls_ecdhe_rsa_with_aes_256_cbc_sha.go b/vendor/github.com/pion/dtls/v2/internal/ciphersuite/tls_ecdhe_rsa_with_aes_256_cbc_sha.go new file mode 100644 index 0000000..0d82dc3 --- /dev/null +++ b/vendor/github.com/pion/dtls/v2/internal/ciphersuite/tls_ecdhe_rsa_with_aes_256_cbc_sha.go @@ -0,0 +1,22 @@ +package ciphersuite + +import "github.com/pion/dtls/v2/pkg/crypto/clientcertificate" + +// TLSEcdheRsaWithAes256CbcSha implements the TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA CipherSuite +type TLSEcdheRsaWithAes256CbcSha struct { + TLSEcdheEcdsaWithAes256CbcSha +} + +// CertificateType returns what type of certificate this CipherSuite exchanges +func (c *TLSEcdheRsaWithAes256CbcSha) CertificateType() clientcertificate.Type { + return clientcertificate.RSASign +} + +// ID returns the ID of the CipherSuite +func (c *TLSEcdheRsaWithAes256CbcSha) ID() ID { + return TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA +} + +func (c *TLSEcdheRsaWithAes256CbcSha) String() string { + return "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA" +} diff --git a/vendor/github.com/pion/dtls/v2/internal/ciphersuite/tls_psk_with_aes_128_cbc_sha256.go b/vendor/github.com/pion/dtls/v2/internal/ciphersuite/tls_psk_with_aes_128_cbc_sha256.go new file mode 100644 index 0000000..43e5e38 --- /dev/null +++ b/vendor/github.com/pion/dtls/v2/internal/ciphersuite/tls_psk_with_aes_128_cbc_sha256.go @@ -0,0 +1,100 @@ +package ciphersuite + +import ( + "crypto/sha256" + "fmt" + "hash" + "sync/atomic" + + "github.com/pion/dtls/v2/pkg/crypto/ciphersuite" + "github.com/pion/dtls/v2/pkg/crypto/clientcertificate" + "github.com/pion/dtls/v2/pkg/crypto/prf" + "github.com/pion/dtls/v2/pkg/protocol/recordlayer" +) + +// TLSPskWithAes128CbcSha256 implements the TLS_PSK_WITH_AES_128_CBC_SHA256 CipherSuite +type TLSPskWithAes128CbcSha256 struct { + cbc atomic.Value // *cryptoCBC +} + +// CertificateType returns what type of certificate this CipherSuite exchanges +func (c *TLSPskWithAes128CbcSha256) CertificateType() clientcertificate.Type { + return clientcertificate.Type(0) +} + +// ID returns the ID of the CipherSuite +func (c *TLSPskWithAes128CbcSha256) ID() ID { + return TLS_PSK_WITH_AES_128_CBC_SHA256 +} + +func (c *TLSPskWithAes128CbcSha256) String() string { + return "TLS_PSK_WITH_AES_128_CBC_SHA256" +} + +// HashFunc returns the hashing func for this CipherSuite +func (c *TLSPskWithAes128CbcSha256) HashFunc() func() hash.Hash { + return sha256.New +} + +// AuthenticationType controls what authentication method is using during the handshake +func (c *TLSPskWithAes128CbcSha256) AuthenticationType() AuthenticationType { + return AuthenticationTypePreSharedKey +} + +// IsInitialized returns if the CipherSuite has keying material and can +// encrypt/decrypt packets +func (c *TLSPskWithAes128CbcSha256) IsInitialized() bool { + return c.cbc.Load() != nil +} + +// Init initializes the internal Cipher with keying material +func (c *TLSPskWithAes128CbcSha256) Init(masterSecret, clientRandom, serverRandom []byte, isClient bool) error { + const ( + prfMacLen = 32 + prfKeyLen = 16 + prfIvLen = 16 + ) + + keys, err := prf.GenerateEncryptionKeys(masterSecret, clientRandom, serverRandom, prfMacLen, prfKeyLen, prfIvLen, c.HashFunc()) + if err != nil { + return err + } + + var cbc *ciphersuite.CBC + if isClient { + cbc, err = ciphersuite.NewCBC( + keys.ClientWriteKey, keys.ClientWriteIV, keys.ClientMACKey, + keys.ServerWriteKey, keys.ServerWriteIV, keys.ServerMACKey, + c.HashFunc(), + ) + } else { + cbc, err = ciphersuite.NewCBC( + keys.ServerWriteKey, keys.ServerWriteIV, keys.ServerMACKey, + keys.ClientWriteKey, keys.ClientWriteIV, keys.ClientMACKey, + c.HashFunc(), + ) + } + c.cbc.Store(cbc) + + return err +} + +// Encrypt encrypts a single TLS RecordLayer +func (c *TLSPskWithAes128CbcSha256) Encrypt(pkt *recordlayer.RecordLayer, raw []byte) ([]byte, error) { + cbc := c.cbc.Load() + if cbc == nil { // !c.isInitialized() + return nil, fmt.Errorf("%w, unable to decrypt", errCipherSuiteNotInit) + } + + return cbc.(*ciphersuite.CBC).Encrypt(pkt, raw) +} + +// Decrypt decrypts a single TLS RecordLayer +func (c *TLSPskWithAes128CbcSha256) Decrypt(raw []byte) ([]byte, error) { + cbc := c.cbc.Load() + if cbc == nil { // !c.isInitialized() + return nil, fmt.Errorf("%w, unable to decrypt", errCipherSuiteNotInit) + } + + return cbc.(*ciphersuite.CBC).Decrypt(raw) +} diff --git a/vendor/github.com/pion/dtls/v2/internal/ciphersuite/tls_psk_with_aes_128_ccm.go b/vendor/github.com/pion/dtls/v2/internal/ciphersuite/tls_psk_with_aes_128_ccm.go new file mode 100644 index 0000000..8c13bb1 --- /dev/null +++ b/vendor/github.com/pion/dtls/v2/internal/ciphersuite/tls_psk_with_aes_128_ccm.go @@ -0,0 +1,11 @@ +package ciphersuite + +import ( + "github.com/pion/dtls/v2/pkg/crypto/ciphersuite" + "github.com/pion/dtls/v2/pkg/crypto/clientcertificate" +) + +// NewTLSPskWithAes128Ccm returns the TLS_PSK_WITH_AES_128_CCM CipherSuite +func NewTLSPskWithAes128Ccm() *Aes128Ccm { + return newAes128Ccm(clientcertificate.Type(0), TLS_PSK_WITH_AES_128_CCM, true, ciphersuite.CCMTagLength) +} diff --git a/vendor/github.com/pion/dtls/v2/internal/ciphersuite/tls_psk_with_aes_128_ccm8.go b/vendor/github.com/pion/dtls/v2/internal/ciphersuite/tls_psk_with_aes_128_ccm8.go new file mode 100644 index 0000000..d04abb4 --- /dev/null +++ b/vendor/github.com/pion/dtls/v2/internal/ciphersuite/tls_psk_with_aes_128_ccm8.go @@ -0,0 +1,11 @@ +package ciphersuite + +import ( + "github.com/pion/dtls/v2/pkg/crypto/ciphersuite" + "github.com/pion/dtls/v2/pkg/crypto/clientcertificate" +) + +// NewTLSPskWithAes128Ccm8 returns the TLS_PSK_WITH_AES_128_CCM_8 CipherSuite +func NewTLSPskWithAes128Ccm8() *Aes128Ccm { + return newAes128Ccm(clientcertificate.Type(0), TLS_PSK_WITH_AES_128_CCM_8, true, ciphersuite.CCMTagLength8) +} diff --git a/vendor/github.com/pion/dtls/v2/internal/ciphersuite/tls_psk_with_aes_128_gcm_sha256.go b/vendor/github.com/pion/dtls/v2/internal/ciphersuite/tls_psk_with_aes_128_gcm_sha256.go new file mode 100644 index 0000000..5f10335 --- /dev/null +++ b/vendor/github.com/pion/dtls/v2/internal/ciphersuite/tls_psk_with_aes_128_gcm_sha256.go @@ -0,0 +1,27 @@ +package ciphersuite + +import "github.com/pion/dtls/v2/pkg/crypto/clientcertificate" + +// TLSPskWithAes128GcmSha256 implements the TLS_PSK_WITH_AES_128_GCM_SHA256 CipherSuite +type TLSPskWithAes128GcmSha256 struct { + TLSEcdheEcdsaWithAes128GcmSha256 +} + +// CertificateType returns what type of certificate this CipherSuite exchanges +func (c *TLSPskWithAes128GcmSha256) CertificateType() clientcertificate.Type { + return clientcertificate.Type(0) +} + +// ID returns the ID of the CipherSuite +func (c *TLSPskWithAes128GcmSha256) ID() ID { + return TLS_PSK_WITH_AES_128_GCM_SHA256 +} + +func (c *TLSPskWithAes128GcmSha256) String() string { + return "TLS_PSK_WITH_AES_128_GCM_SHA256" +} + +// AuthenticationType controls what authentication method is using during the handshake +func (c *TLSPskWithAes128GcmSha256) AuthenticationType() AuthenticationType { + return AuthenticationTypePreSharedKey +} diff --git a/vendor/github.com/pion/dtls/v2/internal/closer/closer.go b/vendor/github.com/pion/dtls/v2/internal/closer/closer.go new file mode 100644 index 0000000..b99e13e --- /dev/null +++ b/vendor/github.com/pion/dtls/v2/internal/closer/closer.go @@ -0,0 +1,45 @@ +// Package closer provides signaling channel for shutdown +package closer + +import ( + "context" +) + +// Closer allows for each signaling a channel for shutdown +type Closer struct { + ctx context.Context + closeFunc func() +} + +// NewCloser creates a new instance of Closer +func NewCloser() *Closer { + ctx, closeFunc := context.WithCancel(context.Background()) + return &Closer{ + ctx: ctx, + closeFunc: closeFunc, + } +} + +// NewCloserWithParent creates a new instance of Closer with a parent context +func NewCloserWithParent(ctx context.Context) *Closer { + ctx, closeFunc := context.WithCancel(ctx) + return &Closer{ + ctx: ctx, + closeFunc: closeFunc, + } +} + +// Done returns a channel signaling when it is done +func (c *Closer) Done() <-chan struct{} { + return c.ctx.Done() +} + +// Err returns an error of the context +func (c *Closer) Err() error { + return c.ctx.Err() +} + +// Close sends a signal to trigger the ctx done channel +func (c *Closer) Close() { + c.closeFunc() +} diff --git a/vendor/github.com/pion/dtls/v2/internal/util/util.go b/vendor/github.com/pion/dtls/v2/internal/util/util.go new file mode 100644 index 0000000..746a670 --- /dev/null +++ b/vendor/github.com/pion/dtls/v2/internal/util/util.go @@ -0,0 +1,39 @@ +// Package util contains small helpers used across the repo +package util + +import ( + "encoding/binary" +) + +// BigEndianUint24 returns the value of a big endian uint24 +func BigEndianUint24(raw []byte) uint32 { + if len(raw) < 3 { + return 0 + } + + rawCopy := make([]byte, 4) + copy(rawCopy[1:], raw) + return binary.BigEndian.Uint32(rawCopy) +} + +// PutBigEndianUint24 encodes a uint24 and places into out +func PutBigEndianUint24(out []byte, in uint32) { + tmp := make([]byte, 4) + binary.BigEndian.PutUint32(tmp, in) + copy(out, tmp[1:]) +} + +// PutBigEndianUint48 encodes a uint64 and places into out +func PutBigEndianUint48(out []byte, in uint64) { + tmp := make([]byte, 8) + binary.BigEndian.PutUint64(tmp, in) + copy(out, tmp[2:]) +} + +// Max returns the larger value +func Max(a, b int) int { + if a > b { + return a + } + return b +} diff --git a/vendor/github.com/pion/dtls/v2/listener.go b/vendor/github.com/pion/dtls/v2/listener.go new file mode 100644 index 0000000..bf80345 --- /dev/null +++ b/vendor/github.com/pion/dtls/v2/listener.go @@ -0,0 +1,80 @@ +package dtls + +import ( + "net" + + "github.com/pion/dtls/v2/pkg/protocol" + "github.com/pion/dtls/v2/pkg/protocol/recordlayer" + "github.com/pion/udp" +) + +// Listen creates a DTLS listener +func Listen(network string, laddr *net.UDPAddr, config *Config) (net.Listener, error) { + if err := validateConfig(config); err != nil { + return nil, err + } + + lc := udp.ListenConfig{ + AcceptFilter: func(packet []byte) bool { + pkts, err := recordlayer.UnpackDatagram(packet) + if err != nil || len(pkts) < 1 { + return false + } + h := &recordlayer.Header{} + if err := h.Unmarshal(pkts[0]); err != nil { + return false + } + return h.ContentType == protocol.ContentTypeHandshake + }, + } + parent, err := lc.Listen(network, laddr) + if err != nil { + return nil, err + } + return &listener{ + config: config, + parent: parent, + }, nil +} + +// NewListener creates a DTLS listener which accepts connections from an inner Listener. +func NewListener(inner net.Listener, config *Config) (net.Listener, error) { + if err := validateConfig(config); err != nil { + return nil, err + } + + return &listener{ + config: config, + parent: inner, + }, nil +} + +// listener represents a DTLS listener +type listener struct { + config *Config + parent net.Listener +} + +// Accept waits for and returns the next connection to the listener. +// You have to either close or read on all connection that are created. +// Connection handshake will timeout using ConnectContextMaker in the Config. +// If you want to specify the timeout duration, set ConnectContextMaker. +func (l *listener) Accept() (net.Conn, error) { + c, err := l.parent.Accept() + if err != nil { + return nil, err + } + return Server(c, l.config) +} + +// Close closes the listener. +// Any blocked Accept operations will be unblocked and return errors. +// Already Accepted connections are not closed. +func (l *listener) Close() error { + return l.parent.Close() +} + +// Addr returns the listener's network address. +func (l *listener) Addr() net.Addr { + return l.parent.Addr() +} diff --git a/vendor/github.com/pion/dtls/v2/packet.go b/vendor/github.com/pion/dtls/v2/packet.go new file mode 100644 index 0000000..8366a3c --- /dev/null +++ b/vendor/github.com/pion/dtls/v2/packet.go @@ -0,0 +1,9 @@ +package dtls + +import "github.com/pion/dtls/v2/pkg/protocol/recordlayer" + +type packet struct { + record *recordlayer.RecordLayer + shouldEncrypt bool + resetLocalSequenceNumber bool +} diff --git a/vendor/github.com/pion/dtls/v2/pkg/crypto/ccm/ccm.go b/vendor/github.com/pion/dtls/v2/pkg/crypto/ccm/ccm.go new file mode 100644 index 0000000..20e3436 --- /dev/null +++ b/vendor/github.com/pion/dtls/v2/pkg/crypto/ccm/ccm.go @@ -0,0 +1,251 @@ +// Package ccm implements a CCM, Counter with CBC-MAC +// as per RFC 3610. +// +// See https://tools.ietf.org/html/rfc3610 +// +// This code was lifted from https://github.com/bocajim/dtls/blob/a3300364a283fcb490d28a93d7fcfa7ba437fbbe/ccm/ccm.go +// and as such was not written by the Pions authors. Like Pions this +// code is licensed under MIT. +// +// A request for including CCM into the Go standard library +// can be found as issue #27484 on the https://github.com/golang/go/ +// repository. +package ccm + +import ( + "crypto/cipher" + "crypto/subtle" + "encoding/binary" + "errors" + "math" +) + +// ccm represents a Counter with CBC-MAC with a specific key. +type ccm struct { + b cipher.Block + M uint8 + L uint8 +} + +const ccmBlockSize = 16 + +// CCM is a block cipher in Counter with CBC-MAC mode. +// Providing authenticated encryption with associated data via the cipher.AEAD interface. +type CCM interface { + cipher.AEAD + // MaxLength returns the maxium length of plaintext in calls to Seal. + // The maximum length of ciphertext in calls to Open is MaxLength()+Overhead(). + // The maximum length is related to CCM's `L` parameter (15-noncesize) and + // is 1<<(8*L) - 1 (but also limited by the maxium size of an int). + MaxLength() int +} + +var ( + errInvalidBlockSize = errors.New("ccm: NewCCM requires 128-bit block cipher") + errInvalidTagSize = errors.New("ccm: tagsize must be 4, 6, 8, 10, 12, 14, or 16") + errInvalidNonceSize = errors.New("ccm: invalid nonce size") +) + +// NewCCM returns the given 128-bit block cipher wrapped in CCM. +// The tagsize must be an even integer between 4 and 16 inclusive +// and is used as CCM's `M` parameter. +// The noncesize must be an integer between 7 and 13 inclusive, +// 15-noncesize is used as CCM's `L` parameter. +func NewCCM(b cipher.Block, tagsize, noncesize int) (CCM, error) { + if b.BlockSize() != ccmBlockSize { + return nil, errInvalidBlockSize + } + if tagsize < 4 || tagsize > 16 || tagsize&1 != 0 { + return nil, errInvalidTagSize + } + lensize := 15 - noncesize + if lensize < 2 || lensize > 8 { + return nil, errInvalidNonceSize + } + c := &ccm{b: b, M: uint8(tagsize), L: uint8(lensize)} + return c, nil +} + +func (c *ccm) NonceSize() int { return 15 - int(c.L) } +func (c *ccm) Overhead() int { return int(c.M) } +func (c *ccm) MaxLength() int { return maxlen(c.L, c.Overhead()) } + +func maxlen(l uint8, tagsize int) int { + max := (uint64(1) << (8 * l)) - 1 + if m64 := uint64(math.MaxInt64) - uint64(tagsize); l > 8 || max > m64 { + max = m64 // The maximum lentgh on a 64bit arch + } + if max != uint64(int(max)) { + return math.MaxInt32 - tagsize // We have only 32bit int's + } + return int(max) +} + +// MaxNonceLength returns the maximum nonce length for a given plaintext length. +// A return value <= 0 indicates that plaintext length is too large for +// any nonce length. +func MaxNonceLength(pdatalen int) int { + const tagsize = 16 + for L := 2; L <= 8; L++ { + if maxlen(uint8(L), tagsize) >= pdatalen { + return 15 - L + } + } + return 0 +} + +func (c *ccm) cbcRound(mac, data []byte) { + for i := 0; i < ccmBlockSize; i++ { + mac[i] ^= data[i] + } + c.b.Encrypt(mac, mac) +} + +func (c *ccm) cbcData(mac, data []byte) { + for len(data) >= ccmBlockSize { + c.cbcRound(mac, data[:ccmBlockSize]) + data = data[ccmBlockSize:] + } + if len(data) > 0 { + var block [ccmBlockSize]byte + copy(block[:], data) + c.cbcRound(mac, block[:]) + } +} + +var errPlaintextTooLong = errors.New("ccm: plaintext too large") + +func (c *ccm) tag(nonce, plaintext, adata []byte) ([]byte, error) { + var mac [ccmBlockSize]byte + + if len(adata) > 0 { + mac[0] |= 1 << 6 + } + mac[0] |= (c.M - 2) << 2 + mac[0] |= c.L - 1 + if len(nonce) != c.NonceSize() { + return nil, errInvalidNonceSize + } + if len(plaintext) > c.MaxLength() { + return nil, errPlaintextTooLong + } + binary.BigEndian.PutUint64(mac[ccmBlockSize-8:], uint64(len(plaintext))) + copy(mac[1:ccmBlockSize-c.L], nonce) + c.b.Encrypt(mac[:], mac[:]) + + var block [ccmBlockSize]byte + if n := uint64(len(adata)); n > 0 { + // First adata block includes adata length + i := 2 + if n <= 0xfeff { + binary.BigEndian.PutUint16(block[:i], uint16(n)) + } else { + block[0] = 0xfe + block[1] = 0xff + if n < uint64(1<<32) { + i = 2 + 4 + binary.BigEndian.PutUint32(block[2:i], uint32(n)) + } else { + i = 2 + 8 + binary.BigEndian.PutUint64(block[2:i], n) + } + } + i = copy(block[i:], adata) + c.cbcRound(mac[:], block[:]) + c.cbcData(mac[:], adata[i:]) + } + + if len(plaintext) > 0 { + c.cbcData(mac[:], plaintext) + } + + return mac[:c.M], nil +} + +// sliceForAppend takes a slice and a requested number of bytes. It returns a +// slice with the contents of the given slice followed by that many bytes and a +// second slice that aliases into it and contains only the extra bytes. If the +// original slice has sufficient capacity then no allocation is performed. +// From crypto/cipher/gcm.go +func sliceForAppend(in []byte, n int) (head, tail []byte) { + if total := len(in) + n; cap(in) >= total { + head = in[:total] + } else { + head = make([]byte, total) + copy(head, in) + } + tail = head[len(in):] + return +} + +// Seal encrypts and authenticates plaintext, authenticates the +// additional data and appends the result to dst, returning the updated +// slice. The nonce must be NonceSize() bytes long and unique for all +// time, for a given key. +// The plaintext must be no longer than MaxLength() bytes long. +// +// The plaintext and dst may alias exactly or not at all. +func (c *ccm) Seal(dst, nonce, plaintext, adata []byte) []byte { + tag, err := c.tag(nonce, plaintext, adata) + if err != nil { + // The cipher.AEAD interface doesn't allow for an error return. + panic(err) // nolint + } + + var iv, s0 [ccmBlockSize]byte + iv[0] = c.L - 1 + copy(iv[1:ccmBlockSize-c.L], nonce) + c.b.Encrypt(s0[:], iv[:]) + for i := 0; i < int(c.M); i++ { + tag[i] ^= s0[i] + } + iv[len(iv)-1] |= 1 + stream := cipher.NewCTR(c.b, iv[:]) + ret, out := sliceForAppend(dst, len(plaintext)+int(c.M)) + stream.XORKeyStream(out, plaintext) + copy(out[len(plaintext):], tag) + return ret +} + +var ( + errOpen = errors.New("ccm: message authentication failed") + errCiphertextTooShort = errors.New("ccm: ciphertext too short") + errCiphertextTooLong = errors.New("ccm: ciphertext too long") +) + +func (c *ccm) Open(dst, nonce, ciphertext, adata []byte) ([]byte, error) { + if len(ciphertext) < int(c.M) { + return nil, errCiphertextTooShort + } + if len(ciphertext) > c.MaxLength()+c.Overhead() { + return nil, errCiphertextTooLong + } + + tag := make([]byte, int(c.M)) + copy(tag, ciphertext[len(ciphertext)-int(c.M):]) + ciphertextWithoutTag := ciphertext[:len(ciphertext)-int(c.M)] + + var iv, s0 [ccmBlockSize]byte + iv[0] = c.L - 1 + copy(iv[1:ccmBlockSize-c.L], nonce) + c.b.Encrypt(s0[:], iv[:]) + for i := 0; i < int(c.M); i++ { + tag[i] ^= s0[i] + } + iv[len(iv)-1] |= 1 + stream := cipher.NewCTR(c.b, iv[:]) + + // Cannot decrypt directly to dst since we're not supposed to + // reveal the plaintext to the caller if authentication fails. + plaintext := make([]byte, len(ciphertextWithoutTag)) + stream.XORKeyStream(plaintext, ciphertextWithoutTag) + expectedTag, err := c.tag(nonce, plaintext, adata) + if err != nil { + return nil, err + } + + if subtle.ConstantTimeCompare(tag, expectedTag) != 1 { + return nil, errOpen + } + return append(dst, plaintext...), nil +} diff --git a/vendor/github.com/pion/dtls/v2/pkg/crypto/ciphersuite/cbc.go b/vendor/github.com/pion/dtls/v2/pkg/crypto/ciphersuite/cbc.go new file mode 100644 index 0000000..8ff1634 --- /dev/null +++ b/vendor/github.com/pion/dtls/v2/pkg/crypto/ciphersuite/cbc.go @@ -0,0 +1,164 @@ +package ciphersuite + +import ( //nolint:gci + "crypto/aes" + "crypto/cipher" + "crypto/hmac" + "crypto/rand" + "encoding/binary" + "hash" + + "github.com/pion/dtls/v2/internal/util" + "github.com/pion/dtls/v2/pkg/crypto/prf" + "github.com/pion/dtls/v2/pkg/protocol" + "github.com/pion/dtls/v2/pkg/protocol/recordlayer" +) + +// block ciphers using cipher block chaining. +type cbcMode interface { + cipher.BlockMode + SetIV([]byte) +} + +// CBC Provides an API to Encrypt/Decrypt DTLS 1.2 Packets +type CBC struct { + writeCBC, readCBC cbcMode + writeMac, readMac []byte + h prf.HashFunc +} + +// NewCBC creates a DTLS CBC Cipher +func NewCBC(localKey, localWriteIV, localMac, remoteKey, remoteWriteIV, remoteMac []byte, h prf.HashFunc) (*CBC, error) { + writeBlock, err := aes.NewCipher(localKey) + if err != nil { + return nil, err + } + + readBlock, err := aes.NewCipher(remoteKey) + if err != nil { + return nil, err + } + + return &CBC{ + writeCBC: cipher.NewCBCEncrypter(writeBlock, localWriteIV).(cbcMode), + writeMac: localMac, + + readCBC: cipher.NewCBCDecrypter(readBlock, remoteWriteIV).(cbcMode), + readMac: remoteMac, + h: h, + }, nil +} + +// Encrypt encrypt a DTLS RecordLayer message +func (c *CBC) Encrypt(pkt *recordlayer.RecordLayer, raw []byte) ([]byte, error) { + payload := raw[recordlayer.HeaderSize:] + raw = raw[:recordlayer.HeaderSize] + blockSize := c.writeCBC.BlockSize() + + // Generate + Append MAC + h := pkt.Header + + MAC, err := c.hmac(h.Epoch, h.SequenceNumber, h.ContentType, h.Version, payload, c.writeMac, c.h) + if err != nil { + return nil, err + } + payload = append(payload, MAC...) + + // Generate + Append padding + padding := make([]byte, blockSize-len(payload)%blockSize) + paddingLen := len(padding) + for i := 0; i < paddingLen; i++ { + padding[i] = byte(paddingLen - 1) + } + payload = append(payload, padding...) + + // Generate IV + iv := make([]byte, blockSize) + if _, err := rand.Read(iv); err != nil { + return nil, err + } + + // Set IV + Encrypt + Prepend IV + c.writeCBC.SetIV(iv) + c.writeCBC.CryptBlocks(payload, payload) + payload = append(iv, payload...) + + // Prepend unencrypte header with encrypted payload + raw = append(raw, payload...) + + // Update recordLayer size to include IV+MAC+Padding + binary.BigEndian.PutUint16(raw[recordlayer.HeaderSize-2:], uint16(len(raw)-recordlayer.HeaderSize)) + + return raw, nil +} + +// Decrypt decrypts a DTLS RecordLayer message +func (c *CBC) Decrypt(in []byte) ([]byte, error) { + body := in[recordlayer.HeaderSize:] + blockSize := c.readCBC.BlockSize() + mac := c.h() + + var h recordlayer.Header + err := h.Unmarshal(in) + switch { + case err != nil: + return nil, err + case h.ContentType == protocol.ContentTypeChangeCipherSpec: + // Nothing to encrypt with ChangeCipherSpec + return in, nil + case len(body)%blockSize != 0 || len(body) < blockSize+util.Max(mac.Size()+1, blockSize): + return nil, errNotEnoughRoomForNonce + } + + // Set + remove per record IV + c.readCBC.SetIV(body[:blockSize]) + body = body[blockSize:] + + // Decrypt + c.readCBC.CryptBlocks(body, body) + + // Padding+MAC needs to be checked in constant time + // Otherwise we reveal information about the level of correctness + paddingLen, paddingGood := examinePadding(body) + if paddingGood != 255 { + return nil, errInvalidMAC + } + + macSize := mac.Size() + if len(body) < macSize { + return nil, errInvalidMAC + } + + dataEnd := len(body) - macSize - paddingLen + + expectedMAC := body[dataEnd : dataEnd+macSize] + actualMAC, err := c.hmac(h.Epoch, h.SequenceNumber, h.ContentType, h.Version, body[:dataEnd], c.readMac, c.h) + + // Compute Local MAC and compare + if err != nil || !hmac.Equal(actualMAC, expectedMAC) { + return nil, errInvalidMAC + } + + return append(in[:recordlayer.HeaderSize], body[:dataEnd]...), nil +} + +func (c *CBC) hmac(epoch uint16, sequenceNumber uint64, contentType protocol.ContentType, protocolVersion protocol.Version, payload []byte, key []byte, hf func() hash.Hash) ([]byte, error) { + h := hmac.New(hf, key) + + msg := make([]byte, 13) + + binary.BigEndian.PutUint16(msg, epoch) + util.PutBigEndianUint48(msg[2:], sequenceNumber) + msg[8] = byte(contentType) + msg[9] = protocolVersion.Major + msg[10] = protocolVersion.Minor + binary.BigEndian.PutUint16(msg[11:], uint16(len(payload))) + + if _, err := h.Write(msg); err != nil { + return nil, err + } else if _, err := h.Write(payload); err != nil { + return nil, err + } + + return h.Sum(nil), nil +} diff --git a/vendor/github.com/pion/dtls/v2/pkg/crypto/ciphersuite/ccm.go b/vendor/github.com/pion/dtls/v2/pkg/crypto/ciphersuite/ccm.go new file mode 100644 index 0000000..354b1cc --- /dev/null +++ b/vendor/github.com/pion/dtls/v2/pkg/crypto/ciphersuite/ccm.go @@ -0,0 +1,104 @@ +package ciphersuite + +import ( + "crypto/aes" + "crypto/rand" + "encoding/binary" + "fmt" + + "github.com/pion/dtls/v2/pkg/crypto/ccm" + "github.com/pion/dtls/v2/pkg/protocol" + "github.com/pion/dtls/v2/pkg/protocol/recordlayer" +) + +// CCMTagLen is the length of Authentication Tag +type CCMTagLen int + +// CCM Enums +const ( + CCMTagLength8 CCMTagLen = 8 + CCMTagLength CCMTagLen = 16 + ccmNonceLength = 12 +) + +// CCM Provides an API to Encrypt/Decrypt DTLS 1.2 Packets +type CCM struct { + localCCM, remoteCCM ccm.CCM + localWriteIV, remoteWriteIV []byte + tagLen CCMTagLen +} + +// NewCCM creates a DTLS GCM Cipher +func NewCCM(tagLen CCMTagLen, localKey, localWriteIV, remoteKey, remoteWriteIV []byte) (*CCM, error) { + localBlock, err := aes.NewCipher(localKey) + if err != nil { + return nil, err + } + localCCM, err := ccm.NewCCM(localBlock, int(tagLen), ccmNonceLength) + if err != nil { + return nil, err + } + + remoteBlock, err := aes.NewCipher(remoteKey) + if err != nil { + return nil, err + } + remoteCCM, err := ccm.NewCCM(remoteBlock, int(tagLen), ccmNonceLength) + if err != nil { + return nil, err + } + + return &CCM{ + localCCM: localCCM, + localWriteIV: localWriteIV, + remoteCCM: remoteCCM, + remoteWriteIV: remoteWriteIV, + tagLen: tagLen, + }, nil +} + +// Encrypt encrypt a DTLS RecordLayer message +func (c *CCM) Encrypt(pkt *recordlayer.RecordLayer, raw []byte) ([]byte, error) { + payload := raw[recordlayer.HeaderSize:] + raw = raw[:recordlayer.HeaderSize] + + nonce := append(append([]byte{}, c.localWriteIV[:4]...), make([]byte, 8)...) + if _, err := rand.Read(nonce[4:]); err != nil { + return nil, err + } + + additionalData := generateAEADAdditionalData(&pkt.Header, len(payload)) + encryptedPayload := c.localCCM.Seal(nil, nonce, payload, additionalData) + + encryptedPayload = append(nonce[4:], encryptedPayload...) + raw = append(raw, encryptedPayload...) + + // Update recordLayer size to include explicit nonce + binary.BigEndian.PutUint16(raw[recordlayer.HeaderSize-2:], uint16(len(raw)-recordlayer.HeaderSize)) + return raw, nil +} + +// Decrypt decrypts a DTLS RecordLayer message +func (c *CCM) Decrypt(in []byte) ([]byte, error) { + var h recordlayer.Header + err := h.Unmarshal(in) + switch { + case err != nil: + return nil, err + case h.ContentType == protocol.ContentTypeChangeCipherSpec: + // Nothing to encrypt with ChangeCipherSpec + return in, nil + case len(in) <= (8 + recordlayer.HeaderSize): + return nil, errNotEnoughRoomForNonce + } + + nonce := append(append([]byte{}, c.remoteWriteIV[:4]...), in[recordlayer.HeaderSize:recordlayer.HeaderSize+8]...) + out := in[recordlayer.HeaderSize+8:] + + additionalData := generateAEADAdditionalData(&h, len(out)-int(c.tagLen)) + out, err = c.remoteCCM.Open(out[:0], nonce, out, additionalData) + if err != nil { + return nil, fmt.Errorf("%w: %v", errDecryptPacket, err) + } + return append(in[:recordlayer.HeaderSize], out...), nil +} diff --git a/vendor/github.com/pion/dtls/v2/pkg/crypto/ciphersuite/ciphersuite.go b/vendor/github.com/pion/dtls/v2/pkg/crypto/ciphersuite/ciphersuite.go new file mode 100644 index 0000000..72beffd --- /dev/null +++ b/vendor/github.com/pion/dtls/v2/pkg/crypto/ciphersuite/ciphersuite.go @@ -0,0 +1,72 @@ +// Package ciphersuite provides the crypto operations needed for a DTLS CipherSuite +package ciphersuite + +import ( + "encoding/binary" + "errors" + + "github.com/pion/dtls/v2/pkg/protocol" + "github.com/pion/dtls/v2/pkg/protocol/recordlayer" +) + +var ( + errNotEnoughRoomForNonce = &protocol.InternalError{Err: errors.New("buffer not long enough to contain nonce")} //nolint:goerr113 + errDecryptPacket = &protocol.TemporaryError{Err: errors.New("failed to decrypt packet")} //nolint:goerr113 + errInvalidMAC = &protocol.TemporaryError{Err: errors.New("invalid mac")} //nolint:goerr113 +) + +func generateAEADAdditionalData(h *recordlayer.Header, payloadLen int) []byte { + var additionalData [13]byte + // SequenceNumber MUST be set first + // we only want uint48, clobbering an extra 2 (using uint64, Golang doesn't have uint48) + binary.BigEndian.PutUint64(additionalData[:], h.SequenceNumber) + binary.BigEndian.PutUint16(additionalData[:], h.Epoch) + additionalData[8] = byte(h.ContentType) + additionalData[9] = h.Version.Major + additionalData[10] = h.Version.Minor + binary.BigEndian.PutUint16(additionalData[len(additionalData)-2:], uint16(payloadLen)) + + return additionalData[:] +} + +// examinePadding returns, in constant time, the length of the padding to remove +// from the end of payload. It also returns a byte which is equal to 255 if the +// padding was valid and 0 otherwise. See RFC 2246, Section 6.2.3.2. +// +// https://github.com/golang/go/blob/039c2081d1178f90a8fa2f4e6958693129f8de33/src/crypto/tls/conn.go#L245 +func examinePadding(payload []byte) (toRemove int, good byte) { + if len(payload) < 1 { + return 0, 0 + } + + paddingLen := payload[len(payload)-1] + t := uint(len(payload)-1) - uint(paddingLen) + // if len(payload) >= (paddingLen - 1) then the MSB of t is zero + good = byte(int32(^t) >> 31) + + // The maximum possible padding length plus the actual length field + toCheck := 256 + // The length of the padded data is public, so we can use an if here + if toCheck > len(payload) { + toCheck = len(payload) + } + + for i := 0; i < toCheck; i++ { + t := uint(paddingLen) - uint(i) + // if i <= paddingLen then the MSB of t is zero + mask := byte(int32(^t) >> 31) + b := payload[len(payload)-1-i] + good &^= mask&paddingLen ^ mask&b + } + + // We AND together the bits of good and replicate the result across + // all the bits. + good &= good << 4 + good &= good << 2 + good &= good << 1 + good = uint8(int8(good) >> 7) + + toRemove = int(paddingLen) + 1 + + return toRemove, good +} diff --git a/vendor/github.com/pion/dtls/v2/pkg/crypto/ciphersuite/gcm.go b/vendor/github.com/pion/dtls/v2/pkg/crypto/ciphersuite/gcm.go new file mode 100644 index 0000000..af986d4 --- /dev/null +++ b/vendor/github.com/pion/dtls/v2/pkg/crypto/ciphersuite/gcm.go @@ -0,0 +1,100 @@ +package ciphersuite + +import ( + "crypto/aes" + "crypto/cipher" + "crypto/rand" + "encoding/binary" + "fmt" + + "github.com/pion/dtls/v2/pkg/protocol" + "github.com/pion/dtls/v2/pkg/protocol/recordlayer" +) + +const ( + gcmTagLength = 16 + gcmNonceLength = 12 +) + +// GCM Provides an API to Encrypt/Decrypt DTLS 1.2 Packets +type GCM struct { + localGCM, remoteGCM cipher.AEAD + localWriteIV, remoteWriteIV []byte +} + +// NewGCM creates a DTLS GCM Cipher +func NewGCM(localKey, localWriteIV, remoteKey, remoteWriteIV []byte) (*GCM, error) { + localBlock, err := aes.NewCipher(localKey) + if err != nil { + return nil, err + } + localGCM, err := cipher.NewGCM(localBlock) + if err != nil { + return nil, err + } + + remoteBlock, err := aes.NewCipher(remoteKey) + if err != nil { + return nil, err + } + remoteGCM, err := cipher.NewGCM(remoteBlock) + if err != nil { + return nil, err + } + + return &GCM{ + localGCM: localGCM, + localWriteIV: localWriteIV, + remoteGCM: remoteGCM, + remoteWriteIV: remoteWriteIV, + }, nil +} + +// Encrypt encrypt a DTLS RecordLayer message +func (g *GCM) Encrypt(pkt *recordlayer.RecordLayer, raw []byte) ([]byte, error) { + payload := raw[recordlayer.HeaderSize:] + raw = raw[:recordlayer.HeaderSize] + + nonce := make([]byte, gcmNonceLength) + copy(nonce, g.localWriteIV[:4]) + if _, err := rand.Read(nonce[4:]); err != nil { + return nil, err + } + + additionalData := generateAEADAdditionalData(&pkt.Header, len(payload)) + encryptedPayload := g.localGCM.Seal(nil, nonce, payload, additionalData) + r := make([]byte, len(raw)+len(nonce[4:])+len(encryptedPayload)) + copy(r, raw) + copy(r[len(raw):], nonce[4:]) + copy(r[len(raw)+len(nonce[4:]):], encryptedPayload) + + // Update recordLayer size to include explicit nonce + binary.BigEndian.PutUint16(r[recordlayer.HeaderSize-2:], uint16(len(r)-recordlayer.HeaderSize)) + return r, nil +} + +// Decrypt decrypts a DTLS RecordLayer message +func (g *GCM) Decrypt(in []byte) ([]byte, error) { + var h recordlayer.Header + err := h.Unmarshal(in) + switch { + case err != nil: + return nil, err + case h.ContentType == protocol.ContentTypeChangeCipherSpec: + // Nothing to encrypt with ChangeCipherSpec + return in, nil + case len(in) <= (8 + recordlayer.HeaderSize): + return nil, errNotEnoughRoomForNonce + } + + nonce := make([]byte, 0, gcmNonceLength) + nonce = append(append(nonce, g.remoteWriteIV[:4]...), in[recordlayer.HeaderSize:recordlayer.HeaderSize+8]...) + out := in[recordlayer.HeaderSize+8:] + + additionalData := generateAEADAdditionalData(&h, len(out)-gcmTagLength) + out, err = g.remoteGCM.Open(out[:0], nonce, out, additionalData) + if err != nil { + return nil, fmt.Errorf("%w: %v", errDecryptPacket, err) + } + return append(in[:recordlayer.HeaderSize], out...), nil +} diff --git a/vendor/github.com/pion/dtls/v2/pkg/crypto/clientcertificate/client_certificate.go b/vendor/github.com/pion/dtls/v2/pkg/crypto/clientcertificate/client_certificate.go new file mode 100644 index 0000000..c222c01 --- /dev/null +++ b/vendor/github.com/pion/dtls/v2/pkg/crypto/clientcertificate/client_certificate.go @@ -0,0 +1,22 @@ +// Package clientcertificate provides all the support Client Certificate types +package clientcertificate + +// Type is used to communicate what +// type of certificate is being transported +// +//https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-parameters-2 +type Type byte + +// ClientCertificateType enums +const ( + RSASign Type = 1 + ECDSASign Type = 64 +) + +// Types returns all valid ClientCertificate Types +func Types() map[Type]bool { + return map[Type]bool{ + RSASign: true, + ECDSASign: true, + } +} diff --git a/vendor/github.com/pion/dtls/v2/pkg/crypto/elliptic/elliptic.go b/vendor/github.com/pion/dtls/v2/pkg/crypto/elliptic/elliptic.go new file mode 100644 index 0000000..5b0e4fa --- /dev/null +++ b/vendor/github.com/pion/dtls/v2/pkg/crypto/elliptic/elliptic.go @@ -0,0 +1,99 @@ +// Package elliptic provides elliptic curve cryptography for DTLS +package elliptic + +import ( + "crypto/elliptic" + "crypto/rand" + "errors" + + "golang.org/x/crypto/curve25519" +) + +var errInvalidNamedCurve = errors.New("invalid named curve") + +// CurvePointFormat is used to represent the IANA registered curve points +// +// https://www.iana.org/assignments/tls-parameters/tls-parameters.xml#tls-parameters-9 +type CurvePointFormat byte + +// CurvePointFormat enums +const ( + CurvePointFormatUncompressed CurvePointFormat = 0 +) + +// Keypair is a Curve with a Private/Public Keypair +type Keypair struct { + Curve Curve + PublicKey []byte + PrivateKey []byte +} + +// CurveType is used to represent the IANA registered curve types for TLS +// +// https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-parameters-10 +type CurveType byte + +// CurveType enums +const ( + CurveTypeNamedCurve CurveType = 0x03 +) + +// CurveTypes returns all known curves +func CurveTypes() map[CurveType]struct{} { + return map[CurveType]struct{}{ + CurveTypeNamedCurve: {}, + } +} + +// Curve is used to represent the IANA registered curves for TLS +// +// https://www.iana.org/assignments/tls-parameters/tls-parameters.xml#tls-parameters-8 +type Curve uint16 + +// Curve enums +const ( + P256 Curve = 0x0017 + P384 Curve = 0x0018 + X25519 Curve = 0x001d +) + +// Curves returns all curves we implement +func Curves() map[Curve]bool { + return map[Curve]bool{ + X25519: true, + P256: true, + P384: true, + } +} + +// GenerateKeypair generates a keypair for the given Curve +func GenerateKeypair(c Curve) (*Keypair, error) { + switch c { //nolint:golint + case X25519: + tmp := make([]byte, 32) + if _, err := rand.Read(tmp); err != nil { + return nil, err + } + + var public, private [32]byte + copy(private[:], tmp) + + curve25519.ScalarBaseMult(&public, &private) + return &Keypair{X25519, public[:], private[:]}, nil + case P256: + return ellipticCurveKeypair(P256, elliptic.P256(), elliptic.P256()) + case P384: + return ellipticCurveKeypair(P384, elliptic.P384(), elliptic.P384()) + default: + return nil, errInvalidNamedCurve + } +} + +func ellipticCurveKeypair(nc Curve, c1, c2 elliptic.Curve) (*Keypair, error) { + privateKey, x, y, err := elliptic.GenerateKey(c1, rand.Reader) + if err != nil { + return nil, err + } + + return &Keypair{nc, elliptic.Marshal(c2, x, y), privateKey}, nil +} diff --git a/vendor/github.com/pion/dtls/v2/pkg/crypto/fingerprint/fingerprint.go b/vendor/github.com/pion/dtls/v2/pkg/crypto/fingerprint/fingerprint.go new file mode 100644 index 0000000..215b44e --- /dev/null +++ b/vendor/github.com/pion/dtls/v2/pkg/crypto/fingerprint/fingerprint.go @@ -0,0 +1,50 @@ +// Package fingerprint provides a helper to create fingerprint string from certificate +package fingerprint + +import ( + "crypto" + "crypto/x509" + "errors" + "fmt" +) + +var ( + errHashUnavailable = errors.New("fingerprint: hash algorithm is not linked into the binary") + errInvalidFingerprintLength = errors.New("fingerprint: invalid fingerprint length") +) + +// Fingerprint creates a fingerprint for a certificate using the specified hash algorithm +func Fingerprint(cert *x509.Certificate, algo crypto.Hash) (string, error) { + if !algo.Available() { + return "", errHashUnavailable + } + h := algo.New() + for i := 0; i < len(cert.Raw); { + n, _ := h.Write(cert.Raw[i:]) + // Hash.Writer is specified to be never returning an error. + // https://golang.org/pkg/hash/#Hash + i += n + } + digest := []byte(fmt.Sprintf("%x", h.Sum(nil))) + + digestlen := len(digest) + if digestlen == 0 { + return "", nil + } + if digestlen%2 != 0 { + return "", errInvalidFingerprintLength + } + res := make([]byte, digestlen>>1+digestlen-1) + + pos := 0 + for i, c := range digest { + res[pos] = c + pos++ + if (i)%2 != 0 && i < digestlen-1 { + res[pos] = byte(':') + pos++ + } + } + + return string(res), nil +} diff --git a/vendor/github.com/pion/dtls/v2/pkg/crypto/fingerprint/hash.go b/vendor/github.com/pion/dtls/v2/pkg/crypto/fingerprint/hash.go new file mode 100644 index 0000000..09107db --- /dev/null +++ b/vendor/github.com/pion/dtls/v2/pkg/crypto/fingerprint/hash.go @@ -0,0 +1,37 @@ +package fingerprint + +import ( + "crypto" + "errors" +) + +var errInvalidHashAlgorithm = errors.New("fingerprint: invalid hash algorithm") + +func nameToHash() map[string]crypto.Hash { + return map[string]crypto.Hash{ + "md5": crypto.MD5, // [RFC3279] + "sha-1": crypto.SHA1, // [RFC3279] + "sha-224": crypto.SHA224, // [RFC4055] + "sha-256": crypto.SHA256, // [RFC4055] + "sha-384": crypto.SHA384, // [RFC4055] + "sha-512": crypto.SHA512, // [RFC4055] + } +} + +// HashFromString allows looking up a hash algorithm by it's string representation +func HashFromString(s string) (crypto.Hash, error) { + if h, ok := nameToHash()[s]; ok { + return h, nil + } + return 0, errInvalidHashAlgorithm +} + +// StringFromHash allows looking up a string representation of the crypto.Hash. +func StringFromHash(hash crypto.Hash) (string, error) { + for s, h := range nameToHash() { + if h == hash { + return s, nil + } + } + return "", errInvalidHashAlgorithm +} diff --git a/vendor/github.com/pion/dtls/v2/pkg/crypto/hash/hash.go b/vendor/github.com/pion/dtls/v2/pkg/crypto/hash/hash.go new file mode 100644 index 0000000..660326f --- /dev/null +++ b/vendor/github.com/pion/dtls/v2/pkg/crypto/hash/hash.go @@ -0,0 +1,126 @@ +// Package hash provides TLS HashAlgorithm as defined in TLS 1.2 +package hash + +import ( //nolint:gci + "crypto" + "crypto/md5" //nolint:gosec + "crypto/sha1" //nolint:gosec + "crypto/sha256" + "crypto/sha512" +) + +// Algorithm is used to indicate the hash algorithm used +// https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-parameters-18 +type Algorithm uint16 + +// Supported hash algorithms +const ( + None Algorithm = 0 // Blacklisted + MD5 Algorithm = 1 // Blacklisted + SHA1 Algorithm = 2 // Blacklisted + SHA224 Algorithm = 3 + SHA256 Algorithm = 4 + SHA384 Algorithm = 5 + SHA512 Algorithm = 6 + Ed25519 Algorithm = 8 +) + +// String makes hashAlgorithm printable +func (a Algorithm) String() string { + switch a { + case None: + return "none" + case MD5: + return "md5" // [RFC3279] + case SHA1: + return "sha-1" // [RFC3279] + case SHA224: + return "sha-224" // [RFC4055] + case SHA256: + return "sha-256" // [RFC4055] + case SHA384: + return "sha-384" // [RFC4055] + case SHA512: + return "sha-512" // [RFC4055] + case Ed25519: + return "null" + default: + return "unknown or unsupported hash algorithm" + } +} + +// Digest performs a digest on the passed value +func (a Algorithm) Digest(b []byte) []byte { + switch a { + case None: + return nil + case MD5: + hash := md5.Sum(b) // #nosec + return hash[:] + case SHA1: + hash := sha1.Sum(b) // #nosec + return hash[:] + case SHA224: + hash := sha256.Sum224(b) + return hash[:] + case SHA256: + hash := sha256.Sum256(b) + return hash[:] + case SHA384: + hash := sha512.Sum384(b) + return hash[:] + case SHA512: + hash := sha512.Sum512(b) + return hash[:] + default: + return nil + } +} + +// Insecure returns if the given HashAlgorithm is considered secure in DTLS 1.2 +func (a Algorithm) Insecure() bool { + switch a { + case None, MD5, SHA1: + return true + default: + return false + } +} + +// CryptoHash returns the crypto.Hash implementation for the given HashAlgorithm +func (a Algorithm) CryptoHash() crypto.Hash { + switch a { + case None: + return crypto.Hash(0) + case MD5: + return crypto.MD5 + case SHA1: + return crypto.SHA1 + case SHA224: + return crypto.SHA224 + case SHA256: + return crypto.SHA256 + case SHA384: + return crypto.SHA384 + case SHA512: + return crypto.SHA512 + case Ed25519: + return crypto.Hash(0) + default: + return crypto.Hash(0) + } +} + +// Algorithms returns all the supported Hash Algorithms +func Algorithms() map[Algorithm]struct{} { + return map[Algorithm]struct{}{ + None: {}, + MD5: {}, + SHA1: {}, + SHA224: {}, + SHA256: {}, + SHA384: {}, + SHA512: {}, + Ed25519: {}, + } +} diff --git a/vendor/github.com/pion/dtls/v2/pkg/crypto/prf/prf.go b/vendor/github.com/pion/dtls/v2/pkg/crypto/prf/prf.go new file mode 100644 index 0000000..d33df19 --- /dev/null +++ b/vendor/github.com/pion/dtls/v2/pkg/crypto/prf/prf.go @@ -0,0 +1,224 @@ +// Package prf implements TLS 1.2 Pseudorandom functions +package prf + +import ( //nolint:gci + ellipticStdlib "crypto/elliptic" + "crypto/hmac" + "encoding/binary" + "errors" + "fmt" + "hash" + "math" + + "github.com/pion/dtls/v2/pkg/crypto/elliptic" + "github.com/pion/dtls/v2/pkg/protocol" + "golang.org/x/crypto/curve25519" +) + +const ( + masterSecretLabel = "master secret" + extendedMasterSecretLabel = "extended master secret" + keyExpansionLabel = "key expansion" + verifyDataClientLabel = "client finished" + verifyDataServerLabel = "server finished" +) + +// HashFunc allows callers to decide what hash is used in PRF +type HashFunc func() hash.Hash + +// EncryptionKeys is all the state needed for a TLS CipherSuite +type EncryptionKeys struct { + MasterSecret []byte + ClientMACKey []byte + ServerMACKey []byte + ClientWriteKey []byte + ServerWriteKey []byte + ClientWriteIV []byte + ServerWriteIV []byte +} + +var errInvalidNamedCurve = &protocol.FatalError{Err: errors.New("invalid named curve")} //nolint:goerr113 + +func (e *EncryptionKeys) String() string { + return fmt.Sprintf(`encryptionKeys: +- masterSecret: %#v +- clientMACKey: %#v +- serverMACKey: %#v +- clientWriteKey: %#v +- serverWriteKey: %#v +- clientWriteIV: %#v +- serverWriteIV: %#v +`, + e.MasterSecret, + e.ClientMACKey, + e.ServerMACKey, + e.ClientWriteKey, + e.ServerWriteKey, + e.ClientWriteIV, + e.ServerWriteIV) +} + +// PSKPreMasterSecret generates the PSK Premaster Secret +// The premaster secret is formed as follows: if the PSK is N octets +// long, concatenate a uint16 with the value N, N zero octets, a second +// uint16 with the value N, and the PSK itself. +// +// https://tools.ietf.org/html/rfc4279#section-2 +func PSKPreMasterSecret(psk []byte) []byte { + pskLen := uint16(len(psk)) + + out := append(make([]byte, 2+pskLen+2), psk...) + binary.BigEndian.PutUint16(out, pskLen) + binary.BigEndian.PutUint16(out[2+pskLen:], pskLen) + + return out +} + +// PreMasterSecret implements TLS 1.2 Premaster Secret generation given a keypair and a curve +func PreMasterSecret(publicKey, privateKey []byte, curve elliptic.Curve) ([]byte, error) { + switch curve { + case elliptic.X25519: + return curve25519.X25519(privateKey, publicKey) + case elliptic.P256: + return ellipticCurvePreMasterSecret(publicKey, privateKey, ellipticStdlib.P256(), ellipticStdlib.P256()) + case elliptic.P384: + return ellipticCurvePreMasterSecret(publicKey, privateKey, ellipticStdlib.P384(), ellipticStdlib.P384()) + default: + return nil, errInvalidNamedCurve + } +} + +func ellipticCurvePreMasterSecret(publicKey, privateKey []byte, c1, c2 ellipticStdlib.Curve) ([]byte, error) { + x, y := ellipticStdlib.Unmarshal(c1, publicKey) + if x == nil || y == nil { + return nil, errInvalidNamedCurve + } + + result, _ := c2.ScalarMult(x, y, privateKey) + preMasterSecret := make([]byte, (c2.Params().BitSize+7)>>3) + resultBytes := result.Bytes() + copy(preMasterSecret[len(preMasterSecret)-len(resultBytes):], resultBytes) + return preMasterSecret, nil +} + +// PHash is PRF is the SHA-256 hash function is used for all cipher suites +// defined in this TLS 1.2 document and in TLS documents published prior to this +// document when TLS 1.2 is negotiated. New cipher suites MUST explicitly +// specify a PRF and, in general, SHOULD use the TLS PRF with SHA-256 or a +// stronger standard hash function. +// +// P_hash(secret, seed) = HMAC_hash(secret, A(1) + seed) + +// HMAC_hash(secret, A(2) + seed) + +// HMAC_hash(secret, A(3) + seed) + ... +// +// A() is defined as: +// +// A(0) = seed +// A(i) = HMAC_hash(secret, A(i-1)) +// +// P_hash can be iterated as many times as necessary to produce the +// required quantity of data. For example, if P_SHA256 is being used to +// create 80 bytes of data, it will have to be iterated three times +// (through A(3)), creating 96 bytes of output data; the last 16 bytes +// of the final iteration will then be discarded, leaving 80 bytes of +// output data. +// +// https://tools.ietf.org/html/rfc4346w +func PHash(secret, seed []byte, requestedLength int, h HashFunc) ([]byte, error) { + hmacSHA256 := func(key, data []byte) ([]byte, error) { + mac := hmac.New(h, key) + if _, err := mac.Write(data); err != nil { + return nil, err + } + return mac.Sum(nil), nil + } + + var err error + lastRound := seed + out := []byte{} + + iterations := int(math.Ceil(float64(requestedLength) / float64(h().Size()))) + for i := 0; i < iterations; i++ { + lastRound, err = hmacSHA256(secret, lastRound) + if err != nil { + return nil, err + } + withSecret, err := hmacSHA256(secret, append(lastRound, seed...)) + if err != nil { + return nil, err + } + out = append(out, withSecret...) + } + + return out[:requestedLength], nil +} + +// ExtendedMasterSecret generates a Extended MasterSecret as defined in +// https://tools.ietf.org/html/rfc7627 +func ExtendedMasterSecret(preMasterSecret, sessionHash []byte, h HashFunc) ([]byte, error) { + seed := append([]byte(extendedMasterSecretLabel), sessionHash...) + return PHash(preMasterSecret, seed, 48, h) +} + +// MasterSecret generates a TLS 1.2 MasterSecret +func MasterSecret(preMasterSecret, clientRandom, serverRandom []byte, h HashFunc) ([]byte, error) { + seed := append(append([]byte(masterSecretLabel), clientRandom...), serverRandom...) + return PHash(preMasterSecret, seed, 48, h) +} + +// GenerateEncryptionKeys is the final step TLS 1.2 PRF. Given all state generated so far generates +// the final keys need for encryption +func GenerateEncryptionKeys(masterSecret, clientRandom, serverRandom []byte, macLen, keyLen, ivLen int, h HashFunc) (*EncryptionKeys, error) { + seed := append(append([]byte(keyExpansionLabel), serverRandom...), clientRandom...) + keyMaterial, err := PHash(masterSecret, seed, (2*macLen)+(2*keyLen)+(2*ivLen), h) + if err != nil { + return nil, err + } + + clientMACKey := keyMaterial[:macLen] + keyMaterial = keyMaterial[macLen:] + + serverMACKey := keyMaterial[:macLen] + keyMaterial = keyMaterial[macLen:] + + clientWriteKey := keyMaterial[:keyLen] + keyMaterial = keyMaterial[keyLen:] + + serverWriteKey := keyMaterial[:keyLen] + keyMaterial = keyMaterial[keyLen:] + + clientWriteIV := keyMaterial[:ivLen] + keyMaterial = keyMaterial[ivLen:] + + serverWriteIV := keyMaterial[:ivLen] + + return &EncryptionKeys{ + MasterSecret: masterSecret, + ClientMACKey: clientMACKey, + ServerMACKey: serverMACKey, + ClientWriteKey: clientWriteKey, + ServerWriteKey: serverWriteKey, + ClientWriteIV: clientWriteIV, + ServerWriteIV: serverWriteIV, + }, nil +} + +func prfVerifyData(masterSecret, handshakeBodies []byte, label string, hashFunc HashFunc) ([]byte, error) { + h := hashFunc() + if _, err := h.Write(handshakeBodies); err != nil { + return nil, err + } + + seed := append([]byte(label), h.Sum(nil)...) + return PHash(masterSecret, seed, 12, hashFunc) +} + +// VerifyDataClient is caled on the Client Side to either verify or generate the VerifyData message +func VerifyDataClient(masterSecret, handshakeBodies []byte, h HashFunc) ([]byte, error) { + return prfVerifyData(masterSecret, handshakeBodies, verifyDataClientLabel, h) +} + +// VerifyDataServer is caled on the Server Side to either verify or generate the VerifyData message +func VerifyDataServer(masterSecret, handshakeBodies []byte, h HashFunc) ([]byte, error) { + return prfVerifyData(masterSecret, handshakeBodies, verifyDataServerLabel, h) +} diff --git a/vendor/github.com/pion/dtls/v2/pkg/crypto/signature/signature.go b/vendor/github.com/pion/dtls/v2/pkg/crypto/signature/signature.go new file mode 100644 index 0000000..d9150eb --- /dev/null +++ b/vendor/github.com/pion/dtls/v2/pkg/crypto/signature/signature.go @@ -0,0 +1,24 @@ +// Package signature provides our implemented Signature Algorithms +package signature + +// Algorithm as defined in TLS 1.2 +// https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-parameters-16 +type Algorithm uint16 + +// SignatureAlgorithm enums +const ( + Anonymous Algorithm = 0 + RSA Algorithm = 1 + ECDSA Algorithm = 3 + Ed25519 Algorithm = 7 +) + +// Algorithms returns all implemented Signature Algorithms +func Algorithms() map[Algorithm]struct{} { + return map[Algorithm]struct{}{ + Anonymous: {}, + RSA: {}, + ECDSA: {}, + Ed25519: {}, + } +} diff --git a/vendor/github.com/pion/dtls/v2/pkg/crypto/signaturehash/errors.go b/vendor/github.com/pion/dtls/v2/pkg/crypto/signaturehash/errors.go new file mode 100644 index 0000000..9d9d3b3 --- /dev/null +++ b/vendor/github.com/pion/dtls/v2/pkg/crypto/signaturehash/errors.go @@ -0,0 +1,9 @@ +package signaturehash + +import "errors" + +var ( + errNoAvailableSignatureSchemes = errors.New("connection can not be created, no SignatureScheme satisfy this Config") + errInvalidSignatureAlgorithm = errors.New("invalid signature algorithm") + errInvalidHashAlgorithm = errors.New("invalid hash algorithm") +) diff --git a/vendor/github.com/pion/dtls/v2/pkg/crypto/signaturehash/signaturehash.go b/vendor/github.com/pion/dtls/v2/pkg/crypto/signaturehash/signaturehash.go new file mode 100644 index 0000000..f2017bc --- /dev/null +++ b/vendor/github.com/pion/dtls/v2/pkg/crypto/signaturehash/signaturehash.go @@ -0,0 +1,93 @@ +// Package signaturehash provides the SignatureHashAlgorithm as defined in TLS 1.2 +package signaturehash + +import ( + "crypto" + "crypto/ecdsa" + "crypto/ed25519" + "crypto/rsa" + "crypto/tls" + + "github.com/pion/dtls/v2/pkg/crypto/hash" + "github.com/pion/dtls/v2/pkg/crypto/signature" + "golang.org/x/xerrors" +) + +// Algorithm is a signature/hash algorithm pairs which may be used in +// digital signatures. +// +// https://tools.ietf.org/html/rfc5246#section-7.4.1.4.1 +type Algorithm struct { + Hash hash.Algorithm + Signature signature.Algorithm +} + +// Algorithms are all the know SignatureHash Algorithms +func Algorithms() []Algorithm { + return []Algorithm{ + {hash.SHA256, signature.ECDSA}, + {hash.SHA384, signature.ECDSA}, + {hash.SHA512, signature.ECDSA}, + {hash.SHA256, signature.RSA}, + {hash.SHA384, signature.RSA}, + {hash.SHA512, signature.RSA}, + {hash.Ed25519, signature.Ed25519}, + } +} + +// SelectSignatureScheme returns most preferred and compatible scheme. +func SelectSignatureScheme(sigs []Algorithm, privateKey crypto.PrivateKey) (Algorithm, error) { + for _, ss := range sigs { + if ss.isCompatible(privateKey) { + return ss, nil + } + } + return Algorithm{}, errNoAvailableSignatureSchemes +} + +// isCompatible checks that given private key is compatible with the signature scheme. +func (a *Algorithm) isCompatible(privateKey crypto.PrivateKey) bool { + switch privateKey.(type) { + case ed25519.PrivateKey: + return a.Signature == signature.Ed25519 + case *ecdsa.PrivateKey: + return a.Signature == signature.ECDSA + case *rsa.PrivateKey: + return a.Signature == signature.RSA + default: + return false + } +} + +// ParseSignatureSchemes translates []tls.SignatureScheme to []signatureHashAlgorithm. +// It returns default signature scheme list if no SignatureScheme is passed. +func ParseSignatureSchemes(sigs []tls.SignatureScheme, insecureHashes bool) ([]Algorithm, error) { + if len(sigs) == 0 { + return Algorithms(), nil + } + out := []Algorithm{} + for _, ss := range sigs { + sig := signature.Algorithm(ss & 0xFF) + if _, ok := signature.Algorithms()[sig]; !ok { + return nil, + xerrors.Errorf("SignatureScheme %04x: %w", ss, errInvalidSignatureAlgorithm) + } + h := hash.Algorithm(ss >> 8) + if _, ok := hash.Algorithms()[h]; !ok || (ok && h == hash.None) { + return nil, xerrors.Errorf("SignatureScheme %04x: %w", ss, errInvalidHashAlgorithm) + } + if h.Insecure() && !insecureHashes { + continue + } + out = append(out, Algorithm{ + Hash: h, + Signature: sig, + }) + } + + if len(out) == 0 { + return nil, errNoAvailableSignatureSchemes + } + + return out, nil +} diff --git a/vendor/github.com/pion/dtls/v2/pkg/protocol/alert/alert.go b/vendor/github.com/pion/dtls/v2/pkg/protocol/alert/alert.go new file mode 100644 index 0000000..9eb2e6a --- /dev/null +++ b/vendor/github.com/pion/dtls/v2/pkg/protocol/alert/alert.go @@ -0,0 +1,160 @@ +// Package alert implements TLS alert protocol https://tools.ietf.org/html/rfc5246#section-7.2 +package alert + +import ( + "errors" + "fmt" + + "github.com/pion/dtls/v2/pkg/protocol" +) + +var errBufferTooSmall = &protocol.TemporaryError{Err: errors.New("buffer is too small")} //nolint:goerr113 + +// Level is the level of the TLS Alert +type Level byte + +// Level enums +const ( + Warning Level = 1 + Fatal Level = 2 +) + +func (l Level) String() string { + switch l { + case Warning: + return "Warning" + case Fatal: + return "Fatal" + default: + return "Invalid alert level" + } +} + +// Description is the extended info of the TLS Alert +type Description byte + +// Description enums +const ( + CloseNotify Description = 0 + UnexpectedMessage Description = 10 + BadRecordMac Description = 20 + DecryptionFailed Description = 21 + RecordOverflow Description = 22 + DecompressionFailure Description = 30 + HandshakeFailure Description = 40 + NoCertificate Description = 41 + BadCertificate Description = 42 + UnsupportedCertificate Description = 43 + CertificateRevoked Description = 44 + CertificateExpired Description = 45 + CertificateUnknown Description = 46 + IllegalParameter Description = 47 + UnknownCA Description = 48 + AccessDenied Description = 49 + DecodeError Description = 50 + DecryptError Description = 51 + ExportRestriction Description = 60 + ProtocolVersion Description = 70 + InsufficientSecurity Description = 71 + InternalError Description = 80 + UserCanceled Description = 90 + NoRenegotiation Description = 100 + UnsupportedExtension Description = 110 +) + +func (d Description) String() string { + switch d { + case CloseNotify: + return "CloseNotify" + case UnexpectedMessage: + return "UnexpectedMessage" + case BadRecordMac: + return "BadRecordMac" + case DecryptionFailed: + return "DecryptionFailed" + case RecordOverflow: + return "RecordOverflow" + case DecompressionFailure: + return "DecompressionFailure" + case HandshakeFailure: + return "HandshakeFailure" + case NoCertificate: + return "NoCertificate" + case BadCertificate: + return "BadCertificate" + case UnsupportedCertificate: + return "UnsupportedCertificate" + case CertificateRevoked: + return "CertificateRevoked" + case CertificateExpired: + return "CertificateExpired" + case CertificateUnknown: + return "CertificateUnknown" + case IllegalParameter: + return "IllegalParameter" + case UnknownCA: + return "UnknownCA" + case AccessDenied: + return "AccessDenied" + case DecodeError: + return "DecodeError" + case DecryptError: + return "DecryptError" + case ExportRestriction: + return "ExportRestriction" + case ProtocolVersion: + return "ProtocolVersion" + case InsufficientSecurity: + return "InsufficientSecurity" + case InternalError: + return "InternalError" + case UserCanceled: + return "UserCanceled" + case NoRenegotiation: + return "NoRenegotiation" + case UnsupportedExtension: + return "UnsupportedExtension" + default: + return "Invalid alert description" + } +} + +// Alert is one of the content types supported by the TLS record layer. +// Alert messages convey the severity of the message +// (warning or fatal) and a description of the alert. Alert messages +// with a level of fatal result in the immediate termination of the +// connection. In this case, other connections corresponding to the +// session may continue, but the session identifier MUST be invalidated, +// preventing the failed session from being used to establish new +// connections. Like other messages, alert messages are encrypted and +// compressed, as specified by the current connection state. +// https://tools.ietf.org/html/rfc5246#section-7.2 +type Alert struct { + Level Level + Description Description +} + +// ContentType returns the ContentType of this Content +func (a Alert) ContentType() protocol.ContentType { + return protocol.ContentTypeAlert +} + +// Marshal returns the encoded alert +func (a *Alert) Marshal() ([]byte, error) { + return []byte{byte(a.Level), byte(a.Description)}, nil +} + +// Unmarshal populates the alert from binary data +func (a *Alert) Unmarshal(data []byte) error { + if len(data) != 2 { + return errBufferTooSmall + } + + a.Level = Level(data[0]) + a.Description = Description(data[1]) + return nil +} + +func (a *Alert) String() string { + return fmt.Sprintf("Alert %s: %s", a.Level, a.Description) +} diff --git a/vendor/github.com/pion/dtls/v2/pkg/protocol/application_data.go b/vendor/github.com/pion/dtls/v2/pkg/protocol/application_data.go new file mode 100644 index 0000000..e5fd6f5 --- /dev/null +++ b/vendor/github.com/pion/dtls/v2/pkg/protocol/application_data.go @@ -0,0 +1,26 @@ +package protocol + +// ApplicationData messages are carried by the record layer and are +// fragmented, compressed, and encrypted based on the current connection +// state. The messages are treated as transparent data to the record +// layer. +// https://tools.ietf.org/html/rfc5246#section-10 +type ApplicationData struct { + Data []byte +} + +// ContentType returns the ContentType of this content +func (a ApplicationData) ContentType() ContentType { + return ContentTypeApplicationData +} + +// Marshal encodes the ApplicationData to binary +func (a *ApplicationData) Marshal() ([]byte, error) { + return append([]byte{}, a.Data...), nil +} + +// Unmarshal populates the ApplicationData from binary +func (a *ApplicationData) Unmarshal(data []byte) error { + a.Data = append([]byte{}, data...) + return nil +} diff --git a/vendor/github.com/pion/dtls/v2/pkg/protocol/change_cipher_spec.go b/vendor/github.com/pion/dtls/v2/pkg/protocol/change_cipher_spec.go new file mode 100644 index 0000000..67b0052 --- /dev/null +++ b/vendor/github.com/pion/dtls/v2/pkg/protocol/change_cipher_spec.go @@ -0,0 +1,28 @@ +package protocol + +// ChangeCipherSpec protocol exists to signal transitions in +// ciphering strategies. The protocol consists of a single message, +// which is encrypted and compressed under the current (not the pending) +// connection state. The message consists of a single byte of value 1. +// https://tools.ietf.org/html/rfc5246#section-7.1 +type ChangeCipherSpec struct { +} + +// ContentType returns the ContentType of this content +func (c ChangeCipherSpec) ContentType() ContentType { + return ContentTypeChangeCipherSpec +} + +// Marshal encodes the ChangeCipherSpec to binary +func (c *ChangeCipherSpec) Marshal() ([]byte, error) { + return []byte{0x01}, nil +} + +// Unmarshal populates the ChangeCipherSpec from binary +func (c *ChangeCipherSpec) Unmarshal(data []byte) error { + if len(data) == 1 && data[0] == 0x01 { + return nil + } + + return errInvalidCipherSpec +} diff --git a/vendor/github.com/pion/dtls/v2/pkg/protocol/compression_method.go b/vendor/github.com/pion/dtls/v2/pkg/protocol/compression_method.go new file mode 100644 index 0000000..678e816 --- /dev/null +++ b/vendor/github.com/pion/dtls/v2/pkg/protocol/compression_method.go @@ -0,0 +1,48 @@ +package protocol + +// CompressionMethodID is the ID for a CompressionMethod +type CompressionMethodID byte + +const ( + compressionMethodNull CompressionMethodID = 0 +) + +// CompressionMethod represents a TLS Compression Method +type CompressionMethod struct { + ID CompressionMethodID +} + +// CompressionMethods returns all supported CompressionMethods +func CompressionMethods() map[CompressionMethodID]*CompressionMethod { + return map[CompressionMethodID]*CompressionMethod{ + compressionMethodNull: {ID: compressionMethodNull}, + } +} + +// DecodeCompressionMethods the given compression methods +func DecodeCompressionMethods(buf []byte) ([]*CompressionMethod, error) { + if len(buf) < 1 { + return nil, errBufferTooSmall + } + compressionMethodsCount := int(buf[0]) + c := []*CompressionMethod{} + for i := 0; i < compressionMethodsCount; i++ { + if len(buf) <= i+1 { + return nil, errBufferTooSmall + } + id := CompressionMethodID(buf[i+1]) + if compressionMethod, ok := CompressionMethods()[id]; ok { + c = append(c, compressionMethod) + } + } + return c, nil +} + +// EncodeCompressionMethods the given compression methods +func EncodeCompressionMethods(c []*CompressionMethod) []byte { + out := []byte{byte(len(c))} + for i := len(c); i > 0; i-- { + out = append(out, byte(c[i-1].ID)) + } + return out +} diff --git a/vendor/github.com/pion/dtls/v2/pkg/protocol/content.go b/vendor/github.com/pion/dtls/v2/pkg/protocol/content.go new file mode 100644 index 0000000..47e5c96 --- /dev/null +++ b/vendor/github.com/pion/dtls/v2/pkg/protocol/content.go @@ -0,0 +1,21 @@ +package protocol + +// ContentType represents the IANA Registered ContentTypes +// +// https://tools.ietf.org/html/rfc4346#section-6.2.1 +type ContentType uint8 + +// ContentType enums +const ( + ContentTypeChangeCipherSpec ContentType = 20 + ContentTypeAlert ContentType = 21 + ContentTypeHandshake ContentType = 22 + ContentTypeApplicationData ContentType = 23 +) + +// Content is the top level distinguisher for a DTLS Datagram +type Content interface { + ContentType() ContentType + Marshal() ([]byte, error) + Unmarshal(data []byte) error +} diff --git a/vendor/github.com/pion/dtls/v2/pkg/protocol/errors.go b/vendor/github.com/pion/dtls/v2/pkg/protocol/errors.go new file mode 100644 index 0000000..e52014a --- /dev/null +++ b/vendor/github.com/pion/dtls/v2/pkg/protocol/errors.go @@ -0,0 +1,104 @@ +package protocol + +import ( + "errors" + "fmt" + "net" +) + +var ( + errBufferTooSmall = &TemporaryError{Err: errors.New("buffer is too small")} //nolint:goerr113 + errInvalidCipherSpec = &FatalError{Err: errors.New("cipher spec invalid")} //nolint:goerr113 +) + +// FatalError indicates that the DTLS connection is no longer available. +// It is mainly caused by wrong configuration of server or client. +type FatalError struct { + Err error +} + +// InternalError indicates and internal error caused by the implementation, and the DTLS connection is no longer available. +// It is mainly caused by bugs or tried to use unimplemented features. +type InternalError struct { + Err error +} + +// TemporaryError indicates that the DTLS connection is still available, but the request was failed temporary. +type TemporaryError struct { + Err error +} + +// TimeoutError indicates that the request was timed out. +type TimeoutError struct { + Err error +} + +// HandshakeError indicates that the handshake failed. +type HandshakeError struct { + Err error +} + +// Timeout implements net.Error.Timeout() +func (*FatalError) Timeout() bool { return false } + +// Temporary implements net.Error.Temporary() +func (*FatalError) Temporary() bool { return false } + +// Unwrap implements Go1.13 error unwrapper. +func (e *FatalError) Unwrap() error { return e.Err } + +func (e *FatalError) Error() string { return fmt.Sprintf("dtls fatal: %v", e.Err) } + +// Timeout implements net.Error.Timeout() +func (*InternalError) Timeout() bool { return false } + +// Temporary implements net.Error.Temporary() +func (*InternalError) Temporary() bool { return false } + +// Unwrap implements Go1.13 error unwrapper. +func (e *InternalError) Unwrap() error { return e.Err } + +func (e *InternalError) Error() string { return fmt.Sprintf("dtls internal: %v", e.Err) } + +// Timeout implements net.Error.Timeout() +func (*TemporaryError) Timeout() bool { return false } + +// Temporary implements net.Error.Temporary() +func (*TemporaryError) Temporary() bool { return true } + +// Unwrap implements Go1.13 error unwrapper. +func (e *TemporaryError) Unwrap() error { return e.Err } + +func (e *TemporaryError) Error() string { return fmt.Sprintf("dtls temporary: %v", e.Err) } + +// Timeout implements net.Error.Timeout() +func (*TimeoutError) Timeout() bool { return true } + +// Temporary implements net.Error.Temporary() +func (*TimeoutError) Temporary() bool { return true } + +// Unwrap implements Go1.13 error unwrapper. +func (e *TimeoutError) Unwrap() error { return e.Err } + +func (e *TimeoutError) Error() string { return fmt.Sprintf("dtls timeout: %v", e.Err) } + +// Timeout implements net.Error.Timeout() +func (e *HandshakeError) Timeout() bool { + if netErr, ok := e.Err.(net.Error); ok { + return netErr.Timeout() + } + return false +} + +// Temporary implements net.Error.Temporary() +func (e *HandshakeError) Temporary() bool { + if netErr, ok := e.Err.(net.Error); ok { + return netErr.Temporary() + } + return false +} + +// Unwrap implements Go1.13 error unwrapper. +func (e *HandshakeError) Unwrap() error { return e.Err } + +func (e *HandshakeError) Error() string { return fmt.Sprintf("handshake error: %v", e.Err) } diff --git a/vendor/github.com/pion/dtls/v2/pkg/protocol/extension/errors.go b/vendor/github.com/pion/dtls/v2/pkg/protocol/extension/errors.go new file mode 100644 index 0000000..23ed9b2 --- /dev/null +++ b/vendor/github.com/pion/dtls/v2/pkg/protocol/extension/errors.go @@ -0,0 +1,14 @@ +package extension + +import ( + "errors" + + "github.com/pion/dtls/v2/pkg/protocol" +) + +var ( + errBufferTooSmall = &protocol.TemporaryError{Err: errors.New("buffer is too small")} //nolint:goerr113 + errInvalidExtensionType = &protocol.FatalError{Err: errors.New("invalid extension type")} //nolint:goerr113 + errInvalidSNIFormat = &protocol.FatalError{Err: errors.New("invalid server name format")} //nolint:goerr113 + errLengthMismatch = &protocol.InternalError{Err: errors.New("data length and declared length do not match")} //nolint:goerr113 +) diff --git a/vendor/github.com/pion/dtls/v2/pkg/protocol/extension/extension.go b/vendor/github.com/pion/dtls/v2/pkg/protocol/extension/extension.go new file mode 100644 index 0000000..39b1fc8 --- /dev/null +++ b/vendor/github.com/pion/dtls/v2/pkg/protocol/extension/extension.go @@ -0,0 +1,96 @@ +// Package extension implements the extension values in the ClientHello/ServerHello +package extension + +import "encoding/binary" + +// TypeValue is the 2 byte value for a TLS Extension as registered in the IANA +// +// https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml +type TypeValue uint16 + +// TypeValue constants +const ( + ServerNameTypeValue TypeValue = 0 + SupportedEllipticCurvesTypeValue TypeValue = 10 + SupportedPointFormatsTypeValue TypeValue = 11 + SupportedSignatureAlgorithmsTypeValue TypeValue = 13 + UseSRTPTypeValue TypeValue = 14 + UseExtendedMasterSecretTypeValue TypeValue = 23 + RenegotiationInfoTypeValue TypeValue = 65281 +) + +// Extension represents a single TLS extension +type Extension interface { + Marshal() ([]byte, error) + Unmarshal(data []byte) error + TypeValue() TypeValue +} + +// Unmarshal many extensions at once +func Unmarshal(buf []byte) ([]Extension, error) { + switch { + case len(buf) == 0: + return []Extension{}, nil + case len(buf) < 2: + return nil, errBufferTooSmall + } + + declaredLen := binary.BigEndian.Uint16(buf) + if len(buf)-2 != int(declaredLen) { + return nil, errLengthMismatch + } + + extensions := []Extension{} + unmarshalAndAppend := func(data []byte, e Extension) error { + err := e.Unmarshal(data) + if err != nil { + return err + } + extensions = append(extensions, e) + return nil + } + + for offset := 2; offset < len(buf); { + if len(buf) < (offset + 2) { + return nil, errBufferTooSmall + } + var err error + switch TypeValue(binary.BigEndian.Uint16(buf[offset:])) { + case ServerNameTypeValue: + err = unmarshalAndAppend(buf[offset:], &ServerName{}) + case SupportedEllipticCurvesTypeValue: + err = unmarshalAndAppend(buf[offset:], &SupportedEllipticCurves{}) + case UseSRTPTypeValue: + err = unmarshalAndAppend(buf[offset:], &UseSRTP{}) + case UseExtendedMasterSecretTypeValue: + err = unmarshalAndAppend(buf[offset:], &UseExtendedMasterSecret{}) + case RenegotiationInfoTypeValue: + err = unmarshalAndAppend(buf[offset:], &RenegotiationInfo{}) + default: + } + if err != nil { + return nil, err + } + if len(buf) < (offset + 4) { + return nil, errBufferTooSmall + } + extensionLength := binary.BigEndian.Uint16(buf[offset+2:]) + offset += (4 + int(extensionLength)) + } + return extensions, nil +} + +// Marshal many extensions at once +func Marshal(e []Extension) ([]byte, error) { + extensions := []byte{} + for _, e := range e { + raw, err := e.Marshal() + if err != nil { + return nil, err + } + extensions = append(extensions, raw...) + } + out := []byte{0x00, 0x00} + binary.BigEndian.PutUint16(out, uint16(len(extensions))) + return append(out, extensions...), nil +} diff --git a/vendor/github.com/pion/dtls/v2/pkg/protocol/extension/renegotiation_info.go b/vendor/github.com/pion/dtls/v2/pkg/protocol/extension/renegotiation_info.go new file mode 100644 index 0000000..8378c3d --- /dev/null +++ b/vendor/github.com/pion/dtls/v2/pkg/protocol/extension/renegotiation_info.go @@ -0,0 +1,43 @@ +package extension + +import "encoding/binary" + +const ( + renegotiationInfoHeaderSize = 5 +) + +// RenegotiationInfo allows a Client/Server to +// communicate their renegotation support +// +// https://tools.ietf.org/html/rfc5746 +type RenegotiationInfo struct { + RenegotiatedConnection uint8 +} + +// TypeValue returns the extension TypeValue +func (r RenegotiationInfo) TypeValue() TypeValue { + return RenegotiationInfoTypeValue +} + +// Marshal encodes the extension +func (r *RenegotiationInfo) Marshal() ([]byte, error) { + out := make([]byte, renegotiationInfoHeaderSize) + + binary.BigEndian.PutUint16(out, uint16(r.TypeValue())) + binary.BigEndian.PutUint16(out[2:], uint16(1)) // length + out[4] = r.RenegotiatedConnection + return out, nil +} + +// Unmarshal populates the extension from encoded data +func (r *RenegotiationInfo) Unmarshal(data []byte) error { + if len(data) < renegotiationInfoHeaderSize { + return errBufferTooSmall + } else if TypeValue(binary.BigEndian.Uint16(data)) != r.TypeValue() { + return errInvalidExtensionType + } + + r.RenegotiatedConnection = data[4] + + return nil +} diff --git a/vendor/github.com/pion/dtls/v2/pkg/protocol/extension/server_name.go b/vendor/github.com/pion/dtls/v2/pkg/protocol/extension/server_name.go new file mode 100644 index 0000000..a08033f --- /dev/null +++ b/vendor/github.com/pion/dtls/v2/pkg/protocol/extension/server_name.go @@ -0,0 +1,78 @@ +package extension + +import ( + "strings" + + "golang.org/x/crypto/cryptobyte" +) + +const serverNameTypeDNSHostName = 0 + +// ServerName allows the client to inform the server the specific +// name it wishs to contact. Useful if multiple DNS names resolve +// to one IP +// +// https://tools.ietf.org/html/rfc6066#section-3 +type ServerName struct { + ServerName string +} + +// TypeValue returns the extension TypeValue +func (s ServerName) TypeValue() TypeValue { + return ServerNameTypeValue +} + +// Marshal encodes the extension +func (s *ServerName) Marshal() ([]byte, error) { + var b cryptobyte.Builder + b.AddUint16(uint16(s.TypeValue())) + b.AddUint16LengthPrefixed(func(b *cryptobyte.Builder) { + b.AddUint16LengthPrefixed(func(b *cryptobyte.Builder) { + b.AddUint8(serverNameTypeDNSHostName) + b.AddUint16LengthPrefixed(func(b *cryptobyte.Builder) { + b.AddBytes([]byte(s.ServerName)) + }) + }) + }) + return b.Bytes() +} + +// Unmarshal populates the extension from encoded data +func (s *ServerName) Unmarshal(data []byte) error { + val := cryptobyte.String(data) + var extension uint16 + val.ReadUint16(&extension) + if TypeValue(extension) != s.TypeValue() { + return errInvalidExtensionType + } + + var extData cryptobyte.String + val.ReadUint16LengthPrefixed(&extData) + + var nameList cryptobyte.String + if !extData.ReadUint16LengthPrefixed(&nameList) || nameList.Empty() { + return errInvalidSNIFormat + } + for !nameList.Empty() { + var nameType uint8 + var serverName cryptobyte.String + if !nameList.ReadUint8(&nameType) || + !nameList.ReadUint16LengthPrefixed(&serverName) || + serverName.Empty() { + return errInvalidSNIFormat + } + if nameType != serverNameTypeDNSHostName { + continue + } + if len(s.ServerName) != 0 { + // Multiple names of the same name_type are prohibited. + return errInvalidSNIFormat + } + s.ServerName = string(serverName) + // An SNI value may not include a trailing dot. + if strings.HasSuffix(s.ServerName, ".") { + return errInvalidSNIFormat + } + } + return nil +} diff --git a/vendor/github.com/pion/dtls/v2/pkg/protocol/extension/srtp_protection_profile.go b/vendor/github.com/pion/dtls/v2/pkg/protocol/extension/srtp_protection_profile.go new file mode 100644 index 0000000..2c4d1d4 --- /dev/null +++ b/vendor/github.com/pion/dtls/v2/pkg/protocol/extension/srtp_protection_profile.go @@ -0,0 +1,21 @@ +package extension + +// SRTPProtectionProfile defines the parameters and options that are in effect for the SRTP processing +// https://tools.ietf.org/html/rfc5764#section-4.1.2 +type SRTPProtectionProfile uint16 + +const ( + SRTP_AES128_CM_HMAC_SHA1_80 SRTPProtectionProfile = 0x0001 // nolint + SRTP_AES128_CM_HMAC_SHA1_32 SRTPProtectionProfile = 0x0002 // nolint + SRTP_AEAD_AES_128_GCM SRTPProtectionProfile = 0x0007 // nolint + SRTP_AEAD_AES_256_GCM SRTPProtectionProfile = 0x0008 // nolint +) + +func srtpProtectionProfiles() map[SRTPProtectionProfile]bool { + return map[SRTPProtectionProfile]bool{ + SRTP_AES128_CM_HMAC_SHA1_80: true, + SRTP_AES128_CM_HMAC_SHA1_32: true, + SRTP_AEAD_AES_128_GCM: true, + SRTP_AEAD_AES_256_GCM: true, + } +} diff --git a/vendor/github.com/pion/dtls/v2/pkg/protocol/extension/supported_elliptic_curves.go b/vendor/github.com/pion/dtls/v2/pkg/protocol/extension/supported_elliptic_curves.go new file mode 100644 index 0000000..8f077fc --- /dev/null +++ b/vendor/github.com/pion/dtls/v2/pkg/protocol/extension/supported_elliptic_curves.go @@ -0,0 +1,62 @@ +package extension + +import ( + "encoding/binary" + + "github.com/pion/dtls/v2/pkg/crypto/elliptic" +) + +const ( + supportedGroupsHeaderSize = 6 +) + +// SupportedEllipticCurves allows a Client/Server to communicate +// what curves they both support +// +// https://tools.ietf.org/html/rfc8422#section-5.1.1 +type SupportedEllipticCurves struct { + EllipticCurves []elliptic.Curve +} + +// TypeValue returns the extension TypeValue +func (s SupportedEllipticCurves) TypeValue() TypeValue { + return SupportedEllipticCurvesTypeValue +} + +// Marshal encodes the extension +func (s *SupportedEllipticCurves) Marshal() ([]byte, error) { + out := make([]byte, supportedGroupsHeaderSize) + + binary.BigEndian.PutUint16(out, uint16(s.TypeValue())) + binary.BigEndian.PutUint16(out[2:], uint16(2+(len(s.EllipticCurves)*2))) + binary.BigEndian.PutUint16(out[4:], uint16(len(s.EllipticCurves)*2)) + + for _, v := range s.EllipticCurves { + out = append(out, []byte{0x00, 0x00}...) + binary.BigEndian.PutUint16(out[len(out)-2:], uint16(v)) + } + + return out, nil +} + +// Unmarshal populates the extension from encoded data +func (s *SupportedEllipticCurves) Unmarshal(data []byte) error { + if len(data) <= supportedGroupsHeaderSize { + return errBufferTooSmall + } else if TypeValue(binary.BigEndian.Uint16(data)) != s.TypeValue() { + return errInvalidExtensionType + } + + groupCount := int(binary.BigEndian.Uint16(data[4:]) / 2) + if supportedGroupsHeaderSize+(groupCount*2) > len(data) { + return errLengthMismatch + } + + for i := 0; i < groupCount; i++ { + supportedGroupID := elliptic.Curve(binary.BigEndian.Uint16(data[(supportedGroupsHeaderSize + (i * 2)):])) + if _, ok := elliptic.Curves()[supportedGroupID]; ok { + s.EllipticCurves = append(s.EllipticCurves, supportedGroupID) + } + } + return nil +} diff --git a/vendor/github.com/pion/dtls/v2/pkg/protocol/extension/supported_point_formats.go b/vendor/github.com/pion/dtls/v2/pkg/protocol/extension/supported_point_formats.go new file mode 100644 index 0000000..873d078 --- /dev/null +++ b/vendor/github.com/pion/dtls/v2/pkg/protocol/extension/supported_point_formats.go @@ -0,0 +1,62 @@ +package extension + +import ( + "encoding/binary" + + "github.com/pion/dtls/v2/pkg/crypto/elliptic" +) + +const ( + supportedPointFormatsSize = 5 +) + +// SupportedPointFormats allows a Client/Server to negotiate +// the EllipticCurvePointFormats +// +// https://tools.ietf.org/html/rfc4492#section-5.1.2 +type SupportedPointFormats struct { + PointFormats []elliptic.CurvePointFormat +} + +// TypeValue returns the extension TypeValue +func (s SupportedPointFormats) TypeValue() TypeValue { + return SupportedPointFormatsTypeValue +} + +// Marshal encodes the extension +func (s *SupportedPointFormats) Marshal() ([]byte, error) { + out := make([]byte, supportedPointFormatsSize) + + binary.BigEndian.PutUint16(out, uint16(s.TypeValue())) + binary.BigEndian.PutUint16(out[2:], uint16(1+(len(s.PointFormats)))) + out[4] = byte(len(s.PointFormats)) + + for _, v := range s.PointFormats { + out = append(out, byte(v)) + } + return out, nil +} + +// Unmarshal populates the extension from encoded data +func (s *SupportedPointFormats) Unmarshal(data []byte) error { + if len(data) <= supportedPointFormatsSize { + return errBufferTooSmall + } else if TypeValue(binary.BigEndian.Uint16(data)) != s.TypeValue() { + return errInvalidExtensionType + } + + pointFormatCount := int(binary.BigEndian.Uint16(data[4:])) + if supportedGroupsHeaderSize+(pointFormatCount) > len(data) { + return errLengthMismatch + } + + for i := 0; i < pointFormatCount; i++ { + p := elliptic.CurvePointFormat(data[supportedPointFormatsSize+i]) + switch p { + case elliptic.CurvePointFormatUncompressed: + s.PointFormats = append(s.PointFormats, p) + default: + } + } + return nil +} diff --git a/vendor/github.com/pion/dtls/v2/pkg/protocol/extension/supported_signature_algorithms.go b/vendor/github.com/pion/dtls/v2/pkg/protocol/extension/supported_signature_algorithms.go new file mode 100644 index 0000000..ee284f6 --- /dev/null +++ b/vendor/github.com/pion/dtls/v2/pkg/protocol/extension/supported_signature_algorithms.go @@ -0,0 +1,70 @@ +package extension + +import ( + "encoding/binary" + + "github.com/pion/dtls/v2/pkg/crypto/hash" + "github.com/pion/dtls/v2/pkg/crypto/signature" + "github.com/pion/dtls/v2/pkg/crypto/signaturehash" +) + +const ( + supportedSignatureAlgorithmsHeaderSize = 6 +) + +// SupportedSignatureAlgorithms allows a Client/Server to +// negotiate what SignatureHash Algorithms they both support +// +// https://tools.ietf.org/html/rfc5246#section-7.4.1.4.1 +type SupportedSignatureAlgorithms struct { + SignatureHashAlgorithms []signaturehash.Algorithm +} + +// TypeValue returns the extension TypeValue +func (s SupportedSignatureAlgorithms) TypeValue() TypeValue { + return SupportedSignatureAlgorithmsTypeValue +} + +// Marshal encodes the extension +func (s *SupportedSignatureAlgorithms) Marshal() ([]byte, error) { + out := make([]byte, supportedSignatureAlgorithmsHeaderSize) + + binary.BigEndian.PutUint16(out, uint16(s.TypeValue())) + binary.BigEndian.PutUint16(out[2:], uint16(2+(len(s.SignatureHashAlgorithms)*2))) + binary.BigEndian.PutUint16(out[4:], uint16(len(s.SignatureHashAlgorithms)*2)) + for _, v := range s.SignatureHashAlgorithms { + out = append(out, []byte{0x00, 0x00}...) + out[len(out)-2] = byte(v.Hash) + out[len(out)-1] = byte(v.Signature) + } + + return out, nil +} + +// Unmarshal populates the extension from encoded data +func (s *SupportedSignatureAlgorithms) Unmarshal(data []byte) error { + if len(data) <= supportedSignatureAlgorithmsHeaderSize { + return errBufferTooSmall + } else if TypeValue(binary.BigEndian.Uint16(data)) != s.TypeValue() { + return errInvalidExtensionType + } + + algorithmCount := int(binary.BigEndian.Uint16(data[4:]) / 2) + if supportedSignatureAlgorithmsHeaderSize+(algorithmCount*2) > len(data) { + return errLengthMismatch + } + for i := 0; i < algorithmCount; i++ { + supportedHashAlgorithm := hash.Algorithm(data[supportedSignatureAlgorithmsHeaderSize+(i*2)]) + supportedSignatureAlgorithm := signature.Algorithm(data[supportedSignatureAlgorithmsHeaderSize+(i*2)+1]) + if _, ok := hash.Algorithms()[supportedHashAlgorithm]; ok { + if _, ok := signature.Algorithms()[supportedSignatureAlgorithm]; ok { + s.SignatureHashAlgorithms = append(s.SignatureHashAlgorithms, signaturehash.Algorithm{ + Hash: supportedHashAlgorithm, + Signature: supportedSignatureAlgorithm, + }) + } + } + } + + return nil +} diff --git a/vendor/github.com/pion/dtls/v2/pkg/protocol/extension/use_master_secret.go b/vendor/github.com/pion/dtls/v2/pkg/protocol/extension/use_master_secret.go new file mode 100644 index 0000000..04ddc95 --- /dev/null +++ b/vendor/github.com/pion/dtls/v2/pkg/protocol/extension/use_master_secret.go @@ -0,0 +1,45 @@ +package extension + +import "encoding/binary" + +const ( + useExtendedMasterSecretHeaderSize = 4 +) + +// UseExtendedMasterSecret defines a TLS extension that contextually binds the +// master secret to a log of the full handshake that computes it, thus +// preventing MITM attacks. +type UseExtendedMasterSecret struct { + Supported bool +} + +// TypeValue returns the extension TypeValue +func (u UseExtendedMasterSecret) TypeValue() TypeValue { + return UseExtendedMasterSecretTypeValue +} + +// Marshal encodes the extension +func (u *UseExtendedMasterSecret) Marshal() ([]byte, error) { + if !u.Supported { + return []byte{}, nil + } + + out := make([]byte, useExtendedMasterSecretHeaderSize) + + binary.BigEndian.PutUint16(out, uint16(u.TypeValue())) + binary.BigEndian.PutUint16(out[2:], uint16(0)) // length + return out, nil +} + +// Unmarshal populates the extension from encoded data +func (u *UseExtendedMasterSecret) Unmarshal(data []byte) error { + if len(data) < useExtendedMasterSecretHeaderSize { + return errBufferTooSmall + } else if TypeValue(binary.BigEndian.Uint16(data)) != u.TypeValue() { + return errInvalidExtensionType + } + + u.Supported = true + + return nil +} diff --git a/vendor/github.com/pion/dtls/v2/pkg/protocol/extension/use_srtp.go b/vendor/github.com/pion/dtls/v2/pkg/protocol/extension/use_srtp.go new file mode 100644 index 0000000..729fa3a --- /dev/null +++ b/vendor/github.com/pion/dtls/v2/pkg/protocol/extension/use_srtp.go @@ -0,0 +1,59 @@ +package extension + +import "encoding/binary" + +const ( + useSRTPHeaderSize = 6 +) + +// UseSRTP allows a Client/Server to negotiate what SRTPProtectionProfiles +// they both support +// +// https://tools.ietf.org/html/rfc8422 +type UseSRTP struct { + ProtectionProfiles []SRTPProtectionProfile +} + +// TypeValue returns the extension TypeValue +func (u UseSRTP) TypeValue() TypeValue { + return UseSRTPTypeValue +} + +// Marshal encodes the extension +func (u *UseSRTP) Marshal() ([]byte, error) { + out := make([]byte, useSRTPHeaderSize) + + binary.BigEndian.PutUint16(out, uint16(u.TypeValue())) + binary.BigEndian.PutUint16(out[2:], uint16(2+(len(u.ProtectionProfiles)*2)+ /* MKI Length */ 1)) + binary.BigEndian.PutUint16(out[4:], uint16(len(u.ProtectionProfiles)*2)) + + for _, v := range u.ProtectionProfiles { + out = append(out, []byte{0x00, 0x00}...) + binary.BigEndian.PutUint16(out[len(out)-2:], uint16(v)) + } + + out = append(out, 0x00) /* MKI Length */ + return out, nil +} + +// Unmarshal populates the extension from encoded data +func (u *UseSRTP) Unmarshal(data []byte) error { + if len(data) <= useSRTPHeaderSize { + return errBufferTooSmall + } else if TypeValue(binary.BigEndian.Uint16(data)) != u.TypeValue() { + return errInvalidExtensionType + } + + profileCount := int(binary.BigEndian.Uint16(data[4:]) / 2) + if supportedGroupsHeaderSize+(profileCount*2) > len(data) { + return errLengthMismatch + } + + for i := 0; i < profileCount; i++ { + supportedProfile := SRTPProtectionProfile(binary.BigEndian.Uint16(data[(useSRTPHeaderSize + (i * 2)):])) + if _, ok := srtpProtectionProfiles()[supportedProfile]; ok { + u.ProtectionProfiles = append(u.ProtectionProfiles, supportedProfile) + } + } + return nil +} diff --git a/vendor/github.com/pion/dtls/v2/pkg/protocol/handshake/cipher_suite.go b/vendor/github.com/pion/dtls/v2/pkg/protocol/handshake/cipher_suite.go new file mode 100644 index 0000000..e8fbdea --- /dev/null +++ b/vendor/github.com/pion/dtls/v2/pkg/protocol/handshake/cipher_suite.go @@ -0,0 +1,29 @@ +package handshake + +import "encoding/binary" + +func decodeCipherSuiteIDs(buf []byte) ([]uint16, error) { + if len(buf) < 2 { + return nil, errBufferTooSmall + } + cipherSuitesCount := int(binary.BigEndian.Uint16(buf[0:])) / 2 + rtrn := make([]uint16, cipherSuitesCount) + for i := 0; i < cipherSuitesCount; i++ { + if len(buf) < (i*2 + 4) { + return nil, errBufferTooSmall + } + + rtrn[i] = binary.BigEndian.Uint16(buf[(i*2)+2:]) + } + return rtrn, nil +} + +func encodeCipherSuiteIDs(cipherSuiteIDs []uint16) []byte { + out := []byte{0x00, 0x00} + binary.BigEndian.PutUint16(out[len(out)-2:], uint16(len(cipherSuiteIDs)*2)) + for _, id := range cipherSuiteIDs { + out = append(out, []byte{0x00, 0x00}...) + binary.BigEndian.PutUint16(out[len(out)-2:], id) + } + return out +} diff --git a/vendor/github.com/pion/dtls/v2/pkg/protocol/handshake/errors.go b/vendor/github.com/pion/dtls/v2/pkg/protocol/handshake/errors.go new file mode 100644 index 0000000..ac77c04 --- /dev/null +++ b/vendor/github.com/pion/dtls/v2/pkg/protocol/handshake/errors.go @@ -0,0 +1,25 @@ +package handshake + +import ( + "errors" + + "github.com/pion/dtls/v2/pkg/protocol" +) + +// Typed errors +var ( + errUnableToMarshalFragmented = &protocol.InternalError{Err: errors.New("unable to marshal fragmented handshakes")} //nolint:goerr113 + errHandshakeMessageUnset = &protocol.InternalError{Err: errors.New("handshake message unset, unable to marshal")} //nolint:goerr113 + errBufferTooSmall = &protocol.TemporaryError{Err: errors.New("buffer is too small")} //nolint:goerr113 + errLengthMismatch = &protocol.InternalError{Err: errors.New("data length and declared length do not match")} //nolint:goerr113 + errInvalidClientKeyExchange = &protocol.FatalError{Err: errors.New("unable to determine if ClientKeyExchange is a public key or PSK Identity")} //nolint:goerr113 + errInvalidHashAlgorithm = &protocol.FatalError{Err: errors.New("invalid hash algorithm")} //nolint:goerr113 + errInvalidSignatureAlgorithm = &protocol.FatalError{Err: errors.New("invalid signature algorithm")} //nolint:goerr113 + errCookieTooLong = &protocol.FatalError{Err: errors.New("cookie must not be longer then 255 bytes")} //nolint:goerr113 + errInvalidEllipticCurveType = &protocol.FatalError{Err: errors.New("invalid or unknown elliptic curve type")} //nolint:goerr113 + errInvalidNamedCurve = &protocol.FatalError{Err: errors.New("invalid named curve")} //nolint:goerr113 + errCipherSuiteUnset = &protocol.FatalError{Err: errors.New("server hello can not be created without a cipher suite")} //nolint:goerr113 + errCompressionMethodUnset = &protocol.FatalError{Err: errors.New("server hello can not be created without a compression method")} //nolint:goerr113 + errInvalidCompressionMethod = &protocol.FatalError{Err: errors.New("invalid or unknown compression method")} //nolint:goerr113 + errNotImplemented = &protocol.InternalError{Err: errors.New("feature has not been implemented yet")} //nolint:goerr113 +) diff --git a/vendor/github.com/pion/dtls/v2/pkg/protocol/handshake/handshake.go b/vendor/github.com/pion/dtls/v2/pkg/protocol/handshake/handshake.go new file mode 100644 index 0000000..4aa493e --- /dev/null +++ b/vendor/github.com/pion/dtls/v2/pkg/protocol/handshake/handshake.go @@ -0,0 +1,145 @@ +// Package handshake provides the DTLS wire protocol for handshakes +package handshake + +import ( + "github.com/pion/dtls/v2/internal/util" + "github.com/pion/dtls/v2/pkg/protocol" +) + +// Type is the unique identifier for each handshake message +// https://tools.ietf.org/html/rfc5246#section-7.4 +type Type uint8 + +// Types of DTLS Handshake messages we know about +const ( + TypeHelloRequest Type = 0 + TypeClientHello Type = 1 + TypeServerHello Type = 2 + TypeHelloVerifyRequest Type = 3 + TypeCertificate Type = 11 + TypeServerKeyExchange Type = 12 + TypeCertificateRequest Type = 13 + TypeServerHelloDone Type = 14 + TypeCertificateVerify Type = 15 + TypeClientKeyExchange Type = 16 + TypeFinished Type = 20 +) + +// String returns the string representation of this type +func (t Type) String() string { + switch t { + case TypeHelloRequest: + return "HelloRequest" + case TypeClientHello: + return "ClientHello" + case TypeServerHello: + return "ServerHello" + case TypeHelloVerifyRequest: + return "HelloVerifyRequest" + case TypeCertificate: + return "TypeCertificate" + case TypeServerKeyExchange: + return "ServerKeyExchange" + case TypeCertificateRequest: + return "CertificateRequest" + case TypeServerHelloDone: + return "ServerHelloDone" + case TypeCertificateVerify: + return "CertificateVerify" + case TypeClientKeyExchange: + return "ClientKeyExchange" + case TypeFinished: + return "Finished" + } + return "" +} + +// Message is the body of a Handshake datagram +type Message interface { + Marshal() ([]byte, error) + Unmarshal(data []byte) error + + Type() Type +} + +// Handshake protocol is responsible for selecting a cipher spec and +// generating a master secret, which together comprise the primary +// cryptographic parameters associated with a secure session. The +// handshake protocol can also optionally authenticate parties who have +// certificates signed by a trusted certificate authority. +// https://tools.ietf.org/html/rfc5246#section-7.3 +type Handshake struct { + Header Header + Message Message +} + +// ContentType returns what kind of content this message is carying +func (h Handshake) ContentType() protocol.ContentType { + return protocol.ContentTypeHandshake +} + +// Marshal encodes a handshake into a binary message +func (h *Handshake) Marshal() ([]byte, error) { + if h.Message == nil { + return nil, errHandshakeMessageUnset + } else if h.Header.FragmentOffset != 0 { + return nil, errUnableToMarshalFragmented + } + + msg, err := h.Message.Marshal() + if err != nil { + return nil, err + } + + h.Header.Length = uint32(len(msg)) + h.Header.FragmentLength = h.Header.Length + h.Header.Type = h.Message.Type() + header, err := h.Header.Marshal() + if err != nil { + return nil, err + } + + return append(header, msg...), nil +} + +// Unmarshal decodes a handshake from a binary message +func (h *Handshake) Unmarshal(data []byte) error { + if err := h.Header.Unmarshal(data); err != nil { + return err + } + + reportedLen := util.BigEndianUint24(data[1:]) + if uint32(len(data)-HeaderLength) != reportedLen { + return errLengthMismatch + } else if reportedLen != h.Header.FragmentLength { + return errLengthMismatch + } + + switch Type(data[0]) { + case TypeHelloRequest: + return errNotImplemented + case TypeClientHello: + h.Message = &MessageClientHello{} + case TypeHelloVerifyRequest: + h.Message = &MessageHelloVerifyRequest{} + case TypeServerHello: + h.Message = &MessageServerHello{} + case TypeCertificate: + h.Message = &MessageCertificate{} + case TypeServerKeyExchange: + h.Message = &MessageServerKeyExchange{} + case TypeCertificateRequest: + h.Message = &MessageCertificateRequest{} + case TypeServerHelloDone: + h.Message = &MessageServerHelloDone{} + case TypeClientKeyExchange: + h.Message = &MessageClientKeyExchange{} + case TypeFinished: + h.Message = &MessageFinished{} + case TypeCertificateVerify: + h.Message = &MessageCertificateVerify{} + default: + return errNotImplemented + } + return h.Message.Unmarshal(data[HeaderLength:]) +} diff --git a/vendor/github.com/pion/dtls/v2/pkg/protocol/handshake/header.go b/vendor/github.com/pion/dtls/v2/pkg/protocol/handshake/header.go new file mode 100644 index 0000000..cb6a224 --- /dev/null +++ b/vendor/github.com/pion/dtls/v2/pkg/protocol/handshake/header.go @@ -0,0 +1,50 @@ +package handshake + +import ( + "encoding/binary" + + "github.com/pion/dtls/v2/internal/util" +) + +// HeaderLength msg_len for Handshake messages assumes an extra +// 12 bytes for sequence, fragment and version information vs TLS +const HeaderLength = 12 + +// Header is the static first 12 bytes of each RecordLayer +// of type Handshake. These fields allow us to support message loss, reordering, and +// message fragmentation, +// +// https://tools.ietf.org/html/rfc6347#section-4.2.2 +type Header struct { + Type Type + Length uint32 // uint24 in spec + MessageSequence uint16 + FragmentOffset uint32 // uint24 in spec + FragmentLength uint32 // uint24 in spec +} + +// Marshal encodes the Header +func (h *Header) Marshal() ([]byte, error) { + out := make([]byte, HeaderLength) + + out[0] = byte(h.Type) + util.PutBigEndianUint24(out[1:], h.Length) + binary.BigEndian.PutUint16(out[4:], h.MessageSequence) + util.PutBigEndianUint24(out[6:], h.FragmentOffset) + util.PutBigEndianUint24(out[9:], h.FragmentLength) + return out, nil +} + +// Unmarshal populates the header from encoded data +func (h *Header) Unmarshal(data []byte) error { + if len(data) < HeaderLength { + return errBufferTooSmall + } + + h.Type = Type(data[0]) + h.Length = util.BigEndianUint24(data[1:]) + h.MessageSequence = binary.BigEndian.Uint16(data[4:]) + h.FragmentOffset = util.BigEndianUint24(data[6:]) + h.FragmentLength = util.BigEndianUint24(data[9:]) + return nil +} diff --git a/vendor/github.com/pion/dtls/v2/pkg/protocol/handshake/message_certificate.go b/vendor/github.com/pion/dtls/v2/pkg/protocol/handshake/message_certificate.go new file mode 100644 index 0000000..05fb746 --- /dev/null +++ b/vendor/github.com/pion/dtls/v2/pkg/protocol/handshake/message_certificate.go @@ -0,0 +1,66 @@ +package handshake + +import ( + "github.com/pion/dtls/v2/internal/util" +) + +// MessageCertificate is a DTLS Handshake Message +// it can contain either a Client or Server Certificate +// +// https://tools.ietf.org/html/rfc5246#section-7.4.2 +type MessageCertificate struct { + Certificate [][]byte +} + +// Type returns the Handshake Type +func (m MessageCertificate) Type() Type { + return TypeCertificate +} + +const ( + handshakeMessageCertificateLengthFieldSize = 3 +) + +// Marshal encodes the Handshake +func (m *MessageCertificate) Marshal() ([]byte, error) { + out := make([]byte, handshakeMessageCertificateLengthFieldSize) + + for _, r := range m.Certificate { + // Certificate Length + out = append(out, make([]byte, handshakeMessageCertificateLengthFieldSize)...) + util.PutBigEndianUint24(out[len(out)-handshakeMessageCertificateLengthFieldSize:], uint32(len(r))) + + // Certificate body + out = append(out, append([]byte{}, r...)...) + } + + // Total Payload Size + util.PutBigEndianUint24(out[0:], uint32(len(out[handshakeMessageCertificateLengthFieldSize:]))) + return out, nil +} + +// Unmarshal populates the message from encoded data +func (m *MessageCertificate) Unmarshal(data []byte) error { + if len(data) < handshakeMessageCertificateLengthFieldSize { + return errBufferTooSmall + } + + if certificateBodyLen := int(util.BigEndianUint24(data)); certificateBodyLen+handshakeMessageCertificateLengthFieldSize != len(data) { + return errLengthMismatch + } + + offset := handshakeMessageCertificateLengthFieldSize + for offset < len(data) { + certificateLen := int(util.BigEndianUint24(data[offset:])) + offset += handshakeMessageCertificateLengthFieldSize + + if offset+certificateLen > len(data) { + return errLengthMismatch + } + + m.Certificate = append(m.Certificate, append([]byte{}, data[offset:offset+certificateLen]...)) + offset += certificateLen + } + + return nil +} diff --git a/vendor/github.com/pion/dtls/v2/pkg/protocol/handshake/message_certificate_request.go b/vendor/github.com/pion/dtls/v2/pkg/protocol/handshake/message_certificate_request.go new file mode 100644 index 0000000..e711f39 --- /dev/null +++ b/vendor/github.com/pion/dtls/v2/pkg/protocol/handshake/message_certificate_request.go @@ -0,0 +1,100 @@ +package handshake + +import ( + "encoding/binary" + + "github.com/pion/dtls/v2/pkg/crypto/clientcertificate" + "github.com/pion/dtls/v2/pkg/crypto/hash" + "github.com/pion/dtls/v2/pkg/crypto/signature" + "github.com/pion/dtls/v2/pkg/crypto/signaturehash" +) + +/* +MessageCertificateRequest is so a non-anonymous server can optionally +request a certificate from the client, if appropriate for the selected cipher +suite. This message, if sent, will immediately follow the ServerKeyExchange +message (if it is sent; otherwise, this message follows the +server's Certificate message). + +https://tools.ietf.org/html/rfc5246#section-7.4.4 +*/ +type MessageCertificateRequest struct { + CertificateTypes []clientcertificate.Type + SignatureHashAlgorithms []signaturehash.Algorithm +} + +const ( + messageCertificateRequestMinLength = 5 +) + +// Type returns the Handshake Type +func (m MessageCertificateRequest) Type() Type { + return TypeCertificateRequest +} + +// Marshal encodes the Handshake +func (m *MessageCertificateRequest) Marshal() ([]byte, error) { + out := []byte{byte(len(m.CertificateTypes))} + for _, v := range m.CertificateTypes { + out = append(out, byte(v)) + } + + out = append(out, []byte{0x00, 0x00}...) + binary.BigEndian.PutUint16(out[len(out)-2:], uint16(len(m.SignatureHashAlgorithms)*2)) + for _, v := range m.SignatureHashAlgorithms { + out = append(out, byte(v.Hash)) + out = append(out, byte(v.Signature)) + } + + out = append(out, []byte{0x00, 0x00}...) // Distinguished Names Length + return out, nil +} + +// Unmarshal populates the message from encoded data +func (m *MessageCertificateRequest) Unmarshal(data []byte) error { + if len(data) < messageCertificateRequestMinLength { + return errBufferTooSmall + } + + offset := 0 + certificateTypesLength := int(data[0]) + offset++ + + if (offset + certificateTypesLength) > len(data) { + return errBufferTooSmall + } + + for i := 0; i < certificateTypesLength; i++ { + certType := clientcertificate.Type(data[offset+i]) + if _, ok := clientcertificate.Types()[certType]; ok { + m.CertificateTypes = append(m.CertificateTypes, certType) + } + } + offset += certificateTypesLength + if len(data) < offset+2 { + return errBufferTooSmall + } + signatureHashAlgorithmsLength := int(binary.BigEndian.Uint16(data[offset:])) + offset += 2 + + if (offset + signatureHashAlgorithmsLength) > len(data) { + return errBufferTooSmall + } + + for i := 0; i < signatureHashAlgorithmsLength; i += 2 { + if len(data) < (offset + i + 2) { + return errBufferTooSmall + } + h := hash.Algorithm(data[offset+i]) + s := signature.Algorithm(data[offset+i+1]) + + if _, ok := hash.Algorithms()[h]; !ok { + continue + } else if _, ok := signature.Algorithms()[s]; !ok { + continue + } + m.SignatureHashAlgorithms = append(m.SignatureHashAlgorithms, signaturehash.Algorithm{Signature: s, Hash: h}) + } + + return nil +} diff --git a/vendor/github.com/pion/dtls/v2/pkg/protocol/handshake/message_certificate_verify.go b/vendor/github.com/pion/dtls/v2/pkg/protocol/handshake/message_certificate_verify.go new file mode 100644 index 0000000..fb5e463 --- /dev/null +++ b/vendor/github.com/pion/dtls/v2/pkg/protocol/handshake/message_certificate_verify.go @@ -0,0 +1,61 @@ +package handshake + +import ( + "encoding/binary" + + "github.com/pion/dtls/v2/pkg/crypto/hash" + "github.com/pion/dtls/v2/pkg/crypto/signature" +) + +// MessageCertificateVerify provide explicit verification of a +// client certificate. +// +// https://tools.ietf.org/html/rfc5246#section-7.4.8 +type MessageCertificateVerify struct { + HashAlgorithm hash.Algorithm + SignatureAlgorithm signature.Algorithm + Signature []byte +} + +const handshakeMessageCertificateVerifyMinLength = 4 + +// Type returns the Handshake Type +func (m MessageCertificateVerify) Type() Type { + return TypeCertificateVerify +} + +// Marshal encodes the Handshake +func (m *MessageCertificateVerify) Marshal() ([]byte, error) { + out := make([]byte, 1+1+2+len(m.Signature)) + + out[0] = byte(m.HashAlgorithm) + out[1] = byte(m.SignatureAlgorithm) + binary.BigEndian.PutUint16(out[2:], uint16(len(m.Signature))) + copy(out[4:], m.Signature) + return out, nil +} + +// Unmarshal populates the message from encoded data +func (m *MessageCertificateVerify) Unmarshal(data []byte) error { + if len(data) < handshakeMessageCertificateVerifyMinLength { + return errBufferTooSmall + } + + m.HashAlgorithm = hash.Algorithm(data[0]) + if _, ok := hash.Algorithms()[m.HashAlgorithm]; !ok { + return errInvalidHashAlgorithm + } + + m.SignatureAlgorithm = signature.Algorithm(data[1]) + if _, ok := signature.Algorithms()[m.SignatureAlgorithm]; !ok { + return errInvalidSignatureAlgorithm + } + + signatureLength := int(binary.BigEndian.Uint16(data[2:])) + if (signatureLength + 4) != len(data) { + return errBufferTooSmall + } + + m.Signature = append([]byte{}, data[4:]...) + return nil +} diff --git a/vendor/github.com/pion/dtls/v2/pkg/protocol/handshake/message_client_hello.go b/vendor/github.com/pion/dtls/v2/pkg/protocol/handshake/message_client_hello.go new file mode 100644 index 0000000..cb8a100 --- /dev/null +++ b/vendor/github.com/pion/dtls/v2/pkg/protocol/handshake/message_client_hello.go @@ -0,0 +1,125 @@ +package handshake + +import ( + "encoding/binary" + + "github.com/pion/dtls/v2/pkg/protocol" + "github.com/pion/dtls/v2/pkg/protocol/extension" +) + +/* +MessageClientHello is for when a client first connects to a server it is +required to send the client hello as its first message. The client can also send a +client hello in response to a hello request or on its own +initiative in order to renegotiate the security parameters in an +existing connection. +*/ +type MessageClientHello struct { + Version protocol.Version + Random Random + Cookie []byte + + CipherSuiteIDs []uint16 + CompressionMethods []*protocol.CompressionMethod + Extensions []extension.Extension +} + +const handshakeMessageClientHelloVariableWidthStart = 34 + +// Type returns the Handshake Type +func (m MessageClientHello) Type() Type { + return TypeClientHello +} + +// Marshal encodes the Handshake +func (m *MessageClientHello) Marshal() ([]byte, error) { + if len(m.Cookie) > 255 { + return nil, errCookieTooLong + } + + out := make([]byte, handshakeMessageClientHelloVariableWidthStart) + out[0] = m.Version.Major + out[1] = m.Version.Minor + + rand := m.Random.MarshalFixed() + copy(out[2:], rand[:]) + + out = append(out, 0x00) // SessionID + + out = append(out, byte(len(m.Cookie))) + out = append(out, m.Cookie...) + out = append(out, encodeCipherSuiteIDs(m.CipherSuiteIDs)...) + out = append(out, protocol.EncodeCompressionMethods(m.CompressionMethods)...) + + extensions, err := extension.Marshal(m.Extensions) + if err != nil { + return nil, err + } + + return append(out, extensions...), nil +} + +// Unmarshal populates the message from encoded data +func (m *MessageClientHello) Unmarshal(data []byte) error { + if len(data) < 2+RandomLength { + return errBufferTooSmall + } + + m.Version.Major = data[0] + m.Version.Minor = data[1] + + var random [RandomLength]byte + copy(random[:], data[2:]) + m.Random.UnmarshalFixed(random) + + // rest of packet has variable width sections + currOffset := handshakeMessageClientHelloVariableWidthStart + currOffset += int(data[currOffset]) + 1 // SessionID + + currOffset++ + if len(data) <= currOffset { + return errBufferTooSmall + } + n := int(data[currOffset-1]) + if len(data) <= currOffset+n { + return errBufferTooSmall + } + m.Cookie = append([]byte{}, data[currOffset:currOffset+n]...) + currOffset += len(m.Cookie) + + // Cipher Suites + if len(data) < currOffset { + return errBufferTooSmall + } + cipherSuiteIDs, err := decodeCipherSuiteIDs(data[currOffset:]) + if err != nil { + return err + } + m.CipherSuiteIDs = cipherSuiteIDs + if len(data) < currOffset+2 { + return errBufferTooSmall + } + currOffset += int(binary.BigEndian.Uint16(data[currOffset:])) + 2 + + // Compression Methods + if len(data) < currOffset { + return errBufferTooSmall + } + compressionMethods, err := protocol.DecodeCompressionMethods(data[currOffset:]) + if err != nil { + return err + } + m.CompressionMethods = compressionMethods + if len(data) < currOffset { + return errBufferTooSmall + } + currOffset += int(data[currOffset]) + 1 + + // Extensions + extensions, err := extension.Unmarshal(data[currOffset:]) + if err != nil { + return err + } + m.Extensions = extensions + return nil +} diff --git a/vendor/github.com/pion/dtls/v2/pkg/protocol/handshake/message_client_key_exchange.go b/vendor/github.com/pion/dtls/v2/pkg/protocol/handshake/message_client_key_exchange.go new file mode 100644 index 0000000..f8fc369 --- /dev/null +++ b/vendor/github.com/pion/dtls/v2/pkg/protocol/handshake/message_client_key_exchange.go @@ -0,0 +1,56 @@ +package handshake + +import ( + "encoding/binary" +) + +// MessageClientKeyExchange is a DTLS Handshake Message +// With this message, the premaster secret is set, either by direct +// transmission of the RSA-encrypted secret or by the transmission of +// Diffie-Hellman parameters that will allow each side to agree upon +// the same premaster secret. +// +// https://tools.ietf.org/html/rfc5246#section-7.4.7 +type MessageClientKeyExchange struct { + IdentityHint []byte + PublicKey []byte +} + +// Type returns the Handshake Type +func (m MessageClientKeyExchange) Type() Type { + return TypeClientKeyExchange +} + +// Marshal encodes the Handshake +func (m *MessageClientKeyExchange) Marshal() ([]byte, error) { + switch { + case (m.IdentityHint != nil && m.PublicKey != nil) || (m.IdentityHint == nil && m.PublicKey == nil): + return nil, errInvalidClientKeyExchange + case m.PublicKey != nil: + return append([]byte{byte(len(m.PublicKey))}, m.PublicKey...), nil + default: + out := append([]byte{0x00, 0x00}, m.IdentityHint...) + binary.BigEndian.PutUint16(out, uint16(len(out)-2)) + return out, nil + } +} + +// Unmarshal populates the message from encoded data +func (m *MessageClientKeyExchange) Unmarshal(data []byte) error { + if len(data) < 2 { + return errBufferTooSmall + } + + // If parsed as PSK return early and only populate PSK Identity Hint + if pskLength := binary.BigEndian.Uint16(data); len(data) == int(pskLength+2) { + m.IdentityHint = append([]byte{}, data[2:]...) + return nil + } + + if publicKeyLength := int(data[0]); len(data) != publicKeyLength+1 { + return errBufferTooSmall + } + + m.PublicKey = append([]byte{}, data[1:]...) + return nil +} diff --git a/vendor/github.com/pion/dtls/v2/pkg/protocol/handshake/message_finished.go b/vendor/github.com/pion/dtls/v2/pkg/protocol/handshake/message_finished.go new file mode 100644 index 0000000..c65d42a --- /dev/null +++ b/vendor/github.com/pion/dtls/v2/pkg/protocol/handshake/message_finished.go @@ -0,0 +1,27 @@ +package handshake + +// MessageFinished is a DTLS Handshake Message +// this message is the first one protected with the just +// negotiated algorithms, keys, and secrets. Recipients of Finished +// messages MUST verify that the contents are correct. +// +// https://tools.ietf.org/html/rfc5246#section-7.4.9 +type MessageFinished struct { + VerifyData []byte +} + +// Type returns the Handshake Type +func (m MessageFinished) Type() Type { + return TypeFinished +} + +// Marshal encodes the Handshake +func (m *MessageFinished) Marshal() ([]byte, error) { + return append([]byte{}, m.VerifyData...), nil +} + +// Unmarshal populates the message from encoded data +func (m *MessageFinished) Unmarshal(data []byte) error { + m.VerifyData = append([]byte{}, data...) + return nil +} diff --git a/vendor/github.com/pion/dtls/v2/pkg/protocol/handshake/message_hello_verify_request.go b/vendor/github.com/pion/dtls/v2/pkg/protocol/handshake/message_hello_verify_request.go new file mode 100644 index 0000000..ef834dc --- /dev/null +++ b/vendor/github.com/pion/dtls/v2/pkg/protocol/handshake/message_hello_verify_request.go @@ -0,0 +1,62 @@ +package handshake + +import ( + "github.com/pion/dtls/v2/pkg/protocol" +) + +// MessageHelloVerifyRequest is as follows: +// +// struct { +// ProtocolVersion server_version; +// opaque cookie<0..2^8-1>; +// } HelloVerifyRequest; +// +// The HelloVerifyRequest message type is hello_verify_request(3). +// +// When the client sends its ClientHello message to the server, the server +// MAY respond with a HelloVerifyRequest message. This message contains +// a stateless cookie generated using the technique of [PHOTURIS]. The +// client MUST retransmit the ClientHello with the cookie added. +// +// https://tools.ietf.org/html/rfc6347#section-4.2.1 +type MessageHelloVerifyRequest struct { + Version protocol.Version + Cookie []byte +} + +// Type returns the Handshake Type +func (m MessageHelloVerifyRequest) Type() Type { + return TypeHelloVerifyRequest +} + +// Marshal encodes the Handshake +func (m *MessageHelloVerifyRequest) Marshal() ([]byte, error) { + if len(m.Cookie) > 255 { + return nil, errCookieTooLong + } + + out := make([]byte, 3+len(m.Cookie)) + out[0] = m.Version.Major + out[1] = m.Version.Minor + out[2] = byte(len(m.Cookie)) + copy(out[3:], m.Cookie) + + return out, nil +} + +// Unmarshal populates the message from encoded data +func (m *MessageHelloVerifyRequest) Unmarshal(data []byte) error { + if len(data) < 3 { + return errBufferTooSmall + } + m.Version.Major = data[0] + m.Version.Minor = data[1] + cookieLength := data[2] + if len(data) < (int(cookieLength) + 3) { + return errBufferTooSmall + } + m.Cookie = make([]byte, cookieLength) + + copy(m.Cookie, data[3:3+cookieLength]) + return nil +} diff --git a/vendor/github.com/pion/dtls/v2/pkg/protocol/handshake/message_server_hello.go b/vendor/github.com/pion/dtls/v2/pkg/protocol/handshake/message_server_hello.go new file mode 100644 index 0000000..c4b181f --- /dev/null +++ b/vendor/github.com/pion/dtls/v2/pkg/protocol/handshake/message_server_hello.go @@ -0,0 +1,106 @@ +package handshake + +import ( + "encoding/binary" + + "github.com/pion/dtls/v2/pkg/protocol" + "github.com/pion/dtls/v2/pkg/protocol/extension" +) + +// MessageServerHello is sent in response to a ClientHello +// message when it was able to find an acceptable set of algorithms. +// If it cannot find such a match, it will respond with a handshake +// failure alert. +// +// https://tools.ietf.org/html/rfc5246#section-7.4.1.3 +type MessageServerHello struct { + Version protocol.Version + Random Random + + CipherSuiteID *uint16 + CompressionMethod *protocol.CompressionMethod + Extensions []extension.Extension +} + +const messageServerHelloVariableWidthStart = 2 + RandomLength + +// Type returns the Handshake Type +func (m MessageServerHello) Type() Type { + return TypeServerHello +} + +// Marshal encodes the Handshake +func (m *MessageServerHello) Marshal() ([]byte, error) { + if m.CipherSuiteID == nil { + return nil, errCipherSuiteUnset + } else if m.CompressionMethod == nil { + return nil, errCompressionMethodUnset + } + + out := make([]byte, messageServerHelloVariableWidthStart) + out[0] = m.Version.Major + out[1] = m.Version.Minor + + rand := m.Random.MarshalFixed() + copy(out[2:], rand[:]) + + out = append(out, 0x00) // SessionID + + out = append(out, []byte{0x00, 0x00}...) + binary.BigEndian.PutUint16(out[len(out)-2:], *m.CipherSuiteID) + + out = append(out, byte(m.CompressionMethod.ID)) + + extensions, err := extension.Marshal(m.Extensions) + if err != nil { + return nil, err + } + + return append(out, extensions...), nil +} + +// Unmarshal populates the message from encoded data +func (m *MessageServerHello) Unmarshal(data []byte) error { + if len(data) < 2+RandomLength { + return errBufferTooSmall + } + + m.Version.Major = data[0] + m.Version.Minor = data[1] + + var random [RandomLength]byte + copy(random[:], data[2:]) + m.Random.UnmarshalFixed(random) + + currOffset := messageServerHelloVariableWidthStart + currOffset += int(data[currOffset]) + 1 // SessionID + if len(data) < (currOffset + 2) { + return errBufferTooSmall + } + + m.CipherSuiteID = new(uint16) + *m.CipherSuiteID = binary.BigEndian.Uint16(data[currOffset:]) + currOffset += 2 + + if len(data) < currOffset { + return errBufferTooSmall + } + if compressionMethod, ok := protocol.CompressionMethods()[protocol.CompressionMethodID(data[currOffset])]; ok { + m.CompressionMethod = compressionMethod + currOffset++ + } else { + return errInvalidCompressionMethod + } + + if len(data) <= currOffset { + m.Extensions = []extension.Extension{} + return nil + } + + extensions, err := extension.Unmarshal(data[currOffset:]) + if err != nil { + return err + } + m.Extensions = extensions + return nil +} diff --git a/vendor/github.com/pion/dtls/v2/pkg/protocol/handshake/message_server_hello_done.go b/vendor/github.com/pion/dtls/v2/pkg/protocol/handshake/message_server_hello_done.go new file mode 100644 index 0000000..a004802 --- /dev/null +++ b/vendor/github.com/pion/dtls/v2/pkg/protocol/handshake/message_server_hello_done.go @@ -0,0 +1,22 @@ +package handshake + +// MessageServerHelloDone is final non-encrypted message from server +// this communicates server has sent all its handshake messages and next +// should be MessageFinished +type MessageServerHelloDone struct { +} + +// Type returns the Handshake Type +func (m MessageServerHelloDone) Type() Type { + return TypeServerHelloDone +} + +// Marshal encodes the Handshake +func (m *MessageServerHelloDone) Marshal() ([]byte, error) { + return []byte{}, nil +} + +// Unmarshal populates the message from encoded data +func (m *MessageServerHelloDone) Unmarshal(data []byte) error { + return nil +} diff --git a/vendor/github.com/pion/dtls/v2/pkg/protocol/handshake/message_server_key_exchange.go b/vendor/github.com/pion/dtls/v2/pkg/protocol/handshake/message_server_key_exchange.go new file mode 100644 index 0000000..4148fe0 --- /dev/null +++ b/vendor/github.com/pion/dtls/v2/pkg/protocol/handshake/message_server_key_exchange.go @@ -0,0 +1,119 @@ +package handshake + +import ( + "encoding/binary" + + "github.com/pion/dtls/v2/pkg/crypto/elliptic" + "github.com/pion/dtls/v2/pkg/crypto/hash" + "github.com/pion/dtls/v2/pkg/crypto/signature" +) + +// MessageServerKeyExchange supports ECDH and PSK +type MessageServerKeyExchange struct { + IdentityHint []byte + + EllipticCurveType elliptic.CurveType + NamedCurve elliptic.Curve + PublicKey []byte + HashAlgorithm hash.Algorithm + SignatureAlgorithm signature.Algorithm + Signature []byte +} + +// Type returns the Handshake Type +func (m MessageServerKeyExchange) Type() Type { + return TypeServerKeyExchange +} + +// Marshal encodes the Handshake +func (m *MessageServerKeyExchange) Marshal() ([]byte, error) { + if m.IdentityHint != nil { + out := append([]byte{0x00, 0x00}, m.IdentityHint...) + binary.BigEndian.PutUint16(out, uint16(len(out)-2)) + return out, nil + } + + out := []byte{byte(m.EllipticCurveType), 0x00, 0x00} + binary.BigEndian.PutUint16(out[1:], uint16(m.NamedCurve)) + + out = append(out, byte(len(m.PublicKey))) + out = append(out, m.PublicKey...) + + if m.HashAlgorithm == hash.None && m.SignatureAlgorithm == signature.Anonymous && len(m.Signature) == 0 { + return out, nil + } + + out = append(out, []byte{byte(m.HashAlgorithm), byte(m.SignatureAlgorithm), 0x00, 0x00}...) + binary.BigEndian.PutUint16(out[len(out)-2:], uint16(len(m.Signature))) + out = append(out, m.Signature...) + + return out, nil +} + +// Unmarshal populates the message from encoded data +func (m *MessageServerKeyExchange) Unmarshal(data []byte) error { + if len(data) < 2 { + return errBufferTooSmall + } + + // If parsed as PSK return early and only populate PSK Identity Hint + if pskLength := binary.BigEndian.Uint16(data); len(data) == int(pskLength+2) { + m.IdentityHint = append([]byte{}, data[2:]...) + return nil + } + + if _, ok := elliptic.CurveTypes()[elliptic.CurveType(data[0])]; ok { + m.EllipticCurveType = elliptic.CurveType(data[0]) + } else { + return errInvalidEllipticCurveType + } + + if len(data[1:]) < 2 { + return errBufferTooSmall + } + m.NamedCurve = elliptic.Curve(binary.BigEndian.Uint16(data[1:3])) + if _, ok := elliptic.Curves()[m.NamedCurve]; !ok { + return errInvalidNamedCurve + } + if len(data) < 4 { + return errBufferTooSmall + } + + publicKeyLength := int(data[3]) + offset := 4 + publicKeyLength + if len(data) < offset { + return errBufferTooSmall + } + m.PublicKey = append([]byte{}, data[4:offset]...) + + // Anon connection doesn't contains hashAlgorithm, signatureAlgorithm, signature + if len(data) == offset { + return nil + } else if len(data) <= offset { + return errBufferTooSmall + } + + m.HashAlgorithm = hash.Algorithm(data[offset]) + if _, ok := hash.Algorithms()[m.HashAlgorithm]; !ok { + return errInvalidHashAlgorithm + } + offset++ + if len(data) <= offset { + return errBufferTooSmall + } + m.SignatureAlgorithm = signature.Algorithm(data[offset]) + if _, ok := signature.Algorithms()[m.SignatureAlgorithm]; !ok { + return errInvalidSignatureAlgorithm + } + offset++ + if len(data) < offset+2 { + return errBufferTooSmall + } + signatureLength := int(binary.BigEndian.Uint16(data[offset:])) + offset += 2 + if len(data) < offset+signatureLength { + return errBufferTooSmall + } + m.Signature = append([]byte{}, data[offset:offset+signatureLength]...) + return nil +} diff --git a/vendor/github.com/pion/dtls/v2/pkg/protocol/handshake/random.go b/vendor/github.com/pion/dtls/v2/pkg/protocol/handshake/random.go new file mode 100644 index 0000000..0ade936 --- /dev/null +++ b/vendor/github.com/pion/dtls/v2/pkg/protocol/handshake/random.go @@ -0,0 +1,49 @@ +package handshake + +import ( + "crypto/rand" + "encoding/binary" + "time" +) + +// Consts for Random in Handshake +const ( + RandomBytesLength = 28 + RandomLength = RandomBytesLength + 4 +) + +// Random value that is used in ClientHello and ServerHello +// +// https://tools.ietf.org/html/rfc4346#section-7.4.1.2 +type Random struct { + GMTUnixTime time.Time + RandomBytes [RandomBytesLength]byte +} + +// MarshalFixed encodes the Handshake +func (r *Random) MarshalFixed() [RandomLength]byte { + var out [RandomLength]byte + + binary.BigEndian.PutUint32(out[0:], uint32(r.GMTUnixTime.Unix())) + copy(out[4:], r.RandomBytes[:]) + + return out +} + +// UnmarshalFixed populates the message from encoded data +func (r *Random) UnmarshalFixed(data [RandomLength]byte) { + r.GMTUnixTime = time.Unix(int64(binary.BigEndian.Uint32(data[0:])), 0) + copy(r.RandomBytes[:], data[4:]) +} + +// Populate fills the handshakeRandom with random values +// may be called multiple times +func (r *Random) Populate() error { + r.GMTUnixTime = time.Now() + + tmp := make([]byte, RandomBytesLength) + _, err := rand.Read(tmp) + copy(r.RandomBytes[:], tmp) + + return err +} diff --git a/vendor/github.com/pion/dtls/v2/pkg/protocol/recordlayer/errors.go b/vendor/github.com/pion/dtls/v2/pkg/protocol/recordlayer/errors.go new file mode 100644 index 0000000..7033d40 --- /dev/null +++ b/vendor/github.com/pion/dtls/v2/pkg/protocol/recordlayer/errors.go @@ -0,0 +1,16 @@ +// Package recordlayer implements the TLS Record Layer https://tools.ietf.org/html/rfc5246#section-6 +package recordlayer + +import ( + "errors" + + "github.com/pion/dtls/v2/pkg/protocol" +) + +var ( + errBufferTooSmall = &protocol.TemporaryError{Err: errors.New("buffer is too small")} //nolint:goerr113 + errInvalidPacketLength = &protocol.TemporaryError{Err: errors.New("packet length and declared length do not match")} //nolint:goerr113 + errSequenceNumberOverflow = &protocol.InternalError{Err: errors.New("sequence number overflow")} //nolint:goerr113 + errUnsupportedProtocolVersion = &protocol.FatalError{Err: errors.New("unsupported protocol version")} //nolint:goerr113 + errInvalidContentType = &protocol.TemporaryError{Err: errors.New("invalid content type")} //nolint:goerr113 +) diff --git a/vendor/github.com/pion/dtls/v2/pkg/protocol/recordlayer/header.go b/vendor/github.com/pion/dtls/v2/pkg/protocol/recordlayer/header.go new file mode 100644 index 0000000..65047d7 --- /dev/null +++ b/vendor/github.com/pion/dtls/v2/pkg/protocol/recordlayer/header.go @@ -0,0 +1,61 @@ +package recordlayer + +import ( + "encoding/binary" + + "github.com/pion/dtls/v2/internal/util" + "github.com/pion/dtls/v2/pkg/protocol" +) + +// Header implements a TLS RecordLayer header +type Header struct { + ContentType protocol.ContentType + ContentLen uint16 + Version protocol.Version + Epoch uint16 + SequenceNumber uint64 // uint48 in spec +} + +// RecordLayer enums +const ( + HeaderSize = 13 + MaxSequenceNumber = 0x0000FFFFFFFFFFFF +) + +// Marshal encodes a TLS RecordLayer Header to binary +func (h *Header) Marshal() ([]byte, error) { + if h.SequenceNumber > MaxSequenceNumber { + return nil, errSequenceNumberOverflow + } + + out := make([]byte, HeaderSize) + out[0] = byte(h.ContentType) + out[1] = h.Version.Major + out[2] = h.Version.Minor + binary.BigEndian.PutUint16(out[3:], h.Epoch) + util.PutBigEndianUint48(out[5:], h.SequenceNumber) + binary.BigEndian.PutUint16(out[HeaderSize-2:], h.ContentLen) + return out, nil +} + +// Unmarshal populates a TLS RecordLayer Header from binary +func (h *Header) Unmarshal(data []byte) error { + if len(data) < HeaderSize { + return errBufferTooSmall + } + h.ContentType = protocol.ContentType(data[0]) + h.Version.Major = data[1] + h.Version.Minor = data[2] + h.Epoch = binary.BigEndian.Uint16(data[3:]) + + // SequenceNumber is stored as uint48, make into uint64 + seqCopy := make([]byte, 8) + copy(seqCopy[2:], data[5:11]) + h.SequenceNumber = binary.BigEndian.Uint64(seqCopy) + + if !h.Version.Equal(protocol.Version1_0) && !h.Version.Equal(protocol.Version1_2) { + return errUnsupportedProtocolVersion + } + + return nil +} diff --git a/vendor/github.com/pion/dtls/v2/pkg/protocol/recordlayer/recordlayer.go b/vendor/github.com/pion/dtls/v2/pkg/protocol/recordlayer/recordlayer.go new file mode 100644 index 0000000..67e5a72 --- /dev/null +++ b/vendor/github.com/pion/dtls/v2/pkg/protocol/recordlayer/recordlayer.go @@ -0,0 +1,99 @@ +package recordlayer + +import ( + "encoding/binary" + + "github.com/pion/dtls/v2/pkg/protocol" + "github.com/pion/dtls/v2/pkg/protocol/alert" + "github.com/pion/dtls/v2/pkg/protocol/handshake" +) + +// RecordLayer which handles all data transport. +// The record layer is assumed to sit directly on top of some +// reliable transport such as TCP. The record layer can carry four types of content: +// +// 1. Handshake messages—used for algorithm negotiation and key establishment. +// 2. ChangeCipherSpec messages—really part of the handshake but technically a separate kind of message. +// 3. Alert messages—used to signal that errors have occurred +// 4. Application layer data +// +// The DTLS record layer is extremely similar to that of TLS 1.1. The +// only change is the inclusion of an explicit sequence number in the +// record. This sequence number allows the recipient to correctly +// verify the TLS MAC. +// +// https://tools.ietf.org/html/rfc4347#section-4.1 +type RecordLayer struct { + Header Header + Content protocol.Content +} + +// Marshal encodes the RecordLayer to binary +func (r *RecordLayer) Marshal() ([]byte, error) { + contentRaw, err := r.Content.Marshal() + if err != nil { + return nil, err + } + + r.Header.ContentLen = uint16(len(contentRaw)) + r.Header.ContentType = r.Content.ContentType() + + headerRaw, err := r.Header.Marshal() + if err != nil { + return nil, err + } + + return append(headerRaw, contentRaw...), nil +} + +// Unmarshal populates the RecordLayer from binary +func (r *RecordLayer) Unmarshal(data []byte) error { + if len(data) < HeaderSize { + return errBufferTooSmall + } + if err := r.Header.Unmarshal(data); err != nil { + return err + } + + switch protocol.ContentType(data[0]) { + case protocol.ContentTypeChangeCipherSpec: + r.Content = &protocol.ChangeCipherSpec{} + case protocol.ContentTypeAlert: + r.Content = &alert.Alert{} + case protocol.ContentTypeHandshake: + r.Content = &handshake.Handshake{} + case protocol.ContentTypeApplicationData: + r.Content = &protocol.ApplicationData{} + default: + return errInvalidContentType + } + + return r.Content.Unmarshal(data[HeaderSize:]) +} + +// UnpackDatagram extracts all RecordLayer messages from a single datagram. +// Note that as with TLS, multiple handshake messages may be placed in +// the same DTLS record, provided that there is room and that they are +// part of the same flight. Thus, there are two acceptable ways to pack +// two DTLS messages into the same datagram: in the same record or in +// separate records. +// https://tools.ietf.org/html/rfc6347#section-4.2.3 +func UnpackDatagram(buf []byte) ([][]byte, error) { + out := [][]byte{} + + for offset := 0; len(buf) != offset; { + if len(buf)-offset <= HeaderSize { + return nil, errInvalidPacketLength + } + + pktLen := (HeaderSize + int(binary.BigEndian.Uint16(buf[offset+11:]))) + if offset+pktLen > len(buf) { + return nil, errInvalidPacketLength + } + + out = append(out, buf[offset:offset+pktLen]) + offset += pktLen + } + + return out, nil +} diff --git a/vendor/github.com/pion/dtls/v2/pkg/protocol/version.go b/vendor/github.com/pion/dtls/v2/pkg/protocol/version.go new file mode 100644 index 0000000..d5ddb1d --- /dev/null +++ b/vendor/github.com/pion/dtls/v2/pkg/protocol/version.go @@ -0,0 +1,21 @@ +// Package protocol provides the DTLS wire format +package protocol + +// Version enums +var ( + Version1_0 = Version{Major: 0xfe, Minor: 0xff} //nolint:gochecknoglobals + Version1_2 = Version{Major: 0xfe, Minor: 0xfd} //nolint:gochecknoglobals +) + +// Version is the minor/major value in the RecordLayer +// and ClientHello/ServerHello +// +// https://tools.ietf.org/html/rfc4346#section-6.2.1 +type Version struct { + Major, Minor uint8 +} + +// Equal determines if two protocol versions are equal +func (v Version) Equal(x Version) bool { + return v.Major == x.Major && v.Minor == x.Minor +} diff --git a/vendor/github.com/pion/dtls/v2/renovate.json b/vendor/github.com/pion/dtls/v2/renovate.json new file mode 100644 index 0000000..4400fd9 --- /dev/null +++ b/vendor/github.com/pion/dtls/v2/renovate.json @@ -0,0 +1,15 @@ +{ + "extends": [ + "config:base" + ], + "postUpdateOptions": [ + "gomodTidy" + ], + "commitBody": "Generated by renovateBot", + "packageRules": [ + { + "packagePatterns": ["^golang.org/x/"], + "schedule": ["on the first day of the month"] + } + ] +} diff --git a/vendor/github.com/pion/dtls/v2/resume.go b/vendor/github.com/pion/dtls/v2/resume.go new file mode 100644 index 0000000..40e55e4 --- /dev/null +++ b/vendor/github.com/pion/dtls/v2/resume.go @@ -0,0 +1,19 @@ +package dtls + +import ( + "context" + "net" +) + +// Resume imports an already established dtls connection using a specific dtls state +func Resume(state *State, conn net.Conn, config *Config) (*Conn, error) { + if err := state.initCipherSuite(); err != nil { + return nil, err + } + c, err := createConn(context.Background(), conn, config, state.isClient, state) + if err != nil { + return nil, err + } + + return c, nil +} diff --git a/vendor/github.com/pion/dtls/v2/srtp_protection_profile.go b/vendor/github.com/pion/dtls/v2/srtp_protection_profile.go new file mode 100644 index 0000000..1c3ae55 --- /dev/null +++ b/vendor/github.com/pion/dtls/v2/srtp_protection_profile.go @@ -0,0 +1,14 @@ +package dtls + +import "github.com/pion/dtls/v2/pkg/protocol/extension" + +// SRTPProtectionProfile defines the parameters and options that are in effect for the SRTP processing +// https://tools.ietf.org/html/rfc5764#section-4.1.2 +type SRTPProtectionProfile = extension.SRTPProtectionProfile + +const ( + SRTP_AES128_CM_HMAC_SHA1_80 SRTPProtectionProfile = extension.SRTP_AES128_CM_HMAC_SHA1_80 // nolint + SRTP_AES128_CM_HMAC_SHA1_32 SRTPProtectionProfile = extension.SRTP_AES128_CM_HMAC_SHA1_32 // nolint + SRTP_AEAD_AES_128_GCM SRTPProtectionProfile = extension.SRTP_AEAD_AES_128_GCM // nolint + SRTP_AEAD_AES_256_GCM SRTPProtectionProfile = extension.SRTP_AEAD_AES_256_GCM // nolint +) diff --git a/vendor/github.com/pion/dtls/v2/state.go b/vendor/github.com/pion/dtls/v2/state.go new file mode 100644 index 0000000..53fca6a --- /dev/null +++ b/vendor/github.com/pion/dtls/v2/state.go @@ -0,0 +1,194 @@ +package dtls + +import ( + "bytes" + "encoding/gob" + "sync/atomic" + + "github.com/pion/dtls/v2/pkg/crypto/elliptic" + "github.com/pion/dtls/v2/pkg/crypto/prf" + "github.com/pion/dtls/v2/pkg/protocol/handshake" + "github.com/pion/transport/replaydetector" +) + +// State holds the dtls connection state and implements both encoding.BinaryMarshaler and encoding.BinaryUnmarshaler +type State struct { + localEpoch, remoteEpoch atomic.Value + localSequenceNumber []uint64 // uint48 + localRandom, remoteRandom handshake.Random + masterSecret []byte + cipherSuite CipherSuite // nil if a cipherSuite hasn't been chosen + + srtpProtectionProfile SRTPProtectionProfile // Negotiated SRTPProtectionProfile + PeerCertificates [][]byte + IdentityHint []byte + + isClient bool + + preMasterSecret []byte + extendedMasterSecret bool + + namedCurve elliptic.Curve + localKeypair *elliptic.Keypair + cookie []byte + handshakeSendSequence int + handshakeRecvSequence int + serverName string + remoteRequestedCertificate bool // Did we get a CertificateRequest + localCertificatesVerify []byte // cache CertificateVerify + localVerifyData []byte // cached VerifyData + localKeySignature []byte // cached keySignature + peerCertificatesVerified bool + + replayDetector []replaydetector.ReplayDetector +} + +type serializedState struct { + LocalEpoch uint16 + RemoteEpoch uint16 + LocalRandom [handshake.RandomLength]byte + RemoteRandom [handshake.RandomLength]byte + CipherSuiteID uint16 + MasterSecret []byte + SequenceNumber uint64 + SRTPProtectionProfile uint16 + PeerCertificates [][]byte + IdentityHint []byte + IsClient bool +} + +func (s *State) clone() *State { + serialized := s.serialize() + state := &State{} + state.deserialize(*serialized) + + return state +} + +func (s *State) serialize() *serializedState { + // Marshal random values + localRnd := s.localRandom.MarshalFixed() + remoteRnd := s.remoteRandom.MarshalFixed() + + epoch := s.localEpoch.Load().(uint16) + return &serializedState{ + LocalEpoch: epoch, + RemoteEpoch: s.remoteEpoch.Load().(uint16), + CipherSuiteID: uint16(s.cipherSuite.ID()), + MasterSecret: s.masterSecret, + SequenceNumber: atomic.LoadUint64(&s.localSequenceNumber[epoch]), + LocalRandom: localRnd, + RemoteRandom: remoteRnd, + SRTPProtectionProfile: uint16(s.srtpProtectionProfile), + PeerCertificates: s.PeerCertificates, + IdentityHint: s.IdentityHint, + IsClient: s.isClient, + } +} + +func (s *State) deserialize(serialized serializedState) { + // Set epoch values + epoch := serialized.LocalEpoch + s.localEpoch.Store(serialized.LocalEpoch) + s.remoteEpoch.Store(serialized.RemoteEpoch) + + for len(s.localSequenceNumber) <= int(epoch) { + s.localSequenceNumber = append(s.localSequenceNumber, uint64(0)) + } + + // Set random values + localRandom := &handshake.Random{} + localRandom.UnmarshalFixed(serialized.LocalRandom) + s.localRandom = *localRandom + + remoteRandom := &handshake.Random{} + remoteRandom.UnmarshalFixed(serialized.RemoteRandom) + s.remoteRandom = *remoteRandom + + s.isClient = serialized.IsClient + + // Set master secret + s.masterSecret = serialized.MasterSecret + + // Set cipher suite + s.cipherSuite = cipherSuiteForID(CipherSuiteID(serialized.CipherSuiteID), nil) + + atomic.StoreUint64(&s.localSequenceNumber[epoch], serialized.SequenceNumber) + s.srtpProtectionProfile = SRTPProtectionProfile(serialized.SRTPProtectionProfile) + + // Set remote certificate + s.PeerCertificates = serialized.PeerCertificates + s.IdentityHint = serialized.IdentityHint +} + +func (s *State) initCipherSuite() error { + if s.cipherSuite.IsInitialized() { + return nil + } + + localRandom := s.localRandom.MarshalFixed() + remoteRandom := s.remoteRandom.MarshalFixed() + + var err error + if s.isClient { + err = s.cipherSuite.Init(s.masterSecret, localRandom[:], remoteRandom[:], true) + } else { + err = s.cipherSuite.Init(s.masterSecret, remoteRandom[:], localRandom[:], false) + } + if err != nil { + return err + } + return nil +} + +// MarshalBinary is a binary.BinaryMarshaler.MarshalBinary implementation +func (s *State) MarshalBinary() ([]byte, error) { + serialized := s.serialize() + + var buf bytes.Buffer + enc := gob.NewEncoder(&buf) + if err := enc.Encode(*serialized); err != nil { + return nil, err + } + return buf.Bytes(), nil +} + +// UnmarshalBinary is a binary.BinaryUnmarshaler.UnmarshalBinary implementation +func (s *State) UnmarshalBinary(data []byte) error { + enc := gob.NewDecoder(bytes.NewBuffer(data)) + var serialized serializedState + if err := enc.Decode(&serialized); err != nil { + return err + } + + s.deserialize(serialized) + if err := s.initCipherSuite(); err != nil { + return err + } + return nil +} + +// ExportKeyingMaterial returns length bytes of exported key material in a new +// slice as defined in RFC 5705. +// This allows protocols to use DTLS for key establishment, but +// then use some of the keying material for their own purposes +func (s *State) ExportKeyingMaterial(label string, context []byte, length int) ([]byte, error) { + if s.localEpoch.Load().(uint16) == 0 { + return nil, errHandshakeInProgress + } else if len(context) != 0 { + return nil, errContextUnsupported + } else if _, ok := invalidKeyingLabels()[label]; ok { + return nil, errReservedExportKeyingMaterial + } + + localRandom := s.localRandom.MarshalFixed() + remoteRandom := s.remoteRandom.MarshalFixed() + + seed := []byte(label) + if s.isClient { + seed = append(append(seed, localRandom[:]...), remoteRandom[:]...) + } else { + seed = append(append(seed, remoteRandom[:]...), localRandom[:]...) + } + return prf.PHash(s.masterSecret, seed, length, s.cipherSuite.HashFunc()) +} diff --git a/vendor/github.com/pion/dtls/v2/util.go b/vendor/github.com/pion/dtls/v2/util.go new file mode 100644 index 0000000..745182d --- /dev/null +++ b/vendor/github.com/pion/dtls/v2/util.go @@ -0,0 +1,38 @@ +package dtls + +func findMatchingSRTPProfile(a, b []SRTPProtectionProfile) (SRTPProtectionProfile, bool) { + for _, aProfile := range a { + for _, bProfile := range b { + if aProfile == bProfile { + return aProfile, true + } + } + } + return 0, false +} + +func findMatchingCipherSuite(a, b []CipherSuite) (CipherSuite, bool) { //nolint + for _, aSuite := range a { + for _, bSuite := range b { + if aSuite.ID() == bSuite.ID() { + return aSuite, true + } + } + } + return nil, false +} + +func splitBytes(bytes []byte, splitLen int) [][]byte { + splitBytes := make([][]byte, 0) + numBytes := len(bytes) + for i := 0; i < numBytes; i += splitLen { + j := i + splitLen + if j > numBytes { + j = numBytes + } + + splitBytes = append(splitBytes, bytes[i:j]) + } + + return splitBytes +} diff --git a/vendor/github.com/pion/ice/v2/.gitignore b/vendor/github.com/pion/ice/v2/.gitignore new file mode 100644 index 0000000..83db74b --- /dev/null +++ b/vendor/github.com/pion/ice/v2/.gitignore @@ -0,0 +1,24 @@ +### JetBrains IDE ### +##################### +.idea/ + +### Emacs Temporary Files ### +############################# +*~ + +### Folders ### +############### +bin/ +vendor/ +node_modules/ + +### Files ### +############# +*.ivf +*.ogg +tags +cover.out +*.sw[poe] +*.wasm +examples/sfu-ws/cert.pem +examples/sfu-ws/key.pem diff --git a/vendor/github.com/pion/ice/v2/.golangci.yml b/vendor/github.com/pion/ice/v2/.golangci.yml new file mode 100644 index 0000000..d6162c9 --- /dev/null +++ b/vendor/github.com/pion/ice/v2/.golangci.yml @@ -0,0 +1,89 @@ +linters-settings: + govet: + check-shadowing: true + misspell: + locale: US + exhaustive: + default-signifies-exhaustive: true + gomodguard: + blocked: + modules: + - github.com/pkg/errors: + recommendations: + - errors + +linters: + enable: + - asciicheck # Simple linter to check that your code does not contain non-ASCII identifiers + - bodyclose # checks whether HTTP response body is closed successfully + - deadcode # Finds unused code + - depguard # Go linter that checks if package imports are in a list of acceptable packages + - dogsled # Checks assignments with too many blank identifiers (e.g. x, _, _, _, := f()) + - dupl # Tool for code clone detection + - errcheck # Errcheck is a program for checking for unchecked errors in go programs. These unchecked errors can be critical bugs in some cases + - exhaustive # check exhaustiveness of enum switch statements + - exportloopref # checks for pointers to enclosing loop variables + - gci # Gci control golang package import order and make it always deterministic. + - gochecknoglobals # Checks that no globals are present in Go code + - gochecknoinits # Checks that no init functions are present in Go code + - gocognit # Computes and checks the cognitive complexity of functions + - goconst # Finds repeated strings that could be replaced by a constant + - gocritic # The most opinionated Go source code linter + - godox # Tool for detection of FIXME, TODO and other comment keywords + - goerr113 # Golang linter to check the errors handling expressions + - gofmt # Gofmt checks whether code was gofmt-ed. By default this tool runs with -s option to check for code simplification + - gofumpt # Gofumpt checks whether code was gofumpt-ed. + - goheader # Checks is file header matches to pattern + - goimports # Goimports does everything that gofmt does. Additionally it checks unused imports + - golint # Golint differs from gofmt. Gofmt reformats Go source code, whereas golint prints out style mistakes + - gomodguard # Allow and block list linter for direct Go module dependencies. This is different from depguard where there are different block types for example version constraints and module recommendations. + - goprintffuncname # Checks that printf-like functions are named with `f` at the end + - gosec # Inspects source code for security problems + - gosimple # Linter for Go source code that specializes in simplifying a code + - govet # Vet examines Go source code and reports suspicious constructs, such as Printf calls whose arguments do not align with the format string + - ineffassign # Detects when assignments to existing variables are not used + - misspell # Finds commonly misspelled English words in comments + - nakedret # Finds naked returns in functions greater than a specified function length + - noctx # noctx finds sending http request without context.Context + - scopelint # Scopelint checks for unpinned variables in go programs + - staticcheck # Staticcheck is a go vet on steroids, applying a ton of static analysis checks + - structcheck # Finds unused struct fields + - stylecheck # Stylecheck is a replacement for golint + - typecheck # Like the front-end of a Go compiler, parses and type-checks Go code + - unconvert # Remove unnecessary type conversions + - unparam # Reports unused function parameters + - unused # Checks Go code for unused constants, variables, functions and types + - varcheck # Finds unused global variables and constants + - whitespace # Tool for detection of leading and trailing whitespace + disable: + - funlen # Tool for detection of long functions + - gocyclo # Computes and checks the cyclomatic complexity of functions + - godot # Check if comments end in a period + - gomnd # An analyzer to detect magic numbers. + - lll # Reports long lines + - maligned # Tool to detect Go structs that would take less memory if their fields were sorted + - nestif # Reports deeply nested if statements + - nlreturn # nlreturn checks for a new line before return and branch statements to increase code clarity + - nolintlint # Reports ill-formed or insufficient nolint directives + - prealloc # Finds slice declarations that could potentially be preallocated + - rowserrcheck # checks whether Err of rows is checked successfully + - sqlclosecheck # Checks that sql.Rows and sql.Stmt are closed. + - testpackage # linter that makes you use a separate _test package + - wsl # Whitespace Linter - Forces you to use empty lines! + +issues: + exclude-use-default: false + exclude-rules: + # Allow complex tests, better to be self contained + - path: _test\.go + linters: + - gocognit + + # Allow complex main function in examples + - path: examples + text: "of func `main` is high" + linters: + - gocognit + +run: + skip-dirs-use-default: false diff --git a/vendor/github.com/pion/ice/v2/LICENSE b/vendor/github.com/pion/ice/v2/LICENSE new file mode 100644 index 0000000..ab60297 --- /dev/null +++ b/vendor/github.com/pion/ice/v2/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/pion/ice/v2/README.md b/vendor/github.com/pion/ice/v2/README.md new file mode 100644 index 0000000..cbb55fd --- /dev/null +++ b/vendor/github.com/pion/ice/v2/README.md @@ -0,0 +1,67 @@ +<h1 align="center"> + <br> + Pion ICE + <br> +</h1> +<h4 align="center">A Go implementation of ICE</h4> +<p align="center"> + <a href="https://pion.ly"><img src="https://img.shields.io/badge/pion-ice-gray.svg?longCache=true&colorB=brightgreen" alt="Pion transport"></a> + <a href="http://gophers.slack.com/messages/pion"><img src="https://img.shields.io/badge/join-us%20on%20slack-gray.svg?longCache=true&logo=slack&colorB=brightgreen" alt="Slack Widget"></a> + <br> + <a href="https://travis-ci.org/pion/ice"><img src="https://travis-ci.org/pion/ice.svg?branch=master" alt="Build Status"></a> + <a href="https://pkg.go.dev/github.com/pion/ice"><img src="https://godoc.org/github.com/pion/ice?status.svg" alt="GoDoc"></a> + <a href="https://codecov.io/gh/pion/ice"><img src="https://codecov.io/gh/pion/ice/branch/master/graph/badge.svg" alt="Coverage Status"></a> + <a href="https://goreportcard.com/report/github.com/pion/ice"><img src="https://goreportcard.com/badge/github.com/pion/ice" alt="Go Report Card"></a> + <a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="License: MIT"></a> +</p> +<br> + +### Roadmap +The library is used as a part of our WebRTC implementation. Please refer to that [roadmap](https://github.com/pion/webrtc/issues/9) to track our major milestones. + +### Community +Pion has an active community on the [Golang Slack](https://invite.slack.golangbridge.org/). Sign up and join the **#pion** channel for discussions and support. You can also use [Pion mailing list](https://groups.google.com/forum/#!forum/pion). + +We are always looking to support **your projects**. Please reach out if you have something to build! + +If you need commercial support or don't want to use public methods you can contact us at [team@pion.ly](mailto:team@pion.ly) + +### Contributing +Check out the **[contributing wiki](https://github.com/pion/webrtc/wiki/Contributing)** to join the group of amazing people making this project possible: + +* [John Bradley](https://github.com/kc5nra) - *Original Author* +* [Sean DuBois](https://github.com/Sean-Der) - *Original Author* +* [Michael MacDonald](https://github.com/mjmac) - *Original Author* +* [Michiel De Backker](https://github.com/backkem) - *Original Author* +* [Konstantin Itskov](https://github.com/trivigy) - *Original Author* +* [Luke Curley](https://github.com/kixelated) +* [Hugo Arregui](https://github.com/hugoArregui) +* [Adam Kiss](https://github.com/masterada) +* [Aleksandr Razumov](https://github.com/ernado) +* [Yutaka Takeda](https://github.com/enobufs) +* [Atsushi Watanabe](https://github.com/at-wat) +* [Robert Eperjesi](https://github.com/epes) +* [Sebastian Waisbrot](https://github.com/seppo0010) +* [Zizheng Tai](https://github.com/ZizhengTai) +* [Aaron France](https://github.com/AeroNotix) +* [Chao Yuan](https://github.com/yuanchao0310) +* [Jason Maldonis](https://github.com/jjmaldonis) +* [Nevio Vesic](https://github.com/0x19) +* [David Hamilton](https://github.com/dihamilton) +* [adwpc](https://github.com/adwpc) +* [Ori Bernstein](https://eigenstate.org) +* [Sam Lancia](https://github.com/nerd2) +* [Lander Noterman](https://github.com/LanderN) +* [BUPTCZQ](https://github.com/buptczq) +* [Henry](https://github.com/cryptix) +* [Jerko Steiner](https://github.com/jeremija) +* [Sidney San Martín](https://github.com/s4y) +* [JooYoung Lim](https://github.com/DevRockstarZ) +* [Kory Miller](https://github.com/korymiller1489) +* [ZHENK](https://github.com/scorpionknifes) +* [Assad Obaid](https://github.com/assadobaid) +* [Antoine Baché](https://github.com/Antonito) +* [Will Forcey](https://github.com/wawesomeNOGUI) + +### License +MIT License - see [LICENSE](LICENSE) for full text diff --git a/vendor/github.com/pion/ice/v2/agent.go b/vendor/github.com/pion/ice/v2/agent.go new file mode 100644 index 0000000..9f1ac6a --- /dev/null +++ b/vendor/github.com/pion/ice/v2/agent.go @@ -0,0 +1,1233 @@ +// Package ice implements the Interactive Connectivity Establishment (ICE) +// protocol defined in rfc5245. +package ice + +import ( + "context" + "net" + "strings" + "sync" + "sync/atomic" + "time" + + "github.com/pion/logging" + "github.com/pion/mdns" + "github.com/pion/stun" + "github.com/pion/transport/packetio" + "github.com/pion/transport/vnet" + "golang.org/x/net/proxy" +) + +type bindingRequest struct { + timestamp time.Time + transactionID [stun.TransactionIDSize]byte + destination net.Addr + isUseCandidate bool +} + +// Agent represents the ICE agent +type Agent struct { + chanTask chan task + afterRunFn []func(ctx context.Context) + muAfterRun sync.Mutex + + onConnectionStateChangeHdlr atomic.Value // func(ConnectionState) + onSelectedCandidatePairChangeHdlr atomic.Value // func(Candidate, Candidate) + onCandidateHdlr atomic.Value // func(Candidate) + + // State owned by the taskLoop + onConnected chan struct{} + onConnectedOnce sync.Once + + // force candidate to be contacted immediately (instead of waiting for task ticker) + forceCandidateContact chan bool + + tieBreaker uint64 + lite bool + + connectionState ConnectionState + gatheringState GatheringState + + mDNSMode MulticastDNSMode + mDNSName string + mDNSConn *mdns.Conn + + muHaveStarted sync.Mutex + startedCh <-chan struct{} + startedFn func() + isControlling bool + + maxBindingRequests uint16 + + hostAcceptanceMinWait time.Duration + srflxAcceptanceMinWait time.Duration + prflxAcceptanceMinWait time.Duration + relayAcceptanceMinWait time.Duration + + portmin uint16 + portmax uint16 + + candidateTypes []CandidateType + + // How long connectivity checks can fail before the ICE Agent + // goes to disconnected + disconnectedTimeout time.Duration + + // How long connectivity checks can fail before the ICE Agent + // goes to failed + failedTimeout time.Duration + + // How often should we send keepalive packets? + // 0 means never + keepaliveInterval time.Duration + + // How often should we run our internal taskLoop to check for state changes when connecting + checkInterval time.Duration + + localUfrag string + localPwd string + localCandidates map[NetworkType][]Candidate + + remoteUfrag string + remotePwd string + remoteCandidates map[NetworkType][]Candidate + + checklist []*candidatePair + selector pairCandidateSelector + + selectedPair atomic.Value // *candidatePair + + urls []*URL + networkTypes []NetworkType + + buffer *packetio.Buffer + + // LRU of outbound Binding request Transaction IDs + pendingBindingRequests []bindingRequest + + // 1:1 D-NAT IP address mapping + extIPMapper *externalIPMapper + + // State for closing + done chan struct{} + err atomicError + + gatherCandidateCancel func() + + chanCandidate chan Candidate + chanCandidatePair chan *candidatePair + chanState chan ConnectionState + + loggerFactory logging.LoggerFactory + log logging.LeveledLogger + + net *vnet.Net + tcpMux TCPMux + + interfaceFilter func(string) bool + + insecureSkipVerify bool + + proxyDialer proxy.Dialer +} + +type task struct { + fn func(context.Context, *Agent) + done chan struct{} +} + +// afterRun registers function to be run after the task. +func (a *Agent) afterRun(f func(context.Context)) { + a.muAfterRun.Lock() + a.afterRunFn = append(a.afterRunFn, f) + a.muAfterRun.Unlock() +} + +func (a *Agent) getAfterRunFn() []func(context.Context) { + a.muAfterRun.Lock() + defer a.muAfterRun.Unlock() + fns := a.afterRunFn + a.afterRunFn = nil + return fns +} + +func (a *Agent) ok() error { + select { + case <-a.done: + return a.getErr() + default: + } + return nil +} + +func (a *Agent) getErr() error { + if err := a.err.Load(); err != nil { + return err + } + return ErrClosed +} + +// Run task in serial. Blocking tasks must be cancelable by context. +func (a *Agent) run(ctx context.Context, t func(context.Context, *Agent)) error { + if err := a.ok(); err != nil { + return err + } + done := make(chan struct{}) + select { + case <-ctx.Done(): + return ctx.Err() + case a.chanTask <- task{t, done}: + <-done + return nil + } +} + +// taskLoop handles registered tasks and agent close. +func (a *Agent) taskLoop() { + after := func() { + for { + // Get and run func registered by afterRun(). + fns := a.getAfterRunFn() + if len(fns) == 0 { + break + } + for _, fn := range fns { + fn(a.context()) + } + } + } + defer func() { + a.deleteAllCandidates() + a.startedFn() + + if err := a.buffer.Close(); err != nil { + a.log.Warnf("failed to close buffer: %v", err) + } + + a.closeMulticastConn() + a.updateConnectionState(ConnectionStateClosed) + + after() + + close(a.chanState) + close(a.chanCandidate) + close(a.chanCandidatePair) + }() + + for { + select { + case <-a.done: + return + case t := <-a.chanTask: + t.fn(a.context(), a) + close(t.done) + after() + } + } +} + +// NewAgent creates a new Agent +func NewAgent(config *AgentConfig) (*Agent, error) { //nolint:gocognit + var err error + if config.PortMax < config.PortMin { + return nil, ErrPort + } + + mDNSName := config.MulticastDNSHostName + if mDNSName == "" { + if mDNSName, err = generateMulticastDNSName(); err != nil { + return nil, err + } + } + + if !strings.HasSuffix(mDNSName, ".local") || len(strings.Split(mDNSName, ".")) != 2 { + return nil, ErrInvalidMulticastDNSHostName + } + + mDNSMode := config.MulticastDNSMode + if mDNSMode == 0 { + mDNSMode = MulticastDNSModeQueryOnly + } + + loggerFactory := config.LoggerFactory + if loggerFactory == nil { + loggerFactory = logging.NewDefaultLoggerFactory() + } + log := loggerFactory.NewLogger("ice") + + var mDNSConn *mdns.Conn + mDNSConn, mDNSMode, err = createMulticastDNS(mDNSMode, mDNSName, log) + // Opportunistic mDNS: If we can't open the connection, that's ok: we + // can continue without it. + if err != nil { + log.Warnf("Failed to initialize mDNS %s: %v", mDNSName, err) + } + closeMDNSConn := func() { + if mDNSConn != nil { + if mdnsCloseErr := mDNSConn.Close(); mdnsCloseErr != nil { + log.Warnf("Failed to close mDNS: %v", mdnsCloseErr) + } + } + } + + startedCtx, startedFn := context.WithCancel(context.Background()) + + a := &Agent{ + chanTask: make(chan task), + chanState: make(chan ConnectionState), + chanCandidate: make(chan Candidate), + chanCandidatePair: make(chan *candidatePair), + tieBreaker: globalMathRandomGenerator.Uint64(), + lite: config.Lite, + gatheringState: GatheringStateNew, + connectionState: ConnectionStateNew, + localCandidates: make(map[NetworkType][]Candidate), + remoteCandidates: make(map[NetworkType][]Candidate), + urls: config.Urls, + networkTypes: config.NetworkTypes, + onConnected: make(chan struct{}), + buffer: packetio.NewBuffer(), + done: make(chan struct{}), + startedCh: startedCtx.Done(), + startedFn: startedFn, + portmin: config.PortMin, + portmax: config.PortMax, + loggerFactory: loggerFactory, + log: log, + net: config.Net, + proxyDialer: config.ProxyDialer, + + mDNSMode: mDNSMode, + mDNSName: mDNSName, + mDNSConn: mDNSConn, + + gatherCandidateCancel: func() {}, + + forceCandidateContact: make(chan bool, 1), + + interfaceFilter: config.InterfaceFilter, + + insecureSkipVerify: config.InsecureSkipVerify, + } + + a.tcpMux = config.TCPMux + if a.tcpMux == nil { + a.tcpMux = newInvalidTCPMux() + } + + if a.net == nil { + a.net = vnet.NewNet(nil) + } else if a.net.IsVirtual() { + a.log.Warn("vnet is enabled") + if a.mDNSMode != MulticastDNSModeDisabled { + a.log.Warn("vnet does not support mDNS yet") + } + } + + config.initWithDefaults(a) + + // Make sure the buffer doesn't grow indefinitely. + // NOTE: We actually won't get anywhere close to this limit. + // SRTP will constantly read from the endpoint and drop packets if it's full. + a.buffer.SetLimitSize(maxBufferSize) + + if a.lite && (len(a.candidateTypes) != 1 || a.candidateTypes[0] != CandidateTypeHost) { + closeMDNSConn() + return nil, ErrLiteUsingNonHostCandidates + } + + if config.Urls != nil && len(config.Urls) > 0 && !containsCandidateType(CandidateTypeServerReflexive, a.candidateTypes) && !containsCandidateType(CandidateTypeRelay, a.candidateTypes) { + closeMDNSConn() + return nil, ErrUselessUrlsProvided + } + + if err = config.initExtIPMapping(a); err != nil { + closeMDNSConn() + return nil, err + } + + go a.taskLoop() + a.startOnConnectionStateChangeRoutine() + + // Restart is also used to initialize the agent for the first time + if err := a.Restart(config.LocalUfrag, config.LocalPwd); err != nil { + closeMDNSConn() + _ = a.Close() + return nil, err + } + + return a, nil +} + +// OnConnectionStateChange sets a handler that is fired when the connection state changes +func (a *Agent) OnConnectionStateChange(f func(ConnectionState)) error { + a.onConnectionStateChangeHdlr.Store(f) + return nil +} + +// OnSelectedCandidatePairChange sets a handler that is fired when the final candidate +// pair is selected +func (a *Agent) OnSelectedCandidatePairChange(f func(Candidate, Candidate)) error { + a.onSelectedCandidatePairChangeHdlr.Store(f) + return nil +} + +// OnCandidate sets a handler that is fired when new candidates gathered. When +// the gathering process complete the last candidate is nil. +func (a *Agent) OnCandidate(f func(Candidate)) error { + a.onCandidateHdlr.Store(f) + return nil +} + +func (a *Agent) onSelectedCandidatePairChange(p *candidatePair) { + if h, ok := a.onSelectedCandidatePairChangeHdlr.Load().(func(Candidate, Candidate)); ok { + h(p.local, p.remote) + } +} + +func (a *Agent) onCandidate(c Candidate) { + if onCandidateHdlr, ok := a.onCandidateHdlr.Load().(func(Candidate)); ok { + onCandidateHdlr(c) + } +} + +func (a *Agent) onConnectionStateChange(s ConnectionState) { + if hdlr, ok := a.onConnectionStateChangeHdlr.Load().(func(ConnectionState)); ok { + hdlr(s) + } +} + +func (a *Agent) startOnConnectionStateChangeRoutine() { + go func() { + for { + // CandidatePair and ConnectionState are usually changed at once. + // Blocking one by the other one causes deadlock. + p, isOpen := <-a.chanCandidatePair + if !isOpen { + return + } + a.onSelectedCandidatePairChange(p) + } + }() + go func() { + for { + select { + case s, isOpen := <-a.chanState: + if !isOpen { + for c := range a.chanCandidate { + a.onCandidate(c) + } + return + } + a.onConnectionStateChange(s) + + case c, isOpen := <-a.chanCandidate: + if !isOpen { + for s := range a.chanState { + a.onConnectionStateChange(s) + } + return + } + a.onCandidate(c) + } + } + }() +} + +func (a *Agent) startConnectivityChecks(isControlling bool, remoteUfrag, remotePwd string) error { + a.muHaveStarted.Lock() + defer a.muHaveStarted.Unlock() + select { + case <-a.startedCh: + return ErrMultipleStart + default: + } + if err := a.SetRemoteCredentials(remoteUfrag, remotePwd); err != nil { + return err + } + + a.log.Debugf("Started agent: isControlling? %t, remoteUfrag: %q, remotePwd: %q", isControlling, remoteUfrag, remotePwd) + + return a.run(a.context(), func(ctx context.Context, agent *Agent) { + agent.isControlling = isControlling + agent.remoteUfrag = remoteUfrag + agent.remotePwd = remotePwd + + if isControlling { + a.selector = &controllingSelector{agent: a, log: a.log} + } else { + a.selector = &controlledSelector{agent: a, log: a.log} + } + + if a.lite { + a.selector = &liteSelector{pairCandidateSelector: a.selector} + } + + a.selector.Start() + a.startedFn() + + agent.updateConnectionState(ConnectionStateChecking) + + a.requestConnectivityCheck() + go a.connectivityChecks() + }) +} + +func (a *Agent) connectivityChecks() { + lastConnectionState := ConnectionState(0) + checkingDuration := time.Time{} + + contact := func() { + if err := a.run(a.context(), func(ctx context.Context, a *Agent) { + defer func() { + lastConnectionState = a.connectionState + }() + + switch a.connectionState { + case ConnectionStateFailed: + // The connection is currently failed so don't send any checks + // In the future it may be restarted though + return + case ConnectionStateChecking: + // We have just entered checking for the first time so update our checking timer + if lastConnectionState != a.connectionState { + checkingDuration = time.Now() + } + + // We have been in checking longer then Disconnect+Failed timeout, set the connection to Failed + if time.Since(checkingDuration) > a.disconnectedTimeout+a.failedTimeout { + a.updateConnectionState(ConnectionStateFailed) + return + } + } + + a.selector.ContactCandidates() + }); err != nil { + a.log.Warnf("taskLoop failed: %v", err) + } + } + + for { + interval := defaultKeepaliveInterval + + updateInterval := func(x time.Duration) { + if x != 0 && (interval == 0 || interval > x) { + interval = x + } + } + + switch lastConnectionState { + case ConnectionStateNew, ConnectionStateChecking: // While connecting, check candidates more frequently + updateInterval(a.checkInterval) + case ConnectionStateConnected, ConnectionStateDisconnected: + updateInterval(a.keepaliveInterval) + default: + } + // Ensure we run our task loop as quickly as the minimum of our various configured timeouts + updateInterval(a.disconnectedTimeout) + updateInterval(a.failedTimeout) + + t := time.NewTimer(interval) + select { + case <-a.forceCandidateContact: + t.Stop() + contact() + case <-t.C: + contact() + case <-a.done: + t.Stop() + return + } + } +} + +func (a *Agent) updateConnectionState(newState ConnectionState) { + if a.connectionState != newState { + // Connection has gone to failed, release all gathered candidates + if newState == ConnectionStateFailed { + a.deleteAllCandidates() + } + + a.log.Infof("Setting new connection state: %s", newState) + a.connectionState = newState + + // Call handler after finishing current task since we may be holding the agent lock + // and the handler may also require it + a.afterRun(func(ctx context.Context) { + a.chanState <- newState + }) + } +} + +func (a *Agent) setSelectedPair(p *candidatePair) { + a.log.Tracef("Set selected candidate pair: %s", p) + + if p == nil { + var nilPair *candidatePair + a.selectedPair.Store(nilPair) + return + } + + p.nominated = true + a.selectedPair.Store(p) + + a.updateConnectionState(ConnectionStateConnected) + + // Notify when the selected pair changes + if p != nil { + a.afterRun(func(ctx context.Context) { + select { + case a.chanCandidatePair <- p: + case <-ctx.Done(): + } + }) + } + + // Signal connected + a.onConnectedOnce.Do(func() { close(a.onConnected) }) +} + +func (a *Agent) pingAllCandidates() { + a.log.Trace("pinging all candidates") + + if len(a.checklist) == 0 { + a.log.Warn("pingAllCandidates called with no candidate pairs. Connection is not possible yet.") + } + + for _, p := range a.checklist { + if p.state == CandidatePairStateWaiting { + p.state = CandidatePairStateInProgress + } else if p.state != CandidatePairStateInProgress { + continue + } + + if p.bindingRequestCount > a.maxBindingRequests { + a.log.Tracef("max requests reached for pair %s, marking it as failed\n", p) + p.state = CandidatePairStateFailed + } else { + a.selector.PingCandidate(p.local, p.remote) + p.bindingRequestCount++ + } + } +} + +func (a *Agent) getBestAvailableCandidatePair() *candidatePair { + var best *candidatePair + for _, p := range a.checklist { + if p.state == CandidatePairStateFailed { + continue + } + + if best == nil { + best = p + } else if best.Priority() < p.Priority() { + best = p + } + } + return best +} + +func (a *Agent) getBestValidCandidatePair() *candidatePair { + var best *candidatePair + for _, p := range a.checklist { + if p.state != CandidatePairStateSucceeded { + continue + } + + if best == nil { + best = p + } else if best.Priority() < p.Priority() { + best = p + } + } + return best +} + +func (a *Agent) addPair(local, remote Candidate) *candidatePair { + p := newCandidatePair(local, remote, a.isControlling) + a.checklist = append(a.checklist, p) + return p +} + +func (a *Agent) findPair(local, remote Candidate) *candidatePair { + for _, p := range a.checklist { + if p.local.Equal(local) && p.remote.Equal(remote) { + return p + } + } + return nil +} + +// validateSelectedPair checks if the selected pair is (still) valid +// Note: the caller should hold the agent lock. +func (a *Agent) validateSelectedPair() bool { + selectedPair := a.getSelectedPair() + if selectedPair == nil { + return false + } + + disconnectedTime := time.Since(selectedPair.remote.LastReceived()) + + // Only allow transitions to failed if a.failedTimeout is non-zero + totalTimeToFailure := a.failedTimeout + if totalTimeToFailure != 0 { + totalTimeToFailure += a.disconnectedTimeout + } + + switch { + case totalTimeToFailure != 0 && disconnectedTime > totalTimeToFailure: + a.updateConnectionState(ConnectionStateFailed) + case a.disconnectedTimeout != 0 && disconnectedTime > a.disconnectedTimeout: + a.updateConnectionState(ConnectionStateDisconnected) + default: + a.updateConnectionState(ConnectionStateConnected) + } + + return true +} + +// checkKeepalive sends STUN Binding Indications to the selected pair +// if no packet has been sent on that pair in the last keepaliveInterval +// Note: the caller should hold the agent lock. +func (a *Agent) checkKeepalive() { + selectedPair := a.getSelectedPair() + if selectedPair == nil { + return + } + + if (a.keepaliveInterval != 0) && + ((time.Since(selectedPair.local.LastSent()) > a.keepaliveInterval) || + (time.Since(selectedPair.remote.LastReceived()) > a.keepaliveInterval)) { + // we use binding request instead of indication to support refresh consent schemas + // see https://tools.ietf.org/html/rfc7675 + a.selector.PingCandidate(selectedPair.local, selectedPair.remote) + } +} + +// AddRemoteCandidate adds a new remote candidate +func (a *Agent) AddRemoteCandidate(c Candidate) error { + if c == nil { + return nil + } + + // cannot check for network yet because it might not be applied + // when mDNS hostame is used. + if c.TCPType() == TCPTypeActive { + // TCP Candidates with tcptype active will probe server passive ones, so + // no need to do anything with them. + a.log.Infof("Ignoring remote candidate with tcpType active: %s", c) + return nil + } + + // If we have a mDNS Candidate lets fully resolve it before adding it locally + if c.Type() == CandidateTypeHost && strings.HasSuffix(c.Address(), ".local") { + if a.mDNSMode == MulticastDNSModeDisabled { + a.log.Warnf("remote mDNS candidate added, but mDNS is disabled: (%s)", c.Address()) + return nil + } + + hostCandidate, ok := c.(*CandidateHost) + if !ok { + return ErrAddressParseFailed + } + + go a.resolveAndAddMulticastCandidate(hostCandidate) + return nil + } + + go func() { + if err := a.run(a.context(), func(ctx context.Context, agent *Agent) { + agent.addRemoteCandidate(c) + }); err != nil { + a.log.Warnf("Failed to add remote candidate %s: %v", c.Address(), err) + return + } + }() + return nil +} + +func (a *Agent) resolveAndAddMulticastCandidate(c *CandidateHost) { + if a.mDNSConn == nil { + return + } + _, src, err := a.mDNSConn.Query(c.context(), c.Address()) + if err != nil { + a.log.Warnf("Failed to discover mDNS candidate %s: %v", c.Address(), err) + return + } + + ip, _, _, _ := parseAddr(src) //nolint:dogsled + if ip == nil { + a.log.Warnf("Failed to discover mDNS candidate %s: failed to parse IP", c.Address()) + return + } + + if err = c.setIP(ip); err != nil { + a.log.Warnf("Failed to discover mDNS candidate %s: %v", c.Address(), err) + return + } + + if err = a.run(a.context(), func(ctx context.Context, agent *Agent) { + agent.addRemoteCandidate(c) + }); err != nil { + a.log.Warnf("Failed to add mDNS candidate %s: %v", c.Address(), err) + return + } +} + +func (a *Agent) requestConnectivityCheck() { + select { + case a.forceCandidateContact <- true: + default: + } +} + +// addRemoteCandidate assumes you are holding the lock (must be execute using a.run) +func (a *Agent) addRemoteCandidate(c Candidate) { + set := a.remoteCandidates[c.NetworkType()] + + for _, candidate := range set { + if candidate.Equal(c) { + return + } + } + + set = append(set, c) + a.remoteCandidates[c.NetworkType()] = set + + if localCandidates, ok := a.localCandidates[c.NetworkType()]; ok { + for _, localCandidate := range localCandidates { + a.addPair(localCandidate, c) + } + } + + a.requestConnectivityCheck() +} + +func (a *Agent) addCandidate(ctx context.Context, c Candidate, candidateConn net.PacketConn) error { + return a.run(ctx, func(ctx context.Context, agent *Agent) { + c.start(a, candidateConn, a.startedCh) + + set := a.localCandidates[c.NetworkType()] + for _, candidate := range set { + if candidate.Equal(c) { + if err := c.close(); err != nil { + a.log.Warnf("Failed to close duplicate candidate: %v", err) + } + return + } + } + + set = append(set, c) + a.localCandidates[c.NetworkType()] = set + + if remoteCandidates, ok := a.remoteCandidates[c.NetworkType()]; ok { + for _, remoteCandidate := range remoteCandidates { + a.addPair(c, remoteCandidate) + } + } + + a.requestConnectivityCheck() + + a.chanCandidate <- c + }) +} + +// GetLocalCandidates returns the local candidates +func (a *Agent) GetLocalCandidates() ([]Candidate, error) { + var res []Candidate + + err := a.run(a.context(), func(ctx context.Context, agent *Agent) { + var candidates []Candidate + for _, set := range agent.localCandidates { + candidates = append(candidates, set...) + } + res = candidates + }) + if err != nil { + return nil, err + } + + return res, nil +} + +// GetLocalUserCredentials returns the local user credentials +func (a *Agent) GetLocalUserCredentials() (frag string, pwd string, err error) { + valSet := make(chan struct{}) + err = a.run(a.context(), func(ctx context.Context, agent *Agent) { + frag = agent.localUfrag + pwd = agent.localPwd + close(valSet) + }) + + if err == nil { + <-valSet + } + return +} + +// GetRemoteUserCredentials returns the remote user credentials +func (a *Agent) GetRemoteUserCredentials() (frag string, pwd string, err error) { + valSet := make(chan struct{}) + err = a.run(a.context(), func(ctx context.Context, agent *Agent) { + frag = agent.remoteUfrag + pwd = agent.remotePwd + close(valSet) + }) + + if err == nil { + <-valSet + } + return +} + +// Close cleans up the Agent +func (a *Agent) Close() error { + if err := a.ok(); err != nil { + return err + } + + done := make(chan struct{}) + + a.afterRun(func(context.Context) { + close(done) + }) + + a.gatherCandidateCancel() + a.err.Store(ErrClosed) + + a.tcpMux.RemoveConnByUfrag(a.localUfrag) + + close(a.done) + + <-done + return nil +} + +// Remove all candidates. This closes any listening sockets +// and removes both the local and remote candidate lists. +// +// This is used for restarts, failures and on close +func (a *Agent) deleteAllCandidates() { + for net, cs := range a.localCandidates { + for _, c := range cs { + if err := c.close(); err != nil { + a.log.Warnf("Failed to close candidate %s: %v", c, err) + } + } + delete(a.localCandidates, net) + } + for net, cs := range a.remoteCandidates { + for _, c := range cs { + if err := c.close(); err != nil { + a.log.Warnf("Failed to close candidate %s: %v", c, err) + } + } + delete(a.remoteCandidates, net) + } +} + +func (a *Agent) findRemoteCandidate(networkType NetworkType, addr net.Addr) Candidate { + ip, port, _, ok := parseAddr(addr) + if !ok { + a.log.Warnf("Error parsing addr: %s", addr) + return nil + } + + set := a.remoteCandidates[networkType] + for _, c := range set { + if c.Address() == ip.String() && c.Port() == port { + return c + } + } + return nil +} + +func (a *Agent) sendBindingRequest(m *stun.Message, local, remote Candidate) { + a.log.Tracef("ping STUN from %s to %s\n", local.String(), remote.String()) + + a.invalidatePendingBindingRequests(time.Now()) + a.pendingBindingRequests = append(a.pendingBindingRequests, bindingRequest{ + timestamp: time.Now(), + transactionID: m.TransactionID, + destination: remote.addr(), + isUseCandidate: m.Contains(stun.AttrUseCandidate), + }) + + a.sendSTUN(m, local, remote) +} + +func (a *Agent) sendBindingSuccess(m *stun.Message, local, remote Candidate) { + base := remote + + ip, port, _, ok := parseAddr(base.addr()) + if !ok { + a.log.Warnf("Error parsing addr: %s", base.addr()) + return + } + + if out, err := stun.Build(m, stun.BindingSuccess, + &stun.XORMappedAddress{ + IP: ip, + Port: port, + }, + stun.NewShortTermIntegrity(a.localPwd), + stun.Fingerprint, + ); err != nil { + a.log.Warnf("Failed to handle inbound ICE from: %s to: %s error: %s", local, remote, err) + } else { + a.sendSTUN(out, local, remote) + } +} + +/* Removes pending binding requests that are over maxBindingRequestTimeout old + + Let HTO be the transaction timeout, which SHOULD be 2*RTT if + RTT is known or 500 ms otherwise. + https://tools.ietf.org/html/rfc8445#appendix-B.1 +*/ +func (a *Agent) invalidatePendingBindingRequests(filterTime time.Time) { + initialSize := len(a.pendingBindingRequests) + + temp := a.pendingBindingRequests[:0] + for _, bindingRequest := range a.pendingBindingRequests { + if filterTime.Sub(bindingRequest.timestamp) < maxBindingRequestTimeout { + temp = append(temp, bindingRequest) + } + } + + a.pendingBindingRequests = temp + if bindRequestsRemoved := initialSize - len(a.pendingBindingRequests); bindRequestsRemoved > 0 { + a.log.Tracef("Discarded %d binding requests because they expired", bindRequestsRemoved) + } +} + +// Assert that the passed TransactionID is in our pendingBindingRequests and returns the destination +// If the bindingRequest was valid remove it from our pending cache +func (a *Agent) handleInboundBindingSuccess(id [stun.TransactionIDSize]byte) (bool, *bindingRequest) { + a.invalidatePendingBindingRequests(time.Now()) + for i := range a.pendingBindingRequests { + if a.pendingBindingRequests[i].transactionID == id { + validBindingRequest := a.pendingBindingRequests[i] + a.pendingBindingRequests = append(a.pendingBindingRequests[:i], a.pendingBindingRequests[i+1:]...) + return true, &validBindingRequest + } + } + return false, nil +} + +// handleInbound processes STUN traffic from a remote candidate +func (a *Agent) handleInbound(m *stun.Message, local Candidate, remote net.Addr) { //nolint:gocognit + var err error + if m == nil || local == nil { + return + } + + if m.Type.Method != stun.MethodBinding || + !(m.Type.Class == stun.ClassSuccessResponse || + m.Type.Class == stun.ClassRequest || + m.Type.Class == stun.ClassIndication) { + a.log.Tracef("unhandled STUN from %s to %s class(%s) method(%s)", remote, local, m.Type.Class, m.Type.Method) + return + } + + if a.isControlling { + if m.Contains(stun.AttrICEControlling) { + a.log.Debug("inbound isControlling && a.isControlling == true") + return + } else if m.Contains(stun.AttrUseCandidate) { + a.log.Debug("useCandidate && a.isControlling == true") + return + } + } else { + if m.Contains(stun.AttrICEControlled) { + a.log.Debug("inbound isControlled && a.isControlling == false") + return + } + } + + remoteCandidate := a.findRemoteCandidate(local.NetworkType(), remote) + if m.Type.Class == stun.ClassSuccessResponse { + if err = assertInboundMessageIntegrity(m, []byte(a.remotePwd)); err != nil { + a.log.Warnf("discard message from (%s), %v", remote, err) + return + } + + if remoteCandidate == nil { + a.log.Warnf("discard success message from (%s), no such remote", remote) + return + } + + a.selector.HandleSuccessResponse(m, local, remoteCandidate, remote) + } else if m.Type.Class == stun.ClassRequest { + if err = assertInboundUsername(m, a.localUfrag+":"+a.remoteUfrag); err != nil { + a.log.Warnf("discard message from (%s), %v", remote, err) + return + } else if err = assertInboundMessageIntegrity(m, []byte(a.localPwd)); err != nil { + a.log.Warnf("discard message from (%s), %v", remote, err) + return + } + + if remoteCandidate == nil { + ip, port, networkType, ok := parseAddr(remote) + if !ok { + a.log.Errorf("Failed to create parse remote net.Addr when creating remote prflx candidate") + return + } + + prflxCandidateConfig := CandidatePeerReflexiveConfig{ + Network: networkType.String(), + Address: ip.String(), + Port: port, + Component: local.Component(), + RelAddr: "", + RelPort: 0, + } + + prflxCandidate, err := NewCandidatePeerReflexive(&prflxCandidateConfig) + if err != nil { + a.log.Errorf("Failed to create new remote prflx candidate (%s)", err) + return + } + remoteCandidate = prflxCandidate + + a.log.Debugf("adding a new peer-reflexive candidate: %s ", remote) + a.addRemoteCandidate(remoteCandidate) + } + + a.log.Tracef("inbound STUN (Request) from %s to %s", remote.String(), local.String()) + + a.selector.HandleBindingRequest(m, local, remoteCandidate) + } + + if remoteCandidate != nil { + remoteCandidate.seen(false) + } +} + +// validateNonSTUNTraffic processes non STUN traffic from a remote candidate, +// and returns true if it is an actual remote candidate +func (a *Agent) validateNonSTUNTraffic(local Candidate, remote net.Addr) bool { + var isValidCandidate uint64 + if err := a.run(local.context(), func(ctx context.Context, agent *Agent) { + remoteCandidate := a.findRemoteCandidate(local.NetworkType(), remote) + if remoteCandidate != nil { + remoteCandidate.seen(false) + atomic.AddUint64(&isValidCandidate, 1) + } + }); err != nil { + a.log.Warnf("failed to validate remote candidate: %v", err) + } + + return atomic.LoadUint64(&isValidCandidate) == 1 +} + +func (a *Agent) getSelectedPair() *candidatePair { + selectedPair := a.selectedPair.Load() + + if selectedPair == nil { + return nil + } + + return selectedPair.(*candidatePair) +} + +func (a *Agent) closeMulticastConn() { + if a.mDNSConn != nil { + if err := a.mDNSConn.Close(); err != nil { + a.log.Warnf("failed to close mDNS Conn: %v", err) + } + } +} + +// SetRemoteCredentials sets the credentials of the remote agent +func (a *Agent) SetRemoteCredentials(remoteUfrag, remotePwd string) error { + switch { + case remoteUfrag == "": + return ErrRemoteUfragEmpty + case remotePwd == "": + return ErrRemotePwdEmpty + } + + return a.run(a.context(), func(ctx context.Context, agent *Agent) { + agent.remoteUfrag = remoteUfrag + agent.remotePwd = remotePwd + }) +} + +// Restart restarts the ICE Agent with the provided ufrag/pwd +// If no ufrag/pwd is provided the Agent will generate one itself +// +// Restart must only be called when GatheringState is GatheringStateComplete +// a user must then call GatherCandidates explicitly to start generating new ones +func (a *Agent) Restart(ufrag, pwd string) error { + if ufrag == "" { + var err error + ufrag, err = generateUFrag() + if err != nil { + return err + } + } + if pwd == "" { + var err error + pwd, err = generatePwd() + if err != nil { + return err + } + } + + if len([]rune(ufrag))*8 < 24 { + return ErrLocalUfragInsufficientBits + } + if len([]rune(pwd))*8 < 128 { + return ErrLocalPwdInsufficientBits + } + + var err error + if runErr := a.run(a.context(), func(ctx context.Context, agent *Agent) { + if agent.gatheringState == GatheringStateGathering { + err = ErrRestartWhenGathering + return + } + + // Clear all agent needed to take back to fresh state + agent.localUfrag = ufrag + agent.localPwd = pwd + agent.remoteUfrag = "" + agent.remotePwd = "" + a.gatheringState = GatheringStateNew + a.checklist = make([]*candidatePair, 0) + a.pendingBindingRequests = make([]bindingRequest, 0) + a.setSelectedPair(nil) + a.deleteAllCandidates() + if a.selector != nil { + a.selector.Start() + } + + // Restart is used by NewAgent. Accept/Connect should be used to move to checking + // for new Agents + if a.connectionState != ConnectionStateNew { + a.updateConnectionState(ConnectionStateChecking) + } + }); runErr != nil { + return runErr + } + return err +} + +func (a *Agent) setGatheringState(newState GatheringState) error { + done := make(chan struct{}) + if err := a.run(a.context(), func(ctx context.Context, agent *Agent) { + if a.gatheringState != newState && newState == GatheringStateComplete { + a.chanCandidate <- nil + } + + a.gatheringState = newState + close(done) + }); err != nil { + return err + } + + <-done + return nil +} diff --git a/vendor/github.com/pion/ice/v2/agent_config.go b/vendor/github.com/pion/ice/v2/agent_config.go new file mode 100644 index 0000000..e09ad76 --- /dev/null +++ b/vendor/github.com/pion/ice/v2/agent_config.go @@ -0,0 +1,252 @@ +package ice + +import ( + "time" + + "github.com/pion/logging" + "github.com/pion/transport/vnet" + "golang.org/x/net/proxy" +) + +const ( + // defaultCheckInterval is the interval at which the agent performs candidate checks in the connecting phase + defaultCheckInterval = 200 * time.Millisecond + + // keepaliveInterval used to keep candidates alive + defaultKeepaliveInterval = 2 * time.Second + + // defaultDisconnectedTimeout is the default time till an Agent transitions disconnected + defaultDisconnectedTimeout = 5 * time.Second + + // defaultFailedTimeout is the default time till an Agent transitions to failed after disconnected + defaultFailedTimeout = 25 * time.Second + + // wait time before nominating a host candidate + defaultHostAcceptanceMinWait = 0 + + // wait time before nominating a srflx candidate + defaultSrflxAcceptanceMinWait = 500 * time.Millisecond + + // wait time before nominating a prflx candidate + defaultPrflxAcceptanceMinWait = 1000 * time.Millisecond + + // wait time before nominating a relay candidate + defaultRelayAcceptanceMinWait = 2000 * time.Millisecond + + // max binding request before considering a pair failed + defaultMaxBindingRequests = 7 + + // the number of bytes that can be buffered before we start to error + maxBufferSize = 1000 * 1000 // 1MB + + // wait time before binding requests can be deleted + maxBindingRequestTimeout = 4000 * time.Millisecond +) + +func defaultCandidateTypes() []CandidateType { + return []CandidateType{CandidateTypeHost, CandidateTypeServerReflexive, CandidateTypeRelay} +} + +// AgentConfig collects the arguments to ice.Agent construction into +// a single structure, for future-proofness of the interface +type AgentConfig struct { + Urls []*URL + + // PortMin and PortMax are optional. Leave them 0 for the default UDP port allocation strategy. + PortMin uint16 + PortMax uint16 + + // LocalUfrag and LocalPwd values used to perform connectivity + // checks. The values MUST be unguessable, with at least 128 bits of + // random number generator output used to generate the password, and + // at least 24 bits of output to generate the username fragment. + LocalUfrag string + LocalPwd string + + // MulticastDNSMode controls mDNS behavior for the ICE agent + MulticastDNSMode MulticastDNSMode + + // MulticastDNSHostName controls the hostname for this agent. If none is specified a random one will be generated + MulticastDNSHostName string + + // DisconnectedTimeout defaults to 5 seconds when this property is nil. + // If the duration is 0, the ICE Agent will never go to disconnected + DisconnectedTimeout *time.Duration + + // FailedTimeout defaults to 25 seconds when this property is nil. + // If the duration is 0, we will never go to failed. + FailedTimeout *time.Duration + + // KeepaliveInterval determines how often should we send ICE + // keepalives (should be less then connectiontimeout above) + // when this is nil, it defaults to 10 seconds. + // A keepalive interval of 0 means we never send keepalive packets + KeepaliveInterval *time.Duration + + // NetworkTypes is an optional configuration for disabling or enabling + // support for specific network types. + NetworkTypes []NetworkType + + // CandidateTypes is an optional configuration for disabling or enabling + // support for specific candidate types. + CandidateTypes []CandidateType + + LoggerFactory logging.LoggerFactory + + // checkInterval controls how often our internal task loop runs when + // in the connecting state. Only useful for testing. + checkInterval time.Duration + + // MaxBindingRequests is the max amount of binding requests the agent will send + // over a candidate pair for validation or nomination, if after MaxBindingRequests + // the candidate is yet to answer a binding request or a nomination we set the pair as failed + MaxBindingRequests *uint16 + + // Lite agents do not perform connectivity check and only provide host candidates. + Lite bool + + // NAT1To1IPCandidateType is used along with NAT1To1IPs to specify which candidate type + // the 1:1 NAT IP addresses should be mapped to. + // If unspecified or CandidateTypeHost, NAT1To1IPs are used to replace host candidate IPs. + // If CandidateTypeServerReflexive, it will insert a srflx candidate (as if it was dervied + // from a STUN server) with its port number being the one for the actual host candidate. + // Other values will result in an error. + NAT1To1IPCandidateType CandidateType + + // NAT1To1IPs contains a list of public IP addresses that are to be used as a host + // candidate or srflx candidate. This is used typically for servers that are behind + // 1:1 D-NAT (e.g. AWS EC2 instances) and to eliminate the need of server reflexisive + // candidate gathering. + NAT1To1IPs []string + + // HostAcceptanceMinWait specify a minimum wait time before selecting host candidates + HostAcceptanceMinWait *time.Duration + // HostAcceptanceMinWait specify a minimum wait time before selecting srflx candidates + SrflxAcceptanceMinWait *time.Duration + // HostAcceptanceMinWait specify a minimum wait time before selecting prflx candidates + PrflxAcceptanceMinWait *time.Duration + // HostAcceptanceMinWait specify a minimum wait time before selecting relay candidates + RelayAcceptanceMinWait *time.Duration + + // Net is the our abstracted network interface for internal development purpose only + // (see github.com/pion/transport/vnet) + Net *vnet.Net + + // InterfaceFilter is a function that you can use in order to whitelist or blacklist + // the interfaces which are used to gather ICE candidates. + InterfaceFilter func(string) bool + + // InsecureSkipVerify controls if self-signed certificates are accepted when connecting + // to TURN servers via TLS or DTLS + InsecureSkipVerify bool + + // TCPMux will be used for multiplexing incoming TCP connections for ICE TCP. + // Currently only passive candidates are supported. This functionality is + // experimental and the API might change in the future. + TCPMux TCPMux + + // Proxy Dialer is a dialer that should be implemented by the user based on golang.org/x/net/proxy + // dial interface in order to support corporate proxies + ProxyDialer proxy.Dialer +} + +// initWithDefaults populates an agent and falls back to defaults if fields are unset +func (config *AgentConfig) initWithDefaults(a *Agent) { + if config.MaxBindingRequests == nil { + a.maxBindingRequests = defaultMaxBindingRequests + } else { + a.maxBindingRequests = *config.MaxBindingRequests + } + + if config.HostAcceptanceMinWait == nil { + a.hostAcceptanceMinWait = defaultHostAcceptanceMinWait + } else { + a.hostAcceptanceMinWait = *config.HostAcceptanceMinWait + } + + if config.SrflxAcceptanceMinWait == nil { + a.srflxAcceptanceMinWait = defaultSrflxAcceptanceMinWait + } else { + a.srflxAcceptanceMinWait = *config.SrflxAcceptanceMinWait + } + + if config.PrflxAcceptanceMinWait == nil { + a.prflxAcceptanceMinWait = defaultPrflxAcceptanceMinWait + } else { + a.prflxAcceptanceMinWait = *config.PrflxAcceptanceMinWait + } + + if config.RelayAcceptanceMinWait == nil { + a.relayAcceptanceMinWait = defaultRelayAcceptanceMinWait + } else { + a.relayAcceptanceMinWait = *config.RelayAcceptanceMinWait + } + + if config.DisconnectedTimeout == nil { + a.disconnectedTimeout = defaultDisconnectedTimeout + } else { + a.disconnectedTimeout = *config.DisconnectedTimeout + } + + if config.FailedTimeout == nil { + a.failedTimeout = defaultFailedTimeout + } else { + a.failedTimeout = *config.FailedTimeout + } + + if config.KeepaliveInterval == nil { + a.keepaliveInterval = defaultKeepaliveInterval + } else { + a.keepaliveInterval = *config.KeepaliveInterval + } + + if config.checkInterval == 0 { + a.checkInterval = defaultCheckInterval + } else { + a.checkInterval = config.checkInterval + } + + if config.CandidateTypes == nil || len(config.CandidateTypes) == 0 { + a.candidateTypes = defaultCandidateTypes() + } else { + a.candidateTypes = config.CandidateTypes + } +} + +func (config *AgentConfig) initExtIPMapping(a *Agent) error { + var err error + a.extIPMapper, err = newExternalIPMapper(config.NAT1To1IPCandidateType, config.NAT1To1IPs) + if err != nil { + return err + } + if a.extIPMapper == nil { + return nil // this may happen when config.NAT1To1IPs is an empty array + } + if a.extIPMapper.candidateType == CandidateTypeHost { + if a.mDNSMode == MulticastDNSModeQueryAndGather { + return ErrMulticastDNSWithNAT1To1IPMapping + } + candiHostEnabled := false + for _, candiType := range a.candidateTypes { + if candiType == CandidateTypeHost { + candiHostEnabled = true + break + } + } + if !candiHostEnabled { + return ErrIneffectiveNAT1To1IPMappingHost + } + } else if a.extIPMapper.candidateType == CandidateTypeServerReflexive { + candiSrflxEnabled := false + for _, candiType := range a.candidateTypes { + if candiType == CandidateTypeServerReflexive { + candiSrflxEnabled = true + break + } + } + if !candiSrflxEnabled { + return ErrIneffectiveNAT1To1IPMappingSrflx + } + } + return nil +} diff --git a/vendor/github.com/pion/ice/v2/agent_stats.go b/vendor/github.com/pion/ice/v2/agent_stats.go new file mode 100644 index 0000000..18d9ed8 --- /dev/null +++ b/vendor/github.com/pion/ice/v2/agent_stats.go @@ -0,0 +1,113 @@ +package ice + +import ( + "context" + "time" +) + +// GetCandidatePairsStats returns a list of candidate pair stats +func (a *Agent) GetCandidatePairsStats() []CandidatePairStats { + var res []CandidatePairStats + err := a.run(a.context(), func(ctx context.Context, agent *Agent) { + result := make([]CandidatePairStats, 0, len(agent.checklist)) + for _, cp := range agent.checklist { + stat := CandidatePairStats{ + Timestamp: time.Now(), + LocalCandidateID: cp.local.ID(), + RemoteCandidateID: cp.remote.ID(), + State: cp.state, + Nominated: cp.nominated, + // PacketsSent uint32 + // PacketsReceived uint32 + // BytesSent uint64 + // BytesReceived uint64 + // LastPacketSentTimestamp time.Time + // LastPacketReceivedTimestamp time.Time + // FirstRequestTimestamp time.Time + // LastRequestTimestamp time.Time + // LastResponseTimestamp time.Time + // TotalRoundTripTime float64 + // CurrentRoundTripTime float64 + // AvailableOutgoingBitrate float64 + // AvailableIncomingBitrate float64 + // CircuitBreakerTriggerCount uint32 + // RequestsReceived uint64 + // RequestsSent uint64 + // ResponsesReceived uint64 + // ResponsesSent uint64 + // RetransmissionsReceived uint64 + // RetransmissionsSent uint64 + // ConsentRequestsSent uint64 + // ConsentExpiredTimestamp time.Time + } + result = append(result, stat) + } + res = result + }) + if err != nil { + a.log.Errorf("error getting candidate pairs stats %v", err) + return []CandidatePairStats{} + } + return res +} + +// GetLocalCandidatesStats returns a list of local candidates stats +func (a *Agent) GetLocalCandidatesStats() []CandidateStats { + var res []CandidateStats + err := a.run(a.context(), func(ctx context.Context, agent *Agent) { + result := make([]CandidateStats, 0, len(agent.localCandidates)) + for networkType, localCandidates := range agent.localCandidates { + for _, c := range localCandidates { + stat := CandidateStats{ + Timestamp: time.Now(), + ID: c.ID(), + NetworkType: networkType, + IP: c.Address(), + Port: c.Port(), + CandidateType: c.Type(), + Priority: c.Priority(), + // URL string + RelayProtocol: "udp", + // Deleted bool + } + result = append(result, stat) + } + } + res = result + }) + if err != nil { + a.log.Errorf("error getting candidate pairs stats %v", err) + return []CandidateStats{} + } + return res +} + +// GetRemoteCandidatesStats returns a list of remote candidates stats +func (a *Agent) GetRemoteCandidatesStats() []CandidateStats { + var res []CandidateStats + err := a.run(a.context(), func(ctx context.Context, agent *Agent) { + result := make([]CandidateStats, 0, len(agent.remoteCandidates)) + for networkType, localCandidates := range agent.remoteCandidates { + for _, c := range localCandidates { + stat := CandidateStats{ + Timestamp: time.Now(), + ID: c.ID(), + NetworkType: networkType, + IP: c.Address(), + Port: c.Port(), + CandidateType: c.Type(), + Priority: c.Priority(), + // URL string + RelayProtocol: "udp", + } + result = append(result, stat) + } + } + res = result + }) + if err != nil { + a.log.Errorf("error getting candidate pairs stats %v", err) + return []CandidateStats{} + } + return res +} diff --git a/vendor/github.com/pion/ice/v2/candidate.go b/vendor/github.com/pion/ice/v2/candidate.go new file mode 100644 index 0000000..701f8f8 --- /dev/null +++ b/vendor/github.com/pion/ice/v2/candidate.go @@ -0,0 +1,68 @@ +package ice + +import ( + "context" + "net" + "time" +) + +const ( + receiveMTU = 8192 + defaultLocalPreference = 65535 + + // ComponentRTP indicates that the candidate is used for RTP + ComponentRTP uint16 = 1 + // ComponentRTCP indicates that the candidate is used for RTCP + ComponentRTCP +) + +// Candidate represents an ICE candidate +type Candidate interface { + // An arbitrary string used in the freezing algorithm to + // group similar candidates. It is the same for two candidates that + // have the same type, base IP address, protocol (UDP, TCP, etc.), + // and STUN or TURN server. + Foundation() string + + // ID is a unique identifier for just this candidate + // Unlike the foundation this is different for each candidate + ID() string + + // A component is a piece of a data stream. + // An example is one for RTP, and one for RTCP + Component() uint16 + SetComponent(uint16) + + // The last time this candidate received traffic + LastReceived() time.Time + + // The last time this candidate sent traffic + LastSent() time.Time + + NetworkType() NetworkType + Address() string + Port() int + + Priority() uint32 + + // A transport address related to a + // candidate, which is useful for diagnostics and other purposes + RelatedAddress() *CandidateRelatedAddress + + String() string + Type() CandidateType + TCPType() TCPType + + Equal(other Candidate) bool + + Marshal() string + + addr() net.Addr + agent() *Agent + context() context.Context + + close() error + seen(outbound bool) + start(a *Agent, conn net.PacketConn, initializedCh <-chan struct{}) + writeTo(raw []byte, dst Candidate) (int, error) +} diff --git a/vendor/github.com/pion/ice/v2/candidate_base.go b/vendor/github.com/pion/ice/v2/candidate_base.go new file mode 100644 index 0000000..2f16a8a --- /dev/null +++ b/vendor/github.com/pion/ice/v2/candidate_base.go @@ -0,0 +1,496 @@ +package ice + +import ( + "context" + "fmt" + "hash/crc32" + "net" + "strconv" + "strings" + "sync/atomic" + "time" + + "github.com/pion/logging" + "github.com/pion/stun" +) + +type candidateBase struct { + id string + networkType NetworkType + candidateType CandidateType + + component uint16 + address string + port int + relatedAddress *CandidateRelatedAddress + tcpType TCPType + + resolvedAddr net.Addr + + lastSent atomic.Value + lastReceived atomic.Value + conn net.PacketConn + + currAgent *Agent + closeCh chan struct{} + closedCh chan struct{} + + foundationOverride string + priorityOverride uint32 +} + +// Done implements context.Context +func (c *candidateBase) Done() <-chan struct{} { + return c.closeCh +} + +// Err implements context.Context +func (c *candidateBase) Err() error { + select { + case <-c.closedCh: + return ErrRunCanceled + default: + return nil + } +} + +// Deadline implements context.Context +func (c *candidateBase) Deadline() (deadline time.Time, ok bool) { + return time.Time{}, false +} + +// Value implements context.Context +func (c *candidateBase) Value(key interface{}) interface{} { + return nil +} + +// ID returns Candidate ID +func (c *candidateBase) ID() string { + return c.id +} + +func (c *candidateBase) Foundation() string { + if c.foundationOverride != "" { + return c.foundationOverride + } + + return fmt.Sprintf("%d", crc32.ChecksumIEEE([]byte(c.Type().String()+c.address+c.networkType.String()))) +} + +// Address returns Candidate Address +func (c *candidateBase) Address() string { + return c.address +} + +// Port returns Candidate Port +func (c *candidateBase) Port() int { + return c.port +} + +// Type returns candidate type +func (c *candidateBase) Type() CandidateType { + return c.candidateType +} + +// NetworkType returns candidate NetworkType +func (c *candidateBase) NetworkType() NetworkType { + return c.networkType +} + +// Component returns candidate component +func (c *candidateBase) Component() uint16 { + return c.component +} + +func (c *candidateBase) SetComponent(component uint16) { + c.component = component +} + +// LocalPreference returns the local preference for this candidate +func (c *candidateBase) LocalPreference() uint16 { + if c.NetworkType().IsTCP() { + // RFC 6544, section 4.2 + // + // In Section 4.1.2.1 of [RFC5245], a recommended formula for UDP ICE + // candidate prioritization is defined. For TCP candidates, the same + // formula and candidate type preferences SHOULD be used, and the + // RECOMMENDED type preferences for the new candidate types defined in + // this document (see Section 5) are 105 for NAT-assisted candidates and + // 75 for UDP-tunneled candidates. + // + // (...) + // + // With TCP candidates, the local preference part of the recommended + // priority formula is updated to also include the directionality + // (active, passive, or simultaneous-open) of the TCP connection. The + // RECOMMENDED local preference is then defined as: + // + // local preference = (2^13) * direction-pref + other-pref + // + // The direction-pref MUST be between 0 and 7 (both inclusive), with 7 + // being the most preferred. The other-pref MUST be between 0 and 8191 + // (both inclusive), with 8191 being the most preferred. It is + // RECOMMENDED that the host, UDP-tunneled, and relayed TCP candidates + // have the direction-pref assigned as follows: 6 for active, 4 for + // passive, and 2 for S-O. For the NAT-assisted and server reflexive + // candidates, the RECOMMENDED values are: 6 for S-O, 4 for active, and + // 2 for passive. + // + // (...) + // + // If any two candidates have the same type-preference and direction- + // pref, they MUST have a unique other-pref. With this specification, + // this usually only happens with multi-homed hosts, in which case + // other-pref is the preference for the particular IP address from which + // the candidate was obtained. When there is only a single IP address, + // this value SHOULD be set to the maximum allowed value (8191). + var otherPref uint16 = 8191 + + directionPref := func() uint16 { + switch c.Type() { + case CandidateTypeHost, CandidateTypeRelay: + switch c.tcpType { + case TCPTypeActive: + return 6 + case TCPTypePassive: + return 4 + case TCPTypeSimultaneousOpen: + return 2 + case TCPTypeUnspecified: + return 0 + } + case CandidateTypePeerReflexive, CandidateTypeServerReflexive: + switch c.tcpType { + case TCPTypeSimultaneousOpen: + return 6 + case TCPTypeActive: + return 4 + case TCPTypePassive: + return 2 + case TCPTypeUnspecified: + return 0 + } + case CandidateTypeUnspecified: + return 0 + } + return 0 + }() + + return (1<<13)*directionPref + otherPref + } + + return defaultLocalPreference +} + +// RelatedAddress returns *CandidateRelatedAddress +func (c *candidateBase) RelatedAddress() *CandidateRelatedAddress { + return c.relatedAddress +} + +func (c *candidateBase) TCPType() TCPType { + return c.tcpType +} + +// start runs the candidate using the provided connection +func (c *candidateBase) start(a *Agent, conn net.PacketConn, initializedCh <-chan struct{}) { + if c.conn != nil { + c.agent().log.Warn("Can't start already started candidateBase") + return + } + c.currAgent = a + c.conn = conn + c.closeCh = make(chan struct{}) + c.closedCh = make(chan struct{}) + + go c.recvLoop(initializedCh) +} + +func (c *candidateBase) recvLoop(initializedCh <-chan struct{}) { + defer func() { + close(c.closedCh) + }() + + select { + case <-initializedCh: + case <-c.closeCh: + return + } + + log := c.agent().log + buffer := make([]byte, receiveMTU) + for { + n, srcAddr, err := c.conn.ReadFrom(buffer) + if err != nil { + return + } + + handleInboundCandidateMsg(c, c, buffer[:n], srcAddr, log) + } +} + +func handleInboundCandidateMsg(ctx context.Context, c Candidate, buffer []byte, srcAddr net.Addr, log logging.LeveledLogger) { + if stun.IsMessage(buffer) { + m := &stun.Message{ + Raw: make([]byte, len(buffer)), + } + // Explicitly copy raw buffer so Message can own the memory. + copy(m.Raw, buffer) + if err := m.Decode(); err != nil { + log.Warnf("Failed to handle decode ICE from %s to %s: %v", c.addr(), srcAddr, err) + return + } + err := c.agent().run(ctx, func(ctx context.Context, agent *Agent) { + agent.handleInbound(m, c, srcAddr) + }) + if err != nil { + log.Warnf("Failed to handle message: %v", err) + } + + return + } + + if !c.agent().validateNonSTUNTraffic(c, srcAddr) { + log.Warnf("Discarded message from %s, not a valid remote candidate", c.addr()) + return + } + + // NOTE This will return packetio.ErrFull if the buffer ever manages to fill up. + if _, err := c.agent().buffer.Write(buffer); err != nil { + log.Warnf("failed to write packet") + } +} + +// close stops the recvLoop +func (c *candidateBase) close() error { + // If conn has never been started will be nil + if c.Done() == nil { + return nil + } + + // Assert that conn has not already been closed + select { + case <-c.Done(): + return nil + default: + } + + var firstErr error + + // Unblock recvLoop + close(c.closeCh) + if err := c.conn.SetDeadline(time.Now()); err != nil { + firstErr = err + } + + // Close the conn + if err := c.conn.Close(); err != nil && firstErr == nil { + firstErr = err + } + + if firstErr != nil { + return firstErr + } + + // Wait until the recvLoop is closed + <-c.closedCh + + return nil +} + +func (c *candidateBase) writeTo(raw []byte, dst Candidate) (int, error) { + n, err := c.conn.WriteTo(raw, dst.addr()) + if err != nil { + c.agent().log.Warnf("%s: %v", errSendPacket, err) + return n, nil + } + c.seen(true) + return n, nil +} + +// Priority computes the priority for this ICE Candidate +func (c *candidateBase) Priority() uint32 { + if c.priorityOverride != 0 { + return c.priorityOverride + } + + // The local preference MUST be an integer from 0 (lowest preference) to + // 65535 (highest preference) inclusive. When there is only a single IP + // address, this value SHOULD be set to 65535. If there are multiple + // candidates for a particular component for a particular data stream + // that have the same type, the local preference MUST be unique for each + // one. + return (1<<24)*uint32(c.Type().Preference()) + + (1<<8)*uint32(c.LocalPreference()) + + uint32(256-c.Component()) +} + +// Equal is used to compare two candidateBases +func (c *candidateBase) Equal(other Candidate) bool { + return c.NetworkType() == other.NetworkType() && + c.Type() == other.Type() && + c.Address() == other.Address() && + c.Port() == other.Port() && + c.TCPType() == other.TCPType() && + c.RelatedAddress().Equal(other.RelatedAddress()) +} + +// String makes the candidateBase printable +func (c *candidateBase) String() string { + return fmt.Sprintf("%s %s %s:%d%s", c.NetworkType(), c.Type(), c.Address(), c.Port(), c.relatedAddress) +} + +// LastReceived returns a time.Time indicating the last time +// this candidate was received +func (c *candidateBase) LastReceived() time.Time { + lastReceived := c.lastReceived.Load() + if lastReceived == nil { + return time.Time{} + } + return lastReceived.(time.Time) +} + +func (c *candidateBase) setLastReceived(t time.Time) { + c.lastReceived.Store(t) +} + +// LastSent returns a time.Time indicating the last time +// this candidate was sent +func (c *candidateBase) LastSent() time.Time { + lastSent := c.lastSent.Load() + if lastSent == nil { + return time.Time{} + } + return lastSent.(time.Time) +} + +func (c *candidateBase) setLastSent(t time.Time) { + c.lastSent.Store(t) +} + +func (c *candidateBase) seen(outbound bool) { + if outbound { + c.setLastSent(time.Now()) + } else { + c.setLastReceived(time.Now()) + } +} + +func (c *candidateBase) addr() net.Addr { + return c.resolvedAddr +} + +func (c *candidateBase) agent() *Agent { + return c.currAgent +} + +func (c *candidateBase) context() context.Context { + return c +} + +// Marshal returns the string representation of the ICECandidate +func (c *candidateBase) Marshal() string { + val := fmt.Sprintf("%s %d %s %d %s %d typ %s", + c.Foundation(), + c.Component(), + c.NetworkType().NetworkShort(), + c.Priority(), + c.Address(), + c.Port(), + c.Type()) + + if c.tcpType != TCPTypeUnspecified { + val += fmt.Sprintf(" tcptype %s", c.tcpType.String()) + } + + if c.RelatedAddress() != nil { + val = fmt.Sprintf("%s raddr %s rport %d", + val, + c.RelatedAddress().Address, + c.RelatedAddress().Port) + } + + return val +} + +// UnmarshalCandidate creates a Candidate from its string representation +func UnmarshalCandidate(raw string) (Candidate, error) { + split := strings.Fields(raw) + if len(split) < 8 { + return nil, fmt.Errorf("%w (%d)", errAttributeTooShortICECandidate, len(split)) + } + + // Foundation + foundation := split[0] + + // Component + rawComponent, err := strconv.ParseUint(split[1], 10, 16) + if err != nil { + return nil, fmt.Errorf("%w: %v", errParseComponent, err) + } + component := uint16(rawComponent) + + // Protocol + protocol := split[2] + + // Priority + priorityRaw, err := strconv.ParseUint(split[3], 10, 32) + if err != nil { + return nil, fmt.Errorf("%w: %v", errParsePriority, err) + } + priority := uint32(priorityRaw) + + // Address + address := split[4] + + // Port + rawPort, err := strconv.ParseUint(split[5], 10, 16) + if err != nil { + return nil, fmt.Errorf("%w: %v", errParsePort, err) + } + port := int(rawPort) + typ := split[7] + + relatedAddress := "" + relatedPort := 0 + tcpType := TCPTypeUnspecified + + if len(split) > 8 { + split = split[8:] + + if split[0] == "raddr" { + if len(split) < 4 { + return nil, fmt.Errorf("%w: incorrect length", errParseRelatedAddr) + } + + // RelatedAddress + relatedAddress = split[1] + + // RelatedPort + rawRelatedPort, parseErr := strconv.ParseUint(split[3], 10, 16) + if parseErr != nil { + return nil, fmt.Errorf("%w: %v", errParsePort, parseErr) + } + relatedPort = int(rawRelatedPort) + } else if split[0] == "tcptype" { + if len(split) < 2 { + return nil, fmt.Errorf("%w: incorrect length", errParseTypType) + } + + tcpType = NewTCPType(split[1]) + } + } + + switch typ { + case "host": + return NewCandidateHost(&CandidateHostConfig{"", protocol, address, port, component, priority, foundation, tcpType}) + case "srflx": + return NewCandidateServerReflexive(&CandidateServerReflexiveConfig{"", protocol, address, port, component, priority, foundation, relatedAddress, relatedPort}) + case "prflx": + return NewCandidatePeerReflexive(&CandidatePeerReflexiveConfig{"", protocol, address, port, component, priority, foundation, relatedAddress, relatedPort}) + case "relay": + return NewCandidateRelay(&CandidateRelayConfig{"", protocol, address, port, component, priority, foundation, relatedAddress, relatedPort, nil}) + default: + } + + return nil, fmt.Errorf("%w (%s)", errUnknownCandidateTyp, typ) +} diff --git a/vendor/github.com/pion/ice/v2/candidate_host.go b/vendor/github.com/pion/ice/v2/candidate_host.go new file mode 100644 index 0000000..b03dbdb --- /dev/null +++ b/vendor/github.com/pion/ice/v2/candidate_host.go @@ -0,0 +1,76 @@ +package ice + +import ( + "net" + "strings" +) + +// CandidateHost is a candidate of type host +type CandidateHost struct { + candidateBase + + network string +} + +// CandidateHostConfig is the config required to create a new CandidateHost +type CandidateHostConfig struct { + CandidateID string + Network string + Address string + Port int + Component uint16 + Priority uint32 + Foundation string + TCPType TCPType +} + +// NewCandidateHost creates a new host candidate +func NewCandidateHost(config *CandidateHostConfig) (*CandidateHost, error) { + candidateID := config.CandidateID + + if candidateID == "" { + candidateID = globalCandidateIDGenerator.Generate() + } + + c := &CandidateHost{ + candidateBase: candidateBase{ + id: candidateID, + address: config.Address, + candidateType: CandidateTypeHost, + component: config.Component, + port: config.Port, + tcpType: config.TCPType, + foundationOverride: config.Foundation, + priorityOverride: config.Priority, + }, + network: config.Network, + } + + if !strings.HasSuffix(config.Address, ".local") { + ip := net.ParseIP(config.Address) + if ip == nil { + return nil, ErrAddressParseFailed + } + + if err := c.setIP(ip); err != nil { + return nil, err + } + } else { + // Until mDNS candidate is resolved assume it is UDPv4 + c.candidateBase.networkType = NetworkTypeUDP4 + } + + return c, nil +} + +func (c *CandidateHost) setIP(ip net.IP) error { + networkType, err := determineNetworkType(c.network, ip) + if err != nil { + return err + } + + c.candidateBase.networkType = networkType + c.candidateBase.resolvedAddr = createAddr(networkType, ip, c.port) + + return nil +} diff --git a/vendor/github.com/pion/ice/v2/candidate_peer_reflexive.go b/vendor/github.com/pion/ice/v2/candidate_peer_reflexive.go new file mode 100644 index 0000000..0b330d1 --- /dev/null +++ b/vendor/github.com/pion/ice/v2/candidate_peer_reflexive.go @@ -0,0 +1,60 @@ +// Package ice ... +//nolint:dupl +package ice + +import "net" + +// CandidatePeerReflexive ... +type CandidatePeerReflexive struct { + candidateBase +} + +// CandidatePeerReflexiveConfig is the config required to create a new CandidatePeerReflexive +type CandidatePeerReflexiveConfig struct { + CandidateID string + Network string + Address string + Port int + Component uint16 + Priority uint32 + Foundation string + RelAddr string + RelPort int +} + +// NewCandidatePeerReflexive creates a new peer reflective candidate +func NewCandidatePeerReflexive(config *CandidatePeerReflexiveConfig) (*CandidatePeerReflexive, error) { + ip := net.ParseIP(config.Address) + if ip == nil { + return nil, ErrAddressParseFailed + } + + networkType, err := determineNetworkType(config.Network, ip) + if err != nil { + return nil, err + } + + candidateID := config.CandidateID + candidateIDGenerator := newCandidateIDGenerator() + if candidateID == "" { + candidateID = candidateIDGenerator.Generate() + } + + return &CandidatePeerReflexive{ + candidateBase: candidateBase{ + id: candidateID, + networkType: networkType, + candidateType: CandidateTypePeerReflexive, + address: config.Address, + port: config.Port, + resolvedAddr: createAddr(networkType, ip, config.Port), + component: config.Component, + foundationOverride: config.Foundation, + priorityOverride: config.Priority, + relatedAddress: &CandidateRelatedAddress{ + Address: config.RelAddr, + Port: config.RelPort, + }, + }, + }, nil +} diff --git a/vendor/github.com/pion/ice/v2/candidate_relay.go b/vendor/github.com/pion/ice/v2/candidate_relay.go new file mode 100644 index 0000000..44762f7 --- /dev/null +++ b/vendor/github.com/pion/ice/v2/candidate_relay.go @@ -0,0 +1,73 @@ +package ice + +import ( + "net" +) + +// CandidateRelay ... +type CandidateRelay struct { + candidateBase + + onClose func() error +} + +// CandidateRelayConfig is the config required to create a new CandidateRelay +type CandidateRelayConfig struct { + CandidateID string + Network string + Address string + Port int + Component uint16 + Priority uint32 + Foundation string + RelAddr string + RelPort int + OnClose func() error +} + +// NewCandidateRelay creates a new relay candidate +func NewCandidateRelay(config *CandidateRelayConfig) (*CandidateRelay, error) { + candidateID := config.CandidateID + + if candidateID == "" { + candidateID = globalCandidateIDGenerator.Generate() + } + + ip := net.ParseIP(config.Address) + if ip == nil { + return nil, ErrAddressParseFailed + } + + networkType, err := determineNetworkType(config.Network, ip) + if err != nil { + return nil, err + } + + return &CandidateRelay{ + candidateBase: candidateBase{ + id: candidateID, + networkType: networkType, + candidateType: CandidateTypeRelay, + address: config.Address, + port: config.Port, + resolvedAddr: &net.UDPAddr{IP: ip, Port: config.Port}, + component: config.Component, + foundationOverride: config.Foundation, + priorityOverride: config.Priority, + relatedAddress: &CandidateRelatedAddress{ + Address: config.RelAddr, + Port: config.RelPort, + }, + }, + onClose: config.OnClose, + }, nil +} + +func (c *CandidateRelay) close() error { + err := c.candidateBase.close() + if c.onClose != nil { + err = c.onClose() + c.onClose = nil + } + return err +} diff --git a/vendor/github.com/pion/ice/v2/candidate_server_reflexive.go b/vendor/github.com/pion/ice/v2/candidate_server_reflexive.go new file mode 100644 index 0000000..125a537 --- /dev/null +++ b/vendor/github.com/pion/ice/v2/candidate_server_reflexive.go @@ -0,0 +1,59 @@ +// Package ice ... +//nolint:dupl +package ice + +import "net" + +// CandidateServerReflexive ... +type CandidateServerReflexive struct { + candidateBase +} + +// CandidateServerReflexiveConfig is the config required to create a new CandidateServerReflexive +type CandidateServerReflexiveConfig struct { + CandidateID string + Network string + Address string + Port int + Component uint16 + Priority uint32 + Foundation string + RelAddr string + RelPort int +} + +// NewCandidateServerReflexive creates a new server reflective candidate +func NewCandidateServerReflexive(config *CandidateServerReflexiveConfig) (*CandidateServerReflexive, error) { + ip := net.ParseIP(config.Address) + if ip == nil { + return nil, ErrAddressParseFailed + } + + networkType, err := determineNetworkType(config.Network, ip) + if err != nil { + return nil, err + } + + candidateID := config.CandidateID + if candidateID == "" { + candidateID = globalCandidateIDGenerator.Generate() + } + + return &CandidateServerReflexive{ + candidateBase: candidateBase{ + id: candidateID, + networkType: networkType, + candidateType: CandidateTypeServerReflexive, + address: config.Address, + port: config.Port, + resolvedAddr: &net.UDPAddr{IP: ip, Port: config.Port}, + component: config.Component, + foundationOverride: config.Foundation, + priorityOverride: config.Priority, + relatedAddress: &CandidateRelatedAddress{ + Address: config.RelAddr, + Port: config.RelPort, + }, + }, + }, nil +} diff --git a/vendor/github.com/pion/ice/v2/candidatepair.go b/vendor/github.com/pion/ice/v2/candidatepair.go new file mode 100644 index 0000000..49dec37 --- /dev/null +++ b/vendor/github.com/pion/ice/v2/candidatepair.go @@ -0,0 +1,98 @@ +package ice + +import ( + "fmt" + + "github.com/pion/stun" +) + +func newCandidatePair(local, remote Candidate, controlling bool) *candidatePair { + return &candidatePair{ + iceRoleControlling: controlling, + remote: remote, + local: local, + state: CandidatePairStateWaiting, + } +} + +// candidatePair represents a combination of a local and remote candidate +type candidatePair struct { + iceRoleControlling bool + remote Candidate + local Candidate + bindingRequestCount uint16 + state CandidatePairState + nominated bool +} + +func (p *candidatePair) String() string { + if p == nil { + return "" + } + + return fmt.Sprintf("prio %d (local, prio %d) %s <-> %s (remote, prio %d)", + p.Priority(), p.local.Priority(), p.local, p.remote, p.remote.Priority()) +} + +func (p *candidatePair) Equal(other *candidatePair) bool { + if p == nil && other == nil { + return true + } + if p == nil || other == nil { + return false + } + return p.local.Equal(other.local) && p.remote.Equal(other.remote) +} + +// RFC 5245 - 5.7.2. Computing Pair Priority and Ordering Pairs +// Let G be the priority for the candidate provided by the controlling +// agent. Let D be the priority for the candidate provided by the +// controlled agent. +// pair priority = 2^32*MIN(G,D) + 2*MAX(G,D) + (G>D?1:0) +func (p *candidatePair) Priority() uint64 { + var g uint32 + var d uint32 + if p.iceRoleControlling { + g = p.local.Priority() + d = p.remote.Priority() + } else { + g = p.remote.Priority() + d = p.local.Priority() + } + + // Just implement these here rather + // than fooling around with the math package + min := func(x, y uint32) uint64 { + if x < y { + return uint64(x) + } + return uint64(y) + } + max := func(x, y uint32) uint64 { + if x > y { + return uint64(x) + } + return uint64(y) + } + cmp := func(x, y uint32) uint64 { + if x > y { + return uint64(1) + } + return uint64(0) + } + + // 1<<32 overflows uint32; and if both g && d are + // maxUint32, this result would overflow uint64 + return (1<<32-1)*min(g, d) + 2*max(g, d) + cmp(g, d) +} + +func (p *candidatePair) Write(b []byte) (int, error) { + return p.local.writeTo(b, p.remote) +} + +func (a *Agent) sendSTUN(msg *stun.Message, local, remote Candidate) { + _, err := local.writeTo(msg.Raw, remote) + if err != nil { + a.log.Tracef("failed to send STUN message: %s", err) + } +} diff --git a/vendor/github.com/pion/ice/v2/candidatepair_state.go b/vendor/github.com/pion/ice/v2/candidatepair_state.go new file mode 100644 index 0000000..28c7187 --- /dev/null +++ b/vendor/github.com/pion/ice/v2/candidatepair_state.go @@ -0,0 +1,37 @@ +package ice + +// CandidatePairState represent the ICE candidate pair state +type CandidatePairState int + +const ( + // CandidatePairStateWaiting means a check has not been performed for + // this pair + CandidatePairStateWaiting = iota + 1 + + // CandidatePairStateInProgress means a check has been sent for this pair, + // but the transaction is in progress. + CandidatePairStateInProgress + + // CandidatePairStateFailed means a check for this pair was already done + // and failed, either never producing any response or producing an unrecoverable + // failure response. + CandidatePairStateFailed + + // CandidatePairStateSucceeded means a check for this pair was already + // done and produced a successful result. + CandidatePairStateSucceeded +) + +func (c CandidatePairState) String() string { + switch c { + case CandidatePairStateWaiting: + return "waiting" + case CandidatePairStateInProgress: + return "in-progress" + case CandidatePairStateFailed: + return "failed" + case CandidatePairStateSucceeded: + return "succeeded" + } + return "Unknown candidate pair state" +} diff --git a/vendor/github.com/pion/ice/v2/candidaterelatedaddress.go b/vendor/github.com/pion/ice/v2/candidaterelatedaddress.go new file mode 100644 index 0000000..18cf318 --- /dev/null +++ b/vendor/github.com/pion/ice/v2/candidaterelatedaddress.go @@ -0,0 +1,30 @@ +package ice + +import "fmt" + +// CandidateRelatedAddress convey transport addresses related to the +// candidate, useful for diagnostics and other purposes. +type CandidateRelatedAddress struct { + Address string + Port int +} + +// String makes CandidateRelatedAddress printable +func (c *CandidateRelatedAddress) String() string { + if c == nil { + return "" + } + + return fmt.Sprintf(" related %s:%d", c.Address, c.Port) +} + +// Equal allows comparing two CandidateRelatedAddresses. +// The CandidateRelatedAddress are allowed to be nil. +func (c *CandidateRelatedAddress) Equal(other *CandidateRelatedAddress) bool { + if c == nil && other == nil { + return true + } + return c != nil && other != nil && + c.Address == other.Address && + c.Port == other.Port +} diff --git a/vendor/github.com/pion/ice/v2/candidatetype.go b/vendor/github.com/pion/ice/v2/candidatetype.go new file mode 100644 index 0000000..376c408 --- /dev/null +++ b/vendor/github.com/pion/ice/v2/candidatetype.go @@ -0,0 +1,62 @@ +package ice + +// CandidateType represents the type of candidate +type CandidateType byte + +// CandidateType enum +const ( + CandidateTypeUnspecified CandidateType = iota + CandidateTypeHost + CandidateTypeServerReflexive + CandidateTypePeerReflexive + CandidateTypeRelay +) + +// String makes CandidateType printable +func (c CandidateType) String() string { + switch c { + case CandidateTypeHost: + return "host" + case CandidateTypeServerReflexive: + return "srflx" + case CandidateTypePeerReflexive: + return "prflx" + case CandidateTypeRelay: + return "relay" + case CandidateTypeUnspecified: + return "Unknown candidate type" + } + return "Unknown candidate type" +} + +// Preference returns the preference weight of a CandidateType +// +// 4.1.2.2. Guidelines for Choosing Type and Local Preferences +// The RECOMMENDED values are 126 for host candidates, 100 +// for server reflexive candidates, 110 for peer reflexive candidates, +// and 0 for relayed candidates. +func (c CandidateType) Preference() uint16 { + switch c { + case CandidateTypeHost: + return 126 + case CandidateTypePeerReflexive: + return 110 + case CandidateTypeServerReflexive: + return 100 + case CandidateTypeRelay, CandidateTypeUnspecified: + return 0 + } + return 0 +} + +func containsCandidateType(candidateType CandidateType, candidateTypeList []CandidateType) bool { + if candidateTypeList == nil { + return false + } + for _, ct := range candidateTypeList { + if ct == candidateType { + return true + } + } + return false +} diff --git a/vendor/github.com/pion/ice/v2/codecov.yml b/vendor/github.com/pion/ice/v2/codecov.yml new file mode 100644 index 0000000..085200a --- /dev/null +++ b/vendor/github.com/pion/ice/v2/codecov.yml @@ -0,0 +1,20 @@ +# +# DO NOT EDIT THIS FILE +# +# It is automatically copied from https://github.com/pion/.goassets repository. +# + +coverage: + status: + project: + default: + # Allow decreasing 2% of total coverage to avoid noise. + threshold: 2% + patch: + default: + target: 70% + only_pulls: true + +ignore: + - "examples/*" + - "examples/**/*" diff --git a/vendor/github.com/pion/ice/v2/context.go b/vendor/github.com/pion/ice/v2/context.go new file mode 100644 index 0000000..627d81e --- /dev/null +++ b/vendor/github.com/pion/ice/v2/context.go @@ -0,0 +1,37 @@ +package ice + +import ( + "context" + "time" +) + +func (a *Agent) context() context.Context { + return agentContext(a.done) +} + +type agentContext chan struct{} + +// Done implements context.Context +func (a agentContext) Done() <-chan struct{} { + return (chan struct{})(a) +} + +// Err implements context.Context +func (a agentContext) Err() error { + select { + case <-(chan struct{})(a): + return ErrRunCanceled + default: + return nil + } +} + +// Deadline implements context.Context +func (a agentContext) Deadline() (deadline time.Time, ok bool) { + return time.Time{}, false +} + +// Value implements context.Context +func (a agentContext) Value(key interface{}) interface{} { + return nil +} diff --git a/vendor/github.com/pion/ice/v2/errors.go b/vendor/github.com/pion/ice/v2/errors.go new file mode 100644 index 0000000..e7dd625 --- /dev/null +++ b/vendor/github.com/pion/ice/v2/errors.go @@ -0,0 +1,132 @@ +package ice + +import "errors" + +var ( + // ErrUnknownType indicates an error with Unknown info. + ErrUnknownType = errors.New("Unknown") + + // ErrSchemeType indicates the scheme type could not be parsed. + ErrSchemeType = errors.New("unknown scheme type") + + // ErrSTUNQuery indicates query arguments are provided in a STUN URL. + ErrSTUNQuery = errors.New("queries not supported in stun address") + + // ErrInvalidQuery indicates an malformed query is provided. + ErrInvalidQuery = errors.New("invalid query") + + // ErrHost indicates malformed hostname is provided. + ErrHost = errors.New("invalid hostname") + + // ErrPort indicates malformed port is provided. + ErrPort = errors.New("invalid port") + + // ErrLocalUfragInsufficientBits indicates local username fragment insufficient bits are provided. + // Have to be at least 24 bits long + ErrLocalUfragInsufficientBits = errors.New("local username fragment is less than 24 bits long") + + // ErrLocalPwdInsufficientBits indicates local passoword insufficient bits are provided. + // Have to be at least 128 bits long + ErrLocalPwdInsufficientBits = errors.New("local password is less than 128 bits long") + + // ErrProtoType indicates an unsupported transport type was provided. + ErrProtoType = errors.New("invalid transport protocol type") + + // ErrClosed indicates the agent is closed + ErrClosed = errors.New("the agent is closed") + + // ErrNoCandidatePairs indicates agent does not have a valid candidate pair + ErrNoCandidatePairs = errors.New("no candidate pairs available") + + // ErrCanceledByCaller indicates agent connection was canceled by the caller + ErrCanceledByCaller = errors.New("connecting canceled by caller") + + // ErrMultipleStart indicates agent was started twice + ErrMultipleStart = errors.New("attempted to start agent twice") + + // ErrRemoteUfragEmpty indicates agent was started with an empty remote ufrag + ErrRemoteUfragEmpty = errors.New("remote ufrag is empty") + + // ErrRemotePwdEmpty indicates agent was started with an empty remote pwd + ErrRemotePwdEmpty = errors.New("remote pwd is empty") + + // ErrNoOnCandidateHandler indicates agent was started without OnCandidate + ErrNoOnCandidateHandler = errors.New("no OnCandidate provided") + + // ErrMultipleGatherAttempted indicates GatherCandidates has been called multiple times + ErrMultipleGatherAttempted = errors.New("attempting to gather candidates during gathering state") + + // ErrUsernameEmpty indicates agent was give TURN URL with an empty Username + ErrUsernameEmpty = errors.New("username is empty") + + // ErrPasswordEmpty indicates agent was give TURN URL with an empty Password + ErrPasswordEmpty = errors.New("password is empty") + + // ErrAddressParseFailed indicates we were unable to parse a candidate address + ErrAddressParseFailed = errors.New("failed to parse address") + + // ErrLiteUsingNonHostCandidates indicates non host candidates were selected for a lite agent + ErrLiteUsingNonHostCandidates = errors.New("lite agents must only use host candidates") + + // ErrUselessUrlsProvided indicates that one or more URL was provided to the agent but no host + // candidate required them + ErrUselessUrlsProvided = errors.New("agent does not need URL with selected candidate types") + + // ErrUnsupportedNAT1To1IPCandidateType indicates that the specified NAT1To1IPCandidateType is + // unsupported + ErrUnsupportedNAT1To1IPCandidateType = errors.New("unsupported 1:1 NAT IP candidate type") + + // ErrInvalidNAT1To1IPMapping indicates that the given 1:1 NAT IP mapping is invalid + ErrInvalidNAT1To1IPMapping = errors.New("invalid 1:1 NAT IP mapping") + + // ErrExternalMappedIPNotFound in NAT1To1IPMapping + ErrExternalMappedIPNotFound = errors.New("external mapped IP not found") + + // ErrMulticastDNSWithNAT1To1IPMapping indicates that the mDNS gathering cannot be used along + // with 1:1 NAT IP mapping for host candidate. + ErrMulticastDNSWithNAT1To1IPMapping = errors.New("mDNS gathering cannot be used with 1:1 NAT IP mapping for host candidate") + + // ErrIneffectiveNAT1To1IPMappingHost indicates that 1:1 NAT IP mapping for host candidate is + // requested, but the host candidate type is disabled. + ErrIneffectiveNAT1To1IPMappingHost = errors.New("1:1 NAT IP mapping for host candidate ineffective") + + // ErrIneffectiveNAT1To1IPMappingSrflx indicates that 1:1 NAT IP mapping for srflx candidate is + // requested, but the srflx candidate type is disabled. + ErrIneffectiveNAT1To1IPMappingSrflx = errors.New("1:1 NAT IP mapping for srflx candidate ineffective") + + // ErrInvalidMulticastDNSHostName indicates an invalid MulticastDNSHostName + ErrInvalidMulticastDNSHostName = errors.New("invalid mDNS HostName, must end with .local and can only contain a single '.'") + + // ErrRestartWhenGathering indicates Restart was called when Agent is in GatheringStateGathering + ErrRestartWhenGathering = errors.New("ICE Agent can not be restarted when gathering") + + // ErrRunCanceled indicates a run operation was canceled by its individual done + ErrRunCanceled = errors.New("run was canceled by done") + + // ErrTCPMuxNotInitialized indicates TCPMux is not initialized and that invalidTCPMux is used. + ErrTCPMuxNotInitialized = errors.New("TCPMux is not initialized") + + // ErrTCPRemoteAddrAlreadyExists indicates we already have the connection with same remote addr. + ErrTCPRemoteAddrAlreadyExists = errors.New("conn with same remote addr already exists") + + errSendPacket = errors.New("failed to send packet") + errAttributeTooShortICECandidate = errors.New("attribute not long enough to be ICE candidate") + errParseComponent = errors.New("could not parse component") + errParsePriority = errors.New("could not parse priority") + errParsePort = errors.New("could not parse port") + errParseRelatedAddr = errors.New("could not parse related addresses") + errParseTypType = errors.New("could not parse typtype") + errUnknownCandidateTyp = errors.New("unknown candidate typ") + errGetXorMappedAddrResponse = errors.New("failed to get XOR-MAPPED-ADDRESS response") + errConnectionAddrAlreadyExist = errors.New("connection with same remote address already exists") + errReadingStreamingPacket = errors.New("error reading streaming packet") + errWriting = errors.New("error writing to") + errClosingConnection = errors.New("error closing connection") + errDetermineNetworkType = errors.New("unable to determine networkType") + errMissingProtocolScheme = errors.New("missing protocol scheme") + errTooManyColonsAddr = errors.New("too many colons in address") + errRead = errors.New("unexpected error trying to read") + errUnknownRole = errors.New("unknown role") + errMismatchUsername = errors.New("username mismatch") + errICEWriteSTUNMessage = errors.New("the ICE conn can't write STUN messages") +) diff --git a/vendor/github.com/pion/ice/v2/external_ip_mapper.go b/vendor/github.com/pion/ice/v2/external_ip_mapper.go new file mode 100644 index 0000000..5310cc0 --- /dev/null +++ b/vendor/github.com/pion/ice/v2/external_ip_mapper.go @@ -0,0 +1,143 @@ +package ice + +import ( + "net" + "strings" +) + +func validateIPString(ipStr string) (net.IP, bool, error) { + ip := net.ParseIP(ipStr) + if ip == nil { + return nil, false, ErrInvalidNAT1To1IPMapping + } + return ip, (ip.To4() != nil), nil +} + +// ipMapping holds the mapping of local and external IP address for a particular IP family +type ipMapping struct { + ipSole net.IP // when non-nil, this is the sole external IP for one local IP assumed + ipMap map[string]net.IP // local-to-external IP mapping (k: local, v: external) +} + +func (m *ipMapping) setSoleIP(ip net.IP) error { + if m.ipSole != nil || len(m.ipMap) > 0 { + return ErrInvalidNAT1To1IPMapping + } + + m.ipSole = ip + + return nil +} + +func (m *ipMapping) addIPMapping(locIP, extIP net.IP) error { + if m.ipSole != nil { + return ErrInvalidNAT1To1IPMapping + } + + locIPStr := locIP.String() + + // check if dup of local IP + if _, ok := m.ipMap[locIPStr]; ok { + return ErrInvalidNAT1To1IPMapping + } + + m.ipMap[locIPStr] = extIP + + return nil +} + +func (m *ipMapping) findExternalIP(locIP net.IP) (net.IP, error) { + if m.ipSole != nil { + return m.ipSole, nil + } + + extIP, ok := m.ipMap[locIP.String()] + if !ok { + return nil, ErrExternalMappedIPNotFound + } + + return extIP, nil +} + +type externalIPMapper struct { + ipv4Mapping ipMapping + ipv6Mapping ipMapping + candidateType CandidateType +} + +func newExternalIPMapper(candidateType CandidateType, ips []string) (*externalIPMapper, error) { //nolint:gocognit + if len(ips) == 0 { + return nil, nil + } + if candidateType == CandidateTypeUnspecified { + candidateType = CandidateTypeHost // defaults to host + } else if candidateType != CandidateTypeHost && candidateType != CandidateTypeServerReflexive { + return nil, ErrUnsupportedNAT1To1IPCandidateType + } + + m := &externalIPMapper{ + ipv4Mapping: ipMapping{ipMap: map[string]net.IP{}}, + ipv6Mapping: ipMapping{ipMap: map[string]net.IP{}}, + candidateType: candidateType, + } + + for _, extIPStr := range ips { + ipPair := strings.Split(extIPStr, "/") + if len(ipPair) == 0 || len(ipPair) > 2 { + return nil, ErrInvalidNAT1To1IPMapping + } + + extIP, isExtIPv4, err := validateIPString(ipPair[0]) + if err != nil { + return nil, err + } + if len(ipPair) == 1 { + if isExtIPv4 { + if err := m.ipv4Mapping.setSoleIP(extIP); err != nil { + return nil, err + } + } else { + if err := m.ipv6Mapping.setSoleIP(extIP); err != nil { + return nil, err + } + } + } else { + locIP, isLocIPv4, err := validateIPString(ipPair[1]) + if err != nil { + return nil, err + } + if isExtIPv4 { + if !isLocIPv4 { + return nil, ErrInvalidNAT1To1IPMapping + } + + if err := m.ipv4Mapping.addIPMapping(locIP, extIP); err != nil { + return nil, err + } + } else { + if isLocIPv4 { + return nil, ErrInvalidNAT1To1IPMapping + } + + if err := m.ipv6Mapping.addIPMapping(locIP, extIP); err != nil { + return nil, err + } + } + } + } + + return m, nil +} + +func (m *externalIPMapper) findExternalIP(localIPStr string) (net.IP, error) { + locIP, isLocIPv4, err := validateIPString(localIPStr) + if err != nil { + return nil, err + } + + if isLocIPv4 { + return m.ipv4Mapping.findExternalIP(locIP) + } + + return m.ipv6Mapping.findExternalIP(locIP) +} diff --git a/vendor/github.com/pion/ice/v2/gather.go b/vendor/github.com/pion/ice/v2/gather.go new file mode 100644 index 0000000..3bc3d77 --- /dev/null +++ b/vendor/github.com/pion/ice/v2/gather.go @@ -0,0 +1,497 @@ +package ice + +import ( + "context" + "crypto/tls" + "errors" + "fmt" + "net" + "reflect" + "sync" + "time" + + "github.com/pion/dtls/v2" + "github.com/pion/logging" + "github.com/pion/turn/v2" +) + +const ( + stunGatherTimeout = time.Second * 5 +) + +type closeable interface { + Close() error +} + +// Close a net.Conn and log if we have a failure +func closeConnAndLog(c closeable, log logging.LeveledLogger, msg string) { + if c == nil || (reflect.ValueOf(c).Kind() == reflect.Ptr && reflect.ValueOf(c).IsNil()) { + log.Warnf("Conn is not allocated (%s)", msg) + return + } + + log.Warnf(msg) + if err := c.Close(); err != nil { + log.Warnf("Failed to close conn: %v", err) + } +} + +// fakePacketConn wraps a net.Conn and emulates net.PacketConn +type fakePacketConn struct { + nextConn net.Conn +} + +func (f *fakePacketConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) { + n, err = f.nextConn.Read(p) + addr = f.nextConn.RemoteAddr() + return +} +func (f *fakePacketConn) Close() error { return f.nextConn.Close() } +func (f *fakePacketConn) LocalAddr() net.Addr { return f.nextConn.LocalAddr() } +func (f *fakePacketConn) SetDeadline(t time.Time) error { return f.nextConn.SetDeadline(t) } +func (f *fakePacketConn) SetReadDeadline(t time.Time) error { return f.nextConn.SetReadDeadline(t) } +func (f *fakePacketConn) SetWriteDeadline(t time.Time) error { return f.nextConn.SetWriteDeadline(t) } +func (f *fakePacketConn) WriteTo(p []byte, addr net.Addr) (n int, err error) { + return f.nextConn.Write(p) +} + +// GatherCandidates initiates the trickle based gathering process. +func (a *Agent) GatherCandidates() error { + var gatherErr error + + if runErr := a.run(a.context(), func(ctx context.Context, agent *Agent) { + if a.gatheringState != GatheringStateNew { + gatherErr = ErrMultipleGatherAttempted + return + } else if a.onCandidateHdlr.Load() == nil { + gatherErr = ErrNoOnCandidateHandler + return + } + + a.gatherCandidateCancel() // Cancel previous gathering routine + ctx, cancel := context.WithCancel(ctx) + a.gatherCandidateCancel = cancel + + go a.gatherCandidates(ctx) + }); runErr != nil { + return runErr + } + return gatherErr +} + +func (a *Agent) gatherCandidates(ctx context.Context) { + if err := a.setGatheringState(GatheringStateGathering); err != nil { + a.log.Warnf("failed to set gatheringState to GatheringStateGathering: %v", err) + return + } + + var wg sync.WaitGroup + for _, t := range a.candidateTypes { + switch t { + case CandidateTypeHost: + wg.Add(1) + go func() { + a.gatherCandidatesLocal(ctx, a.networkTypes) + wg.Done() + }() + case CandidateTypeServerReflexive: + wg.Add(1) + go func() { + a.gatherCandidatesSrflx(ctx, a.urls, a.networkTypes) + wg.Done() + }() + if a.extIPMapper != nil && a.extIPMapper.candidateType == CandidateTypeServerReflexive { + wg.Add(1) + go func() { + a.gatherCandidatesSrflxMapped(ctx, a.networkTypes) + wg.Done() + }() + } + case CandidateTypeRelay: + wg.Add(1) + go func() { + a.gatherCandidatesRelay(ctx, a.urls) + wg.Done() + }() + case CandidateTypePeerReflexive, CandidateTypeUnspecified: + } + } + // Block until all STUN and TURN URLs have been gathered (or timed out) + wg.Wait() + + if err := a.setGatheringState(GatheringStateComplete); err != nil { + a.log.Warnf("failed to set gatheringState to GatheringStateComplete: %v", err) + } +} + +func (a *Agent) gatherCandidatesLocal(ctx context.Context, networkTypes []NetworkType) { //nolint:gocognit + networks := map[string]struct{}{} + for _, networkType := range networkTypes { + if networkType.IsTCP() { + networks[tcp] = struct{}{} + } else { + networks[udp] = struct{}{} + } + } + + localIPs, err := localInterfaces(a.net, a.interfaceFilter, networkTypes) + if err != nil { + a.log.Warnf("failed to iterate local interfaces, host candidates will not be gathered %s", err) + return + } + + for _, ip := range localIPs { + mappedIP := ip + if a.mDNSMode != MulticastDNSModeQueryAndGather && a.extIPMapper != nil && a.extIPMapper.candidateType == CandidateTypeHost { + if _mappedIP, err := a.extIPMapper.findExternalIP(ip.String()); err == nil { + mappedIP = _mappedIP + } else { + a.log.Warnf("1:1 NAT mapping is enabled but no external IP is found for %s\n", ip.String()) + } + } + + address := mappedIP.String() + if a.mDNSMode == MulticastDNSModeQueryAndGather { + address = a.mDNSName + } + + for network := range networks { + var port int + var conn net.PacketConn + var err error + + var tcpType TCPType + switch network { + case tcp: + // Handle ICE TCP passive mode + + a.log.Debugf("GetConn by ufrag: %s\n", a.localUfrag) + conn, err = a.tcpMux.GetConnByUfrag(a.localUfrag) + if err != nil { + if !errors.Is(err, ErrTCPMuxNotInitialized) { + a.log.Warnf("error getting tcp conn by ufrag: %s %s %s\n", network, ip, a.localUfrag) + } + continue + } + port = conn.LocalAddr().(*net.TCPAddr).Port + tcpType = TCPTypePassive + // is there a way to verify that the listen address is even + // accessible from the current interface. + case udp: + conn, err = listenUDPInPortRange(a.net, a.log, int(a.portmax), int(a.portmin), network, &net.UDPAddr{IP: ip, Port: 0}) + if err != nil { + a.log.Warnf("could not listen %s %s\n", network, ip) + continue + } + + port = conn.LocalAddr().(*net.UDPAddr).Port + } + hostConfig := CandidateHostConfig{ + Network: network, + Address: address, + Port: port, + Component: ComponentRTP, + TCPType: tcpType, + } + + c, err := NewCandidateHost(&hostConfig) + if err != nil { + closeConnAndLog(conn, a.log, fmt.Sprintf("Failed to create host candidate: %s %s %d: %v\n", network, mappedIP, port, err)) + continue + } + + if a.mDNSMode == MulticastDNSModeQueryAndGather { + if err = c.setIP(ip); err != nil { + closeConnAndLog(conn, a.log, fmt.Sprintf("Failed to create host candidate: %s %s %d: %v\n", network, mappedIP, port, err)) + continue + } + } + + if err := a.addCandidate(ctx, c, conn); err != nil { + if closeErr := c.close(); closeErr != nil { + a.log.Warnf("Failed to close candidate: %v", closeErr) + } + a.log.Warnf("Failed to append to localCandidates and run onCandidateHdlr: %v\n", err) + } + } + } +} + +func (a *Agent) gatherCandidatesSrflxMapped(ctx context.Context, networkTypes []NetworkType) { + var wg sync.WaitGroup + defer wg.Wait() + + for _, networkType := range networkTypes { + if networkType.IsTCP() { + continue + } + + network := networkType.String() + wg.Add(1) + go func() { + defer wg.Done() + conn, err := listenUDPInPortRange(a.net, a.log, int(a.portmax), int(a.portmin), network, &net.UDPAddr{IP: nil, Port: 0}) + if err != nil { + a.log.Warnf("Failed to listen %s: %v\n", network, err) + return + } + + laddr := conn.LocalAddr().(*net.UDPAddr) + mappedIP, err := a.extIPMapper.findExternalIP(laddr.IP.String()) + if err != nil { + closeConnAndLog(conn, a.log, fmt.Sprintf("1:1 NAT mapping is enabled but no external IP is found for %s\n", laddr.IP.String())) + return + } + + srflxConfig := CandidateServerReflexiveConfig{ + Network: network, + Address: mappedIP.String(), + Port: laddr.Port, + Component: ComponentRTP, + RelAddr: laddr.IP.String(), + RelPort: laddr.Port, + } + c, err := NewCandidateServerReflexive(&srflxConfig) + if err != nil { + closeConnAndLog(conn, a.log, fmt.Sprintf("Failed to create server reflexive candidate: %s %s %d: %v\n", + network, + mappedIP.String(), + laddr.Port, + err)) + return + } + + if err := a.addCandidate(ctx, c, conn); err != nil { + if closeErr := c.close(); closeErr != nil { + a.log.Warnf("Failed to close candidate: %v", closeErr) + } + a.log.Warnf("Failed to append to localCandidates and run onCandidateHdlr: %v\n", err) + } + }() + } +} + +func (a *Agent) gatherCandidatesSrflx(ctx context.Context, urls []*URL, networkTypes []NetworkType) { + var wg sync.WaitGroup + defer wg.Wait() + + for _, networkType := range networkTypes { + if networkType.IsTCP() { + continue + } + + for i := range urls { + wg.Add(1) + go func(url URL, network string) { + defer wg.Done() + hostPort := fmt.Sprintf("%s:%d", url.Host, url.Port) + serverAddr, err := a.net.ResolveUDPAddr(network, hostPort) + if err != nil { + a.log.Warnf("failed to resolve stun host: %s: %v", hostPort, err) + return + } + + conn, err := listenUDPInPortRange(a.net, a.log, int(a.portmax), int(a.portmin), network, &net.UDPAddr{IP: nil, Port: 0}) + if err != nil { + closeConnAndLog(conn, a.log, fmt.Sprintf("Failed to listen for %s: %v\n", serverAddr.String(), err)) + return + } + + xoraddr, err := getXORMappedAddr(conn, serverAddr, stunGatherTimeout) + if err != nil { + closeConnAndLog(conn, a.log, fmt.Sprintf("could not get server reflexive address %s %s: %v\n", network, url, err)) + return + } + + ip := xoraddr.IP + port := xoraddr.Port + + laddr := conn.LocalAddr().(*net.UDPAddr) + srflxConfig := CandidateServerReflexiveConfig{ + Network: network, + Address: ip.String(), + Port: port, + Component: ComponentRTP, + RelAddr: laddr.IP.String(), + RelPort: laddr.Port, + } + c, err := NewCandidateServerReflexive(&srflxConfig) + if err != nil { + closeConnAndLog(conn, a.log, fmt.Sprintf("Failed to create server reflexive candidate: %s %s %d: %v\n", network, ip, port, err)) + return + } + + if err := a.addCandidate(ctx, c, conn); err != nil { + if closeErr := c.close(); closeErr != nil { + a.log.Warnf("Failed to close candidate: %v", closeErr) + } + a.log.Warnf("Failed to append to localCandidates and run onCandidateHdlr: %v\n", err) + } + }(*urls[i], networkType.String()) + } + } +} + +func (a *Agent) gatherCandidatesRelay(ctx context.Context, urls []*URL) { //nolint:gocognit + var wg sync.WaitGroup + defer wg.Wait() + + network := NetworkTypeUDP4.String() + for i := range urls { + switch { + case urls[i].Scheme != SchemeTypeTURN && urls[i].Scheme != SchemeTypeTURNS: + continue + case urls[i].Username == "": + a.log.Errorf("Failed to gather relay candidates: %v", ErrUsernameEmpty) + return + case urls[i].Password == "": + a.log.Errorf("Failed to gather relay candidates: %v", ErrPasswordEmpty) + return + } + + wg.Add(1) + go func(url URL) { + defer wg.Done() + TURNServerAddr := fmt.Sprintf("%s:%d", url.Host, url.Port) + var ( + locConn net.PacketConn + err error + RelAddr string + RelPort int + ) + + switch { + case url.Proto == ProtoTypeUDP && url.Scheme == SchemeTypeTURN: + if locConn, err = a.net.ListenPacket(network, "0.0.0.0:0"); err != nil { + a.log.Warnf("Failed to listen %s: %v\n", network, err) + return + } + + RelAddr = locConn.LocalAddr().(*net.UDPAddr).IP.String() + RelPort = locConn.LocalAddr().(*net.UDPAddr).Port + case a.proxyDialer != nil && url.Proto == ProtoTypeTCP && + (url.Scheme == SchemeTypeTURN || url.Scheme == SchemeTypeTURNS): + conn, connectErr := a.proxyDialer.Dial(NetworkTypeTCP4.String(), TURNServerAddr) + if connectErr != nil { + a.log.Warnf("Failed to Dial TCP Addr %s via proxy dialer: %v\n", TURNServerAddr, connectErr) + return + } + + RelAddr = conn.LocalAddr().(*net.TCPAddr).IP.String() + RelPort = conn.LocalAddr().(*net.TCPAddr).Port + locConn = turn.NewSTUNConn(conn) + + case url.Proto == ProtoTypeTCP && url.Scheme == SchemeTypeTURN: + tcpAddr, connectErr := net.ResolveTCPAddr(NetworkTypeTCP4.String(), TURNServerAddr) + if connectErr != nil { + a.log.Warnf("Failed to resolve TCP Addr %s: %v\n", TURNServerAddr, connectErr) + return + } + + conn, connectErr := net.DialTCP(NetworkTypeTCP4.String(), nil, tcpAddr) + if connectErr != nil { + a.log.Warnf("Failed to Dial TCP Addr %s: %v\n", TURNServerAddr, connectErr) + return + } + + RelAddr = conn.LocalAddr().(*net.TCPAddr).IP.String() + RelPort = conn.LocalAddr().(*net.TCPAddr).Port + locConn = turn.NewSTUNConn(conn) + case url.Proto == ProtoTypeUDP && url.Scheme == SchemeTypeTURNS: + udpAddr, connectErr := net.ResolveUDPAddr(network, TURNServerAddr) + if connectErr != nil { + a.log.Warnf("Failed to resolve UDP Addr %s: %v\n", TURNServerAddr, connectErr) + return + } + + conn, connectErr := dtls.Dial(network, udpAddr, &dtls.Config{ + InsecureSkipVerify: a.insecureSkipVerify, //nolint:gosec + }) + if connectErr != nil { + a.log.Warnf("Failed to Dial DTLS Addr %s: %v\n", TURNServerAddr, connectErr) + return + } + + RelAddr = conn.LocalAddr().(*net.UDPAddr).IP.String() + RelPort = conn.LocalAddr().(*net.UDPAddr).Port + locConn = &fakePacketConn{conn} + case url.Proto == ProtoTypeTCP && url.Scheme == SchemeTypeTURNS: + conn, connectErr := tls.Dial(NetworkTypeTCP4.String(), TURNServerAddr, &tls.Config{ + InsecureSkipVerify: a.insecureSkipVerify, //nolint:gosec + }) + if connectErr != nil { + a.log.Warnf("Failed to Dial TLS Addr %s: %v\n", TURNServerAddr, connectErr) + return + } + RelAddr = conn.LocalAddr().(*net.TCPAddr).IP.String() + RelPort = conn.LocalAddr().(*net.TCPAddr).Port + locConn = turn.NewSTUNConn(conn) + default: + a.log.Warnf("Unable to handle URL in gatherCandidatesRelay %v\n", url) + return + } + + client, err := turn.NewClient(&turn.ClientConfig{ + TURNServerAddr: TURNServerAddr, + Conn: locConn, + Username: url.Username, + Password: url.Password, + LoggerFactory: a.loggerFactory, + Net: a.net, + }) + if err != nil { + closeConnAndLog(locConn, a.log, fmt.Sprintf("Failed to build new turn.Client %s %s\n", TURNServerAddr, err)) + return + } + + if err = client.Listen(); err != nil { + client.Close() + closeConnAndLog(locConn, a.log, fmt.Sprintf("Failed to listen on turn.Client %s %s\n", TURNServerAddr, err)) + return + } + + relayConn, err := client.Allocate() + if err != nil { + client.Close() + closeConnAndLog(locConn, a.log, fmt.Sprintf("Failed to allocate on turn.Client %s %s\n", TURNServerAddr, err)) + return + } + + raddr := relayConn.LocalAddr().(*net.UDPAddr) + relayConfig := CandidateRelayConfig{ + Network: network, + Component: ComponentRTP, + Address: raddr.IP.String(), + Port: raddr.Port, + RelAddr: RelAddr, + RelPort: RelPort, + OnClose: func() error { + client.Close() + return locConn.Close() + }, + } + relayConnClose := func() { + if relayConErr := relayConn.Close(); relayConErr != nil { + a.log.Warnf("Failed to close relay %v", relayConErr) + } + } + candidate, err := NewCandidateRelay(&relayConfig) + if err != nil { + relayConnClose() + + client.Close() + closeConnAndLog(locConn, a.log, fmt.Sprintf("Failed to create relay candidate: %s %s: %v\n", network, raddr.String(), err)) + return + } + + if err := a.addCandidate(ctx, candidate, relayConn); err != nil { + relayConnClose() + + if closeErr := candidate.close(); closeErr != nil { + a.log.Warnf("Failed to close candidate: %v", closeErr) + } + a.log.Warnf("Failed to append to localCandidates and run onCandidateHdlr: %v\n", err) + } + }(*urls[i]) + } +} diff --git a/vendor/github.com/pion/ice/v2/go.mod b/vendor/github.com/pion/ice/v2/go.mod new file mode 100644 index 0000000..410c2e3 --- /dev/null +++ b/vendor/github.com/pion/ice/v2/go.mod @@ -0,0 +1,16 @@ +module github.com/pion/ice/v2 + +go 1.13 + +require ( + github.com/google/uuid v1.1.5 + github.com/pion/dtls/v2 v2.0.4 + github.com/pion/logging v0.2.2 + github.com/pion/mdns v0.0.4 + github.com/pion/randutil v0.1.0 + github.com/pion/stun v0.3.5 + github.com/pion/transport v0.12.1 + github.com/pion/turn/v2 v2.0.5 + github.com/stretchr/testify v1.6.1 + golang.org/x/net v0.0.0-20201201195509-5d6afe98e0b7 +) diff --git a/vendor/github.com/pion/ice/v2/go.sum b/vendor/github.com/pion/ice/v2/go.sum new file mode 100644 index 0000000..7e8981a --- /dev/null +++ b/vendor/github.com/pion/ice/v2/go.sum @@ -0,0 +1,60 @@ +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/google/uuid v1.1.5 h1:kxhtnfFVi+rYdOALN0B3k9UT86zVJKfBimRaciULW4I= +github.com/google/uuid v1.1.5/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/pion/dtls/v2 v2.0.4 h1:WuUcqi6oYMu/noNTz92QrF1DaFj4eXbhQ6dzaaAwOiI= +github.com/pion/dtls/v2 v2.0.4/go.mod h1:qAkFscX0ZHoI1E07RfYPoRw3manThveu+mlTDdOxoGI= +github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY= +github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms= +github.com/pion/mdns v0.0.4 h1:O4vvVqr4DGX63vzmO6Fw9vpy3lfztVWHGCQfyw0ZLSY= +github.com/pion/mdns v0.0.4/go.mod h1:R1sL0p50l42S5lJs91oNdUL58nm0QHrhxnSegr++qC0= +github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA= +github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8= +github.com/pion/stun v0.3.5 h1:uLUCBCkQby4S1cf6CGuR9QrVOKcvUwFeemaC865QHDg= +github.com/pion/stun v0.3.5/go.mod h1:gDMim+47EeEtfWogA37n6qXZS88L5V6LqFcf+DZA2UA= +github.com/pion/transport v0.8.10/go.mod h1:tBmha/UCjpum5hqTWhfAEs3CO4/tHSg0MYRhSzR+CZ8= +github.com/pion/transport v0.10.0/go.mod h1:BnHnUipd0rZQyTVB2SBGojFHT9CBt5C5TcsJSQGkvSE= +github.com/pion/transport v0.10.1 h1:2W+yJT+0mOQ160ThZYUx5Zp2skzshiNgxrNE9GUfhJM= +github.com/pion/transport v0.10.1/go.mod h1:PBis1stIILMiis0PewDw91WJeLJkyIMcEk+DwKOzf4A= +github.com/pion/transport v0.12.1 h1:6v8lxQGVZpwSICEZjhl/CCv6aErINZlrm3O5ncFXj/c= +github.com/pion/transport v0.12.1/go.mod h1:N3+vZQD9HlDP5GWkZ85LohxNsDcNgofQmyL6ojX5d8Q= +github.com/pion/turn/v2 v2.0.5 h1:iwMHqDfPEDEOFzwWKT56eFmh6DYC6o/+xnLAEzgISbA= +github.com/pion/turn/v2 v2.0.5/go.mod h1:APg43CFyt/14Uy7heYUOGWdkem/Wu4PhCO/bjyrTqMw= +github.com/pion/udp v0.1.0 h1:uGxQsNyrqG3GLINv36Ff60covYmfrLoxzwnCsIYspXI= +github.com/pion/udp v0.1.0/go.mod h1:BPELIjbwE9PRbd/zxI/KYBnbo7B6+oA6YuEaNE8lths= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897 h1:pLI5jrR7OSLijeIDcmRxNmw2api+jEfxLoykJVice/E= +golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20191126235420-ef20fe5d7933/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201031054903-ff519b6c9102 h1:42cLlJJdEh+ySyeUUbEQ5bsTiq8voBeTuweGVkY6Puw= +golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201201195509-5d6afe98e0b7 h1:3uJsdck53FDIpWwLeAXlia9p4C8j0BO2xZrqzKpL0D8= +golang.org/x/net v0.0.0-20201201195509-5d6afe98e0b7/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f h1:+Nyd8tzPX9R7BWHguqsrbFdRx3WQ/1ib8I44HXV5yTA= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/vendor/github.com/pion/ice/v2/ice.go b/vendor/github.com/pion/ice/v2/ice.go new file mode 100644 index 0000000..d7094f6 --- /dev/null +++ b/vendor/github.com/pion/ice/v2/ice.go @@ -0,0 +1,76 @@ +package ice + +// ConnectionState is an enum showing the state of a ICE Connection +type ConnectionState int + +// List of supported States +const ( + // ConnectionStateNew ICE agent is gathering addresses + ConnectionStateNew = iota + 1 + + // ConnectionStateChecking ICE agent has been given local and remote candidates, and is attempting to find a match + ConnectionStateChecking + + // ConnectionStateConnected ICE agent has a pairing, but is still checking other pairs + ConnectionStateConnected + + // ConnectionStateCompleted ICE agent has finished + ConnectionStateCompleted + + // ConnectionStateFailed ICE agent never could successfully connect + ConnectionStateFailed + + // ConnectionStateDisconnected ICE agent connected successfully, but has entered a failed state + ConnectionStateDisconnected + + // ConnectionStateClosed ICE agent has finished and is no longer handling requests + ConnectionStateClosed +) + +func (c ConnectionState) String() string { + switch c { + case ConnectionStateNew: + return "New" + case ConnectionStateChecking: + return "Checking" + case ConnectionStateConnected: + return "Connected" + case ConnectionStateCompleted: + return "Completed" + case ConnectionStateFailed: + return "Failed" + case ConnectionStateDisconnected: + return "Disconnected" + case ConnectionStateClosed: + return "Closed" + default: + return "Invalid" + } +} + +// GatheringState describes the state of the candidate gathering process +type GatheringState int + +const ( + // GatheringStateNew indicates candidate gatering is not yet started + GatheringStateNew GatheringState = iota + 1 + + // GatheringStateGathering indicates candidate gatering is ongoing + GatheringStateGathering + + // GatheringStateComplete indicates candidate gatering has been completed + GatheringStateComplete +) + +func (t GatheringState) String() string { + switch t { + case GatheringStateNew: + return "new" + case GatheringStateGathering: + return "gathering" + case GatheringStateComplete: + return "complete" + default: + return ErrUnknownType.Error() + } +} diff --git a/vendor/github.com/pion/ice/v2/icecontrol.go b/vendor/github.com/pion/ice/v2/icecontrol.go new file mode 100644 index 0000000..ede2e09 --- /dev/null +++ b/vendor/github.com/pion/ice/v2/icecontrol.go @@ -0,0 +1,87 @@ +package ice + +import ( + "encoding/binary" + + "github.com/pion/stun" +) + +// tiebreaker is common helper for ICE-{CONTROLLED,CONTROLLING} +// and represents the so-called tiebreaker number. +type tiebreaker uint64 + +const tiebreakerSize = 8 // 64 bit + +// AddToAs adds tiebreaker value to m as t attribute. +func (a tiebreaker) AddToAs(m *stun.Message, t stun.AttrType) error { + v := make([]byte, tiebreakerSize) + binary.BigEndian.PutUint64(v, uint64(a)) + m.Add(t, v) + return nil +} + +// GetFromAs decodes tiebreaker value in message getting it as for t type. +func (a *tiebreaker) GetFromAs(m *stun.Message, t stun.AttrType) error { + v, err := m.Get(t) + if err != nil { + return err + } + if err = stun.CheckSize(t, len(v), tiebreakerSize); err != nil { + return err + } + *a = tiebreaker(binary.BigEndian.Uint64(v)) + return nil +} + +// AttrControlled represents ICE-CONTROLLED attribute. +type AttrControlled uint64 + +// AddTo adds ICE-CONTROLLED to message. +func (c AttrControlled) AddTo(m *stun.Message) error { + return tiebreaker(c).AddToAs(m, stun.AttrICEControlled) +} + +// GetFrom decodes ICE-CONTROLLED from message. +func (c *AttrControlled) GetFrom(m *stun.Message) error { + return (*tiebreaker)(c).GetFromAs(m, stun.AttrICEControlled) +} + +// AttrControlling represents ICE-CONTROLLING attribute. +type AttrControlling uint64 + +// AddTo adds ICE-CONTROLLING to message. +func (c AttrControlling) AddTo(m *stun.Message) error { + return tiebreaker(c).AddToAs(m, stun.AttrICEControlling) +} + +// GetFrom decodes ICE-CONTROLLING from message. +func (c *AttrControlling) GetFrom(m *stun.Message) error { + return (*tiebreaker)(c).GetFromAs(m, stun.AttrICEControlling) +} + +// AttrControl is helper that wraps ICE-{CONTROLLED,CONTROLLING}. +type AttrControl struct { + Role Role + Tiebreaker uint64 +} + +// AddTo adds ICE-CONTROLLED or ICE-CONTROLLING attribute depending on Role. +func (c AttrControl) AddTo(m *stun.Message) error { + if c.Role == Controlling { + return tiebreaker(c.Tiebreaker).AddToAs(m, stun.AttrICEControlling) + } + return tiebreaker(c.Tiebreaker).AddToAs(m, stun.AttrICEControlled) +} + +// GetFrom decodes Role and Tiebreaker value from message. +func (c *AttrControl) GetFrom(m *stun.Message) error { + if m.Contains(stun.AttrICEControlling) { + c.Role = Controlling + return (*tiebreaker)(&c.Tiebreaker).GetFromAs(m, stun.AttrICEControlling) + } + if m.Contains(stun.AttrICEControlled) { + c.Role = Controlled + return (*tiebreaker)(&c.Tiebreaker).GetFromAs(m, stun.AttrICEControlled) + } + return stun.ErrAttributeNotFound +} diff --git a/vendor/github.com/pion/ice/v2/mdns.go b/vendor/github.com/pion/ice/v2/mdns.go new file mode 100644 index 0000000..5a431d1 --- /dev/null +++ b/vendor/github.com/pion/ice/v2/mdns.go @@ -0,0 +1,63 @@ +package ice + +import ( + "net" + + "github.com/google/uuid" + "github.com/pion/logging" + "github.com/pion/mdns" + "golang.org/x/net/ipv4" +) + +// MulticastDNSMode represents the different Multicast modes ICE can run in +type MulticastDNSMode byte + +// MulticastDNSMode enum +const ( + // MulticastDNSModeDisabled means remote mDNS candidates will be discarded, and local host candidates will use IPs + MulticastDNSModeDisabled MulticastDNSMode = iota + 1 + + // MulticastDNSModeQueryOnly means remote mDNS candidates will be accepted, and local host candidates will use IPs + MulticastDNSModeQueryOnly + + // MulticastDNSModeQueryAndGather means remote mDNS candidates will be accepted, and local host candidates will use mDNS + MulticastDNSModeQueryAndGather +) + +func generateMulticastDNSName() (string, error) { + // https://tools.ietf.org/id/draft-ietf-rtcweb-mdns-ice-candidates-02.html#gathering + // The unique name MUST consist of a version 4 UUID as defined in [RFC4122], followed by “.local”. + u, err := uuid.NewRandom() + return u.String() + ".local", err +} + +func createMulticastDNS(mDNSMode MulticastDNSMode, mDNSName string, log logging.LeveledLogger) (*mdns.Conn, MulticastDNSMode, error) { + if mDNSMode == MulticastDNSModeDisabled { + return nil, mDNSMode, nil + } + + addr, mdnsErr := net.ResolveUDPAddr("udp4", mdns.DefaultAddress) + if mdnsErr != nil { + return nil, mDNSMode, mdnsErr + } + + l, mdnsErr := net.ListenUDP("udp4", addr) + if mdnsErr != nil { + // If ICE fails to start MulticastDNS server just warn the user and continue + log.Errorf("Failed to enable mDNS, continuing in mDNS disabled mode: (%s)", mdnsErr) + return nil, MulticastDNSModeDisabled, nil + } + + switch mDNSMode { + case MulticastDNSModeQueryOnly: + conn, err := mdns.Server(ipv4.NewPacketConn(l), &mdns.Config{}) + return conn, mDNSMode, err + case MulticastDNSModeQueryAndGather: + conn, err := mdns.Server(ipv4.NewPacketConn(l), &mdns.Config{ + LocalNames: []string{mDNSName}, + }) + return conn, mDNSMode, err + default: + return nil, mDNSMode, nil + } +} diff --git a/vendor/github.com/pion/ice/v2/networktype.go b/vendor/github.com/pion/ice/v2/networktype.go new file mode 100644 index 0000000..462ff2d --- /dev/null +++ b/vendor/github.com/pion/ice/v2/networktype.go @@ -0,0 +1,130 @@ +package ice + +import ( + "fmt" + "net" + "strings" +) + +const ( + udp = "udp" + tcp = "tcp" +) + +func supportedNetworkTypes() []NetworkType { + return []NetworkType{ + NetworkTypeUDP4, + NetworkTypeUDP6, + NetworkTypeTCP4, + NetworkTypeTCP6, + } +} + +// NetworkType represents the type of network +type NetworkType int + +const ( + // NetworkTypeUDP4 indicates UDP over IPv4. + NetworkTypeUDP4 NetworkType = iota + 1 + + // NetworkTypeUDP6 indicates UDP over IPv6. + NetworkTypeUDP6 + + // NetworkTypeTCP4 indicates TCP over IPv4. + NetworkTypeTCP4 + + // NetworkTypeTCP6 indicates TCP over IPv6. + NetworkTypeTCP6 +) + +func (t NetworkType) String() string { + switch t { + case NetworkTypeUDP4: + return "udp4" + case NetworkTypeUDP6: + return "udp6" + case NetworkTypeTCP4: + return "tcp4" + case NetworkTypeTCP6: + return "tcp6" + default: + return ErrUnknownType.Error() + } +} + +// IsUDP returns true when network is UDP4 or UDP6. +func (t NetworkType) IsUDP() bool { + return t == NetworkTypeUDP4 || t == NetworkTypeUDP6 +} + +// IsTCP returns true when network is TCP4 or TCP6. +func (t NetworkType) IsTCP() bool { + return t == NetworkTypeTCP4 || t == NetworkTypeTCP6 +} + +// NetworkShort returns the short network description +func (t NetworkType) NetworkShort() string { + switch t { + case NetworkTypeUDP4, NetworkTypeUDP6: + return udp + case NetworkTypeTCP4, NetworkTypeTCP6: + return tcp + default: + return ErrUnknownType.Error() + } +} + +// IsReliable returns true if the network is reliable +func (t NetworkType) IsReliable() bool { + switch t { + case NetworkTypeUDP4, NetworkTypeUDP6: + return false + case NetworkTypeTCP4, NetworkTypeTCP6: + return true + } + return false +} + +// IsIPv4 returns whether the network type is IPv4 or not. +func (t NetworkType) IsIPv4() bool { + switch t { + case NetworkTypeUDP4, NetworkTypeTCP4: + return true + case NetworkTypeUDP6, NetworkTypeTCP6: + return false + } + return false +} + +// IsIPv6 returns whether the network type is IPv6 or not. +func (t NetworkType) IsIPv6() bool { + switch t { + case NetworkTypeUDP4, NetworkTypeTCP4: + return false + case NetworkTypeUDP6, NetworkTypeTCP6: + return true + } + return false +} + +// determineNetworkType determines the type of network based on +// the short network string and an IP address. +func determineNetworkType(network string, ip net.IP) (NetworkType, error) { + ipv4 := ip.To4() != nil + + switch { + case strings.HasPrefix(strings.ToLower(network), udp): + if ipv4 { + return NetworkTypeUDP4, nil + } + return NetworkTypeUDP6, nil + + case strings.HasPrefix(strings.ToLower(network), tcp): + if ipv4 { + return NetworkTypeTCP4, nil + } + return NetworkTypeTCP6, nil + } + + return NetworkType(0), fmt.Errorf("%w from %s %s", errDetermineNetworkType, network, ip) +} diff --git a/vendor/github.com/pion/ice/v2/priority.go b/vendor/github.com/pion/ice/v2/priority.go new file mode 100644 index 0000000..4218299 --- /dev/null +++ b/vendor/github.com/pion/ice/v2/priority.go @@ -0,0 +1,33 @@ +package ice + +import ( + "encoding/binary" + + "github.com/pion/stun" +) + +// PriorityAttr represents PRIORITY attribute. +type PriorityAttr uint32 + +const prioritySize = 4 // 32 bit + +// AddTo adds PRIORITY attribute to message. +func (p PriorityAttr) AddTo(m *stun.Message) error { + v := make([]byte, prioritySize) + binary.BigEndian.PutUint32(v, uint32(p)) + m.Add(stun.AttrPriority, v) + return nil +} + +// GetFrom decodes PRIORITY attribute from message. +func (p *PriorityAttr) GetFrom(m *stun.Message) error { + v, err := m.Get(stun.AttrPriority) + if err != nil { + return err + } + if err = stun.CheckSize(stun.AttrPriority, len(v), prioritySize); err != nil { + return err + } + *p = PriorityAttr(binary.BigEndian.Uint32(v)) + return nil +} diff --git a/vendor/github.com/pion/ice/v2/rand.go b/vendor/github.com/pion/ice/v2/rand.go new file mode 100644 index 0000000..918783e --- /dev/null +++ b/vendor/github.com/pion/ice/v2/rand.go @@ -0,0 +1,53 @@ +package ice + +import "github.com/pion/randutil" + +const ( + runesAlpha = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" + runesDigit = "0123456789" + runesCandidateIDFoundation = runesAlpha + runesDigit + "+/" + + lenUFrag = 16 + lenPwd = 32 +) + +// Seeding random generator each time limits number of generated sequence to 31-bits, +// and causes collision on low time accuracy environments. +// Use global random generator seeded by crypto grade random. +var ( + globalMathRandomGenerator = randutil.NewMathRandomGenerator() //nolint:gochecknoglobals + globalCandidateIDGenerator = candidateIDGenerator{globalMathRandomGenerator} //nolint:gochecknoglobals +) + +// candidateIDGenerator is a random candidate ID generator. +// Candidate ID is used in SDP and always shared to the other peer. +// It doesn't require cryptographic random. +type candidateIDGenerator struct { + randutil.MathRandomGenerator +} + +func newCandidateIDGenerator() *candidateIDGenerator { + return &candidateIDGenerator{ + randutil.NewMathRandomGenerator(), + } +} + +func (g *candidateIDGenerator) Generate() string { + // https://tools.ietf.org/html/rfc5245#section-15.1 + // candidate-id = "candidate" ":" foundation + // foundation = 1*32ice-char + // ice-char = ALPHA / DIGIT / "+" / "/" + return "candidate:" + g.MathRandomGenerator.GenerateString(32, runesCandidateIDFoundation) +} + +// generatePwd generates ICE pwd. +// This internally uses generateCryptoRandomString. +func generatePwd() (string, error) { + return randutil.GenerateCryptoRandomString(lenPwd, runesAlpha) +} + +// generateUFrag generates ICE user fragment. +// This internally uses generateCryptoRandomString. +func generateUFrag() (string, error) { + return randutil.GenerateCryptoRandomString(lenUFrag, runesAlpha) +} diff --git a/vendor/github.com/pion/ice/v2/renovate.json b/vendor/github.com/pion/ice/v2/renovate.json new file mode 100644 index 0000000..4400fd9 --- /dev/null +++ b/vendor/github.com/pion/ice/v2/renovate.json @@ -0,0 +1,15 @@ +{ + "extends": [ + "config:base" + ], + "postUpdateOptions": [ + "gomodTidy" + ], + "commitBody": "Generated by renovateBot", + "packageRules": [ + { + "packagePatterns": ["^golang.org/x/"], + "schedule": ["on the first day of the month"] + } + ] +} diff --git a/vendor/github.com/pion/ice/v2/role.go b/vendor/github.com/pion/ice/v2/role.go new file mode 100644 index 0000000..7a8bc06 --- /dev/null +++ b/vendor/github.com/pion/ice/v2/role.go @@ -0,0 +1,43 @@ +package ice + +import ( + "fmt" +) + +// Role represents ICE agent role, which can be controlling or controlled. +type Role byte + +// Possible ICE agent roles. +const ( + Controlling Role = iota + Controlled +) + +// UnmarshalText implements TextUnmarshaler. +func (r *Role) UnmarshalText(text []byte) error { + switch string(text) { + case "controlling": + *r = Controlling + case "controlled": + *r = Controlled + default: + return fmt.Errorf("%w %q", errUnknownRole, text) + } + return nil +} + +// MarshalText implements TextMarshaler. +func (r Role) MarshalText() (text []byte, err error) { + return []byte(r.String()), nil +} + +func (r Role) String() string { + switch r { + case Controlling: + return "controlling" + case Controlled: + return "controlled" + default: + return "unknown" + } +} diff --git a/vendor/github.com/pion/ice/v2/selection.go b/vendor/github.com/pion/ice/v2/selection.go new file mode 100644 index 0000000..e0cfb10 --- /dev/null +++ b/vendor/github.com/pion/ice/v2/selection.go @@ -0,0 +1,287 @@ +package ice + +import ( + "net" + "time" + + "github.com/pion/logging" + "github.com/pion/stun" +) + +type pairCandidateSelector interface { + Start() + ContactCandidates() + PingCandidate(local, remote Candidate) + HandleSuccessResponse(m *stun.Message, local, remote Candidate, remoteAddr net.Addr) + HandleBindingRequest(m *stun.Message, local, remote Candidate) +} + +type controllingSelector struct { + startTime time.Time + agent *Agent + nominatedPair *candidatePair + log logging.LeveledLogger +} + +func (s *controllingSelector) Start() { + s.startTime = time.Now() + s.nominatedPair = nil +} + +func (s *controllingSelector) isNominatable(c Candidate) bool { + switch { + case c.Type() == CandidateTypeHost: + return time.Since(s.startTime).Nanoseconds() > s.agent.hostAcceptanceMinWait.Nanoseconds() + case c.Type() == CandidateTypeServerReflexive: + return time.Since(s.startTime).Nanoseconds() > s.agent.srflxAcceptanceMinWait.Nanoseconds() + case c.Type() == CandidateTypePeerReflexive: + return time.Since(s.startTime).Nanoseconds() > s.agent.prflxAcceptanceMinWait.Nanoseconds() + case c.Type() == CandidateTypeRelay: + return time.Since(s.startTime).Nanoseconds() > s.agent.relayAcceptanceMinWait.Nanoseconds() + } + + s.log.Errorf("isNominatable invalid candidate type %s", c.Type().String()) + return false +} + +func (s *controllingSelector) ContactCandidates() { + switch { + case s.agent.getSelectedPair() != nil: + if s.agent.validateSelectedPair() { + s.log.Trace("checking keepalive") + s.agent.checkKeepalive() + } + case s.nominatedPair != nil: + s.nominatePair(s.nominatedPair) + default: + p := s.agent.getBestValidCandidatePair() + if p != nil && s.isNominatable(p.local) && s.isNominatable(p.remote) { + s.log.Tracef("Nominatable pair found, nominating (%s, %s)", p.local.String(), p.remote.String()) + p.nominated = true + s.nominatedPair = p + s.nominatePair(p) + return + } + s.agent.pingAllCandidates() + } +} + +func (s *controllingSelector) nominatePair(pair *candidatePair) { + // The controlling agent MUST include the USE-CANDIDATE attribute in + // order to nominate a candidate pair (Section 8.1.1). The controlled + // agent MUST NOT include the USE-CANDIDATE attribute in a Binding + // request. + msg, err := stun.Build(stun.BindingRequest, stun.TransactionID, + stun.NewUsername(s.agent.remoteUfrag+":"+s.agent.localUfrag), + UseCandidate(), + AttrControlling(s.agent.tieBreaker), + PriorityAttr(pair.local.Priority()), + stun.NewShortTermIntegrity(s.agent.remotePwd), + stun.Fingerprint, + ) + if err != nil { + s.log.Error(err.Error()) + return + } + + s.log.Tracef("ping STUN (nominate candidate pair) from %s to %s\n", pair.local.String(), pair.remote.String()) + s.agent.sendBindingRequest(msg, pair.local, pair.remote) +} + +func (s *controllingSelector) HandleBindingRequest(m *stun.Message, local, remote Candidate) { + s.agent.sendBindingSuccess(m, local, remote) + + p := s.agent.findPair(local, remote) + + if p == nil { + s.agent.addPair(local, remote) + return + } + + if p.state == CandidatePairStateSucceeded && s.nominatedPair == nil && s.agent.getSelectedPair() == nil { + bestPair := s.agent.getBestAvailableCandidatePair() + if bestPair == nil { + s.log.Tracef("No best pair available\n") + } else if bestPair.Equal(p) && s.isNominatable(p.local) && s.isNominatable(p.remote) { + s.log.Tracef("The candidate (%s, %s) is the best candidate available, marking it as nominated\n", + p.local.String(), p.remote.String()) + s.nominatedPair = p + s.nominatePair(p) + } + } +} + +func (s *controllingSelector) HandleSuccessResponse(m *stun.Message, local, remote Candidate, remoteAddr net.Addr) { + ok, pendingRequest := s.agent.handleInboundBindingSuccess(m.TransactionID) + if !ok { + s.log.Warnf("discard message from (%s), unknown TransactionID 0x%x", remote, m.TransactionID) + return + } + + transactionAddr := pendingRequest.destination + + // Assert that NAT is not symmetric + // https://tools.ietf.org/html/rfc8445#section-7.2.5.2.1 + if !addrEqual(transactionAddr, remoteAddr) { + s.log.Debugf("discard message: transaction source and destination does not match expected(%s), actual(%s)", transactionAddr, remote) + return + } + + s.log.Tracef("inbound STUN (SuccessResponse) from %s to %s", remote.String(), local.String()) + p := s.agent.findPair(local, remote) + + if p == nil { + // This shouldn't happen + s.log.Error("Success response from invalid candidate pair") + return + } + + p.state = CandidatePairStateSucceeded + s.log.Tracef("Found valid candidate pair: %s", p) + if pendingRequest.isUseCandidate && s.agent.getSelectedPair() == nil { + s.agent.setSelectedPair(p) + } +} + +func (s *controllingSelector) PingCandidate(local, remote Candidate) { + msg, err := stun.Build(stun.BindingRequest, stun.TransactionID, + stun.NewUsername(s.agent.remoteUfrag+":"+s.agent.localUfrag), + AttrControlling(s.agent.tieBreaker), + PriorityAttr(local.Priority()), + stun.NewShortTermIntegrity(s.agent.remotePwd), + stun.Fingerprint, + ) + if err != nil { + s.log.Error(err.Error()) + return + } + + s.agent.sendBindingRequest(msg, local, remote) +} + +type controlledSelector struct { + agent *Agent + log logging.LeveledLogger +} + +func (s *controlledSelector) Start() { +} + +func (s *controlledSelector) ContactCandidates() { + if s.agent.getSelectedPair() != nil { + if s.agent.validateSelectedPair() { + s.log.Trace("checking keepalive") + s.agent.checkKeepalive() + } + } else { + s.agent.pingAllCandidates() + } +} + +func (s *controlledSelector) PingCandidate(local, remote Candidate) { + msg, err := stun.Build(stun.BindingRequest, stun.TransactionID, + stun.NewUsername(s.agent.remoteUfrag+":"+s.agent.localUfrag), + AttrControlled(s.agent.tieBreaker), + PriorityAttr(local.Priority()), + stun.NewShortTermIntegrity(s.agent.remotePwd), + stun.Fingerprint, + ) + if err != nil { + s.log.Error(err.Error()) + return + } + + s.agent.sendBindingRequest(msg, local, remote) +} + +func (s *controlledSelector) HandleSuccessResponse(m *stun.Message, local, remote Candidate, remoteAddr net.Addr) { + // nolint:godox + // TODO according to the standard we should specifically answer a failed nomination: + // https://tools.ietf.org/html/rfc8445#section-7.3.1.5 + // If the controlled agent does not accept the request from the + // controlling agent, the controlled agent MUST reject the nomination + // request with an appropriate error code response (e.g., 400) + // [RFC5389]. + + ok, pendingRequest := s.agent.handleInboundBindingSuccess(m.TransactionID) + if !ok { + s.log.Warnf("discard message from (%s), unknown TransactionID 0x%x", remote, m.TransactionID) + return + } + + transactionAddr := pendingRequest.destination + + // Assert that NAT is not symmetric + // https://tools.ietf.org/html/rfc8445#section-7.2.5.2.1 + if !addrEqual(transactionAddr, remoteAddr) { + s.log.Debugf("discard message: transaction source and destination does not match expected(%s), actual(%s)", transactionAddr, remote) + return + } + + s.log.Tracef("inbound STUN (SuccessResponse) from %s to %s", remote.String(), local.String()) + + p := s.agent.findPair(local, remote) + if p == nil { + // This shouldn't happen + s.log.Error("Success response from invalid candidate pair") + return + } + + p.state = CandidatePairStateSucceeded + s.log.Tracef("Found valid candidate pair: %s", p) +} + +func (s *controlledSelector) HandleBindingRequest(m *stun.Message, local, remote Candidate) { + useCandidate := m.Contains(stun.AttrUseCandidate) + + p := s.agent.findPair(local, remote) + + if p == nil { + p = s.agent.addPair(local, remote) + } + + if useCandidate { + // https://tools.ietf.org/html/rfc8445#section-7.3.1.5 + + if p.state == CandidatePairStateSucceeded { + // If the state of this pair is Succeeded, it means that the check + // previously sent by this pair produced a successful response and + // generated a valid pair (Section 7.2.5.3.2). The agent sets the + // nominated flag value of the valid pair to true. + if selectedPair := s.agent.getSelectedPair(); selectedPair == nil { + s.agent.setSelectedPair(p) + } + s.agent.sendBindingSuccess(m, local, remote) + } else { + // If the received Binding request triggered a new check to be + // enqueued in the triggered-check queue (Section 7.3.1.4), once the + // check is sent and if it generates a successful response, and + // generates a valid pair, the agent sets the nominated flag of the + // pair to true. If the request fails (Section 7.2.5.2), the agent + // MUST remove the candidate pair from the valid list, set the + // candidate pair state to Failed, and set the checklist state to + // Failed. + s.PingCandidate(local, remote) + } + } else { + s.agent.sendBindingSuccess(m, local, remote) + s.PingCandidate(local, remote) + } +} + +type liteSelector struct { + pairCandidateSelector +} + +// A lite selector should not contact candidates +func (s *liteSelector) ContactCandidates() { + if _, ok := s.pairCandidateSelector.(*controllingSelector); ok { + // nolint:godox + // pion/ice#96 + // TODO: implement lite controlling agent. For now falling back to full agent. + // This only happens if both peers are lite. See RFC 8445 S6.1.1 and S6.2 + s.pairCandidateSelector.ContactCandidates() + } else if v, ok := s.pairCandidateSelector.(*controlledSelector); ok { + v.agent.validateSelectedPair() + } +} diff --git a/vendor/github.com/pion/ice/v2/stats.go b/vendor/github.com/pion/ice/v2/stats.go new file mode 100644 index 0000000..f59d89f --- /dev/null +++ b/vendor/github.com/pion/ice/v2/stats.go @@ -0,0 +1,177 @@ +package ice + +import ( + "time" +) + +// CandidatePairStats contains ICE candidate pair statistics +type CandidatePairStats struct { + // Timestamp is the timestamp associated with this object. + Timestamp time.Time + + // LocalCandidateID is the ID of the local candidate + LocalCandidateID string + + // RemoteCandidateID is the ID of the remote candidate + RemoteCandidateID string + + // State represents the state of the checklist for the local and remote + // candidates in a pair. + State CandidatePairState + + // Nominated is true when this valid pair that should be used for media + // if it is the highest-priority one amongst those whose nominated flag is set + Nominated bool + + // PacketsSent represents the total number of packets sent on this candidate pair. + PacketsSent uint32 + + // PacketsReceived represents the total number of packets received on this candidate pair. + PacketsReceived uint32 + + // BytesSent represents the total number of payload bytes sent on this candidate pair + // not including headers or padding. + BytesSent uint64 + + // BytesReceived represents the total number of payload bytes received on this candidate pair + // not including headers or padding. + BytesReceived uint64 + + // LastPacketSentTimestamp represents the timestamp at which the last packet was + // sent on this particular candidate pair, excluding STUN packets. + LastPacketSentTimestamp time.Time + + // LastPacketReceivedTimestamp represents the timestamp at which the last packet + // was received on this particular candidate pair, excluding STUN packets. + LastPacketReceivedTimestamp time.Time + + // FirstRequestTimestamp represents the timestamp at which the first STUN request + // was sent on this particular candidate pair. + FirstRequestTimestamp time.Time + + // LastRequestTimestamp represents the timestamp at which the last STUN request + // was sent on this particular candidate pair. The average interval between two + // consecutive connectivity checks sent can be calculated with + // (LastRequestTimestamp - FirstRequestTimestamp) / RequestsSent. + LastRequestTimestamp time.Time + + // LastResponseTimestamp represents the timestamp at which the last STUN response + // was received on this particular candidate pair. + LastResponseTimestamp time.Time + + // TotalRoundTripTime represents the sum of all round trip time measurements + // in seconds since the beginning of the session, based on STUN connectivity + // check responses (ResponsesReceived), including those that reply to requests + // that are sent in order to verify consent. The average round trip time can + // be computed from TotalRoundTripTime by dividing it by ResponsesReceived. + TotalRoundTripTime float64 + + // CurrentRoundTripTime represents the latest round trip time measured in seconds, + // computed from both STUN connectivity checks, including those that are sent + // for consent verification. + CurrentRoundTripTime float64 + + // AvailableOutgoingBitrate is calculated by the underlying congestion control + // by combining the available bitrate for all the outgoing RTP streams using + // this candidate pair. The bitrate measurement does not count the size of the + // IP or other transport layers like TCP or UDP. It is similar to the TIAS defined + // in RFC 3890, i.e., it is measured in bits per second and the bitrate is calculated + // over a 1 second window. + AvailableOutgoingBitrate float64 + + // AvailableIncomingBitrate is calculated by the underlying congestion control + // by combining the available bitrate for all the incoming RTP streams using + // this candidate pair. The bitrate measurement does not count the size of the + // IP or other transport layers like TCP or UDP. It is similar to the TIAS defined + // in RFC 3890, i.e., it is measured in bits per second and the bitrate is + // calculated over a 1 second window. + AvailableIncomingBitrate float64 + + // CircuitBreakerTriggerCount represents the number of times the circuit breaker + // is triggered for this particular 5-tuple, ceasing transmission. + CircuitBreakerTriggerCount uint32 + + // RequestsReceived represents the total number of connectivity check requests + // received (including retransmissions). It is impossible for the receiver to + // tell whether the request was sent in order to check connectivity or check + // consent, so all connectivity checks requests are counted here. + RequestsReceived uint64 + + // RequestsSent represents the total number of connectivity check requests + // sent (not including retransmissions). + RequestsSent uint64 + + // ResponsesReceived represents the total number of connectivity check responses received. + ResponsesReceived uint64 + + // ResponsesSent epresents the total number of connectivity check responses sent. + // Since we cannot distinguish connectivity check requests and consent requests, + // all responses are counted. + ResponsesSent uint64 + + // RetransmissionsReceived represents the total number of connectivity check + // request retransmissions received. + RetransmissionsReceived uint64 + + // RetransmissionsSent represents the total number of connectivity check + // request retransmissions sent. + RetransmissionsSent uint64 + + // ConsentRequestsSent represents the total number of consent requests sent. + ConsentRequestsSent uint64 + + // ConsentExpiredTimestamp represents the timestamp at which the latest valid + // STUN binding response expired. + ConsentExpiredTimestamp time.Time +} + +// CandidateStats contains ICE candidate statistics related to the ICETransport objects. +type CandidateStats struct { + // Timestamp is the timestamp associated with this object. + Timestamp time.Time + + // ID is the candidate ID + ID string + + // NetworkType represents the type of network interface used by the base of a + // local candidate (the address the ICE agent sends from). Only present for + // local candidates; it's not possible to know what type of network interface + // a remote candidate is using. + // + // Note: + // This stat only tells you about the network interface used by the first "hop"; + // it's possible that a connection will be bottlenecked by another type of network. + // For example, when using Wi-Fi tethering, the networkType of the relevant candidate + // would be "wifi", even when the next hop is over a cellular connection. + NetworkType NetworkType + + // IP is the IP address of the candidate, allowing for IPv4 addresses and + // IPv6 addresses, but fully qualified domain names (FQDNs) are not allowed. + IP string + + // Port is the port number of the candidate. + Port int + + // CandidateType is the "Type" field of the ICECandidate. + CandidateType CandidateType + + // Priority is the "Priority" field of the ICECandidate. + Priority uint32 + + // URL is the URL of the TURN or STUN server indicated in the that translated + // this IP address. It is the URL address surfaced in an PeerConnectionICEEvent. + URL string + + // RelayProtocol is the protocol used by the endpoint to communicate with the + // TURN server. This is only present for local candidates. Valid values for + // the TURN URL protocol is one of udp, tcp, or tls. + RelayProtocol string + + // Deleted is true if the candidate has been deleted/freed. For host candidates, + // this means that any network resources (typically a socket) associated with the + // candidate have been released. For TURN candidates, this means the TURN allocation + // is no longer active. + // + // Only defined for local candidates. For remote candidates, this property is not applicable. + Deleted bool +} diff --git a/vendor/github.com/pion/ice/v2/stun.go b/vendor/github.com/pion/ice/v2/stun.go new file mode 100644 index 0000000..bef7c87 --- /dev/null +++ b/vendor/github.com/pion/ice/v2/stun.go @@ -0,0 +1,24 @@ +package ice + +import ( + "fmt" + + "github.com/pion/stun" +) + +func assertInboundUsername(m *stun.Message, expectedUsername string) error { + var username stun.Username + if err := username.GetFrom(m); err != nil { + return err + } + if string(username) != expectedUsername { + return fmt.Errorf("%w expected(%x) actual(%x)", errMismatchUsername, expectedUsername, string(username)) + } + + return nil +} + +func assertInboundMessageIntegrity(m *stun.Message, key []byte) error { + messageIntegrityAttr := stun.MessageIntegrity(key) + return messageIntegrityAttr.Check(m) +} diff --git a/vendor/github.com/pion/ice/v2/tcp_mux.go b/vendor/github.com/pion/ice/v2/tcp_mux.go new file mode 100644 index 0000000..1a9a797 --- /dev/null +++ b/vendor/github.com/pion/ice/v2/tcp_mux.go @@ -0,0 +1,295 @@ +package ice + +import ( + "encoding/binary" + "io" + "net" + "strings" + "sync" + + "github.com/pion/logging" + "github.com/pion/stun" +) + +// TCPMux is allows grouping multiple TCP net.Conns and using them like UDP +// net.PacketConns. The main implementation of this is TCPMuxDefault, and this +// interface exists to: +// 1. prevent SEGV panics when TCPMuxDefault is not initialized by using the +// invalidTCPMux implementation, and +// 2. allow mocking in tests. +type TCPMux interface { + io.Closer + GetConnByUfrag(ufrag string) (net.PacketConn, error) + RemoveConnByUfrag(ufrag string) +} + +// invalidTCPMux is an implementation of TCPMux that always returns ErroTCPMuxNotInitialized. +type invalidTCPMux struct { +} + +func newInvalidTCPMux() *invalidTCPMux { + return &invalidTCPMux{} +} + +// Close implements TCPMux interface. +func (m *invalidTCPMux) Close() error { + return ErrTCPMuxNotInitialized +} + +// GetConnByUfrag implements TCPMux interface. +func (m *invalidTCPMux) GetConnByUfrag(ufrag string) (net.PacketConn, error) { + return nil, ErrTCPMuxNotInitialized +} + +// RemoveConnByUfrag implements TCPMux interface. +func (m *invalidTCPMux) RemoveConnByUfrag(ufrag string) {} + +// TCPMuxDefault muxes TCP net.Conns into net.PacketConns and groups them by +// Ufrag. It is a default implementation of TCPMux interface. +type TCPMuxDefault struct { + params *TCPMuxParams + closed bool + + // conns is a map of all tcpPacketConns indexed by ufrag + conns map[string]*tcpPacketConn + + mu sync.Mutex + wg sync.WaitGroup +} + +// TCPMuxParams are parameters for TCPMux. +type TCPMuxParams struct { + Listener net.Listener + Logger logging.LeveledLogger + ReadBufferSize int +} + +// NewTCPMuxDefault creates a new instance of TCPMuxDefault. +func NewTCPMuxDefault(params TCPMuxParams) *TCPMuxDefault { + if params.Logger == nil { + params.Logger = logging.NewDefaultLoggerFactory().NewLogger("ice") + } + + m := &TCPMuxDefault{ + params: ¶ms, + + conns: map[string]*tcpPacketConn{}, + } + + m.wg.Add(1) + go func() { + defer m.wg.Done() + m.start() + }() + + return m +} + +func (m *TCPMuxDefault) start() { + m.params.Logger.Infof("Listening TCP on %s\n", m.params.Listener.Addr()) + for { + conn, err := m.params.Listener.Accept() + if err != nil { + m.params.Logger.Infof("Error accepting connection: %s\n", err) + return + } + + m.params.Logger.Debugf("Accepted connection from: %s to %s", conn.RemoteAddr(), conn.LocalAddr()) + + m.wg.Add(1) + go func() { + defer m.wg.Done() + m.handleConn(conn) + }() + } +} + +// LocalAddr returns the listening address of this TCPMuxDefault. +func (m *TCPMuxDefault) LocalAddr() net.Addr { + return m.params.Listener.Addr() +} + +// GetConnByUfrag retrieves an existing or creates a new net.PacketConn. +func (m *TCPMuxDefault) GetConnByUfrag(ufrag string) (net.PacketConn, error) { + m.mu.Lock() + defer m.mu.Unlock() + + if m.closed { + return nil, io.ErrClosedPipe + } + + conn, ok := m.conns[ufrag] + + if ok { + return conn, nil + // return nil, fmt.Errorf("duplicate ufrag %v", ufrag) + } + + conn = m.createConn(ufrag, m.LocalAddr()) + + return conn, nil +} + +func (m *TCPMuxDefault) createConn(ufrag string, localAddr net.Addr) *tcpPacketConn { + conn := newTCPPacketConn(tcpPacketParams{ + ReadBuffer: m.params.ReadBufferSize, + LocalAddr: localAddr, + Logger: m.params.Logger, + }) + m.conns[ufrag] = conn + + m.wg.Add(1) + go func() { + defer m.wg.Done() + <-conn.CloseChannel() + m.RemoveConnByUfrag(ufrag) + }() + + return conn +} + +func (m *TCPMuxDefault) closeAndLogError(closer io.Closer) { + err := closer.Close() + if err != nil { + m.params.Logger.Warnf("Error closing connection: %s", err) + } +} + +func (m *TCPMuxDefault) handleConn(conn net.Conn) { + buf := make([]byte, receiveMTU) + + n, err := readStreamingPacket(conn, buf) + if err != nil { + m.params.Logger.Warnf("Error reading first packet: %s", err) + return + } + + buf = buf[:n] + + msg := &stun.Message{ + Raw: make([]byte, len(buf)), + } + // Explicitly copy raw buffer so Message can own the memory. + copy(msg.Raw, buf) + if err = msg.Decode(); err != nil { + m.closeAndLogError(conn) + m.params.Logger.Warnf("Failed to handle decode ICE from %s to %s: %v\n", conn.RemoteAddr(), conn.LocalAddr(), err) + return + } + + if m == nil || msg.Type.Method != stun.MethodBinding { // not a stun + m.closeAndLogError(conn) + m.params.Logger.Warnf("Not a STUN message from %s to %s\n", conn.RemoteAddr(), conn.LocalAddr()) + return + } + + for _, attr := range msg.Attributes { + m.params.Logger.Debugf("msg attr: %s\n", attr.String()) + } + + attr, err := msg.Get(stun.AttrUsername) + if err != nil { + m.closeAndLogError(conn) + m.params.Logger.Warnf("No Username attribute in STUN message from %s to %s\n", conn.RemoteAddr(), conn.LocalAddr()) + return + } + + ufrag := strings.Split(string(attr), ":")[0] + m.params.Logger.Debugf("Ufrag: %s\n", ufrag) + + m.mu.Lock() + defer m.mu.Unlock() + + packetConn, ok := m.conns[ufrag] + if !ok { + packetConn = m.createConn(ufrag, conn.LocalAddr()) + } + + if err := packetConn.AddConn(conn, buf); err != nil { + m.closeAndLogError(conn) + m.params.Logger.Warnf("Error adding conn to tcpPacketConn from %s to %s: %s\n", conn.RemoteAddr(), conn.LocalAddr(), err) + return + } +} + +// Close closes the listener and waits for all goroutines to exit. +func (m *TCPMuxDefault) Close() error { + m.mu.Lock() + m.closed = true + + for _, conn := range m.conns { + m.closeAndLogError(conn) + } + m.conns = map[string]*tcpPacketConn{} + + err := m.params.Listener.Close() + + m.mu.Unlock() + + m.wg.Wait() + + return err +} + +// RemoveConnByUfrag closes and removes a net.PacketConn by Ufrag. +func (m *TCPMuxDefault) RemoveConnByUfrag(ufrag string) { + m.mu.Lock() + defer m.mu.Unlock() + + if conn, ok := m.conns[ufrag]; ok { + m.closeAndLogError(conn) + delete(m.conns, ufrag) + } +} + +const streamingPacketHeaderLen = 2 + +// readStreamingPacket reads 1 packet from stream +// read packet bytes https://tools.ietf.org/html/rfc4571#section-2 +// 2-byte length header prepends each packet: +// 0 1 2 3 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// ----------------------------------------------------------------- +// | LENGTH | RTP or RTCP packet ... | +// ----------------------------------------------------------------- +func readStreamingPacket(conn net.Conn, buf []byte) (int, error) { + header := make([]byte, streamingPacketHeaderLen) + var bytesRead, n int + var err error + + for bytesRead < streamingPacketHeaderLen { + if n, err = conn.Read(header[bytesRead:streamingPacketHeaderLen]); err != nil { + return 0, err + } + bytesRead += n + } + + length := int(binary.BigEndian.Uint16(header)) + + if length > cap(buf) { + return length, io.ErrShortBuffer + } + + bytesRead = 0 + for bytesRead < length { + if n, err = conn.Read(buf[bytesRead:length]); err != nil { + return 0, err + } + bytesRead += n + } + + return bytesRead, nil +} + +func writeStreamingPacket(conn net.Conn, buf []byte) (int, error) { + bufferCopy := make([]byte, streamingPacketHeaderLen+len(buf)) + binary.BigEndian.PutUint16(bufferCopy, uint16(len(buf))) + copy(bufferCopy[2:], buf) + + n, err := conn.Write(bufferCopy) + if err != nil { + return 0, err + } + + return n - streamingPacketHeaderLen, nil +} diff --git a/vendor/github.com/pion/ice/v2/tcp_packet_conn.go b/vendor/github.com/pion/ice/v2/tcp_packet_conn.go new file mode 100644 index 0000000..dc4eaf0 --- /dev/null +++ b/vendor/github.com/pion/ice/v2/tcp_packet_conn.go @@ -0,0 +1,240 @@ +package ice + +import ( + "fmt" + "io" + "net" + "sync" + "time" + + "github.com/pion/logging" +) + +type tcpPacketConn struct { + params *tcpPacketParams + + // conns is a map of net.Conns indexed by remote net.Addr.String() + conns map[string]net.Conn + + recvChan chan streamingPacket + + mu sync.Mutex + wg sync.WaitGroup + closedChan chan struct{} + closeOnce sync.Once +} + +type streamingPacket struct { + Data []byte + RAddr net.Addr + Err error +} + +type tcpPacketParams struct { + ReadBuffer int + LocalAddr net.Addr + Logger logging.LeveledLogger +} + +func newTCPPacketConn(params tcpPacketParams) *tcpPacketConn { + p := &tcpPacketConn{ + params: ¶ms, + + conns: map[string]net.Conn{}, + + recvChan: make(chan streamingPacket, params.ReadBuffer), + closedChan: make(chan struct{}), + } + + return p +} + +func (t *tcpPacketConn) AddConn(conn net.Conn, firstPacketData []byte) error { + t.params.Logger.Infof("AddConn: %s %s", conn.RemoteAddr().Network(), conn.RemoteAddr()) + + t.mu.Lock() + defer t.mu.Unlock() + + select { + case <-t.closedChan: + return io.ErrClosedPipe + default: + } + + if _, ok := t.conns[conn.RemoteAddr().String()]; ok { + return fmt.Errorf("%w: %s", errConnectionAddrAlreadyExist, conn.RemoteAddr().String()) + } + + t.conns[conn.RemoteAddr().String()] = conn + + t.wg.Add(1) + go func() { + if firstPacketData != nil { + t.recvChan <- streamingPacket{firstPacketData, conn.RemoteAddr(), nil} + } + defer t.wg.Done() + t.startReading(conn) + }() + + return nil +} + +func (t *tcpPacketConn) startReading(conn net.Conn) { + buf := make([]byte, receiveMTU) + + for { + n, err := readStreamingPacket(conn, buf) + // t.params.Logger.Infof("readStreamingPacket read %d bytes", n) + if err != nil { + t.params.Logger.Infof("%w: %s\n", errReadingStreamingPacket, err) + t.handleRecv(streamingPacket{nil, conn.RemoteAddr(), err}) + t.removeConn(conn) + return + } + + data := make([]byte, n) + copy(data, buf[:n]) + + // t.params.Logger.Infof("Writing read streaming packet to recvChan: %d bytes", len(data)) + t.handleRecv(streamingPacket{data, conn.RemoteAddr(), nil}) + } +} + +func (t *tcpPacketConn) handleRecv(pkt streamingPacket) { + t.mu.Lock() + + recvChan := t.recvChan + if t.isClosed() { + recvChan = nil + } + + t.mu.Unlock() + + select { + case recvChan <- pkt: + case <-t.closedChan: + } +} + +func (t *tcpPacketConn) isClosed() bool { + select { + case <-t.closedChan: + return true + default: + return false + } +} + +// WriteTo is for passive and s-o candidates. +func (t *tcpPacketConn) ReadFrom(b []byte) (n int, raddr net.Addr, err error) { + pkt, ok := <-t.recvChan + + if !ok { + return 0, nil, io.ErrClosedPipe + } + + if pkt.Err != nil { + return 0, pkt.RAddr, pkt.Err + } + + if cap(b) < len(pkt.Data) { + return 0, pkt.RAddr, io.ErrShortBuffer + } + + n = len(pkt.Data) + copy(b, pkt.Data[:n]) + return n, pkt.RAddr, err +} + +// WriteTo is for active and s-o candidates. +func (t *tcpPacketConn) WriteTo(buf []byte, raddr net.Addr) (n int, err error) { + t.mu.Lock() + defer t.mu.Unlock() + + conn, ok := t.conns[raddr.String()] + if !ok { + return 0, io.ErrClosedPipe + // conn, err := net.DialTCP(tcp, nil, raddr.(*net.TCPAddr)) + + // if err != nil { + // t.params.Logger.Tracef("DialTCP error: %s", err) + // return 0, err + // } + + // go t.startReading(conn) + // t.conns[raddr.String()] = conn + } + + n, err = writeStreamingPacket(conn, buf) + if err != nil { + t.params.Logger.Tracef("%w %s\n", errWriting, raddr) + return n, err + } + + return n, err +} + +func (t *tcpPacketConn) closeAndLogError(closer io.Closer) { + err := closer.Close() + if err != nil { + t.params.Logger.Warnf("%w: %s", errClosingConnection, err) + } +} + +func (t *tcpPacketConn) removeConn(conn net.Conn) { + t.mu.Lock() + defer t.mu.Unlock() + + t.closeAndLogError(conn) + + delete(t.conns, conn.RemoteAddr().String()) +} + +func (t *tcpPacketConn) Close() error { + t.mu.Lock() + + var shouldCloseRecvChan bool + t.closeOnce.Do(func() { + close(t.closedChan) + shouldCloseRecvChan = true + }) + + for _, conn := range t.conns { + t.closeAndLogError(conn) + delete(t.conns, conn.RemoteAddr().String()) + } + + t.mu.Unlock() + + t.wg.Wait() + + if shouldCloseRecvChan { + close(t.recvChan) + } + + return nil +} + +func (t *tcpPacketConn) LocalAddr() net.Addr { + return t.params.LocalAddr +} + +func (t *tcpPacketConn) SetDeadline(tm time.Time) error { + return nil +} + +func (t *tcpPacketConn) SetReadDeadline(tm time.Time) error { + return nil +} + +func (t *tcpPacketConn) SetWriteDeadline(tm time.Time) error { + return nil +} + +func (t *tcpPacketConn) CloseChannel() <-chan struct{} { + return t.closedChan +} + +func (t *tcpPacketConn) String() string { + return fmt.Sprintf("tcpPacketConn{LocalAddr: %s}", t.params.LocalAddr) +} diff --git a/vendor/github.com/pion/ice/v2/tcptype.go b/vendor/github.com/pion/ice/v2/tcptype.go new file mode 100644 index 0000000..6700fe5 --- /dev/null +++ b/vendor/github.com/pion/ice/v2/tcptype.go @@ -0,0 +1,48 @@ +package ice + +import "strings" + +// TCPType is the type of ICE TCP candidate as described in +// ttps://tools.ietf.org/html/rfc6544#section-4.5 +type TCPType int + +const ( + // TCPTypeUnspecified is the default value. For example UDP candidates do not + // need this field. + TCPTypeUnspecified TCPType = iota + // TCPTypeActive is active TCP candidate, which initiates TCP connections. + TCPTypeActive + // TCPTypePassive is passive TCP candidate, only accepts TCP connections. + TCPTypePassive + // TCPTypeSimultaneousOpen is like active and passive at the same time. + TCPTypeSimultaneousOpen +) + +// NewTCPType creates a new TCPType from string. +func NewTCPType(value string) TCPType { + switch strings.ToLower(value) { + case "active": + return TCPTypeActive + case "passive": + return TCPTypePassive + case "so": + return TCPTypeSimultaneousOpen + default: + return TCPTypeUnspecified + } +} + +func (t TCPType) String() string { + switch t { + case TCPTypeUnspecified: + return "" + case TCPTypeActive: + return "active" + case TCPTypePassive: + return "passive" + case TCPTypeSimultaneousOpen: + return "so" + default: + return ErrUnknownType.Error() + } +} diff --git a/vendor/github.com/pion/ice/v2/transport.go b/vendor/github.com/pion/ice/v2/transport.go new file mode 100644 index 0000000..d1c82ff --- /dev/null +++ b/vendor/github.com/pion/ice/v2/transport.go @@ -0,0 +1,145 @@ +package ice + +import ( + "context" + "net" + "sync/atomic" + "time" + + "github.com/pion/stun" +) + +// Dial connects to the remote agent, acting as the controlling ice agent. +// Dial blocks until at least one ice candidate pair has successfully connected. +func (a *Agent) Dial(ctx context.Context, remoteUfrag, remotePwd string) (*Conn, error) { + return a.connect(ctx, true, remoteUfrag, remotePwd) +} + +// Accept connects to the remote agent, acting as the controlled ice agent. +// Accept blocks until at least one ice candidate pair has successfully connected. +func (a *Agent) Accept(ctx context.Context, remoteUfrag, remotePwd string) (*Conn, error) { + return a.connect(ctx, false, remoteUfrag, remotePwd) +} + +// Conn represents the ICE connection. +// At the moment the lifetime of the Conn is equal to the Agent. +type Conn struct { + bytesReceived uint64 + bytesSent uint64 + agent *Agent +} + +// BytesSent returns the number of bytes sent +func (c *Conn) BytesSent() uint64 { + return atomic.LoadUint64(&c.bytesSent) +} + +// BytesReceived returns the number of bytes received +func (c *Conn) BytesReceived() uint64 { + return atomic.LoadUint64(&c.bytesReceived) +} + +func (a *Agent) connect(ctx context.Context, isControlling bool, remoteUfrag, remotePwd string) (*Conn, error) { + err := a.ok() + if err != nil { + return nil, err + } + err = a.startConnectivityChecks(isControlling, remoteUfrag, remotePwd) + if err != nil { + return nil, err + } + + // block until pair selected + select { + case <-a.done: + return nil, a.getErr() + case <-ctx.Done(): + return nil, ErrCanceledByCaller + case <-a.onConnected: + } + + return &Conn{ + agent: a, + }, nil +} + +// Read implements the Conn Read method. +func (c *Conn) Read(p []byte) (int, error) { + err := c.agent.ok() + if err != nil { + return 0, err + } + + n, err := c.agent.buffer.Read(p) + atomic.AddUint64(&c.bytesReceived, uint64(n)) + return n, err +} + +// Write implements the Conn Write method. +func (c *Conn) Write(p []byte) (int, error) { + err := c.agent.ok() + if err != nil { + return 0, err + } + + if stun.IsMessage(p) { + return 0, errICEWriteSTUNMessage + } + + pair := c.agent.getSelectedPair() + if pair == nil { + if err = c.agent.run(c.agent.context(), func(ctx context.Context, a *Agent) { + pair = a.getBestValidCandidatePair() + }); err != nil { + return 0, err + } + + if pair == nil { + return 0, err + } + } + + atomic.AddUint64(&c.bytesSent, uint64(len(p))) + return pair.Write(p) +} + +// Close implements the Conn Close method. It is used to close +// the connection. Any calls to Read and Write will be unblocked and return an error. +func (c *Conn) Close() error { + return c.agent.Close() +} + +// LocalAddr returns the local address of the current selected pair or nil if there is none. +func (c *Conn) LocalAddr() net.Addr { + pair := c.agent.getSelectedPair() + if pair == nil { + return nil + } + + return pair.local.addr() +} + +// RemoteAddr returns the remote address of the current selected pair or nil if there is none. +func (c *Conn) RemoteAddr() net.Addr { + pair := c.agent.getSelectedPair() + if pair == nil { + return nil + } + + return pair.remote.addr() +} + +// SetDeadline is a stub +func (c *Conn) SetDeadline(t time.Time) error { + return nil +} + +// SetReadDeadline is a stub +func (c *Conn) SetReadDeadline(t time.Time) error { + return nil +} + +// SetWriteDeadline is a stub +func (c *Conn) SetWriteDeadline(t time.Time) error { + return nil +} diff --git a/vendor/github.com/pion/ice/v2/url.go b/vendor/github.com/pion/ice/v2/url.go new file mode 100644 index 0000000..390591e --- /dev/null +++ b/vendor/github.com/pion/ice/v2/url.go @@ -0,0 +1,225 @@ +package ice + +import ( + "net" + "net/url" + "strconv" +) + +// SchemeType indicates the type of server used in the ice.URL structure. +type SchemeType int + +// Unknown defines default public constant to use for "enum" like struct +// comparisons when no value was defined. +const Unknown = iota + +const ( + // SchemeTypeSTUN indicates the URL represents a STUN server. + SchemeTypeSTUN SchemeType = iota + 1 + + // SchemeTypeSTUNS indicates the URL represents a STUNS (secure) server. + SchemeTypeSTUNS + + // SchemeTypeTURN indicates the URL represents a TURN server. + SchemeTypeTURN + + // SchemeTypeTURNS indicates the URL represents a TURNS (secure) server. + SchemeTypeTURNS +) + +// NewSchemeType defines a procedure for creating a new SchemeType from a raw +// string naming the scheme type. +func NewSchemeType(raw string) SchemeType { + switch raw { + case "stun": + return SchemeTypeSTUN + case "stuns": + return SchemeTypeSTUNS + case "turn": + return SchemeTypeTURN + case "turns": + return SchemeTypeTURNS + default: + return SchemeType(Unknown) + } +} + +func (t SchemeType) String() string { + switch t { + case SchemeTypeSTUN: + return "stun" + case SchemeTypeSTUNS: + return "stuns" + case SchemeTypeTURN: + return "turn" + case SchemeTypeTURNS: + return "turns" + default: + return ErrUnknownType.Error() + } +} + +// ProtoType indicates the transport protocol type that is used in the ice.URL +// structure. +type ProtoType int + +const ( + // ProtoTypeUDP indicates the URL uses a UDP transport. + ProtoTypeUDP ProtoType = iota + 1 + + // ProtoTypeTCP indicates the URL uses a TCP transport. + ProtoTypeTCP +) + +// NewProtoType defines a procedure for creating a new ProtoType from a raw +// string naming the transport protocol type. +func NewProtoType(raw string) ProtoType { + switch raw { + case "udp": + return ProtoTypeUDP + case "tcp": + return ProtoTypeTCP + default: + return ProtoType(Unknown) + } +} + +func (t ProtoType) String() string { + switch t { + case ProtoTypeUDP: + return "udp" + case ProtoTypeTCP: + return "tcp" + default: + return ErrUnknownType.Error() + } +} + +// URL represents a STUN (rfc7064) or TURN (rfc7065) URL +type URL struct { + Scheme SchemeType + Host string + Port int + Username string + Password string + Proto ProtoType +} + +// ParseURL parses a STUN or TURN urls following the ABNF syntax described in +// https://tools.ietf.org/html/rfc7064 and https://tools.ietf.org/html/rfc7065 +// respectively. +func ParseURL(raw string) (*URL, error) { //nolint:gocognit + rawParts, err := url.Parse(raw) + if err != nil { + return nil, err + } + + var u URL + u.Scheme = NewSchemeType(rawParts.Scheme) + if u.Scheme == SchemeType(Unknown) { + return nil, ErrSchemeType + } + + var rawPort string + if u.Host, rawPort, err = net.SplitHostPort(rawParts.Opaque); err != nil { + if e, ok := err.(*net.AddrError); ok { + if e.Err == "missing port in address" { + nextRawURL := u.Scheme.String() + ":" + rawParts.Opaque + switch { + case u.Scheme == SchemeTypeSTUN || u.Scheme == SchemeTypeTURN: + nextRawURL += ":3478" + if rawParts.RawQuery != "" { + nextRawURL += "?" + rawParts.RawQuery + } + return ParseURL(nextRawURL) + case u.Scheme == SchemeTypeSTUNS || u.Scheme == SchemeTypeTURNS: + nextRawURL += ":5349" + if rawParts.RawQuery != "" { + nextRawURL += "?" + rawParts.RawQuery + } + return ParseURL(nextRawURL) + } + } + } + return nil, err + } + + if u.Host == "" { + return nil, ErrHost + } + + if u.Port, err = strconv.Atoi(rawPort); err != nil { + return nil, ErrPort + } + + switch u.Scheme { + case SchemeTypeSTUN: + qArgs, err := url.ParseQuery(rawParts.RawQuery) + if err != nil || len(qArgs) > 0 { + return nil, ErrSTUNQuery + } + u.Proto = ProtoTypeUDP + case SchemeTypeSTUNS: + qArgs, err := url.ParseQuery(rawParts.RawQuery) + if err != nil || len(qArgs) > 0 { + return nil, ErrSTUNQuery + } + u.Proto = ProtoTypeTCP + case SchemeTypeTURN: + proto, err := parseProto(rawParts.RawQuery) + if err != nil { + return nil, err + } + + u.Proto = proto + if u.Proto == ProtoType(Unknown) { + u.Proto = ProtoTypeUDP + } + case SchemeTypeTURNS: + proto, err := parseProto(rawParts.RawQuery) + if err != nil { + return nil, err + } + + u.Proto = proto + if u.Proto == ProtoType(Unknown) { + u.Proto = ProtoTypeTCP + } + } + + return &u, nil +} + +func parseProto(raw string) (ProtoType, error) { + qArgs, err := url.ParseQuery(raw) + if err != nil || len(qArgs) > 1 { + return ProtoType(Unknown), ErrInvalidQuery + } + + var proto ProtoType + if rawProto := qArgs.Get("transport"); rawProto != "" { + if proto = NewProtoType(rawProto); proto == ProtoType(0) { + return ProtoType(Unknown), ErrProtoType + } + return proto, nil + } + + if len(qArgs) > 0 { + return ProtoType(Unknown), ErrInvalidQuery + } + + return proto, nil +} + +func (u URL) String() string { + rawURL := u.Scheme.String() + ":" + net.JoinHostPort(u.Host, strconv.Itoa(u.Port)) + if u.Scheme == SchemeTypeTURN || u.Scheme == SchemeTypeTURNS { + rawURL += "?transport=" + u.Proto.String() + } + return rawURL +} + +// IsSecure returns whether the this URL's scheme describes secure scheme or not. +func (u URL) IsSecure() bool { + return u.Scheme == SchemeTypeSTUNS || u.Scheme == SchemeTypeTURNS +} diff --git a/vendor/github.com/pion/ice/v2/usecandidate.go b/vendor/github.com/pion/ice/v2/usecandidate.go new file mode 100644 index 0000000..f168c08 --- /dev/null +++ b/vendor/github.com/pion/ice/v2/usecandidate.go @@ -0,0 +1,23 @@ +package ice + +import "github.com/pion/stun" + +// UseCandidateAttr represents USE-CANDIDATE attribute. +type UseCandidateAttr struct{} + +// AddTo adds USE-CANDIDATE attribute to message. +func (UseCandidateAttr) AddTo(m *stun.Message) error { + m.Add(stun.AttrUseCandidate, nil) + return nil +} + +// IsSet returns true if USE-CANDIDATE attribute is set. +func (UseCandidateAttr) IsSet(m *stun.Message) bool { + _, err := m.Get(stun.AttrUseCandidate) + return err == nil +} + +// UseCandidate is shorthand for UseCandidateAttr. +func UseCandidate() UseCandidateAttr { + return UseCandidateAttr{} +} diff --git a/vendor/github.com/pion/ice/v2/util.go b/vendor/github.com/pion/ice/v2/util.go new file mode 100644 index 0000000..7eb13c8 --- /dev/null +++ b/vendor/github.com/pion/ice/v2/util.go @@ -0,0 +1,233 @@ +package ice + +import ( + "fmt" + "net" + "sync/atomic" + "time" + + "github.com/pion/logging" + "github.com/pion/stun" + "github.com/pion/transport/vnet" +) + +type atomicError struct{ v atomic.Value } + +func (a *atomicError) Store(err error) { + a.v.Store(struct{ error }{err}) +} + +func (a *atomicError) Load() error { + err, _ := a.v.Load().(struct{ error }) + return err.error +} + +// The conditions of invalidation written below are defined in +// https://tools.ietf.org/html/rfc8445#section-5.1.1.1 +func isSupportedIPv6(ip net.IP) bool { + if len(ip) != net.IPv6len || + isZeros(ip[0:12]) || // !(IPv4-compatible IPv6) + ip[0] == 0xfe && ip[1]&0xc0 == 0xc0 || // !(IPv6 site-local unicast) + ip.IsLinkLocalUnicast() || + ip.IsLinkLocalMulticast() { + return false + } + return true +} + +func isZeros(ip net.IP) bool { + for i := 0; i < len(ip); i++ { + if ip[i] != 0 { + return false + } + } + return true +} + +func parseAddr(in net.Addr) (net.IP, int, NetworkType, bool) { + switch addr := in.(type) { + case *net.UDPAddr: + return addr.IP, addr.Port, NetworkTypeUDP4, true + case *net.TCPAddr: + return addr.IP, addr.Port, NetworkTypeTCP4, true + } + return nil, 0, 0, false +} + +func createAddr(network NetworkType, ip net.IP, port int) net.Addr { + switch { + case network.IsTCP(): + return &net.TCPAddr{IP: ip, Port: port} + default: + return &net.UDPAddr{IP: ip, Port: port} + } +} + +func addrEqual(a, b net.Addr) bool { + aIP, aPort, aType, aOk := parseAddr(a) + if !aOk { + return false + } + + bIP, bPort, bType, bOk := parseAddr(b) + if !bOk { + return false + } + + return aType == bType && aIP.Equal(bIP) && aPort == bPort +} + +// getXORMappedAddr initiates a stun requests to serverAddr using conn, reads the response and returns +// the XORMappedAddress returned by the stun server. +// +// Adapted from stun v0.2. +func getXORMappedAddr(conn net.PacketConn, serverAddr net.Addr, deadline time.Duration) (*stun.XORMappedAddress, error) { + if deadline > 0 { + if err := conn.SetReadDeadline(time.Now().Add(deadline)); err != nil { + return nil, err + } + } + defer func() { + if deadline > 0 { + _ = conn.SetReadDeadline(time.Time{}) + } + }() + resp, err := stunRequest( + func(p []byte) (int, error) { + n, _, errr := conn.ReadFrom(p) + return n, errr + }, + func(b []byte) (int, error) { + return conn.WriteTo(b, serverAddr) + }, + ) + if err != nil { + return nil, err + } + var addr stun.XORMappedAddress + if err = addr.GetFrom(resp); err != nil { + return nil, fmt.Errorf("%w: %v", errGetXorMappedAddrResponse, err) + } + return &addr, nil +} + +func stunRequest(read func([]byte) (int, error), write func([]byte) (int, error)) (*stun.Message, error) { + req, err := stun.Build(stun.BindingRequest, stun.TransactionID) + if err != nil { + return nil, err + } + if _, err = write(req.Raw); err != nil { + return nil, err + } + const maxMessageSize = 1280 + bs := make([]byte, maxMessageSize) + n, err := read(bs) + if err != nil { + return nil, err + } + res := &stun.Message{Raw: bs[:n]} + if err := res.Decode(); err != nil { + return nil, err + } + return res, nil +} + +func localInterfaces(vnet *vnet.Net, interfaceFilter func(string) bool, networkTypes []NetworkType) ([]net.IP, error) { //nolint:gocognit + ips := []net.IP{} + ifaces, err := vnet.Interfaces() + if err != nil { + return ips, err + } + + var IPv4Requested, IPv6Requested bool + for _, typ := range networkTypes { + if typ.IsIPv4() { + IPv4Requested = true + } + + if typ.IsIPv6() { + IPv6Requested = true + } + } + + for _, iface := range ifaces { + if iface.Flags&net.FlagUp == 0 { + continue // interface down + } + if iface.Flags&net.FlagLoopback != 0 { + continue // loopback interface + } + + if interfaceFilter != nil && !interfaceFilter(iface.Name) { + continue + } + + addrs, err := iface.Addrs() + if err != nil { + continue + } + + for _, addr := range addrs { + var ip net.IP + switch addr := addr.(type) { + case *net.IPNet: + ip = addr.IP + case *net.IPAddr: + ip = addr.IP + } + if ip == nil || ip.IsLoopback() { + continue + } + + if ipv4 := ip.To4(); ipv4 == nil { + if !IPv6Requested { + continue + } else if !isSupportedIPv6(ip) { + continue + } + } else if !IPv4Requested { + continue + } + + ips = append(ips, ip) + } + } + return ips, nil +} + +func listenUDPInPortRange(vnet *vnet.Net, log logging.LeveledLogger, portMax, portMin int, network string, laddr *net.UDPAddr) (vnet.UDPPacketConn, error) { + if (laddr.Port != 0) || ((portMin == 0) && (portMax == 0)) { + return vnet.ListenUDP(network, laddr) + } + var i, j int + i = portMin + if i == 0 { + i = 1 + } + j = portMax + if j == 0 { + j = 0xFFFF + } + if i > j { + return nil, ErrPort + } + + portStart := globalMathRandomGenerator.Intn(j-i+1) + i + portCurrent := portStart + for { + laddr = &net.UDPAddr{IP: laddr.IP, Port: portCurrent} + c, e := vnet.ListenUDP(network, laddr) + if e == nil { + return c, e + } + log.Debugf("failed to listen %s: %v", laddr.String(), e) + portCurrent++ + if portCurrent > j { + portCurrent = i + } + if portCurrent == portStart { + break + } + } + return nil, ErrPort +} diff --git a/vendor/github.com/pion/interceptor/.gitignore b/vendor/github.com/pion/interceptor/.gitignore new file mode 100644 index 0000000..83db74b --- /dev/null +++ b/vendor/github.com/pion/interceptor/.gitignore @@ -0,0 +1,24 @@ +### JetBrains IDE ### +##################### +.idea/ + +### Emacs Temporary Files ### +############################# +*~ + +### Folders ### +############### +bin/ +vendor/ +node_modules/ + +### Files ### +############# +*.ivf +*.ogg +tags +cover.out +*.sw[poe] +*.wasm +examples/sfu-ws/cert.pem +examples/sfu-ws/key.pem diff --git a/vendor/github.com/pion/interceptor/.golangci.yml b/vendor/github.com/pion/interceptor/.golangci.yml new file mode 100644 index 0000000..d6162c9 --- /dev/null +++ b/vendor/github.com/pion/interceptor/.golangci.yml @@ -0,0 +1,89 @@ +linters-settings: + govet: + check-shadowing: true + misspell: + locale: US + exhaustive: + default-signifies-exhaustive: true + gomodguard: + blocked: + modules: + - github.com/pkg/errors: + recommendations: + - errors + +linters: + enable: + - asciicheck # Simple linter to check that your code does not contain non-ASCII identifiers + - bodyclose # checks whether HTTP response body is closed successfully + - deadcode # Finds unused code + - depguard # Go linter that checks if package imports are in a list of acceptable packages + - dogsled # Checks assignments with too many blank identifiers (e.g. x, _, _, _, := f()) + - dupl # Tool for code clone detection + - errcheck # Errcheck is a program for checking for unchecked errors in go programs. These unchecked errors can be critical bugs in some cases + - exhaustive # check exhaustiveness of enum switch statements + - exportloopref # checks for pointers to enclosing loop variables + - gci # Gci control golang package import order and make it always deterministic. + - gochecknoglobals # Checks that no globals are present in Go code + - gochecknoinits # Checks that no init functions are present in Go code + - gocognit # Computes and checks the cognitive complexity of functions + - goconst # Finds repeated strings that could be replaced by a constant + - gocritic # The most opinionated Go source code linter + - godox # Tool for detection of FIXME, TODO and other comment keywords + - goerr113 # Golang linter to check the errors handling expressions + - gofmt # Gofmt checks whether code was gofmt-ed. By default this tool runs with -s option to check for code simplification + - gofumpt # Gofumpt checks whether code was gofumpt-ed. + - goheader # Checks is file header matches to pattern + - goimports # Goimports does everything that gofmt does. Additionally it checks unused imports + - golint # Golint differs from gofmt. Gofmt reformats Go source code, whereas golint prints out style mistakes + - gomodguard # Allow and block list linter for direct Go module dependencies. This is different from depguard where there are different block types for example version constraints and module recommendations. + - goprintffuncname # Checks that printf-like functions are named with `f` at the end + - gosec # Inspects source code for security problems + - gosimple # Linter for Go source code that specializes in simplifying a code + - govet # Vet examines Go source code and reports suspicious constructs, such as Printf calls whose arguments do not align with the format string + - ineffassign # Detects when assignments to existing variables are not used + - misspell # Finds commonly misspelled English words in comments + - nakedret # Finds naked returns in functions greater than a specified function length + - noctx # noctx finds sending http request without context.Context + - scopelint # Scopelint checks for unpinned variables in go programs + - staticcheck # Staticcheck is a go vet on steroids, applying a ton of static analysis checks + - structcheck # Finds unused struct fields + - stylecheck # Stylecheck is a replacement for golint + - typecheck # Like the front-end of a Go compiler, parses and type-checks Go code + - unconvert # Remove unnecessary type conversions + - unparam # Reports unused function parameters + - unused # Checks Go code for unused constants, variables, functions and types + - varcheck # Finds unused global variables and constants + - whitespace # Tool for detection of leading and trailing whitespace + disable: + - funlen # Tool for detection of long functions + - gocyclo # Computes and checks the cyclomatic complexity of functions + - godot # Check if comments end in a period + - gomnd # An analyzer to detect magic numbers. + - lll # Reports long lines + - maligned # Tool to detect Go structs that would take less memory if their fields were sorted + - nestif # Reports deeply nested if statements + - nlreturn # nlreturn checks for a new line before return and branch statements to increase code clarity + - nolintlint # Reports ill-formed or insufficient nolint directives + - prealloc # Finds slice declarations that could potentially be preallocated + - rowserrcheck # checks whether Err of rows is checked successfully + - sqlclosecheck # Checks that sql.Rows and sql.Stmt are closed. + - testpackage # linter that makes you use a separate _test package + - wsl # Whitespace Linter - Forces you to use empty lines! + +issues: + exclude-use-default: false + exclude-rules: + # Allow complex tests, better to be self contained + - path: _test\.go + linters: + - gocognit + + # Allow complex main function in examples + - path: examples + text: "of func `main` is high" + linters: + - gocognit + +run: + skip-dirs-use-default: false diff --git a/vendor/github.com/pion/interceptor/LICENSE b/vendor/github.com/pion/interceptor/LICENSE new file mode 100644 index 0000000..ab60297 --- /dev/null +++ b/vendor/github.com/pion/interceptor/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/pion/interceptor/README.md b/vendor/github.com/pion/interceptor/README.md new file mode 100644 index 0000000..007c340 --- /dev/null +++ b/vendor/github.com/pion/interceptor/README.md @@ -0,0 +1,81 @@ +<h1 align="center"> + <br> + Pion Interceptor + <br> +</h1> +<h4 align="center">RTCP and RTCP processors for building real time communications</h4> +<p align="center"> + <a href="https://pion.ly"><img src="https://img.shields.io/badge/pion-interceptor-gray.svg?longCache=true&colorB=brightgreen" alt="Pion Interceptor"></a> + <a href="https://pion.ly/slack"><img src="https://img.shields.io/badge/join-us%20on%20slack-gray.svg?longCache=true&logo=slack&colorB=brightgreen" alt="Slack Widget"></a> + <br> + <a href="https://pkg.go.dev/github.com/pion/interceptor"><img src="https://godoc.org/github.com/pion/interceptor?status.svg" alt="GoDoc"></a> + <a href="https://codecov.io/gh/pion/interceptor"><img src="https://codecov.io/gh/pion/interceptor/branch/master/graph/badge.svg" alt="Coverage Status"></a> + <a href="https://goreportcard.com/report/github.com/pion/interceptor"><img src="https://goreportcard.com/badge/github.com/pion/interceptor" alt="Go Report Card"></a> + <a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="License: MIT"></a> +</p> +<br> + +Interceptor is a framework for building RTP/RTCP communication software. This framework defines +a interface that each interceptor must satisfy. These interceptors are then run sequentially. We +also then provide common interceptors that will be useful for building RTC software. + +This package was built for [pion/webrtc](https://github.com/pion/webrtc), but we designed it to be consumable +by anyone. With the following tenets in mind. + +* Useful defaults. Each interceptor will be configured to give you a good default experience. +* Unblock unique use cases. New use cases are what is driving WebRTC, we want to empower them. +* Encourage modification. Add your own interceptors without forking. Mixing with the ones we provide. +* Empower learning. This code base should be useful to read and learn even if you aren't using Pion. + +#### Current Interceptors +* NACK Generator/Responder + +#### Planned Interceptors +* [Sender and Receiver Reports](https://tools.ietf.org/html/rfc3550#section-6.4) + - Bandwidth Estimation from Receiver Reports +* [Transport Wide Congestion Control Feedback](https://tools.ietf.org/html/draft-holmer-rmcat-transport-wide-cc-extensions-01) + - [NADA](https://tools.ietf.org/html/rfc8698) + - [Google Congestion Control](https://tools.ietf.org/html/draft-ietf-rmcat-gcc-02) +* JitterBuffer, re-order packets and wait for arrival +* [FlexFec](https://tools.ietf.org/html/draft-ietf-payload-flexible-fec-scheme-20) +* [webrtc-stats](https://www.w3.org/TR/webrtc-stats/) compliant statistics generation + +### Interceptor Public API +The public interface is defined in [interceptor.go](https://github.com/pion/interceptor/blob/master/interceptor.go). +The methods you need to satisy are broken up into 4 groups. + +* `BindRTCPWriter` and `BindRTCPReader` allow you to inspect/modify RTCP traffic. +* `BindLocalStream` and `BindRemoteStream` notify you of a new SSRC stream and allow you to inspect/modify. +* `UnbindLocalStream` and `UnbindRemoteStream` notify you when a SSRC stream has been removed +* `Close` called when the interceptor is closed. + +Interceptors also pass Attributes between each other. These are a collection of key/value pairs and are useful for storing metadata +or caching. + +[noop.go](https://github.com/pion/interceptor/blob/master/noop.go) is an interceptor that satisfies this interface, but does nothing. +You can embed this interceptor as a starting point so you only need to define exactly what you need. + +[chain.go]( https://github.com/pion/interceptor/blob/master/chain.go) is used to combine multiple interceptors into one. They are called +sequentially as the packet moves through them. + +### Examples +The [examples](https://github.com/pion/interceptor/blob/master/examples) directory provides some basic examples. If you need more please file an issue! +You should also look in [pion/webrtc](https://github.com/pion/webrtc) for real world examples. + +### Community +Pion has an active community on the [Golang Slack](https://invite.slack.golangbridge.org/). Sign up and join the **#pion** channel for discussions and support. You can also use [Pion mailing list](https://groups.google.com/forum/#!forum/pion). + +We are always looking to support **your projects**. Please reach out if you have something to build! + +If you need commercial support or don't want to use public methods you can contact us at [team@pion.ly](mailto:team@pion.ly) + +### Contributing +Check out the **[contributing wiki](https://github.com/pion/webrtc/wiki/Contributing)** to join the group of amazing people making this project possible: + +* [Adam Kiss](https://github.com/masterada) - *Original Author* +* [Sean DuBois](https://github.com/sean-der) - *Original Author* +* [Atsushi Watanabe](https://github.com/at-wat) +* [Alessandro Ros](https://github.com/aler9) + +### License +MIT License - see [LICENSE](LICENSE) for full text diff --git a/vendor/github.com/pion/interceptor/chain.go b/vendor/github.com/pion/interceptor/chain.go new file mode 100644 index 0000000..d53c307 --- /dev/null +++ b/vendor/github.com/pion/interceptor/chain.go @@ -0,0 +1,75 @@ +package interceptor + +// Chain is an interceptor that runs all child interceptors in order. +type Chain struct { + interceptors []Interceptor +} + +// NewChain returns a new Chain interceptor. +func NewChain(interceptors []Interceptor) *Chain { + return &Chain{interceptors: interceptors} +} + +// BindRTCPReader lets you modify any incoming RTCP packets. It is called once per sender/receiver, however this might +// change in the future. The returned method will be called once per packet batch. +func (i *Chain) BindRTCPReader(reader RTCPReader) RTCPReader { + for _, interceptor := range i.interceptors { + reader = interceptor.BindRTCPReader(reader) + } + + return reader +} + +// BindRTCPWriter lets you modify any outgoing RTCP packets. It is called once per PeerConnection. The returned method +// will be called once per packet batch. +func (i *Chain) BindRTCPWriter(writer RTCPWriter) RTCPWriter { + for _, interceptor := range i.interceptors { + writer = interceptor.BindRTCPWriter(writer) + } + + return writer +} + +// BindLocalStream lets you modify any outgoing RTP packets. It is called once for per LocalStream. The returned method +// will be called once per rtp packet. +func (i *Chain) BindLocalStream(ctx *StreamInfo, writer RTPWriter) RTPWriter { + for _, interceptor := range i.interceptors { + writer = interceptor.BindLocalStream(ctx, writer) + } + + return writer +} + +// UnbindLocalStream is called when the Stream is removed. It can be used to clean up any data related to that track. +func (i *Chain) UnbindLocalStream(ctx *StreamInfo) { + for _, interceptor := range i.interceptors { + interceptor.UnbindLocalStream(ctx) + } +} + +// BindRemoteStream lets you modify any incoming RTP packets. It is called once for per RemoteStream. The returned method +// will be called once per rtp packet. +func (i *Chain) BindRemoteStream(ctx *StreamInfo, reader RTPReader) RTPReader { + for _, interceptor := range i.interceptors { + reader = interceptor.BindRemoteStream(ctx, reader) + } + + return reader +} + +// UnbindRemoteStream is called when the Stream is removed. It can be used to clean up any data related to that track. +func (i *Chain) UnbindRemoteStream(ctx *StreamInfo) { + for _, interceptor := range i.interceptors { + interceptor.UnbindRemoteStream(ctx) + } +} + +// Close closes the Interceptor, cleaning up any data if necessary. +func (i *Chain) Close() error { + var errs []error + for _, interceptor := range i.interceptors { + errs = append(errs, interceptor.Close()) + } + + return flattenErrs(errs) +} diff --git a/vendor/github.com/pion/interceptor/codecov.yml b/vendor/github.com/pion/interceptor/codecov.yml new file mode 100644 index 0000000..085200a --- /dev/null +++ b/vendor/github.com/pion/interceptor/codecov.yml @@ -0,0 +1,20 @@ +# +# DO NOT EDIT THIS FILE +# +# It is automatically copied from https://github.com/pion/.goassets repository. +# + +coverage: + status: + project: + default: + # Allow decreasing 2% of total coverage to avoid noise. + threshold: 2% + patch: + default: + target: 70% + only_pulls: true + +ignore: + - "examples/*" + - "examples/**/*" diff --git a/vendor/github.com/pion/interceptor/errors.go b/vendor/github.com/pion/interceptor/errors.go new file mode 100644 index 0000000..45e1252 --- /dev/null +++ b/vendor/github.com/pion/interceptor/errors.go @@ -0,0 +1,51 @@ +package interceptor + +import ( + "errors" + "strings" +) + +func flattenErrs(errs []error) error { + errs2 := []error{} + for _, e := range errs { + if e != nil { + errs2 = append(errs2, e) + } + } + if len(errs2) == 0 { + return nil + } + return multiError(errs2) +} + +type multiError []error + +func (me multiError) Error() string { + var errstrings []string + + for _, err := range me { + if err != nil { + errstrings = append(errstrings, err.Error()) + } + } + + if len(errstrings) == 0 { + return "multiError must contain multiple error but is empty" + } + + return strings.Join(errstrings, "\n") +} + +func (me multiError) Is(err error) bool { + for _, e := range me { + if errors.Is(e, err) { + return true + } + if me2, ok := e.(multiError); ok { + if me2.Is(err) { + return true + } + } + } + return false +} diff --git a/vendor/github.com/pion/interceptor/go.mod b/vendor/github.com/pion/interceptor/go.mod new file mode 100644 index 0000000..1370bc9 --- /dev/null +++ b/vendor/github.com/pion/interceptor/go.mod @@ -0,0 +1,10 @@ +module github.com/pion/interceptor + +go 1.15 + +require ( + github.com/pion/logging v0.2.2 + github.com/pion/rtcp v1.2.6 + github.com/pion/rtp v1.6.2 + github.com/stretchr/testify v1.7.0 +) diff --git a/vendor/github.com/pion/interceptor/go.sum b/vendor/github.com/pion/interceptor/go.sum new file mode 100644 index 0000000..104d018 --- /dev/null +++ b/vendor/github.com/pion/interceptor/go.sum @@ -0,0 +1,21 @@ +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY= +github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms= +github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA= +github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8= +github.com/pion/rtcp v1.2.6 h1:1zvwBbyd0TeEuuWftrd/4d++m+/kZSeiguxU61LFWpo= +github.com/pion/rtcp v1.2.6/go.mod h1:52rMNPWFsjr39z9B9MhnkqhPLoeHTv1aN63o/42bWE0= +github.com/pion/rtp v1.6.2 h1:iGBerLX6JiDjB9NXuaPzHyxHFG9JsIEdgwTC0lp5n/U= +github.com/pion/rtp v1.6.2/go.mod h1:bDb5n+BFZxXx0Ea7E5qe+klMuqiBrP+w8XSjiWtCUko= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/vendor/github.com/pion/interceptor/interceptor.go b/vendor/github.com/pion/interceptor/interceptor.go new file mode 100644 index 0000000..2d1b97e --- /dev/null +++ b/vendor/github.com/pion/interceptor/interceptor.go @@ -0,0 +1,108 @@ +// Package interceptor contains the Interceptor interface, with some useful interceptors that should be safe to use +// in most cases. +package interceptor + +import ( + "io" + + "github.com/pion/rtcp" + "github.com/pion/rtp" +) + +// Interceptor can be used to add functionality to you PeerConnections by modifying any incoming/outgoing rtp/rtcp +// packets, or sending your own packets as needed. +type Interceptor interface { + + // BindRTCPReader lets you modify any incoming RTCP packets. It is called once per sender/receiver, however this might + // change in the future. The returned method will be called once per packet batch. + BindRTCPReader(reader RTCPReader) RTCPReader + + // BindRTCPWriter lets you modify any outgoing RTCP packets. It is called once per PeerConnection. The returned method + // will be called once per packet batch. + BindRTCPWriter(writer RTCPWriter) RTCPWriter + + // BindLocalStream lets you modify any outgoing RTP packets. It is called once for per LocalStream. The returned method + // will be called once per rtp packet. + BindLocalStream(info *StreamInfo, writer RTPWriter) RTPWriter + + // UnbindLocalStream is called when the Stream is removed. It can be used to clean up any data related to that track. + UnbindLocalStream(info *StreamInfo) + + // BindRemoteStream lets you modify any incoming RTP packets. It is called once for per RemoteStream. The returned method + // will be called once per rtp packet. + BindRemoteStream(info *StreamInfo, reader RTPReader) RTPReader + + // UnbindRemoteStream is called when the Stream is removed. It can be used to clean up any data related to that track. + UnbindRemoteStream(info *StreamInfo) + + io.Closer +} + +// RTPWriter is used by Interceptor.BindLocalStream. +type RTPWriter interface { + // Write a rtp packet + Write(header *rtp.Header, payload []byte, attributes Attributes) (int, error) +} + +// RTPReader is used by Interceptor.BindRemoteStream. +type RTPReader interface { + // Read a rtp packet + Read([]byte, Attributes) (int, Attributes, error) +} + +// RTCPWriter is used by Interceptor.BindRTCPWriter. +type RTCPWriter interface { + // Write a batch of rtcp packets + Write(pkts []rtcp.Packet, attributes Attributes) (int, error) +} + +// RTCPReader is used by Interceptor.BindRTCPReader. +type RTCPReader interface { + // Read a batch of rtcp packets + Read([]byte, Attributes) (int, Attributes, error) +} + +// Attributes are a generic key/value store used by interceptors +type Attributes map[interface{}]interface{} + +// RTPWriterFunc is an adapter for RTPWrite interface +type RTPWriterFunc func(header *rtp.Header, payload []byte, attributes Attributes) (int, error) + +// RTPReaderFunc is an adapter for RTPReader interface +type RTPReaderFunc func([]byte, Attributes) (int, Attributes, error) + +// RTCPWriterFunc is an adapter for RTCPWriter interface +type RTCPWriterFunc func(pkts []rtcp.Packet, attributes Attributes) (int, error) + +// RTCPReaderFunc is an adapter for RTCPReader interface +type RTCPReaderFunc func([]byte, Attributes) (int, Attributes, error) + +// Write a rtp packet +func (f RTPWriterFunc) Write(header *rtp.Header, payload []byte, attributes Attributes) (int, error) { + return f(header, payload, attributes) +} + +// Read a rtp packet +func (f RTPReaderFunc) Read(b []byte, a Attributes) (int, Attributes, error) { + return f(b, a) +} + +// Write a batch of rtcp packets +func (f RTCPWriterFunc) Write(pkts []rtcp.Packet, attributes Attributes) (int, error) { + return f(pkts, attributes) +} + +// Read a batch of rtcp packets +func (f RTCPReaderFunc) Read(b []byte, a Attributes) (int, Attributes, error) { + return f(b, a) +} + +// Get returns the attribute associated with key. +func (a Attributes) Get(key interface{}) interface{} { + return a[key] +} + +// Set sets the attribute associated with key to the given value. +func (a Attributes) Set(key interface{}, val interface{}) { + a[key] = val +} diff --git a/vendor/github.com/pion/interceptor/noop.go b/vendor/github.com/pion/interceptor/noop.go new file mode 100644 index 0000000..2dc4e8e --- /dev/null +++ b/vendor/github.com/pion/interceptor/noop.go @@ -0,0 +1,40 @@ +package interceptor + +// NoOp is an Interceptor that does not modify any packets. It can embedded in other interceptors, so it's +// possible to implement only a subset of the methods. +type NoOp struct{} + +// BindRTCPReader lets you modify any incoming RTCP packets. It is called once per sender/receiver, however this might +// change in the future. The returned method will be called once per packet batch. +func (i *NoOp) BindRTCPReader(reader RTCPReader) RTCPReader { + return reader +} + +// BindRTCPWriter lets you modify any outgoing RTCP packets. It is called once per PeerConnection. The returned method +// will be called once per packet batch. +func (i *NoOp) BindRTCPWriter(writer RTCPWriter) RTCPWriter { + return writer +} + +// BindLocalStream lets you modify any outgoing RTP packets. It is called once for per LocalStream. The returned method +// will be called once per rtp packet. +func (i *NoOp) BindLocalStream(_ *StreamInfo, writer RTPWriter) RTPWriter { + return writer +} + +// UnbindLocalStream is called when the Stream is removed. It can be used to clean up any data related to that track. +func (i *NoOp) UnbindLocalStream(_ *StreamInfo) {} + +// BindRemoteStream lets you modify any incoming RTP packets. It is called once for per RemoteStream. The returned method +// will be called once per rtp packet. +func (i *NoOp) BindRemoteStream(_ *StreamInfo, reader RTPReader) RTPReader { + return reader +} + +// UnbindRemoteStream is called when the Stream is removed. It can be used to clean up any data related to that track. +func (i *NoOp) UnbindRemoteStream(_ *StreamInfo) {} + +// Close closes the Interceptor, cleaning up any data if necessary. +func (i *NoOp) Close() error { + return nil +} diff --git a/vendor/github.com/pion/interceptor/pkg/nack/errors.go b/vendor/github.com/pion/interceptor/pkg/nack/errors.go new file mode 100644 index 0000000..bbfc773 --- /dev/null +++ b/vendor/github.com/pion/interceptor/pkg/nack/errors.go @@ -0,0 +1,6 @@ +package nack + +import "errors" + +// ErrInvalidSize is returned by newReceiveLog/newSendBuffer, when an incorrect buffer size is supplied. +var ErrInvalidSize = errors.New("invalid buffer size") diff --git a/vendor/github.com/pion/interceptor/pkg/nack/generator_interceptor.go b/vendor/github.com/pion/interceptor/pkg/nack/generator_interceptor.go new file mode 100644 index 0000000..447a949 --- /dev/null +++ b/vendor/github.com/pion/interceptor/pkg/nack/generator_interceptor.go @@ -0,0 +1,162 @@ +package nack + +import ( + "math/rand" + "sync" + "time" + + "github.com/pion/interceptor" + "github.com/pion/logging" + "github.com/pion/rtcp" + "github.com/pion/rtp" +) + +// GeneratorInterceptor interceptor generates nack feedback messages. +type GeneratorInterceptor struct { + interceptor.NoOp + size uint16 + skipLastN uint16 + interval time.Duration + m sync.Mutex + wg sync.WaitGroup + close chan struct{} + log logging.LeveledLogger + + receiveLogs map[uint32]*receiveLog + receiveLogsMu sync.Mutex +} + +// NewGeneratorInterceptor returns a new GeneratorInterceptor interceptor +func NewGeneratorInterceptor(opts ...GeneratorOption) (*GeneratorInterceptor, error) { + r := &GeneratorInterceptor{ + size: 8192, + skipLastN: 0, + interval: time.Millisecond * 100, + receiveLogs: map[uint32]*receiveLog{}, + close: make(chan struct{}), + log: logging.NewDefaultLoggerFactory().NewLogger("nack_generator"), + } + + for _, opt := range opts { + if err := opt(r); err != nil { + return nil, err + } + } + + if _, err := newReceiveLog(r.size); err != nil { + return nil, err + } + + return r, nil +} + +// BindRTCPWriter lets you modify any outgoing RTCP packets. It is called once per PeerConnection. The returned method +// will be called once per packet batch. +func (n *GeneratorInterceptor) BindRTCPWriter(writer interceptor.RTCPWriter) interceptor.RTCPWriter { + n.m.Lock() + defer n.m.Unlock() + + if n.isClosed() { + return writer + } + + n.wg.Add(1) + + go n.loop(writer) + + return writer +} + +// BindRemoteStream lets you modify any incoming RTP packets. It is called once for per RemoteStream. The returned method +// will be called once per rtp packet. +func (n *GeneratorInterceptor) BindRemoteStream(info *interceptor.StreamInfo, reader interceptor.RTPReader) interceptor.RTPReader { + if !streamSupportNack(info) { + return reader + } + + // error is already checked in NewGeneratorInterceptor + receiveLog, _ := newReceiveLog(n.size) + n.receiveLogsMu.Lock() + n.receiveLogs[info.SSRC] = receiveLog + n.receiveLogsMu.Unlock() + + return interceptor.RTPReaderFunc(func(b []byte, a interceptor.Attributes) (int, interceptor.Attributes, error) { + i, attr, err := reader.Read(b, a) + if err != nil { + return 0, nil, err + } + + pkt := rtp.Packet{} + if err = pkt.Unmarshal(b[:i]); err != nil { + return 0, nil, err + } + receiveLog.add(pkt.Header.SequenceNumber) + + return i, attr, nil + }) +} + +// UnbindLocalStream is called when the Stream is removed. It can be used to clean up any data related to that track. +func (n *GeneratorInterceptor) UnbindLocalStream(info *interceptor.StreamInfo) { + n.receiveLogsMu.Lock() + delete(n.receiveLogs, info.SSRC) + n.receiveLogsMu.Unlock() +} + +// Close closes the interceptor +func (n *GeneratorInterceptor) Close() error { + defer n.wg.Wait() + n.m.Lock() + defer n.m.Unlock() + + if !n.isClosed() { + close(n.close) + } + + return nil +} + +func (n *GeneratorInterceptor) loop(rtcpWriter interceptor.RTCPWriter) { + defer n.wg.Done() + + senderSSRC := rand.Uint32() // #nosec + + ticker := time.NewTicker(n.interval) + for { + select { + case <-ticker.C: + func() { + n.receiveLogsMu.Lock() + defer n.receiveLogsMu.Unlock() + + for ssrc, receiveLog := range n.receiveLogs { + missing := receiveLog.missingSeqNumbers(n.skipLastN) + if len(missing) == 0 { + continue + } + + nack := &rtcp.TransportLayerNack{ + SenderSSRC: senderSSRC, + MediaSSRC: ssrc, + Nacks: rtcp.NackPairsFromSequenceNumbers(missing), + } + + if _, err := rtcpWriter.Write([]rtcp.Packet{nack}, interceptor.Attributes{}); err != nil { + n.log.Warnf("failed sending nack: %+v", err) + } + } + }() + case <-n.close: + return + } + } +} + +func (n *GeneratorInterceptor) isClosed() bool { + select { + case <-n.close: + return true + default: + return false + } +} diff --git a/vendor/github.com/pion/interceptor/pkg/nack/generator_option.go b/vendor/github.com/pion/interceptor/pkg/nack/generator_option.go new file mode 100644 index 0000000..092f5db --- /dev/null +++ b/vendor/github.com/pion/interceptor/pkg/nack/generator_option.go @@ -0,0 +1,44 @@ +package nack + +import ( + "time" + + "github.com/pion/logging" +) + +// GeneratorOption can be used to configure GeneratorInterceptor +type GeneratorOption func(r *GeneratorInterceptor) error + +// GeneratorSize sets the size of the interceptor. +// Size must be one of: 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768 +func GeneratorSize(size uint16) GeneratorOption { + return func(r *GeneratorInterceptor) error { + r.size = size + return nil + } +} + +// GeneratorSkipLastN sets the number of packets (n-1 packets before the last received packets) to ignore when generating +// nack requests. +func GeneratorSkipLastN(skipLastN uint16) GeneratorOption { + return func(r *GeneratorInterceptor) error { + r.skipLastN = skipLastN + return nil + } +} + +// GeneratorLog sets a logger for the interceptor +func GeneratorLog(log logging.LeveledLogger) GeneratorOption { + return func(r *GeneratorInterceptor) error { + r.log = log + return nil + } +} + +// GeneratorInterval sets the nack send interval for the interceptor +func GeneratorInterval(interval time.Duration) GeneratorOption { + return func(r *GeneratorInterceptor) error { + r.interval = interval + return nil + } +} diff --git a/vendor/github.com/pion/interceptor/pkg/nack/nack.go b/vendor/github.com/pion/interceptor/pkg/nack/nack.go new file mode 100644 index 0000000..a658e7f --- /dev/null +++ b/vendor/github.com/pion/interceptor/pkg/nack/nack.go @@ -0,0 +1,14 @@ +// Package nack provides interceptors to implement sending and receiving negative acknowledgements +package nack + +import "github.com/pion/interceptor" + +func streamSupportNack(info *interceptor.StreamInfo) bool { + for _, fb := range info.RTCPFeedback { + if fb.Type == "nack" && fb.Parameter == "" { + return true + } + } + + return false +} diff --git a/vendor/github.com/pion/interceptor/pkg/nack/receive_log.go b/vendor/github.com/pion/interceptor/pkg/nack/receive_log.go new file mode 100644 index 0000000..8107f59 --- /dev/null +++ b/vendor/github.com/pion/interceptor/pkg/nack/receive_log.go @@ -0,0 +1,134 @@ +package nack + +import ( + "fmt" + "sync" +) + +type receiveLog struct { + packets []uint64 + size uint16 + end uint16 + started bool + lastConsecutive uint16 + m sync.RWMutex +} + +func newReceiveLog(size uint16) (*receiveLog, error) { + allowedSizes := make([]uint16, 0) + correctSize := false + for i := 6; i < 16; i++ { + if size == 1<<i { + correctSize = true + break + } + allowedSizes = append(allowedSizes, 1<<i) + } + + if !correctSize { + return nil, fmt.Errorf("%w: %d is not a valid size, allowed sizes: %v", ErrInvalidSize, size, allowedSizes) + } + + return &receiveLog{ + packets: make([]uint64, size/64), + size: size, + }, nil +} + +func (s *receiveLog) add(seq uint16) { + s.m.Lock() + defer s.m.Unlock() + + if !s.started { + s.setReceived(seq) + s.end = seq + s.started = true + s.lastConsecutive = seq + return + } + + diff := seq - s.end + switch { + case diff == 0: + return + case diff < uint16SizeHalf: + // this means a positive diff, in other words seq > end (with counting for rollovers) + for i := s.end + 1; i != seq; i++ { + // clear packets between end and seq (these may contain packets from a "size" ago) + s.delReceived(i) + } + s.end = seq + + if s.lastConsecutive+1 == seq { + s.lastConsecutive = seq + } else if seq-s.lastConsecutive > s.size { + s.lastConsecutive = seq - s.size + s.fixLastConsecutive() // there might be valid packets at the beginning of the buffer now + } + case s.lastConsecutive+1 == seq: + // negative diff, seq < end (with counting for rollovers) + s.lastConsecutive = seq + s.fixLastConsecutive() // there might be other valid packets after seq + } + + s.setReceived(seq) +} + +func (s *receiveLog) get(seq uint16) bool { + s.m.RLock() + defer s.m.RUnlock() + + diff := s.end - seq + if diff >= uint16SizeHalf { + return false + } + + if diff >= s.size { + return false + } + + return s.getReceived(seq) +} + +func (s *receiveLog) missingSeqNumbers(skipLastN uint16) []uint16 { + s.m.RLock() + defer s.m.RUnlock() + + until := s.end - skipLastN + if until-s.lastConsecutive >= uint16SizeHalf { + // until < s.lastConsecutive (counting for rollover) + return nil + } + + missingPacketSeqNums := make([]uint16, 0) + for i := s.lastConsecutive + 1; i != until+1; i++ { + if !s.getReceived(i) { + missingPacketSeqNums = append(missingPacketSeqNums, i) + } + } + + return missingPacketSeqNums +} + +func (s *receiveLog) setReceived(seq uint16) { + pos := seq % s.size + s.packets[pos/64] |= 1 << (pos % 64) +} + +func (s *receiveLog) delReceived(seq uint16) { + pos := seq % s.size + s.packets[pos/64] &^= 1 << (pos % 64) +} + +func (s *receiveLog) getReceived(seq uint16) bool { + pos := seq % s.size + return (s.packets[pos/64] & (1 << (pos % 64))) != 0 +} + +func (s *receiveLog) fixLastConsecutive() { + i := s.lastConsecutive + 1 + for ; i != s.end+1 && s.getReceived(i); i++ { + // find all consecutive packets + } + s.lastConsecutive = i - 1 +} diff --git a/vendor/github.com/pion/interceptor/pkg/nack/responder_interceptor.go b/vendor/github.com/pion/interceptor/pkg/nack/responder_interceptor.go new file mode 100644 index 0000000..121657e --- /dev/null +++ b/vendor/github.com/pion/interceptor/pkg/nack/responder_interceptor.go @@ -0,0 +1,119 @@ +package nack + +import ( + "sync" + + "github.com/pion/interceptor" + "github.com/pion/logging" + "github.com/pion/rtcp" + "github.com/pion/rtp" +) + +// ResponderInterceptor responds to nack feedback messages +type ResponderInterceptor struct { + interceptor.NoOp + size uint16 + log logging.LeveledLogger + + streams map[uint32]*localStream + streamsMu sync.Mutex +} + +type localStream struct { + sendBuffer *sendBuffer + rtpWriter interceptor.RTPWriter +} + +// NewResponderInterceptor returns a new GeneratorInterceptor interceptor +func NewResponderInterceptor(opts ...ResponderOption) (*ResponderInterceptor, error) { + r := &ResponderInterceptor{ + size: 8192, + log: logging.NewDefaultLoggerFactory().NewLogger("nack_responder"), + streams: map[uint32]*localStream{}, + } + + for _, opt := range opts { + if err := opt(r); err != nil { + return nil, err + } + } + + if _, err := newSendBuffer(r.size); err != nil { + return nil, err + } + + return r, nil +} + +// BindRTCPReader lets you modify any incoming RTCP packets. It is called once per sender/receiver, however this might +// change in the future. The returned method will be called once per packet batch. +func (n *ResponderInterceptor) BindRTCPReader(reader interceptor.RTCPReader) interceptor.RTCPReader { + return interceptor.RTCPReaderFunc(func(b []byte, a interceptor.Attributes) (int, interceptor.Attributes, error) { + i, attr, err := reader.Read(b, a) + if err != nil { + return 0, nil, err + } + + pkts, err := rtcp.Unmarshal(b[:i]) + if err != nil { + return 0, nil, err + } + for _, rtcpPacket := range pkts { + nack, ok := rtcpPacket.(*rtcp.TransportLayerNack) + if !ok { + continue + } + + go n.resendPackets(nack) + } + + return i, attr, err + }) +} + +// BindLocalStream lets you modify any outgoing RTP packets. It is called once for per LocalStream. The returned method +// will be called once per rtp packet. +func (n *ResponderInterceptor) BindLocalStream(info *interceptor.StreamInfo, writer interceptor.RTPWriter) interceptor.RTPWriter { + if !streamSupportNack(info) { + return writer + } + + // error is already checked in NewGeneratorInterceptor + sendBuffer, _ := newSendBuffer(n.size) + n.streamsMu.Lock() + n.streams[info.SSRC] = &localStream{sendBuffer: sendBuffer, rtpWriter: writer} + n.streamsMu.Unlock() + + return interceptor.RTPWriterFunc(func(header *rtp.Header, payload []byte, attributes interceptor.Attributes) (int, error) { + sendBuffer.add(&rtp.Packet{Header: *header, Payload: payload}) + return writer.Write(header, payload, attributes) + }) +} + +// UnbindLocalStream is called when the Stream is removed. It can be used to clean up any data related to that track. +func (n *ResponderInterceptor) UnbindLocalStream(info *interceptor.StreamInfo) { + n.streamsMu.Lock() + delete(n.streams, info.SSRC) + n.streamsMu.Unlock() +} + +func (n *ResponderInterceptor) resendPackets(nack *rtcp.TransportLayerNack) { + n.streamsMu.Lock() + stream, ok := n.streams[nack.MediaSSRC] + n.streamsMu.Unlock() + if !ok { + return + } + + for i := range nack.Nacks { + nack.Nacks[i].Range(func(seq uint16) bool { + if p := stream.sendBuffer.get(seq); p != nil { + if _, err := stream.rtpWriter.Write(&p.Header, p.Payload, interceptor.Attributes{}); err != nil { + n.log.Warnf("failed resending nacked packet: %+v", err) + } + } + + return true + }) + } +} diff --git a/vendor/github.com/pion/interceptor/pkg/nack/responder_option.go b/vendor/github.com/pion/interceptor/pkg/nack/responder_option.go new file mode 100644 index 0000000..7ad52c8 --- /dev/null +++ b/vendor/github.com/pion/interceptor/pkg/nack/responder_option.go @@ -0,0 +1,23 @@ +package nack + +import "github.com/pion/logging" + +// ResponderOption can be used to configure ResponderInterceptor +type ResponderOption func(s *ResponderInterceptor) error + +// ResponderSize sets the size of the interceptor. +// Size must be one of: 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768 +func ResponderSize(size uint16) ResponderOption { + return func(r *ResponderInterceptor) error { + r.size = size + return nil + } +} + +// ResponderLog sets a logger for the interceptor +func ResponderLog(log logging.LeveledLogger) ResponderOption { + return func(r *ResponderInterceptor) error { + r.log = log + return nil + } +} diff --git a/vendor/github.com/pion/interceptor/pkg/nack/send_buffer.go b/vendor/github.com/pion/interceptor/pkg/nack/send_buffer.go new file mode 100644 index 0000000..cf3f020 --- /dev/null +++ b/vendor/github.com/pion/interceptor/pkg/nack/send_buffer.go @@ -0,0 +1,74 @@ +package nack + +import ( + "fmt" + + "github.com/pion/rtp" +) + +const ( + uint16SizeHalf = 1 << 15 +) + +type sendBuffer struct { + packets []*rtp.Packet + size uint16 + lastAdded uint16 + started bool +} + +func newSendBuffer(size uint16) (*sendBuffer, error) { + allowedSizes := make([]uint16, 0) + correctSize := false + for i := 0; i < 16; i++ { + if size == 1<<i { + correctSize = true + break + } + allowedSizes = append(allowedSizes, 1<<i) + } + + if !correctSize { + return nil, fmt.Errorf("%w: %d is not a valid size, allowed sizes: %v", ErrInvalidSize, size, allowedSizes) + } + + return &sendBuffer{ + packets: make([]*rtp.Packet, size), + size: size, + }, nil +} + +func (s *sendBuffer) add(packet *rtp.Packet) { + seq := packet.SequenceNumber + if !s.started { + s.packets[seq%s.size] = packet + s.lastAdded = seq + s.started = true + return + } + + diff := seq - s.lastAdded + if diff == 0 { + return + } else if diff < uint16SizeHalf { + for i := s.lastAdded + 1; i != seq; i++ { + s.packets[i%s.size] = nil + } + } + + s.packets[seq%s.size] = packet + s.lastAdded = seq +} + +func (s *sendBuffer) get(seq uint16) *rtp.Packet { + diff := s.lastAdded - seq + if diff >= uint16SizeHalf { + return nil + } + + if diff >= s.size { + return nil + } + + return s.packets[seq%s.size] +} diff --git a/vendor/github.com/pion/interceptor/pkg/report/receiver_interceptor.go b/vendor/github.com/pion/interceptor/pkg/report/receiver_interceptor.go new file mode 100644 index 0000000..5235b99 --- /dev/null +++ b/vendor/github.com/pion/interceptor/pkg/report/receiver_interceptor.go @@ -0,0 +1,166 @@ +package report + +import ( + "sync" + "time" + + "github.com/pion/interceptor" + "github.com/pion/logging" + "github.com/pion/rtcp" + "github.com/pion/rtp" +) + +// ReceiverInterceptor interceptor generates receiver reports. +type ReceiverInterceptor struct { + interceptor.NoOp + interval time.Duration + now func() time.Time + streams sync.Map + log logging.LeveledLogger + m sync.Mutex + wg sync.WaitGroup + close chan struct{} +} + +// NewReceiverInterceptor returns a new ReceiverInterceptor interceptor. +func NewReceiverInterceptor(opts ...ReceiverOption) (*ReceiverInterceptor, error) { + r := &ReceiverInterceptor{ + interval: 1 * time.Second, + now: time.Now, + log: logging.NewDefaultLoggerFactory().NewLogger("receiver_interceptor"), + close: make(chan struct{}), + } + + for _, opt := range opts { + if err := opt(r); err != nil { + return nil, err + } + } + + return r, nil +} + +func (r *ReceiverInterceptor) isClosed() bool { + select { + case <-r.close: + return true + default: + return false + } +} + +// Close closes the interceptor. +func (r *ReceiverInterceptor) Close() error { + defer r.wg.Wait() + r.m.Lock() + defer r.m.Unlock() + + if !r.isClosed() { + close(r.close) + } + + return nil +} + +// BindRTCPWriter lets you modify any outgoing RTCP packets. It is called once per PeerConnection. The returned method +// will be called once per packet batch. +func (r *ReceiverInterceptor) BindRTCPWriter(writer interceptor.RTCPWriter) interceptor.RTCPWriter { + r.m.Lock() + defer r.m.Unlock() + + if r.isClosed() { + return writer + } + + r.wg.Add(1) + + go r.loop(writer) + + return writer +} + +func (r *ReceiverInterceptor) loop(rtcpWriter interceptor.RTCPWriter) { + defer r.wg.Done() + + ticker := time.NewTicker(r.interval) + for { + select { + case <-ticker.C: + now := r.now() + r.streams.Range(func(key, value interface{}) bool { + stream := value.(*receiverStream) + + var pkts []rtcp.Packet + + pkts = append(pkts, stream.generateReport(now)) + + if _, err := rtcpWriter.Write(pkts, interceptor.Attributes{}); err != nil { + r.log.Warnf("failed sending: %+v", err) + } + + return true + }) + + case <-r.close: + return + } + } +} + +// BindRemoteStream lets you modify any incoming RTP packets. It is called once for per RemoteStream. The returned method +// will be called once per rtp packet. +func (r *ReceiverInterceptor) BindRemoteStream(info *interceptor.StreamInfo, reader interceptor.RTPReader) interceptor.RTPReader { + stream := newReceiverStream(info.SSRC, info.ClockRate) + r.streams.Store(info.SSRC, stream) + + return interceptor.RTPReaderFunc(func(b []byte, a interceptor.Attributes) (int, interceptor.Attributes, error) { + i, attr, err := reader.Read(b, a) + if err != nil { + return 0, nil, err + } + + pkt := rtp.Packet{} + if err = pkt.Unmarshal(b[:i]); err != nil { + return 0, nil, err + } + + stream.processRTP(r.now(), &pkt) + + return i, attr, nil + }) +} + +// UnbindLocalStream is called when the Stream is removed. It can be used to clean up any data related to that track. +func (r *ReceiverInterceptor) UnbindLocalStream(info *interceptor.StreamInfo) { + r.streams.Delete(info.SSRC) +} + +// BindRTCPReader lets you modify any incoming RTCP packets. It is called once per sender/receiver, however this might +// change in the future. The returned method will be called once per packet batch. +func (r *ReceiverInterceptor) BindRTCPReader(reader interceptor.RTCPReader) interceptor.RTCPReader { + return interceptor.RTCPReaderFunc(func(b []byte, a interceptor.Attributes) (int, interceptor.Attributes, error) { + i, attr, err := reader.Read(b, a) + if err != nil { + return 0, nil, err + } + + pkts, err := rtcp.Unmarshal(b[:i]) + if err != nil { + return 0, nil, err + } + + for _, pkt := range pkts { + if sr, ok := (pkt).(*rtcp.SenderReport); ok { + value, ok := r.streams.Load(sr.SSRC) + if !ok { + continue + } + + stream := value.(*receiverStream) + stream.processSenderReport(r.now(), sr) + } + } + + return i, attr, nil + }) +} diff --git a/vendor/github.com/pion/interceptor/pkg/report/receiver_option.go b/vendor/github.com/pion/interceptor/pkg/report/receiver_option.go new file mode 100644 index 0000000..0467dc5 --- /dev/null +++ b/vendor/github.com/pion/interceptor/pkg/report/receiver_option.go @@ -0,0 +1,34 @@ +package report + +import ( + "time" + + "github.com/pion/logging" +) + +// ReceiverOption can be used to configure ReceiverInterceptor. +type ReceiverOption func(r *ReceiverInterceptor) error + +// ReceiverLog sets a logger for the interceptor. +func ReceiverLog(log logging.LeveledLogger) ReceiverOption { + return func(r *ReceiverInterceptor) error { + r.log = log + return nil + } +} + +// ReceiverInterval sets send interval for the interceptor. +func ReceiverInterval(interval time.Duration) ReceiverOption { + return func(r *ReceiverInterceptor) error { + r.interval = interval + return nil + } +} + +// ReceiverNow sets an alternative for the time.Now function. +func ReceiverNow(f func() time.Time) ReceiverOption { + return func(r *ReceiverInterceptor) error { + r.now = f + return nil + } +} diff --git a/vendor/github.com/pion/interceptor/pkg/report/receiver_stream.go b/vendor/github.com/pion/interceptor/pkg/report/receiver_stream.go new file mode 100644 index 0000000..569715d --- /dev/null +++ b/vendor/github.com/pion/interceptor/pkg/report/receiver_stream.go @@ -0,0 +1,159 @@ +package report + +import ( + "math/rand" + "sync" + "time" + + "github.com/pion/rtcp" + "github.com/pion/rtp" +) + +type receiverStream struct { + ssrc uint32 + receiverSSRC uint32 + clockRate float64 + + m sync.Mutex + size uint16 + packets []uint64 + started bool + seqnumCycles uint16 + lastSeqnum uint16 + lastReportSeqnum uint16 + lastRTPTimeRTP uint32 + lastRTPTimeTime time.Time + jitter float64 + lastSenderReport uint32 + lastSenderReportTime time.Time + totalLost uint32 +} + +func newReceiverStream(ssrc uint32, clockRate uint32) *receiverStream { + receiverSSRC := rand.Uint32() // #nosec + return &receiverStream{ + ssrc: ssrc, + receiverSSRC: receiverSSRC, + clockRate: float64(clockRate), + size: 128, + packets: make([]uint64, 128), + } +} + +func (stream *receiverStream) processRTP(now time.Time, pkt *rtp.Packet) { + stream.m.Lock() + defer stream.m.Unlock() + + if !stream.started { // first frame + stream.started = true + stream.setReceived(pkt.SequenceNumber) + stream.lastSeqnum = pkt.SequenceNumber + stream.lastReportSeqnum = pkt.SequenceNumber - 1 + stream.lastRTPTimeRTP = pkt.Timestamp + stream.lastRTPTimeTime = now + } else { // following frames + stream.setReceived(pkt.SequenceNumber) + + diff := int32(pkt.SequenceNumber) - int32(stream.lastSeqnum) + if diff > 0 || diff < -0x0FFF { + // overflow + if diff < -0x0FFF { + stream.seqnumCycles++ + } + + // set missing packets as missing + for i := stream.lastSeqnum + 1; i != pkt.SequenceNumber; i++ { + stream.delReceived(i) + } + + stream.lastSeqnum = pkt.SequenceNumber + } + + // compute jitter + // https://tools.ietf.org/html/rfc3550#page-39 + D := now.Sub(stream.lastRTPTimeTime).Seconds()*stream.clockRate - + (float64(pkt.Timestamp) - float64(stream.lastRTPTimeRTP)) + if D < 0 { + D = -D + } + stream.jitter += (D - stream.jitter) / 16 + stream.lastRTPTimeRTP = pkt.Timestamp + stream.lastRTPTimeTime = now + } +} + +func (stream *receiverStream) setReceived(seq uint16) { + pos := seq % stream.size + stream.packets[pos/64] |= 1 << (pos % 64) +} + +func (stream *receiverStream) delReceived(seq uint16) { + pos := seq % stream.size + stream.packets[pos/64] &^= 1 << (pos % 64) +} + +func (stream *receiverStream) getReceived(seq uint16) bool { + pos := seq % stream.size + return (stream.packets[pos/64] & (1 << (pos % 64))) != 0 +} + +func (stream *receiverStream) processSenderReport(now time.Time, sr *rtcp.SenderReport) { + stream.m.Lock() + defer stream.m.Unlock() + + stream.lastSenderReport = uint32(sr.NTPTime >> 16) + stream.lastSenderReportTime = now +} + +func (stream *receiverStream) generateReport(now time.Time) *rtcp.ReceiverReport { + stream.m.Lock() + defer stream.m.Unlock() + + totalSinceReport := stream.lastSeqnum - stream.lastReportSeqnum + totalLostSinceReport := func() uint32 { + if stream.lastSeqnum == stream.lastReportSeqnum { + return 0 + } + + ret := uint32(0) + for i := stream.lastReportSeqnum + 1; i != stream.lastSeqnum; i++ { + if !stream.getReceived(i) { + ret++ + } + } + return ret + }() + stream.totalLost += totalLostSinceReport + + // allow up to 24 bits + if totalLostSinceReport > 0xFFFFFF { + totalLostSinceReport = 0xFFFFFF + } + if stream.totalLost > 0xFFFFFF { + stream.totalLost = 0xFFFFFF + } + + r := &rtcp.ReceiverReport{ + SSRC: stream.receiverSSRC, + Reports: []rtcp.ReceptionReport{ + { + SSRC: stream.ssrc, + LastSequenceNumber: uint32(stream.seqnumCycles)<<16 | uint32(stream.lastSeqnum), + LastSenderReport: stream.lastSenderReport, + FractionLost: uint8(float64(totalLostSinceReport*256) / float64(totalSinceReport)), + TotalLost: stream.totalLost, + Delay: func() uint32 { + if stream.lastSenderReportTime.IsZero() { + return 0 + } + return uint32(now.Sub(stream.lastSenderReportTime).Seconds() * 65536) + }(), + Jitter: uint32(stream.jitter), + }, + }, + } + + stream.lastReportSeqnum = stream.lastSeqnum + + return r +} diff --git a/vendor/github.com/pion/interceptor/pkg/report/report.go b/vendor/github.com/pion/interceptor/pkg/report/report.go new file mode 100644 index 0000000..0a3034c --- /dev/null +++ b/vendor/github.com/pion/interceptor/pkg/report/report.go @@ -0,0 +1,2 @@ +// Package report provides interceptors to implement sending sender and receiver reports. +package report diff --git a/vendor/github.com/pion/interceptor/pkg/report/sender_interceptor.go b/vendor/github.com/pion/interceptor/pkg/report/sender_interceptor.go new file mode 100644 index 0000000..e56fd8c --- /dev/null +++ b/vendor/github.com/pion/interceptor/pkg/report/sender_interceptor.go @@ -0,0 +1,139 @@ +package report + +import ( + "sync" + "time" + + "github.com/pion/interceptor" + "github.com/pion/logging" + "github.com/pion/rtcp" + "github.com/pion/rtp" +) + +func ntpTime(t time.Time) uint64 { + // seconds since 1st January 1900 + s := (float64(t.UnixNano()) / 1000000000) + 2208988800 + + // higher 32 bits are the integer part, lower 32 bits are the fractional part + integerPart := uint32(s) + fractionalPart := uint32((s - float64(integerPart)) * 0xFFFFFFFF) + return uint64(integerPart)<<32 | uint64(fractionalPart) +} + +// SenderInterceptor interceptor generates sender reports. +type SenderInterceptor struct { + interceptor.NoOp + interval time.Duration + now func() time.Time + streams sync.Map + log logging.LeveledLogger + m sync.Mutex + wg sync.WaitGroup + close chan struct{} +} + +// NewSenderInterceptor returns a new SenderInterceptor interceptor. +func NewSenderInterceptor(opts ...SenderOption) (*SenderInterceptor, error) { + s := &SenderInterceptor{ + interval: 1 * time.Second, + now: time.Now, + log: logging.NewDefaultLoggerFactory().NewLogger("sender_interceptor"), + close: make(chan struct{}), + } + + for _, opt := range opts { + if err := opt(s); err != nil { + return nil, err + } + } + + return s, nil +} + +func (s *SenderInterceptor) isClosed() bool { + select { + case <-s.close: + return true + default: + return false + } +} + +// Close closes the interceptor. +func (s *SenderInterceptor) Close() error { + defer s.wg.Wait() + s.m.Lock() + defer s.m.Unlock() + + if !s.isClosed() { + close(s.close) + } + + return nil +} + +// BindRTCPWriter lets you modify any outgoing RTCP packets. It is called once per PeerConnection. The returned method +// will be called once per packet batch. +func (s *SenderInterceptor) BindRTCPWriter(writer interceptor.RTCPWriter) interceptor.RTCPWriter { + s.m.Lock() + defer s.m.Unlock() + + if s.isClosed() { + return writer + } + + s.wg.Add(1) + + go s.loop(writer) + + return writer +} + +func (s *SenderInterceptor) loop(rtcpWriter interceptor.RTCPWriter) { + defer s.wg.Done() + + ticker := time.NewTicker(s.interval) + for { + select { + case <-ticker.C: + now := s.now() + s.streams.Range(func(key, value interface{}) bool { + ssrc := key.(uint32) + stream := value.(*senderStream) + + stream.m.Lock() + defer stream.m.Unlock() + + sr := &rtcp.SenderReport{ + SSRC: ssrc, + NTPTime: ntpTime(now), + RTPTime: stream.lastRTPTimeRTP + uint32(now.Sub(stream.lastRTPTimeTime).Seconds()*stream.clockRate), + PacketCount: stream.packetCount, + OctetCount: stream.octetCount, + } + + if _, err := rtcpWriter.Write([]rtcp.Packet{sr}, interceptor.Attributes{}); err != nil { + s.log.Warnf("failed sending: %+v", err) + } + + return true + }) + + case <-s.close: + return + } + } +} + +// BindLocalStream lets you modify any outgoing RTP packets. It is called once for per LocalStream. The returned method +// will be called once per rtp packet. +func (s *SenderInterceptor) BindLocalStream(info *interceptor.StreamInfo, writer interceptor.RTPWriter) interceptor.RTPWriter { + stream := newSenderStream(info.ClockRate) + s.streams.Store(info.SSRC, stream) + + return interceptor.RTPWriterFunc(func(header *rtp.Header, payload []byte, a interceptor.Attributes) (int, error) { + stream.processRTP(s.now(), header, payload) + + return writer.Write(header, payload, a) + }) +} diff --git a/vendor/github.com/pion/interceptor/pkg/report/sender_option.go b/vendor/github.com/pion/interceptor/pkg/report/sender_option.go new file mode 100644 index 0000000..4cb161a --- /dev/null +++ b/vendor/github.com/pion/interceptor/pkg/report/sender_option.go @@ -0,0 +1,34 @@ +package report + +import ( + "time" + + "github.com/pion/logging" +) + +// SenderOption can be used to configure SenderInterceptor. +type SenderOption func(r *SenderInterceptor) error + +// SenderLog sets a logger for the interceptor. +func SenderLog(log logging.LeveledLogger) SenderOption { + return func(r *SenderInterceptor) error { + r.log = log + return nil + } +} + +// SenderInterval sets send interval for the interceptor. +func SenderInterval(interval time.Duration) SenderOption { + return func(r *SenderInterceptor) error { + r.interval = interval + return nil + } +} + +// SenderNow sets an alternative for the time.Now function. +func SenderNow(f func() time.Time) SenderOption { + return func(r *SenderInterceptor) error { + r.now = f + return nil + } +} diff --git a/vendor/github.com/pion/interceptor/pkg/report/sender_stream.go b/vendor/github.com/pion/interceptor/pkg/report/sender_stream.go new file mode 100644 index 0000000..851d70e --- /dev/null +++ b/vendor/github.com/pion/interceptor/pkg/report/sender_stream.go @@ -0,0 +1,37 @@ +package report + +import ( + "sync" + "time" + + "github.com/pion/rtp" +) + +type senderStream struct { + clockRate float64 + m sync.Mutex + + // data from rtp packets + lastRTPTimeRTP uint32 + lastRTPTimeTime time.Time + packetCount uint32 + octetCount uint32 +} + +func newSenderStream(clockRate uint32) *senderStream { + return &senderStream{ + clockRate: float64(clockRate), + } +} + +func (stream *senderStream) processRTP(now time.Time, header *rtp.Header, payload []byte) { + stream.m.Lock() + defer stream.m.Unlock() + + // always update time to minimize errors + stream.lastRTPTimeRTP = header.Timestamp + stream.lastRTPTimeTime = now + + stream.packetCount++ + stream.octetCount += uint32(len(payload)) +} diff --git a/vendor/github.com/pion/interceptor/registry.go b/vendor/github.com/pion/interceptor/registry.go new file mode 100644 index 0000000..d3eed3b --- /dev/null +++ b/vendor/github.com/pion/interceptor/registry.go @@ -0,0 +1,20 @@ +package interceptor + +// Registry is a collector for interceptors. +type Registry struct { + interceptors []Interceptor +} + +// Add adds a new Interceptor to the registry. +func (i *Registry) Add(icpr Interceptor) { + i.interceptors = append(i.interceptors, icpr) +} + +// Build constructs a single Interceptor from a InterceptorRegistry +func (i *Registry) Build() Interceptor { + if len(i.interceptors) == 0 { + return &NoOp{} + } + + return NewChain(i.interceptors) +} diff --git a/vendor/github.com/pion/interceptor/renovate.json b/vendor/github.com/pion/interceptor/renovate.json new file mode 100644 index 0000000..4400fd9 --- /dev/null +++ b/vendor/github.com/pion/interceptor/renovate.json @@ -0,0 +1,15 @@ +{ + "extends": [ + "config:base" + ], + "postUpdateOptions": [ + "gomodTidy" + ], + "commitBody": "Generated by renovateBot", + "packageRules": [ + { + "packagePatterns": ["^golang.org/x/"], + "schedule": ["on the first day of the month"] + } + ] +} diff --git a/vendor/github.com/pion/interceptor/streaminfo.go b/vendor/github.com/pion/interceptor/streaminfo.go new file mode 100644 index 0000000..956fa53 --- /dev/null +++ b/vendor/github.com/pion/interceptor/streaminfo.go @@ -0,0 +1,34 @@ +package interceptor + +// RTPHeaderExtension represents a negotiated RFC5285 RTP header extension. +type RTPHeaderExtension struct { + URI string + ID int +} + +// StreamInfo is the Context passed when a StreamLocal or StreamRemote has been Binded or Unbinded +type StreamInfo struct { + ID string + Attributes Attributes + SSRC uint32 + PayloadType uint8 + RTPHeaderExtensions []RTPHeaderExtension + MimeType string + ClockRate uint32 + Channels uint16 + SDPFmtpLine string + RTCPFeedback []RTCPFeedback +} + +// RTCPFeedback signals the connection to use additional RTCP packet types. +// https://draft.ortc.org/#dom-rtcrtcpfeedback +type RTCPFeedback struct { + // Type is the type of feedback. + // see: https://draft.ortc.org/#dom-rtcrtcpfeedback + // valid: ack, ccm, nack, goog-remb, transport-cc + Type string + + // The parameter value depends on the type. + // For example, type="nack" parameter="pli" will send Picture Loss Indicator packets. + Parameter string +} diff --git a/vendor/github.com/pion/logging/.golangci.yml b/vendor/github.com/pion/logging/.golangci.yml new file mode 100644 index 0000000..ffb0058 --- /dev/null +++ b/vendor/github.com/pion/logging/.golangci.yml @@ -0,0 +1,13 @@ +linters-settings: + govet: + check-shadowing: true + misspell: + locale: US + +linters: + enable-all: true + +issues: + exclude-use-default: false + max-per-linter: 0 + max-same-issues: 50 diff --git a/vendor/github.com/pion/logging/.travis.yml b/vendor/github.com/pion/logging/.travis.yml new file mode 100644 index 0000000..b96a1ed --- /dev/null +++ b/vendor/github.com/pion/logging/.travis.yml @@ -0,0 +1,19 @@ +language: go + +go: + - "1.x" # use the latest Go release + +env: + - GO111MODULE=on + +before_script: + - curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | bash -s -- -b $GOPATH/bin v1.15.0 + +script: + - golangci-lint run ./... +# - rm -rf examples # Remove examples, no test coverage for them + - go test -coverpkg=$(go list ./... | tr '\n' ',') -coverprofile=cover.out -v -race -covermode=atomic ./... + - bash <(curl -s https://codecov.io/bash) + - bash .github/assert-contributors.sh + - bash .github/lint-disallowed-functions-in-library.sh + - bash .github/lint-commit-message.sh diff --git a/vendor/github.com/pion/logging/LICENSE b/vendor/github.com/pion/logging/LICENSE new file mode 100644 index 0000000..ab60297 --- /dev/null +++ b/vendor/github.com/pion/logging/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/pion/logging/README.md b/vendor/github.com/pion/logging/README.md new file mode 100644 index 0000000..c15471d --- /dev/null +++ b/vendor/github.com/pion/logging/README.md @@ -0,0 +1,41 @@ +<h1 align="center"> + <br> + Pion Logging + <br> +</h1> +<h4 align="center">The Pion logging library</h4> +<p align="center"> + <a href="https://pion.ly"><img src="https://img.shields.io/badge/pion-logging-gray.svg?longCache=true&colorB=brightgreen" alt="Pion transport"></a> + <a href="http://gophers.slack.com/messages/pion"><img src="https://img.shields.io/badge/join-us%20on%20slack-gray.svg?longCache=true&logo=slack&colorB=brightgreen" alt="Slack Widget"></a> + <br> + <a href="https://travis-ci.org/pion/logging"><img src="https://travis-ci.org/pion/logging.svg?branch=master" alt="Build Status"></a> + <a href="https://godoc.org/github.com/pion/logging"><img src="https://godoc.org/github.com/pion/logging?status.svg" alt="GoDoc"></a> + <a href="https://codecov.io/gh/pion/logging"><img src="https://codecov.io/gh/pion/logging/branch/master/graph/badge.svg" alt="Coverage Status"></a> + <a href="https://goreportcard.com/report/github.com/pion/logging"><img src="https://goreportcard.com/badge/github.com/pion/logging" alt="Go Report Card"></a> + <a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="License: MIT"></a> +</p> +<br> + +### Roadmap +The library is used as a part of our WebRTC implementation. Please refer to that [roadmap](https://github.com/pion/webrtc/issues/9) to track our major milestones. + +### Community +Pion has an active community on the [Golang Slack](https://invite.slack.golangbridge.org/). Sign up and join the **#pion** channel for discussions and support. You can also use [Pion mailing list](https://groups.google.com/forum/#!forum/pion). + +We are always looking to support **your projects**. Please reach out if you have something to build! + +If you need commercial support or don't want to use public methods you can contact us at [team@pion.ly](mailto:team@pion.ly) + +### Contributing +Check out the **[contributing wiki](https://github.com/pion/webrtc/wiki/Contributing)** to join the group of amazing people making this project possible: + +* [John Bradley](https://github.com/kc5nra) - *Original Author* +* [Sean DuBois](https://github.com/Sean-Der) - *Original Author* +* [Michael MacDonald](https://github.com/mjmac) - *Original Author* +* [Woodrow Douglass](https://github.com/wdouglass) - *Test coverage* +* [Michiel De Backker](https://github.com/backkem) - *Docs* +* [Hugo Arregui](https://github.com/hugoArregui) - *Custom Logs* +* [Justin Okamoto](https://github.com/justinokamoto) - *Disabled Logs Update* + +### License +MIT License - see [LICENSE](LICENSE) for full text diff --git a/vendor/github.com/pion/logging/go.mod b/vendor/github.com/pion/logging/go.mod new file mode 100644 index 0000000..a1b849b --- /dev/null +++ b/vendor/github.com/pion/logging/go.mod @@ -0,0 +1,3 @@ +module github.com/pion/logging + +go 1.12 diff --git a/vendor/github.com/pion/logging/go.sum b/vendor/github.com/pion/logging/go.sum new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/vendor/github.com/pion/logging/go.sum diff --git a/vendor/github.com/pion/logging/logger.go b/vendor/github.com/pion/logging/logger.go new file mode 100644 index 0000000..35f6505 --- /dev/null +++ b/vendor/github.com/pion/logging/logger.go @@ -0,0 +1,228 @@ +package logging + +import ( + "fmt" + "io" + "log" + "os" + "strings" + "sync" +) + +// Use this abstraction to ensure thread-safe access to the logger's io.Writer +// (which could change at runtime) +type loggerWriter struct { + sync.RWMutex + output io.Writer +} + +func (lw *loggerWriter) SetOutput(output io.Writer) { + lw.Lock() + defer lw.Unlock() + lw.output = output +} + +func (lw *loggerWriter) Write(data []byte) (int, error) { + lw.RLock() + defer lw.RUnlock() + return lw.output.Write(data) +} + +// DefaultLeveledLogger encapsulates functionality for providing logging at +// user-defined levels +type DefaultLeveledLogger struct { + level LogLevel + writer *loggerWriter + trace *log.Logger + debug *log.Logger + info *log.Logger + warn *log.Logger + err *log.Logger +} + +// WithTraceLogger is a chainable configuration function which sets the +// Trace-level logger +func (ll *DefaultLeveledLogger) WithTraceLogger(log *log.Logger) *DefaultLeveledLogger { + ll.trace = log + return ll +} + +// WithDebugLogger is a chainable configuration function which sets the +// Debug-level logger +func (ll *DefaultLeveledLogger) WithDebugLogger(log *log.Logger) *DefaultLeveledLogger { + ll.debug = log + return ll +} + +// WithInfoLogger is a chainable configuration function which sets the +// Info-level logger +func (ll *DefaultLeveledLogger) WithInfoLogger(log *log.Logger) *DefaultLeveledLogger { + ll.info = log + return ll +} + +// WithWarnLogger is a chainable configuration function which sets the +// Warn-level logger +func (ll *DefaultLeveledLogger) WithWarnLogger(log *log.Logger) *DefaultLeveledLogger { + ll.warn = log + return ll +} + +// WithErrorLogger is a chainable configuration function which sets the +// Error-level logger +func (ll *DefaultLeveledLogger) WithErrorLogger(log *log.Logger) *DefaultLeveledLogger { + ll.err = log + return ll +} + +// WithOutput is a chainable configuration function which sets the logger's +// logging output to the supplied io.Writer +func (ll *DefaultLeveledLogger) WithOutput(output io.Writer) *DefaultLeveledLogger { + ll.writer.SetOutput(output) + return ll +} + +func (ll *DefaultLeveledLogger) logf(logger *log.Logger, level LogLevel, format string, args ...interface{}) { + if ll.level.Get() < level { + return + } + + callDepth := 3 // this frame + wrapper func + caller + msg := fmt.Sprintf(format, args...) + if err := logger.Output(callDepth, msg); err != nil { + fmt.Fprintf(os.Stderr, "Unable to log: %s", err) + } +} + +// SetLevel sets the logger's logging level +func (ll *DefaultLeveledLogger) SetLevel(newLevel LogLevel) { + ll.level.Set(newLevel) +} + +// Trace emits the preformatted message if the logger is at or below LogLevelTrace +func (ll *DefaultLeveledLogger) Trace(msg string) { + ll.logf(ll.trace, LogLevelTrace, msg) +} + +// Tracef formats and emits a message if the logger is at or below LogLevelTrace +func (ll *DefaultLeveledLogger) Tracef(format string, args ...interface{}) { + ll.logf(ll.trace, LogLevelTrace, format, args...) +} + +// Debug emits the preformatted message if the logger is at or below LogLevelDebug +func (ll *DefaultLeveledLogger) Debug(msg string) { + ll.logf(ll.debug, LogLevelDebug, msg) +} + +// Debugf formats and emits a message if the logger is at or below LogLevelDebug +func (ll *DefaultLeveledLogger) Debugf(format string, args ...interface{}) { + ll.logf(ll.debug, LogLevelDebug, format, args...) +} + +// Info emits the preformatted message if the logger is at or below LogLevelInfo +func (ll *DefaultLeveledLogger) Info(msg string) { + ll.logf(ll.info, LogLevelInfo, msg) +} + +// Infof formats and emits a message if the logger is at or below LogLevelInfo +func (ll *DefaultLeveledLogger) Infof(format string, args ...interface{}) { + ll.logf(ll.info, LogLevelInfo, format, args...) +} + +// Warn emits the preformatted message if the logger is at or below LogLevelWarn +func (ll *DefaultLeveledLogger) Warn(msg string) { + ll.logf(ll.warn, LogLevelWarn, msg) +} + +// Warnf formats and emits a message if the logger is at or below LogLevelWarn +func (ll *DefaultLeveledLogger) Warnf(format string, args ...interface{}) { + ll.logf(ll.warn, LogLevelWarn, format, args...) +} + +// Error emits the preformatted message if the logger is at or below LogLevelError +func (ll *DefaultLeveledLogger) Error(msg string) { + ll.logf(ll.err, LogLevelError, msg) +} + +// Errorf formats and emits a message if the logger is at or below LogLevelError +func (ll *DefaultLeveledLogger) Errorf(format string, args ...interface{}) { + ll.logf(ll.err, LogLevelError, format, args...) +} + +// NewDefaultLeveledLoggerForScope returns a configured LeveledLogger +func NewDefaultLeveledLoggerForScope(scope string, level LogLevel, writer io.Writer) *DefaultLeveledLogger { + if writer == nil { + writer = os.Stdout + } + logger := &DefaultLeveledLogger{ + writer: &loggerWriter{output: writer}, + level: level, + } + return logger. + WithTraceLogger(log.New(logger.writer, fmt.Sprintf("%s TRACE: ", scope), log.Lmicroseconds|log.Lshortfile)). + WithDebugLogger(log.New(logger.writer, fmt.Sprintf("%s DEBUG: ", scope), log.Lmicroseconds|log.Lshortfile)). + WithInfoLogger(log.New(logger.writer, fmt.Sprintf("%s INFO: ", scope), log.LstdFlags)). + WithWarnLogger(log.New(logger.writer, fmt.Sprintf("%s WARNING: ", scope), log.LstdFlags)). + WithErrorLogger(log.New(logger.writer, fmt.Sprintf("%s ERROR: ", scope), log.LstdFlags)) +} + +// DefaultLoggerFactory define levels by scopes and creates new DefaultLeveledLogger +type DefaultLoggerFactory struct { + Writer io.Writer + DefaultLogLevel LogLevel + ScopeLevels map[string]LogLevel +} + +// NewDefaultLoggerFactory creates a new DefaultLoggerFactory +func NewDefaultLoggerFactory() *DefaultLoggerFactory { + factory := DefaultLoggerFactory{} + factory.DefaultLogLevel = LogLevelError + factory.ScopeLevels = make(map[string]LogLevel) + factory.Writer = os.Stdout + + logLevels := map[string]LogLevel{ + "DISABLE": LogLevelDisabled, + "ERROR": LogLevelError, + "WARN": LogLevelWarn, + "INFO": LogLevelInfo, + "DEBUG": LogLevelDebug, + "TRACE": LogLevelTrace, + } + + for name, level := range logLevels { + env := os.Getenv(fmt.Sprintf("PION_LOG_%s", name)) + + if env == "" { + env = os.Getenv(fmt.Sprintf("PIONS_LOG_%s", name)) + } + + if env == "" { + continue + } + + if strings.ToLower(env) == "all" { + factory.DefaultLogLevel = level + continue + } + + scopes := strings.Split(strings.ToLower(env), ",") + for _, scope := range scopes { + factory.ScopeLevels[scope] = level + } + } + + return &factory +} + +// NewLogger returns a configured LeveledLogger for the given , argsscope +func (f *DefaultLoggerFactory) NewLogger(scope string) LeveledLogger { + logLevel := f.DefaultLogLevel + if f.ScopeLevels != nil { + scopeLevel, found := f.ScopeLevels[scope] + + if found { + logLevel = scopeLevel + } + } + return NewDefaultLeveledLoggerForScope(scope, logLevel, f.Writer) +} diff --git a/vendor/github.com/pion/logging/scoped.go b/vendor/github.com/pion/logging/scoped.go new file mode 100644 index 0000000..678bab4 --- /dev/null +++ b/vendor/github.com/pion/logging/scoped.go @@ -0,0 +1,72 @@ +package logging + +import ( + "sync/atomic" +) + +// LogLevel represents the level at which the logger will emit log messages +type LogLevel int32 + +// Set updates the LogLevel to the supplied value +func (ll *LogLevel) Set(newLevel LogLevel) { + atomic.StoreInt32((*int32)(ll), int32(newLevel)) +} + +// Get retrieves the current LogLevel value +func (ll *LogLevel) Get() LogLevel { + return LogLevel(atomic.LoadInt32((*int32)(ll))) +} + +func (ll LogLevel) String() string { + switch ll { + case LogLevelDisabled: + return "Disabled" + case LogLevelError: + return "Error" + case LogLevelWarn: + return "Warn" + case LogLevelInfo: + return "Info" + case LogLevelDebug: + return "Debug" + case LogLevelTrace: + return "Trace" + default: + return "UNKNOWN" + } +} + +const ( + // LogLevelDisabled completely disables logging of any events + LogLevelDisabled LogLevel = iota + // LogLevelError is for fatal errors which should be handled by user code, + // but are logged to ensure that they are seen + LogLevelError + // LogLevelWarn is for logging abnormal, but non-fatal library operation + LogLevelWarn + // LogLevelInfo is for logging normal library operation (e.g. state transitions, etc.) + LogLevelInfo + // LogLevelDebug is for logging low-level library information (e.g. internal operations) + LogLevelDebug + // LogLevelTrace is for logging very low-level library information (e.g. network traces) + LogLevelTrace +) + +// LeveledLogger is the basic pion Logger interface +type LeveledLogger interface { + Trace(msg string) + Tracef(format string, args ...interface{}) + Debug(msg string) + Debugf(format string, args ...interface{}) + Info(msg string) + Infof(format string, args ...interface{}) + Warn(msg string) + Warnf(format string, args ...interface{}) + Error(msg string) + Errorf(format string, args ...interface{}) +} + +// LoggerFactory is the basic pion LoggerFactory interface +type LoggerFactory interface { + NewLogger(scope string) LeveledLogger +} diff --git a/vendor/github.com/pion/mdns/.golangci.yml b/vendor/github.com/pion/mdns/.golangci.yml new file mode 100644 index 0000000..fb2ff86 --- /dev/null +++ b/vendor/github.com/pion/mdns/.golangci.yml @@ -0,0 +1,13 @@ +linters-settings: + govet: + check-shadowing: true + misspell: + locale: US + +linters: + enable-all: true + disable: + - funlen + +issues: + exclude-use-default: false diff --git a/vendor/github.com/pion/mdns/.travis.yml b/vendor/github.com/pion/mdns/.travis.yml new file mode 100644 index 0000000..5efedaf --- /dev/null +++ b/vendor/github.com/pion/mdns/.travis.yml @@ -0,0 +1,19 @@ +language: go + +go: + - "1.x" # use the latest Go release + +env: + - GO111MODULE=on + +before_script: + - curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | bash -s -- -b $GOPATH/bin v1.18.0 + +script: + - golangci-lint run ./... + - rm -rf examples # Remove examples, no test coverage for them + - go test -coverpkg=$(go list ./... | tr '\n' ',') -coverprofile=cover.out -v -race -covermode=atomic ./... + - bash <(curl -s https://codecov.io/bash) + - bash .github/assert-contributors.sh + - bash .github/lint-disallowed-functions-in-library.sh + - bash .github/lint-commit-message.sh diff --git a/vendor/github.com/pion/mdns/LICENSE b/vendor/github.com/pion/mdns/LICENSE new file mode 100644 index 0000000..ab60297 --- /dev/null +++ b/vendor/github.com/pion/mdns/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/pion/mdns/README.md b/vendor/github.com/pion/mdns/README.md new file mode 100644 index 0000000..0be76a7 --- /dev/null +++ b/vendor/github.com/pion/mdns/README.md @@ -0,0 +1,63 @@ +<h1 align="center"> + <br> + Pion mDNS + <br> +</h1> +<h4 align="center">A Go implementation of mDNS</h4> +<p align="center"> + <a href="https://pion.ly"><img src="https://img.shields.io/badge/pion-mdns-gray.svg?longCache=true&colorB=brightgreen" alt="Pion mDNS"></a> + <a href="https://pion.ly/slack"><img src="https://img.shields.io/badge/join-us%20on%20slack-gray.svg?longCache=true&logo=slack&colorB=brightgreen" alt="Slack Widget"></a> + <br> + <a href="https://travis-ci.org/pion/mdns"><img src="https://travis-ci.org/pion/mdns.svg?branch=master" alt="Build Status"></a> + <a href="https://godoc.org/github.com/pion/mdns"><img src="https://godoc.org/github.com/pion/mdns?status.svg" alt="GoDoc"></a> + <a href="https://codecov.io/gh/pion/mdns"><img src="https://codecov.io/gh/pion/mdns/branch/master/graph/badge.svg" alt="Coverage Status"></a> + <a href="https://goreportcard.com/report/github.com/pion/mdns"><img src="https://goreportcard.com/badge/github.com/pion/mdns" alt="Go Report Card"></a> + <a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="License: MIT"></a> +</p> +<br> + +Go mDNS implementation. The original user is Pion WebRTC, but we would love to see it work for everyone. + +### Running Server +For a mDNS server that responds to queries for `pion-test.local` +```sh +go run examples/listen/main.go +``` + + +### Running Client +To query using Pion you can run the `query` example +```sh +go run examples/query/main.go +``` + +You can use the macOS client +``` +dns-sd -q pion-test.local +``` + +Or the avahi client +``` +avahi-resolve -a pion-test.local +``` + +### References +https://tools.ietf.org/html/rfc6762 +https://tools.ietf.org/id/draft-ietf-rtcweb-mdns-ice-candidates-02.html + +### Community +Pion has an active community on the [Golang Slack](https://invite.slack.golangbridge.org/). Sign up and join the **#pion** channel for discussions and support. You can also use [Pion mailing list](https://groups.google.com/forum/#!forum/pion). + +We are always looking to support **your projects**. Please reach out if you have something to build! + +If you need commercial support or don't want to use public methods you can contact us at [team@pion.ly](mailto:team@pion.ly) + +### Contributing +Check out the **[contributing wiki](https://github.com/pion/webrtc/wiki/Contributing)** to join the group of amazing people making this project possible: + +* [Sean DuBois](https://github.com/Sean-Der) - *Original Author* +* [Konstantin Itskov](https://github.com/trivigy) - Contributor +* [Hugo Arregui](https://github.com/hugoArregui) + +### License +MIT License - see [LICENSE](LICENSE) for full text diff --git a/vendor/github.com/pion/mdns/config.go b/vendor/github.com/pion/mdns/config.go new file mode 100644 index 0000000..ebfa4e5 --- /dev/null +++ b/vendor/github.com/pion/mdns/config.go @@ -0,0 +1,27 @@ +package mdns + +import ( + "time" + + "github.com/pion/logging" +) + +const ( + // DefaultAddress is the default used by mDNS + // and in most cases should be the address that the + // net.Conn passed to Server is bound to + DefaultAddress = "224.0.0.0:5353" +) + +// Config is used to configure a mDNS client or server. +type Config struct { + // QueryInterval controls how ofter we sends Queries until we + // get a response for the requested name + QueryInterval time.Duration + + // LocalNames are the names that we will generate answers for + // when we get questions + LocalNames []string + + LoggerFactory logging.LoggerFactory +} diff --git a/vendor/github.com/pion/mdns/conn.go b/vendor/github.com/pion/mdns/conn.go new file mode 100644 index 0000000..a8aafb2 --- /dev/null +++ b/vendor/github.com/pion/mdns/conn.go @@ -0,0 +1,316 @@ +package mdns + +import ( + "context" + "math/big" + "net" + "sync" + "time" + + "github.com/pion/logging" + "golang.org/x/net/dns/dnsmessage" + "golang.org/x/net/ipv4" +) + +// Conn represents a mDNS Server +type Conn struct { + mu sync.RWMutex + log logging.LeveledLogger + + socket *ipv4.PacketConn + dstAddr *net.UDPAddr + + queryInterval time.Duration + localNames []string + queries []query + + closed chan interface{} +} + +type query struct { + nameWithSuffix string + queryResultChan chan queryResult +} + +type queryResult struct { + answer dnsmessage.ResourceHeader + addr net.Addr +} + +const ( + inboundBufferSize = 512 + defaultQueryInterval = time.Second + destinationAddress = "224.0.0.251:5353" + maxMessageRecords = 3 + responseTTL = 120 +) + +// Server establishes a mDNS connection over an existing conn +func Server(conn *ipv4.PacketConn, config *Config) (*Conn, error) { + if config == nil { + return nil, errNilConfig + } + + ifaces, err := net.Interfaces() + if err != nil { + return nil, err + } + + joinErrCount := 0 + for i := range ifaces { + if err = conn.JoinGroup(&ifaces[i], &net.UDPAddr{IP: net.IPv4(224, 0, 0, 251)}); err != nil { + joinErrCount++ + } + } + if joinErrCount >= len(ifaces) { + return nil, errJoiningMulticastGroup + } + + dstAddr, err := net.ResolveUDPAddr("udp", destinationAddress) + if err != nil { + return nil, err + + } + + loggerFactory := config.LoggerFactory + if loggerFactory == nil { + loggerFactory = logging.NewDefaultLoggerFactory() + } + + localNames := []string{} + for _, l := range config.LocalNames { + localNames = append(localNames, l+".") + } + + c := &Conn{ + queryInterval: defaultQueryInterval, + queries: []query{}, + socket: conn, + dstAddr: dstAddr, + localNames: localNames, + log: loggerFactory.NewLogger("mdns"), + closed: make(chan interface{}), + } + if config.QueryInterval != 0 { + c.queryInterval = config.QueryInterval + } + + go c.start() + return c, nil +} + +// Close closes the mDNS Conn +func (c *Conn) Close() error { + select { + case <-c.closed: + return nil + default: + } + + if err := c.socket.Close(); err != nil { + return err + } + + <-c.closed + return nil +} + +// Query sends mDNS Queries for the following name until +// either the Context is canceled/expires or we get a result +func (c *Conn) Query(ctx context.Context, name string) (dnsmessage.ResourceHeader, net.Addr, error) { + select { + case <-c.closed: + return dnsmessage.ResourceHeader{}, nil, errConnectionClosed + default: + } + + nameWithSuffix := name + "." + + queryChan := make(chan queryResult, 1) + c.mu.Lock() + c.queries = append(c.queries, query{nameWithSuffix, queryChan}) + ticker := time.NewTicker(c.queryInterval) + c.mu.Unlock() + + c.sendQuestion(nameWithSuffix) + for { + select { + case <-ticker.C: + c.sendQuestion(nameWithSuffix) + case <-c.closed: + return dnsmessage.ResourceHeader{}, nil, errConnectionClosed + case res := <-queryChan: + return res.answer, res.addr, nil + case <-ctx.Done(): + return dnsmessage.ResourceHeader{}, nil, errContextElapsed + } + } +} + +func ipToBytes(ip net.IP) (out [4]byte) { + rawIP := ip.To4() + if rawIP == nil { + return + } + + ipInt := big.NewInt(0) + ipInt.SetBytes(rawIP) + copy(out[:], ipInt.Bytes()) + return +} + +func interfaceForRemote(remote string) (net.IP, error) { + conn, err := net.Dial("udp", remote) + if err != nil { + return nil, err + } + + localAddr := conn.LocalAddr().(*net.UDPAddr) + if err := conn.Close(); err != nil { + return nil, err + } + + return localAddr.IP, nil +} + +func (c *Conn) sendQuestion(name string) { + packedName, err := dnsmessage.NewName(name) + if err != nil { + c.log.Warnf("Failed to construct mDNS packet %v", err) + return + } + + msg := dnsmessage.Message{ + Header: dnsmessage.Header{}, + Questions: []dnsmessage.Question{ + { + Type: dnsmessage.TypeA, + Class: dnsmessage.ClassINET, + Name: packedName, + }, + }, + } + + rawQuery, err := msg.Pack() + if err != nil { + c.log.Warnf("Failed to construct mDNS packet %v", err) + return + } + + if _, err := c.socket.WriteTo(rawQuery, nil, c.dstAddr); err != nil { + c.log.Warnf("Failed to send mDNS packet %v", err) + return + } +} + +func (c *Conn) sendAnswer(name string, dst net.IP) { + packedName, err := dnsmessage.NewName(name) + if err != nil { + c.log.Warnf("Failed to construct mDNS packet %v", err) + return + } + + msg := dnsmessage.Message{ + Header: dnsmessage.Header{ + Response: true, + Authoritative: true, + }, + Answers: []dnsmessage.Resource{ + { + Header: dnsmessage.ResourceHeader{ + Type: dnsmessage.TypeA, + Class: dnsmessage.ClassINET, + Name: packedName, + TTL: responseTTL, + }, + Body: &dnsmessage.AResource{ + A: ipToBytes(dst), + }, + }, + }, + } + + rawAnswer, err := msg.Pack() + if err != nil { + c.log.Warnf("Failed to construct mDNS packet %v", err) + return + } + + if _, err := c.socket.WriteTo(rawAnswer, nil, c.dstAddr); err != nil { + c.log.Warnf("Failed to send mDNS packet %v", err) + return + } +} + +func (c *Conn) start() { + defer func() { + c.mu.Lock() + defer c.mu.Unlock() + close(c.closed) + }() + + b := make([]byte, inboundBufferSize) + p := dnsmessage.Parser{} + + for { + n, _, src, err := c.socket.ReadFrom(b) + if err != nil { + return + } + + func() { + c.mu.RLock() + defer c.mu.RUnlock() + + if _, err := p.Start(b[:n]); err != nil { + c.log.Warnf("Failed to parse mDNS packet %v", err) + return + } + + for i := 0; i <= maxMessageRecords; i++ { + q, err := p.Question() + if err == dnsmessage.ErrSectionDone { + break + } else if err != nil { + c.log.Warnf("Failed to parse mDNS packet %v", err) + return + } + + for _, localName := range c.localNames { + if localName == q.Name.String() { + + localAddress, err := interfaceForRemote(src.String()) + if err != nil { + c.log.Warnf("Failed to get local interface to communicate with %s: %v", src.String(), err) + continue + } + + c.sendAnswer(q.Name.String(), localAddress) + } + } + } + + for i := 0; i <= maxMessageRecords; i++ { + a, err := p.AnswerHeader() + if err == dnsmessage.ErrSectionDone { + return + } + if err != nil { + c.log.Warnf("Failed to parse mDNS packet %v", err) + return + } + + if a.Type != dnsmessage.TypeA && a.Type != dnsmessage.TypeAAAA { + continue + } + + for i := len(c.queries) - 1; i >= 0; i-- { + if c.queries[i].nameWithSuffix == a.Name.String() { + c.queries[i].queryResultChan <- queryResult{a, src} + c.queries = append(c.queries[:i], c.queries[i+1:]...) + } + } + } + }() + } +} diff --git a/vendor/github.com/pion/mdns/errors.go b/vendor/github.com/pion/mdns/errors.go new file mode 100644 index 0000000..2f8dc92 --- /dev/null +++ b/vendor/github.com/pion/mdns/errors.go @@ -0,0 +1,10 @@ +package mdns + +import "errors" + +var ( + errJoiningMulticastGroup = errors.New("mDNS: failed to join multicast group") + errConnectionClosed = errors.New("mDNS: connection is closed") + errContextElapsed = errors.New("mDNS: context has elapsed") + errNilConfig = errors.New("mDNS: config must not be nil") +) diff --git a/vendor/github.com/pion/mdns/go.mod b/vendor/github.com/pion/mdns/go.mod new file mode 100644 index 0000000..8359ed9 --- /dev/null +++ b/vendor/github.com/pion/mdns/go.mod @@ -0,0 +1,9 @@ +module github.com/pion/mdns + +go 1.12 + +require ( + github.com/pion/logging v0.2.2 + github.com/pion/transport v0.8.10 + golang.org/x/net v0.0.0-20191126235420-ef20fe5d7933 +) diff --git a/vendor/github.com/pion/mdns/go.sum b/vendor/github.com/pion/mdns/go.sum new file mode 100644 index 0000000..047fa8b --- /dev/null +++ b/vendor/github.com/pion/mdns/go.sum @@ -0,0 +1,16 @@ +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY= +github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms= +github.com/pion/transport v0.8.10 h1:lTiobMEw2PG6BH/mgIVqTV2mBp/mPT+IJLaN8ZxgdHk= +github.com/pion/transport v0.8.10/go.mod h1:tBmha/UCjpum5hqTWhfAEs3CO4/tHSg0MYRhSzR+CZ8= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/net v0.0.0-20191126235420-ef20fe5d7933 h1:e6HwijUxhDe+hPNjZQQn9bA5PW3vNmnN64U2ZW759Lk= +golang.org/x/net v0.0.0-20191126235420-ef20fe5d7933/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/vendor/github.com/pion/mdns/renovate.json b/vendor/github.com/pion/mdns/renovate.json new file mode 100644 index 0000000..4400fd9 --- /dev/null +++ b/vendor/github.com/pion/mdns/renovate.json @@ -0,0 +1,15 @@ +{ + "extends": [ + "config:base" + ], + "postUpdateOptions": [ + "gomodTidy" + ], + "commitBody": "Generated by renovateBot", + "packageRules": [ + { + "packagePatterns": ["^golang.org/x/"], + "schedule": ["on the first day of the month"] + } + ] +} diff --git a/vendor/github.com/pion/randutil/.travis.yml b/vendor/github.com/pion/randutil/.travis.yml new file mode 100644 index 0000000..f04a896 --- /dev/null +++ b/vendor/github.com/pion/randutil/.travis.yml @@ -0,0 +1,142 @@ +# +# DO NOT EDIT THIS FILE +# +# It is automatically copied from https://github.com/pion/.goassets repository. +# If this repository should have package specific CI config, +# remove the repository name from .goassets/.github/workflows/assets-sync.yml. +# +# If you want to update the shared CI config, send a PR to +# https://github.com/pion/.goassets instead of this repository. +# + +dist: bionic +language: go + + +branches: + only: + - master + +env: + global: + - GO111MODULE=on + - GOLANGCI_LINT_VERSION=1.19.1 + +cache: + directories: + - ${HOME}/.cache/go-build + - ${GOPATH}/pkg/mod + npm: true + yarn: true + +_lint_job: &lint_job + env: CACHE_NAME=lint + before_install: + - if [ -f .github/.ci.conf ]; then . .github/.ci.conf; fi + install: skip + before_script: + - | + curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh \ + | bash -s - -b $GOPATH/bin v${GOLANGCI_LINT_VERSION} + script: + - bash .github/assert-contributors.sh + - bash .github/lint-disallowed-functions-in-library.sh + - bash .github/lint-commit-message.sh + - bash .github/lint-filename.sh + - golangci-lint run ./... +_test_job: &test_job + env: CACHE_NAME=test + before_install: + - if [ -f .github/.ci.conf ]; then . .github/.ci.conf; fi + - go mod download + install: + - go build ./... + script: + # If you want to specify repository specific test packages rule, + # add `TEST_PACKAGES=$(command to list test target packages)` to .github/.ci.conf + - testpkgs=${TEST_PACKAGES:-$(go list ./... | grep -v examples)} + - coverpkgs=$(echo "${testpkgs}" | paste -s -d ',') + - | + go test \ + -coverpkg=${coverpkgs} -coverprofile=cover.out -covermode=atomic \ + ${TEST_EXTRA_ARGS:-} \ + -v -race ${testpkgs} + - if [ -n "${TEST_HOOK}" ]; then ${TEST_HOOK}; fi + after_success: + - travis_retry bash <(curl -s https://codecov.io/bash) -c -F go +_test_i386_job: &test_i386_job + env: CACHE_NAME=test386 + services: docker + before_install: + - if [ -f .github/.ci.conf ]; then . .github/.ci.conf; fi + script: + - testpkgs=${TEST_PACKAGES:-$(go list ./... | grep -v examples)} + - | + docker run \ + -u $(id -u):$(id -g) \ + -e "GO111MODULE=on" \ + -e "CGO_ENABLED=0" \ + -v ${PWD}:/go/src/github.com/pion/$(basename ${PWD}) \ + -v ${HOME}/gopath/pkg/mod:/go/pkg/mod \ + -v ${HOME}/.cache/go-build:/.cache/go-build \ + -w /go/src/github.com/pion/$(basename ${PWD}) \ + -it i386/golang:${GO_VERSION}-alpine \ + /usr/local/go/bin/go test \ + ${TEST_EXTRA_ARGS:-} \ + -v ${testpkgs} +_test_wasm_job: &test_wasm_job + env: CACHE_NAME=wasm + language: node_js + node_js: 12 + before_install: + - if [ -f .github/.ci.conf ]; then . .github/.ci.conf; fi + - if ${SKIP_WASM_TEST:-false}; then exit 0; fi + install: + # Manually download and install Go instead of using gimme. + # It looks like gimme Go causes some errors on go-test for Wasm. + - curl -sSfL https://dl.google.com/go/go${GO_VERSION}.linux-amd64.tar.gz | tar -C ~ -xzf - + - export GOROOT=${HOME}/go + - export PATH=${GOROOT}/bin:${PATH} + - yarn install + - export GO_JS_WASM_EXEC=${GO_JS_WASM_EXEC:-${GOROOT}/misc/wasm/go_js_wasm_exec} + script: + - testpkgs=${TEST_PACKAGES:-$(go list ./... | grep -v examples)} + - coverpkgs=$(echo "${testpkgs}" | paste -s -d ',') + - | + GOOS=js GOARCH=wasm go test \ + -coverpkg=${coverpkgs} -coverprofile=cover.out -covermode=atomic \ + -exec="${GO_JS_WASM_EXEC}" \ + -v ${testpkgs} + after_success: + - travis_retry bash <(curl -s https://codecov.io/bash) -c -F wasm + +jobs: + include: + - <<: *lint_job + name: Lint 1.14 + go: 1.14 + - <<: *test_job + name: Test 1.13 + go: 1.13 + - <<: *test_job + name: Test 1.14 + go: 1.14 + - <<: *test_i386_job + name: Test i386 1.13 + env: GO_VERSION=1.13 + go: 1.14 # Go version for host environment only for `go list`. + # All tests are done on the version specified by GO_VERSION. + - <<: *test_i386_job + name: Test i386 1.14 + env: GO_VERSION=1.14 + go: 1.14 # Go version for host environment only for `go list`. + # All tests are done on the version specified by GO_VERSION. + - <<: *test_wasm_job + name: Test WASM 1.13 + env: GO_VERSION=1.13 + - <<: *test_wasm_job + name: Test WASM 1.14 + env: GO_VERSION=1.14 + +notifications: + email: false diff --git a/vendor/github.com/pion/randutil/LICENSE b/vendor/github.com/pion/randutil/LICENSE new file mode 100644 index 0000000..5b5a394 --- /dev/null +++ b/vendor/github.com/pion/randutil/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 Pion + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/pion/randutil/README.md b/vendor/github.com/pion/randutil/README.md new file mode 100644 index 0000000..94baf77 --- /dev/null +++ b/vendor/github.com/pion/randutil/README.md @@ -0,0 +1,14 @@ +# randutil +Helper library for cryptographic and mathmatical randoms + +### Community +Pion has an active community on the [Golang Slack](https://invite.slack.golangbridge.org/). Sign up and join the **#pion** channel for discussions and support. You can also use [Pion mailing list](https://groups.google.com/forum/#!forum/pion). + +We are always looking to support **your projects**. Please reach out if you have something to build! + +If you need commercial support or don't want to use public methods you can contact us at [team@pion.ly](mailto:team@pion.ly) + +### Contributing +Check out the **[contributing wiki](https://github.com/pion/webrtc/wiki/Contributing)** to join the group of amazing people making this project possible: + +* [Atsushi Watanabe](https://github.com/at-wat) - *Original Author* diff --git a/vendor/github.com/pion/randutil/codecov.yml b/vendor/github.com/pion/randutil/codecov.yml new file mode 100644 index 0000000..085200a --- /dev/null +++ b/vendor/github.com/pion/randutil/codecov.yml @@ -0,0 +1,20 @@ +# +# DO NOT EDIT THIS FILE +# +# It is automatically copied from https://github.com/pion/.goassets repository. +# + +coverage: + status: + project: + default: + # Allow decreasing 2% of total coverage to avoid noise. + threshold: 2% + patch: + default: + target: 70% + only_pulls: true + +ignore: + - "examples/*" + - "examples/**/*" diff --git a/vendor/github.com/pion/randutil/crypto.go b/vendor/github.com/pion/randutil/crypto.go new file mode 100644 index 0000000..d10df98 --- /dev/null +++ b/vendor/github.com/pion/randutil/crypto.go @@ -0,0 +1,30 @@ +package randutil + +import ( + crand "crypto/rand" + "encoding/binary" + "math/big" +) + +// GenerateCryptoRandomString generates a random string for cryptographic usage. +func GenerateCryptoRandomString(n int, runes string) (string, error) { + letters := []rune(runes) + b := make([]rune, n) + for i := range b { + v, err := crand.Int(crand.Reader, big.NewInt(int64(len(letters)))) + if err != nil { + return "", err + } + b[i] = letters[v.Int64()] + } + return string(b), nil +} + +// CryptoUint64 returns cryptographic random uint64. +func CryptoUint64() (uint64, error) { + var v uint64 + if err := binary.Read(crand.Reader, binary.LittleEndian, &v); err != nil { + return 0, err + } + return v, nil +} diff --git a/vendor/github.com/pion/randutil/go.mod b/vendor/github.com/pion/randutil/go.mod new file mode 100644 index 0000000..2a824e0 --- /dev/null +++ b/vendor/github.com/pion/randutil/go.mod @@ -0,0 +1,3 @@ +module github.com/pion/randutil + +go 1.14 diff --git a/vendor/github.com/pion/randutil/math.go b/vendor/github.com/pion/randutil/math.go new file mode 100644 index 0000000..fbbf460 --- /dev/null +++ b/vendor/github.com/pion/randutil/math.go @@ -0,0 +1,72 @@ +package randutil + +import ( + mrand "math/rand" // used for non-crypto unique ID and random port selection + "sync" + "time" +) + +// MathRandomGenerator is a random generator for non-crypto usage. +type MathRandomGenerator interface { + // Intn returns random integer within [0:n). + Intn(n int) int + + // Uint32 returns random 32-bit unsigned integer. + Uint32() uint32 + + // Uint64 returns random 64-bit unsigned integer. + Uint64() uint64 + + // GenerateString returns ranom string using given set of runes. + // It can be used for generating unique ID to avoid name collision. + // + // Caution: DO NOT use this for cryptographic usage. + GenerateString(n int, runes string) string +} + +type mathRandomGenerator struct { + r *mrand.Rand + mu sync.Mutex +} + +// NewMathRandomGenerator creates new mathmatical random generator. +// Random generator is seeded by crypto random. +func NewMathRandomGenerator() MathRandomGenerator { + seed, err := CryptoUint64() + if err != nil { + // crypto/rand is unavailable. Fallback to seed by time. + seed = uint64(time.Now().UnixNano()) + } + + return &mathRandomGenerator{r: mrand.New(mrand.NewSource(int64(seed)))} +} + +func (g *mathRandomGenerator) Intn(n int) int { + g.mu.Lock() + v := g.r.Intn(n) + g.mu.Unlock() + return v +} + +func (g *mathRandomGenerator) Uint32() uint32 { + g.mu.Lock() + v := g.r.Uint32() + g.mu.Unlock() + return v +} + +func (g *mathRandomGenerator) Uint64() uint64 { + g.mu.Lock() + v := g.r.Uint64() + g.mu.Unlock() + return v +} + +func (g *mathRandomGenerator) GenerateString(n int, runes string) string { + letters := []rune(runes) + b := make([]rune, n) + for i := range b { + b[i] = letters[g.Intn(len(letters))] + } + return string(b) +} diff --git a/vendor/github.com/pion/randutil/renovate.json b/vendor/github.com/pion/randutil/renovate.json new file mode 100644 index 0000000..4400fd9 --- /dev/null +++ b/vendor/github.com/pion/randutil/renovate.json @@ -0,0 +1,15 @@ +{ + "extends": [ + "config:base" + ], + "postUpdateOptions": [ + "gomodTidy" + ], + "commitBody": "Generated by renovateBot", + "packageRules": [ + { + "packagePatterns": ["^golang.org/x/"], + "schedule": ["on the first day of the month"] + } + ] +} diff --git a/vendor/github.com/pion/rtcp/.gitignore b/vendor/github.com/pion/rtcp/.gitignore new file mode 100644 index 0000000..83db74b --- /dev/null +++ b/vendor/github.com/pion/rtcp/.gitignore @@ -0,0 +1,24 @@ +### JetBrains IDE ### +##################### +.idea/ + +### Emacs Temporary Files ### +############################# +*~ + +### Folders ### +############### +bin/ +vendor/ +node_modules/ + +### Files ### +############# +*.ivf +*.ogg +tags +cover.out +*.sw[poe] +*.wasm +examples/sfu-ws/cert.pem +examples/sfu-ws/key.pem diff --git a/vendor/github.com/pion/rtcp/.golangci.yml b/vendor/github.com/pion/rtcp/.golangci.yml new file mode 100644 index 0000000..d6162c9 --- /dev/null +++ b/vendor/github.com/pion/rtcp/.golangci.yml @@ -0,0 +1,89 @@ +linters-settings: + govet: + check-shadowing: true + misspell: + locale: US + exhaustive: + default-signifies-exhaustive: true + gomodguard: + blocked: + modules: + - github.com/pkg/errors: + recommendations: + - errors + +linters: + enable: + - asciicheck # Simple linter to check that your code does not contain non-ASCII identifiers + - bodyclose # checks whether HTTP response body is closed successfully + - deadcode # Finds unused code + - depguard # Go linter that checks if package imports are in a list of acceptable packages + - dogsled # Checks assignments with too many blank identifiers (e.g. x, _, _, _, := f()) + - dupl # Tool for code clone detection + - errcheck # Errcheck is a program for checking for unchecked errors in go programs. These unchecked errors can be critical bugs in some cases + - exhaustive # check exhaustiveness of enum switch statements + - exportloopref # checks for pointers to enclosing loop variables + - gci # Gci control golang package import order and make it always deterministic. + - gochecknoglobals # Checks that no globals are present in Go code + - gochecknoinits # Checks that no init functions are present in Go code + - gocognit # Computes and checks the cognitive complexity of functions + - goconst # Finds repeated strings that could be replaced by a constant + - gocritic # The most opinionated Go source code linter + - godox # Tool for detection of FIXME, TODO and other comment keywords + - goerr113 # Golang linter to check the errors handling expressions + - gofmt # Gofmt checks whether code was gofmt-ed. By default this tool runs with -s option to check for code simplification + - gofumpt # Gofumpt checks whether code was gofumpt-ed. + - goheader # Checks is file header matches to pattern + - goimports # Goimports does everything that gofmt does. Additionally it checks unused imports + - golint # Golint differs from gofmt. Gofmt reformats Go source code, whereas golint prints out style mistakes + - gomodguard # Allow and block list linter for direct Go module dependencies. This is different from depguard where there are different block types for example version constraints and module recommendations. + - goprintffuncname # Checks that printf-like functions are named with `f` at the end + - gosec # Inspects source code for security problems + - gosimple # Linter for Go source code that specializes in simplifying a code + - govet # Vet examines Go source code and reports suspicious constructs, such as Printf calls whose arguments do not align with the format string + - ineffassign # Detects when assignments to existing variables are not used + - misspell # Finds commonly misspelled English words in comments + - nakedret # Finds naked returns in functions greater than a specified function length + - noctx # noctx finds sending http request without context.Context + - scopelint # Scopelint checks for unpinned variables in go programs + - staticcheck # Staticcheck is a go vet on steroids, applying a ton of static analysis checks + - structcheck # Finds unused struct fields + - stylecheck # Stylecheck is a replacement for golint + - typecheck # Like the front-end of a Go compiler, parses and type-checks Go code + - unconvert # Remove unnecessary type conversions + - unparam # Reports unused function parameters + - unused # Checks Go code for unused constants, variables, functions and types + - varcheck # Finds unused global variables and constants + - whitespace # Tool for detection of leading and trailing whitespace + disable: + - funlen # Tool for detection of long functions + - gocyclo # Computes and checks the cyclomatic complexity of functions + - godot # Check if comments end in a period + - gomnd # An analyzer to detect magic numbers. + - lll # Reports long lines + - maligned # Tool to detect Go structs that would take less memory if their fields were sorted + - nestif # Reports deeply nested if statements + - nlreturn # nlreturn checks for a new line before return and branch statements to increase code clarity + - nolintlint # Reports ill-formed or insufficient nolint directives + - prealloc # Finds slice declarations that could potentially be preallocated + - rowserrcheck # checks whether Err of rows is checked successfully + - sqlclosecheck # Checks that sql.Rows and sql.Stmt are closed. + - testpackage # linter that makes you use a separate _test package + - wsl # Whitespace Linter - Forces you to use empty lines! + +issues: + exclude-use-default: false + exclude-rules: + # Allow complex tests, better to be self contained + - path: _test\.go + linters: + - gocognit + + # Allow complex main function in examples + - path: examples + text: "of func `main` is high" + linters: + - gocognit + +run: + skip-dirs-use-default: false diff --git a/vendor/github.com/pion/rtcp/LICENSE b/vendor/github.com/pion/rtcp/LICENSE new file mode 100644 index 0000000..ab60297 --- /dev/null +++ b/vendor/github.com/pion/rtcp/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/pion/rtcp/README.md b/vendor/github.com/pion/rtcp/README.md new file mode 100644 index 0000000..a054dae --- /dev/null +++ b/vendor/github.com/pion/rtcp/README.md @@ -0,0 +1,49 @@ +<h1 align="center"> + <br> + Pion RTCP + <br> +</h1> +<h4 align="center">A Go implementation of RTCP</h4> +<p align="center"> + <a href="https://pion.ly"><img src="https://img.shields.io/badge/pion-rtcp-gray.svg?longCache=true&colorB=brightgreen" alt="Pion RTCP"></a> + <a href="https://sourcegraph.com/github.com/pion/rtcp?badge"><img src="https://sourcegraph.com/github.com/pion/rtcp/-/badge.svg" alt="Sourcegraph Widget"></a> + <a href="https://pion.ly/slack"><img src="https://img.shields.io/badge/join-us%20on%20slack-gray.svg?longCache=true&logo=slack&colorB=brightgreen" alt="Slack Widget"></a> + <br> + <a href="https://travis-ci.org/pion/rtcp"><img src="https://travis-ci.org/pion/rtcp.svg?branch=master" alt="Build Status"></a> + <a href="https://pkg.go.dev/github.com/pion/rtcp"><img src="https://godoc.org/github.com/pion/rtcp?status.svg" alt="GoDoc"></a> + <a href="https://codecov.io/gh/pion/rtcp"><img src="https://codecov.io/gh/pion/rtcp/branch/master/graph/badge.svg" alt="Coverage Status"></a> + <a href="https://goreportcard.com/report/github.com/pion/rtcp"><img src="https://goreportcard.com/badge/github.com/pion/rtcp" alt="Go Report Card"></a> + <a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="License: MIT"></a> +</p> +<br> + +See [DESIGN.md](DESIGN.md) for an overview of features and future goals. + +### Roadmap +The library is used as a part of our WebRTC implementation. Please refer to that [roadmap](https://github.com/pion/webrtc/issues/9) to track our major milestones. + +### Community +Pion has an active community on the [Golang Slack](https://invite.slack.golangbridge.org/). Sign up and join the **#pion** channel for discussions and support. You can also use [Pion mailing list](https://groups.google.com/forum/#!forum/pion). + +We are always looking to support **your projects**. Please reach out if you have something to build! + +If you need commercial support or don't want to use public methods you can contact us at [team@pion.ly](mailto:team@pion.ly) + +### Contributing +Check out the **[contributing wiki](https://github.com/pion/webrtc/wiki/Contributing)** to join the group of amazing people making this project possible: + +* [Max Hawkins](https://github.com/maxhawkins) - *Original Author* +* [Woodrow Douglass](https://github.com/wdouglass) *RTCP, RTP improvements, G.722 support, Bugfixes* +* [Sean DuBois](https://github.com/Sean-Der) - *Linter fixes* +* [adwpc](https://github.com/adwpc) +* [Luke Curley](https://github.com/kixelated) +* [Hugo Arregui](https://github.com/hugoArregui) +* [Atsushi Watanabe](https://github.com/at-wat) +* [Juliusz Chroboczek](https://github.com/jech) +* [Gabor Pongracz](https://github.com/pongraczgabor87) +* [Simone Gotti](https://github.com/sgotti) +* [lllf](https://github.com/LittleLightLittleFire) +* [cnderrauber](https://github.com/cnderrauber) + +### License +MIT License - see [LICENSE](LICENSE) for full text diff --git a/vendor/github.com/pion/rtcp/codecov.yml b/vendor/github.com/pion/rtcp/codecov.yml new file mode 100644 index 0000000..085200a --- /dev/null +++ b/vendor/github.com/pion/rtcp/codecov.yml @@ -0,0 +1,20 @@ +# +# DO NOT EDIT THIS FILE +# +# It is automatically copied from https://github.com/pion/.goassets repository. +# + +coverage: + status: + project: + default: + # Allow decreasing 2% of total coverage to avoid noise. + threshold: 2% + patch: + default: + target: 70% + only_pulls: true + +ignore: + - "examples/*" + - "examples/**/*" diff --git a/vendor/github.com/pion/rtcp/compound_packet.go b/vendor/github.com/pion/rtcp/compound_packet.go new file mode 100644 index 0000000..2c74279 --- /dev/null +++ b/vendor/github.com/pion/rtcp/compound_packet.go @@ -0,0 +1,136 @@ +package rtcp + +// A CompoundPacket is a collection of RTCP packets transmitted as a single packet with +// the underlying protocol (for example UDP). +// +// To maximize the resolution of receiption statistics, the first Packet in a CompoundPacket +// must always be either a SenderReport or a ReceiverReport. This is true even if no data +// has been sent or received, in which case an empty ReceiverReport must be sent, and even +// if the only other RTCP packet in the compound packet is a Goodbye. +// +// Next, a SourceDescription containing a CNAME item must be included in each CompoundPacket +// to identify the source and to begin associating media for purposes such as lip-sync. +// +// Other RTCP packet types may follow in any order. Packet types may appear more than once. +type CompoundPacket []Packet + +var _ Packet = (*CompoundPacket)(nil) // assert is a Packet + +// Validate returns an error if this is not an RFC-compliant CompoundPacket. +func (c CompoundPacket) Validate() error { + if len(c) == 0 { + return errEmptyCompound + } + + // SenderReport and ReceiverReport are the only types that + // are allowed to be the first packet in a compound datagram + switch c[0].(type) { + case *SenderReport, *ReceiverReport: + // ok + default: + return errBadFirstPacket + } + + for _, pkt := range c[1:] { + switch p := pkt.(type) { + // If the number of RecetpionReports exceeds 31 additional ReceiverReports + // can be included here. + case *ReceiverReport: + continue + + // A SourceDescription containing a CNAME must be included in every + // CompoundPacket. + case *SourceDescription: + var hasCNAME bool + for _, c := range p.Chunks { + for _, it := range c.Items { + if it.Type == SDESCNAME { + hasCNAME = true + } + } + } + + if !hasCNAME { + return errMissingCNAME + } + + return nil + + // Other packets are not permitted before the CNAME + default: + return errPacketBeforeCNAME + } + } + + // CNAME never reached + return errMissingCNAME +} + +// CNAME returns the CNAME that *must* be present in every CompoundPacket +func (c CompoundPacket) CNAME() (string, error) { + var err error + + if len(c) < 1 { + return "", errEmptyCompound + } + + for _, pkt := range c[1:] { + sdes, ok := pkt.(*SourceDescription) + if ok { + for _, c := range sdes.Chunks { + for _, it := range c.Items { + if it.Type == SDESCNAME { + return it.Text, err + } + } + } + } else { + _, ok := pkt.(*ReceiverReport) + if !ok { + err = errPacketBeforeCNAME + } + } + } + return "", errMissingCNAME +} + +// Marshal encodes the CompoundPacket as binary. +func (c CompoundPacket) Marshal() ([]byte, error) { + if err := c.Validate(); err != nil { + return nil, err + } + + p := []Packet(c) + return Marshal(p) +} + +// Unmarshal decodes a CompoundPacket from binary. +func (c *CompoundPacket) Unmarshal(rawData []byte) error { + out := make(CompoundPacket, 0) + for len(rawData) != 0 { + p, processed, err := unmarshal(rawData) + if err != nil { + return err + } + + out = append(out, p) + rawData = rawData[processed:] + } + *c = out + + if err := c.Validate(); err != nil { + return err + } + + return nil +} + +// DestinationSSRC returns the synchronization sources associated with this +// CompoundPacket's reception report. +func (c CompoundPacket) DestinationSSRC() []uint32 { + if len(c) == 0 { + return nil + } + + return c[0].DestinationSSRC() +} diff --git a/vendor/github.com/pion/rtcp/doc.go b/vendor/github.com/pion/rtcp/doc.go new file mode 100644 index 0000000..fa948df --- /dev/null +++ b/vendor/github.com/pion/rtcp/doc.go @@ -0,0 +1,39 @@ +/* +Package rtcp implements encoding and decoding of RTCP packets according to RFCs 3550 and 5506. + +RTCP is a sister protocol of the Real-time Transport Protocol (RTP). Its basic functionality +and packet structure is defined in RFC 3550. RTCP provides out-of-band statistics and control +information for an RTP session. It partners with RTP in the delivery and packaging of multimedia data, +but does not transport any media data itself. + +The primary function of RTCP is to provide feedback on the quality of service (QoS) +in media distribution by periodically sending statistics information such as transmitted octet +and packet counts, packet loss, packet delay variation, and round-trip delay time to participants +in a streaming multimedia session. An application may use this information to control quality of +service parameters, perhaps by limiting flow, or using a different codec. + +Decoding RTCP packets: + + pkt, err := rtcp.Unmarshal(rtcpData) + // ... + + switch p := pkt.(type) { + case *rtcp.CompoundPacket: + ... + case *rtcp.PictureLossIndication: + ... + default: + ... + } + +Encoding RTCP packets: + + pkt := &rtcp.PictureLossIndication{ + SenderSSRC: senderSSRC, + MediaSSRC: mediaSSRC + } + pliData, err := pkt.Marshal() + // ... + +*/ +package rtcp diff --git a/vendor/github.com/pion/rtcp/errors.go b/vendor/github.com/pion/rtcp/errors.go new file mode 100644 index 0000000..d1b00c5 --- /dev/null +++ b/vendor/github.com/pion/rtcp/errors.go @@ -0,0 +1,30 @@ +package rtcp + +import "errors" + +var ( + errWrongMarshalSize = errors.New("rtcp: wrong marshal size") + errInvalidTotalLost = errors.New("rtcp: invalid total lost count") + errInvalidHeader = errors.New("rtcp: invalid header") + errEmptyCompound = errors.New("rtcp: empty compound packet") + errBadFirstPacket = errors.New("rtcp: first packet in compound must be SR or RR") + errMissingCNAME = errors.New("rtcp: compound missing SourceDescription with CNAME") + errPacketBeforeCNAME = errors.New("rtcp: feedback packet seen before CNAME") + errTooManyReports = errors.New("rtcp: too many reports") + errTooManyChunks = errors.New("rtcp: too many chunks") + errTooManySources = errors.New("rtcp: too many sources") + errPacketTooShort = errors.New("rtcp: packet too short") + errWrongType = errors.New("rtcp: wrong packet type") + errSDESTextTooLong = errors.New("rtcp: sdes must be < 255 octets long") + errSDESMissingType = errors.New("rtcp: sdes item missing type") + errReasonTooLong = errors.New("rtcp: reason must be < 255 octets long") + errBadVersion = errors.New("rtcp: invalid packet version") + errWrongPadding = errors.New("rtcp: invalid padding value") + errWrongFeedbackType = errors.New("rtcp: wrong feedback message type") + errWrongPayloadType = errors.New("rtcp: wrong payload type") + errHeaderTooSmall = errors.New("rtcp: header length is too small") + errSSRCMustBeZero = errors.New("rtcp: media SSRC must be 0") + errMissingREMBidentifier = errors.New("missing REMB identifier") + errSSRCNumAndLengthMismatch = errors.New("SSRC num and length do not match") + errInvalidSizeOrStartIndex = errors.New("invalid size or startIndex") +) diff --git a/vendor/github.com/pion/rtcp/full_intra_request.go b/vendor/github.com/pion/rtcp/full_intra_request.go new file mode 100644 index 0000000..74ca928 --- /dev/null +++ b/vendor/github.com/pion/rtcp/full_intra_request.go @@ -0,0 +1,107 @@ +package rtcp + +import ( + "encoding/binary" + "fmt" +) + +// A FIREntry is a (SSRC, seqno) pair, as carried by FullIntraRequest. +type FIREntry struct { + SSRC uint32 + SequenceNumber uint8 +} + +// The FullIntraRequest packet is used to reliably request an Intra frame +// in a video stream. See RFC 5104 Section 3.5.1. This is not for loss +// recovery, which should use PictureLossIndication (PLI) instead. +type FullIntraRequest struct { + SenderSSRC uint32 + MediaSSRC uint32 + + FIR []FIREntry +} + +const ( + firOffset = 8 +) + +var _ Packet = (*FullIntraRequest)(nil) + +// Marshal encodes the FullIntraRequest +func (p FullIntraRequest) Marshal() ([]byte, error) { + rawPacket := make([]byte, firOffset+(len(p.FIR)*8)) + binary.BigEndian.PutUint32(rawPacket, p.SenderSSRC) + binary.BigEndian.PutUint32(rawPacket[4:], p.MediaSSRC) + for i, fir := range p.FIR { + binary.BigEndian.PutUint32(rawPacket[firOffset+8*i:], fir.SSRC) + rawPacket[firOffset+8*i+4] = fir.SequenceNumber + } + h := p.Header() + hData, err := h.Marshal() + if err != nil { + return nil, err + } + + return append(hData, rawPacket...), nil +} + +// Unmarshal decodes the TransportLayerNack +func (p *FullIntraRequest) Unmarshal(rawPacket []byte) error { + if len(rawPacket) < (headerLength + ssrcLength) { + return errPacketTooShort + } + + var h Header + if err := h.Unmarshal(rawPacket); err != nil { + return err + } + + if len(rawPacket) < (headerLength + int(4*h.Length)) { + return errPacketTooShort + } + + if h.Type != TypePayloadSpecificFeedback || h.Count != FormatFIR { + return errWrongType + } + + p.SenderSSRC = binary.BigEndian.Uint32(rawPacket[headerLength:]) + p.MediaSSRC = binary.BigEndian.Uint32(rawPacket[headerLength+ssrcLength:]) + for i := headerLength + firOffset; i < (headerLength + int(h.Length*4)); i += 8 { + p.FIR = append(p.FIR, FIREntry{ + binary.BigEndian.Uint32(rawPacket[i:]), + rawPacket[i+4], + }) + } + return nil +} + +// Header returns the Header associated with this packet. +func (p *FullIntraRequest) Header() Header { + return Header{ + Count: FormatFIR, + Type: TypePayloadSpecificFeedback, + Length: uint16((p.len() / 4) - 1), + } +} + +func (p *FullIntraRequest) len() int { + return headerLength + firOffset + len(p.FIR)*8 +} + +func (p *FullIntraRequest) String() string { + out := fmt.Sprintf("FullIntraRequest %x %x", + p.SenderSSRC, p.MediaSSRC) + for _, e := range p.FIR { + out += fmt.Sprintf(" (%x %v)", e.SSRC, e.SequenceNumber) + } + return out +} + +// DestinationSSRC returns an array of SSRC values that this packet refers to. +func (p *FullIntraRequest) DestinationSSRC() []uint32 { + ssrcs := make([]uint32, 0, len(p.FIR)) + for _, entry := range p.FIR { + ssrcs = append(ssrcs, entry.SSRC) + } + return ssrcs +} diff --git a/vendor/github.com/pion/rtcp/fuzz.go b/vendor/github.com/pion/rtcp/fuzz.go new file mode 100644 index 0000000..2ea4fb1 --- /dev/null +++ b/vendor/github.com/pion/rtcp/fuzz.go @@ -0,0 +1,51 @@ +// +build gofuzz + +package rtcp + +import ( + "bytes" + "io" +) + +// Fuzz implements a randomized fuzz test of the rtcp +// parser using go-fuzz. +// +// To run the fuzzer, first download go-fuzz: +// `go get github.com/dvyukov/go-fuzz/...` +// +// Then build the testing package: +// `go-fuzz-build github.com/pion/webrtc` +// +// And run the fuzzer on the corpus: +// ``` +// mkdir workdir +// +// # optionally add a starter corpus of valid rtcp packets. +// # the corpus should be as compact and diverse as possible. +// cp -r ~/my-rtcp-packets workdir/corpus +// +// go-fuzz -bin=ase-fuzz.zip -workdir=workdir +// ```` +func Fuzz(data []byte) int { + r := NewReader(bytes.NewReader(data)) + for { + _, data, err := r.ReadPacket() + if err == io.EOF { + break + } + if err != nil { + return 0 + } + + packet, err := Unmarshal(data) + if err != nil { + return 0 + } + + if _, err := packet.Marshal(); err != nil { + return 0 + } + } + + return 1 +} diff --git a/vendor/github.com/pion/rtcp/go.mod b/vendor/github.com/pion/rtcp/go.mod new file mode 100644 index 0000000..28b6e9d --- /dev/null +++ b/vendor/github.com/pion/rtcp/go.mod @@ -0,0 +1,5 @@ +module github.com/pion/rtcp + +go 1.13 + +require github.com/stretchr/testify v1.6.1 diff --git a/vendor/github.com/pion/rtcp/go.sum b/vendor/github.com/pion/rtcp/go.sum new file mode 100644 index 0000000..afe7890 --- /dev/null +++ b/vendor/github.com/pion/rtcp/go.sum @@ -0,0 +1,11 @@ +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/vendor/github.com/pion/rtcp/goodbye.go b/vendor/github.com/pion/rtcp/goodbye.go new file mode 100644 index 0000000..b8718b3 --- /dev/null +++ b/vendor/github.com/pion/rtcp/goodbye.go @@ -0,0 +1,146 @@ +package rtcp + +import ( + "encoding/binary" +) + +// The Goodbye packet indicates that one or more sources are no longer active. +type Goodbye struct { + // The SSRC/CSRC identifiers that are no longer active + Sources []uint32 + // Optional text indicating the reason for leaving, e.g., "camera malfunction" or "RTP loop detected" + Reason string +} + +var _ Packet = (*Goodbye)(nil) // assert is a Packet + +// Marshal encodes the Goodbye packet in binary +func (g Goodbye) Marshal() ([]byte, error) { + /* + * 0 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * |V=2|P| SC | PT=BYE=203 | length | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | SSRC/CSRC | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * : ... : + * +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ + * (opt) | length | reason for leaving ... + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ + + rawPacket := make([]byte, g.len()) + packetBody := rawPacket[headerLength:] + + if len(g.Sources) > countMax { + return nil, errTooManySources + } + + for i, s := range g.Sources { + binary.BigEndian.PutUint32(packetBody[i*ssrcLength:], s) + } + + if g.Reason != "" { + reason := []byte(g.Reason) + + if len(reason) > sdesMaxOctetCount { + return nil, errReasonTooLong + } + + reasonOffset := len(g.Sources) * ssrcLength + packetBody[reasonOffset] = uint8(len(reason)) + copy(packetBody[reasonOffset+1:], reason) + } + + hData, err := g.Header().Marshal() + if err != nil { + return nil, err + } + copy(rawPacket, hData) + + return rawPacket, nil +} + +// Unmarshal decodes the Goodbye packet from binary +func (g *Goodbye) Unmarshal(rawPacket []byte) error { + /* + * 0 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * |V=2|P| SC | PT=BYE=203 | length | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | SSRC/CSRC | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * : ... : + * +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ + * (opt) | length | reason for leaving ... + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ + + var header Header + if err := header.Unmarshal(rawPacket); err != nil { + return err + } + + if header.Type != TypeGoodbye { + return errWrongType + } + + if getPadding(len(rawPacket)) != 0 { + return errPacketTooShort + } + + g.Sources = make([]uint32, header.Count) + + reasonOffset := int(headerLength + header.Count*ssrcLength) + if reasonOffset > len(rawPacket) { + return errPacketTooShort + } + + for i := 0; i < int(header.Count); i++ { + offset := headerLength + i*ssrcLength + + g.Sources[i] = binary.BigEndian.Uint32(rawPacket[offset:]) + } + + if reasonOffset < len(rawPacket) { + reasonLen := int(rawPacket[reasonOffset]) + reasonEnd := reasonOffset + 1 + reasonLen + + if reasonEnd > len(rawPacket) { + return errPacketTooShort + } + + g.Reason = string(rawPacket[reasonOffset+1 : reasonEnd]) + } + + return nil +} + +// Header returns the Header associated with this packet. +func (g *Goodbye) Header() Header { + return Header{ + Padding: false, + Count: uint8(len(g.Sources)), + Type: TypeGoodbye, + Length: uint16((g.len() / 4) - 1), + } +} + +func (g *Goodbye) len() int { + srcsLength := len(g.Sources) * ssrcLength + reasonLength := len(g.Reason) + 1 + + l := headerLength + srcsLength + reasonLength + + // align to 32-bit boundary + return l + getPadding(l) +} + +// DestinationSSRC returns an array of SSRC values that this packet refers to. +func (g *Goodbye) DestinationSSRC() []uint32 { + out := make([]uint32, len(g.Sources)) + copy(out, g.Sources) + return out +} diff --git a/vendor/github.com/pion/rtcp/header.go b/vendor/github.com/pion/rtcp/header.go new file mode 100644 index 0000000..055ca18 --- /dev/null +++ b/vendor/github.com/pion/rtcp/header.go @@ -0,0 +1,140 @@ +package rtcp + +import ( + "encoding/binary" +) + +// PacketType specifies the type of an RTCP packet +type PacketType uint8 + +// RTCP packet types registered with IANA. See: https://www.iana.org/assignments/rtp-parameters/rtp-parameters.xhtml#rtp-parameters-4 +const ( + TypeSenderReport PacketType = 200 // RFC 3550, 6.4.1 + TypeReceiverReport PacketType = 201 // RFC 3550, 6.4.2 + TypeSourceDescription PacketType = 202 // RFC 3550, 6.5 + TypeGoodbye PacketType = 203 // RFC 3550, 6.6 + TypeApplicationDefined PacketType = 204 // RFC 3550, 6.7 (unimplemented) + TypeTransportSpecificFeedback PacketType = 205 // RFC 4585, 6051 + TypePayloadSpecificFeedback PacketType = 206 // RFC 4585, 6.3 + +) + +// Transport and Payload specific feedback messages overload the count field to act as a message type. those are listed here +const ( + FormatSLI uint8 = 2 + FormatPLI uint8 = 1 + FormatFIR uint8 = 4 + FormatTLN uint8 = 1 + FormatRRR uint8 = 5 + FormatREMB uint8 = 15 + + //https://tools.ietf.org/html/draft-holmer-rmcat-transport-wide-cc-extensions-01#page-5 + FormatTCC uint8 = 15 +) + +func (p PacketType) String() string { + switch p { + case TypeSenderReport: + return "SR" + case TypeReceiverReport: + return "RR" + case TypeSourceDescription: + return "SDES" + case TypeGoodbye: + return "BYE" + case TypeApplicationDefined: + return "APP" + case TypeTransportSpecificFeedback: + return "TSFB" + case TypePayloadSpecificFeedback: + return "PSFB" + default: + return string(p) + } +} + +const rtpVersion = 2 + +// A Header is the common header shared by all RTCP packets +type Header struct { + // If the padding bit is set, this individual RTCP packet contains + // some additional padding octets at the end which are not part of + // the control information but are included in the length field. + Padding bool + // The number of reception reports, sources contained or FMT in this packet (depending on the Type) + Count uint8 + // The RTCP packet type for this packet + Type PacketType + // The length of this RTCP packet in 32-bit words minus one, + // including the header and any padding. + Length uint16 +} + +const ( + headerLength = 4 + versionShift = 6 + versionMask = 0x3 + paddingShift = 5 + paddingMask = 0x1 + countShift = 0 + countMask = 0x1f + countMax = (1 << 5) - 1 +) + +// Marshal encodes the Header in binary +func (h Header) Marshal() ([]byte, error) { + /* + * 0 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * |V=2|P| RC | PT=SR=200 | length | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ + rawPacket := make([]byte, headerLength) + + rawPacket[0] |= rtpVersion << versionShift + + if h.Padding { + rawPacket[0] |= 1 << paddingShift + } + + if h.Count > 31 { + return nil, errInvalidHeader + } + rawPacket[0] |= h.Count << countShift + + rawPacket[1] = uint8(h.Type) + + binary.BigEndian.PutUint16(rawPacket[2:], h.Length) + + return rawPacket, nil +} + +// Unmarshal decodes the Header from binary +func (h *Header) Unmarshal(rawPacket []byte) error { + if len(rawPacket) < headerLength { + return errPacketTooShort + } + + /* + * 0 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * |V=2|P| RC | PT | length | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ + + version := rawPacket[0] >> versionShift & versionMask + if version != rtpVersion { + return errBadVersion + } + + h.Padding = (rawPacket[0] >> paddingShift & paddingMask) > 0 + h.Count = rawPacket[0] >> countShift & countMask + + h.Type = PacketType(rawPacket[1]) + + h.Length = binary.BigEndian.Uint16(rawPacket[2:]) + + return nil +} diff --git a/vendor/github.com/pion/rtcp/packet.go b/vendor/github.com/pion/rtcp/packet.go new file mode 100644 index 0000000..de08003 --- /dev/null +++ b/vendor/github.com/pion/rtcp/packet.go @@ -0,0 +1,115 @@ +package rtcp + +// Packet represents an RTCP packet, a protocol used for out-of-band statistics and control information for an RTP session +type Packet interface { + // DestinationSSRC returns an array of SSRC values that this packet refers to. + DestinationSSRC() []uint32 + + Marshal() ([]byte, error) + Unmarshal(rawPacket []byte) error +} + +// Unmarshal takes an entire udp datagram (which may consist of multiple RTCP packets) and +// returns the unmarshaled packets it contains. +// +// If this is a reduced-size RTCP packet a feedback packet (Goodbye, SliceLossIndication, etc) +// will be returned. Otherwise, the underlying type of the returned packet will be +// CompoundPacket. +func Unmarshal(rawData []byte) ([]Packet, error) { + var packets []Packet + for len(rawData) != 0 { + p, processed, err := unmarshal(rawData) + if err != nil { + return nil, err + } + + packets = append(packets, p) + rawData = rawData[processed:] + } + + switch len(packets) { + // Empty packet + case 0: + return nil, errInvalidHeader + // Multiple Packets + default: + return packets, nil + } +} + +// Marshal takes an array of Packets and serializes them to a single buffer +func Marshal(packets []Packet) ([]byte, error) { + out := make([]byte, 0) + for _, p := range packets { + data, err := p.Marshal() + if err != nil { + return nil, err + } + out = append(out, data...) + } + return out, nil +} + +// unmarshal is a factory which pulls the first RTCP packet from a bytestream, +// and returns it's parsed representation, and the amount of data that was processed. +func unmarshal(rawData []byte) (packet Packet, bytesprocessed int, err error) { + var h Header + + err = h.Unmarshal(rawData) + if err != nil { + return nil, 0, err + } + + bytesprocessed = int(h.Length+1) * 4 + if bytesprocessed > len(rawData) { + return nil, 0, errPacketTooShort + } + inPacket := rawData[:bytesprocessed] + + switch h.Type { + case TypeSenderReport: + packet = new(SenderReport) + + case TypeReceiverReport: + packet = new(ReceiverReport) + + case TypeSourceDescription: + packet = new(SourceDescription) + + case TypeGoodbye: + packet = new(Goodbye) + + case TypeTransportSpecificFeedback: + switch h.Count { + case FormatTLN: + packet = new(TransportLayerNack) + case FormatRRR: + packet = new(RapidResynchronizationRequest) + case FormatTCC: + packet = new(TransportLayerCC) + default: + packet = new(RawPacket) + } + + case TypePayloadSpecificFeedback: + switch h.Count { + case FormatPLI: + packet = new(PictureLossIndication) + case FormatSLI: + packet = new(SliceLossIndication) + case FormatREMB: + packet = new(ReceiverEstimatedMaximumBitrate) + case FormatFIR: + packet = new(FullIntraRequest) + default: + packet = new(RawPacket) + } + + default: + packet = new(RawPacket) + } + + err = packet.Unmarshal(inPacket) + + return packet, bytesprocessed, err +} diff --git a/vendor/github.com/pion/rtcp/picture_loss_indication.go b/vendor/github.com/pion/rtcp/picture_loss_indication.go new file mode 100644 index 0000000..7216ecd --- /dev/null +++ b/vendor/github.com/pion/rtcp/picture_loss_indication.go @@ -0,0 +1,91 @@ +package rtcp + +import ( + "encoding/binary" + "fmt" +) + +// The PictureLossIndication packet informs the encoder about the loss of an undefined amount of coded video data belonging to one or more pictures +type PictureLossIndication struct { + // SSRC of sender + SenderSSRC uint32 + + // SSRC where the loss was experienced + MediaSSRC uint32 +} + +var _ Packet = (*PictureLossIndication)(nil) // assert is a Packet + +const ( + pliLength = 2 +) + +// Marshal encodes the PictureLossIndication in binary +func (p PictureLossIndication) Marshal() ([]byte, error) { + /* + * PLI does not require parameters. Therefore, the length field MUST be + * 2, and there MUST NOT be any Feedback Control Information. + * + * The semantics of this FB message is independent of the payload type. + */ + rawPacket := make([]byte, p.len()) + packetBody := rawPacket[headerLength:] + + binary.BigEndian.PutUint32(packetBody, p.SenderSSRC) + binary.BigEndian.PutUint32(packetBody[4:], p.MediaSSRC) + + h := Header{ + Count: FormatPLI, + Type: TypePayloadSpecificFeedback, + Length: pliLength, + } + hData, err := h.Marshal() + if err != nil { + return nil, err + } + copy(rawPacket, hData) + + return rawPacket, nil +} + +// Unmarshal decodes the PictureLossIndication from binary +func (p *PictureLossIndication) Unmarshal(rawPacket []byte) error { + if len(rawPacket) < (headerLength + (ssrcLength * 2)) { + return errPacketTooShort + } + + var h Header + if err := h.Unmarshal(rawPacket); err != nil { + return err + } + + if h.Type != TypePayloadSpecificFeedback || h.Count != FormatPLI { + return errWrongType + } + + p.SenderSSRC = binary.BigEndian.Uint32(rawPacket[headerLength:]) + p.MediaSSRC = binary.BigEndian.Uint32(rawPacket[headerLength+ssrcLength:]) + return nil +} + +// Header returns the Header associated with this packet. +func (p *PictureLossIndication) Header() Header { + return Header{ + Count: FormatPLI, + Type: TypePayloadSpecificFeedback, + Length: pliLength, + } +} + +func (p *PictureLossIndication) len() int { + return headerLength + ssrcLength*2 +} + +func (p *PictureLossIndication) String() string { + return fmt.Sprintf("PictureLossIndication %x %x", p.SenderSSRC, p.MediaSSRC) +} + +// DestinationSSRC returns an array of SSRC values that this packet refers to. +func (p *PictureLossIndication) DestinationSSRC() []uint32 { + return []uint32{p.MediaSSRC} +} diff --git a/vendor/github.com/pion/rtcp/rapid_resynchronization_request.go b/vendor/github.com/pion/rtcp/rapid_resynchronization_request.go new file mode 100644 index 0000000..5d27055 --- /dev/null +++ b/vendor/github.com/pion/rtcp/rapid_resynchronization_request.go @@ -0,0 +1,88 @@ +package rtcp + +import ( + "encoding/binary" + "fmt" +) + +// The RapidResynchronizationRequest packet informs the encoder about the loss of an undefined amount of coded video data belonging to one or more pictures +type RapidResynchronizationRequest struct { + // SSRC of sender + SenderSSRC uint32 + + // SSRC of the media source + MediaSSRC uint32 +} + +var _ Packet = (*RapidResynchronizationRequest)(nil) // assert is a Packet + +const ( + rrrLength = 2 + rrrHeaderLength = ssrcLength * 2 + rrrMediaOffset = 4 +) + +// Marshal encodes the RapidResynchronizationRequest in binary +func (p RapidResynchronizationRequest) Marshal() ([]byte, error) { + /* + * RRR does not require parameters. Therefore, the length field MUST be + * 2, and there MUST NOT be any Feedback Control Information. + * + * The semantics of this FB message is independent of the payload type. + */ + rawPacket := make([]byte, p.len()) + packetBody := rawPacket[headerLength:] + + binary.BigEndian.PutUint32(packetBody, p.SenderSSRC) + binary.BigEndian.PutUint32(packetBody[rrrMediaOffset:], p.MediaSSRC) + + hData, err := p.Header().Marshal() + if err != nil { + return nil, err + } + copy(rawPacket, hData) + + return rawPacket, nil +} + +// Unmarshal decodes the RapidResynchronizationRequest from binary +func (p *RapidResynchronizationRequest) Unmarshal(rawPacket []byte) error { + if len(rawPacket) < (headerLength + (ssrcLength * 2)) { + return errPacketTooShort + } + + var h Header + if err := h.Unmarshal(rawPacket); err != nil { + return err + } + + if h.Type != TypeTransportSpecificFeedback || h.Count != FormatRRR { + return errWrongType + } + + p.SenderSSRC = binary.BigEndian.Uint32(rawPacket[headerLength:]) + p.MediaSSRC = binary.BigEndian.Uint32(rawPacket[headerLength+ssrcLength:]) + return nil +} + +func (p *RapidResynchronizationRequest) len() int { + return headerLength + rrrHeaderLength +} + +// Header returns the Header associated with this packet. +func (p *RapidResynchronizationRequest) Header() Header { + return Header{ + Count: FormatRRR, + Type: TypeTransportSpecificFeedback, + Length: rrrLength, + } +} + +// DestinationSSRC returns an array of SSRC values that this packet refers to. +func (p *RapidResynchronizationRequest) DestinationSSRC() []uint32 { + return []uint32{p.MediaSSRC} +} + +func (p *RapidResynchronizationRequest) String() string { + return fmt.Sprintf("RapidResynchronizationRequest %x %x", p.SenderSSRC, p.MediaSSRC) +} diff --git a/vendor/github.com/pion/rtcp/raw_packet.go b/vendor/github.com/pion/rtcp/raw_packet.go new file mode 100644 index 0000000..3cb6eaf --- /dev/null +++ b/vendor/github.com/pion/rtcp/raw_packet.go @@ -0,0 +1,44 @@ +package rtcp + +import "fmt" + +// RawPacket represents an unparsed RTCP packet. It's returned by Unmarshal when +// a packet with an unknown type is encountered. +type RawPacket []byte + +var _ Packet = (*RawPacket)(nil) // assert is a Packet + +// Marshal encodes the packet in binary. +func (r RawPacket) Marshal() ([]byte, error) { + return r, nil +} + +// Unmarshal decodes the packet from binary. +func (r *RawPacket) Unmarshal(b []byte) error { + if len(b) < (headerLength) { + return errPacketTooShort + } + *r = b + + var h Header + return h.Unmarshal(b) +} + +// Header returns the Header associated with this packet. +func (r RawPacket) Header() Header { + var h Header + if err := h.Unmarshal(r); err != nil { + return Header{} + } + return h +} + +// DestinationSSRC returns an array of SSRC values that this packet refers to. +func (r *RawPacket) DestinationSSRC() []uint32 { + return []uint32{} +} + +func (r RawPacket) String() string { + out := fmt.Sprintf("RawPacket: %v", ([]byte)(r)) + return out +} diff --git a/vendor/github.com/pion/rtcp/receiver_estimated_maximum_bitrate.go b/vendor/github.com/pion/rtcp/receiver_estimated_maximum_bitrate.go new file mode 100644 index 0000000..d37f49e --- /dev/null +++ b/vendor/github.com/pion/rtcp/receiver_estimated_maximum_bitrate.go @@ -0,0 +1,284 @@ +package rtcp + +import ( + "bytes" + "encoding/binary" + "fmt" + "math/bits" +) + +// ReceiverEstimatedMaximumBitrate contains the receiver's estimated maximum bitrate. +// see: https://tools.ietf.org/html/draft-alvestrand-rmcat-remb-03 +type ReceiverEstimatedMaximumBitrate struct { + // SSRC of sender + SenderSSRC uint32 + + // Estimated maximum bitrate + Bitrate uint64 + + // SSRC entries which this packet applies to + SSRCs []uint32 +} + +var _ Packet = (*ReceiverEstimatedMaximumBitrate)(nil) // assert is a Packet + +// Marshal serializes the packet and returns a byte slice. +func (p ReceiverEstimatedMaximumBitrate) Marshal() (buf []byte, err error) { + // Allocate a buffer of the exact output size. + buf = make([]byte, p.MarshalSize()) + + // Write to our buffer. + n, err := p.MarshalTo(buf) + if err != nil { + return nil, err + } + + // This will always be true but just to be safe. + if n != len(buf) { + return nil, errWrongMarshalSize + } + + return buf, nil +} + +// MarshalSize returns the size of the packet when marshaled. +// This can be used in conjunction with `MarshalTo` to avoid allocations. +func (p ReceiverEstimatedMaximumBitrate) MarshalSize() (n int) { + return 20 + 4*len(p.SSRCs) +} + +// MarshalTo serializes the packet to the given byte slice. +func (p ReceiverEstimatedMaximumBitrate) MarshalTo(buf []byte) (n int, err error) { + /* + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |V=2|P| FMT=15 | PT=206 | length | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | SSRC of packet sender | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | SSRC of media source | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Unique identifier 'R' 'E' 'M' 'B' | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Num SSRC | BR Exp | BR Mantissa | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | SSRC feedback | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | ... | + */ + + size := p.MarshalSize() + if len(buf) < size { + return 0, errPacketTooShort + } + + buf[0] = 143 // v=2, p=0, fmt=15 + buf[1] = 206 + + // Length of this packet in 32-bit words minus one. + length := uint16((p.MarshalSize() / 4) - 1) + binary.BigEndian.PutUint16(buf[2:4], length) + + binary.BigEndian.PutUint32(buf[4:8], p.SenderSSRC) + binary.BigEndian.PutUint32(buf[8:12], 0) // always zero + + // ALL HAIL REMB + buf[12] = 'R' + buf[13] = 'E' + buf[14] = 'M' + buf[15] = 'B' + + // Write the length of the ssrcs to follow at the end + buf[16] = byte(len(p.SSRCs)) + + // We can only encode 18 bits of information in the mantissa. + // The exponent lets us shift to the left up to 64 places (6-bits). + // We actually need a uint82 to encode the largest possible number, + // but uint64 should be good enough for 2.3 exabytes per second. + + // So we need to truncate the bitrate and use the exponent for the shift. + // bitrate = mantissa * (1 << exp) + + // Calculate the total shift based on the leading number of zeroes. + // This will be negative if there is no shift required. + shift := uint(64 - bits.LeadingZeros64(p.Bitrate)) + + var mantissa uint + var exp uint + + if shift <= 18 { + // Fit everything in the mantissa because we can. + mantissa = uint(p.Bitrate) + exp = 0 + } else { + // We can only use 18 bits of precision, so truncate. + mantissa = uint(p.Bitrate >> (shift - 18)) + exp = shift - 18 + } + + // We can't quite use the binary package because + // a) it's a uint24 and b) the exponent is only 6-bits + // Just trust me; this is big-endian encoding. + buf[17] = byte((exp << 2) | (mantissa >> 16)) + buf[18] = byte(mantissa >> 8) + buf[19] = byte(mantissa) + + // Write the SSRCs at the very end. + n = 20 + for _, ssrc := range p.SSRCs { + binary.BigEndian.PutUint32(buf[n:n+4], ssrc) + n += 4 + } + + return n, nil +} + +// Unmarshal reads a REMB packet from the given byte slice. +func (p *ReceiverEstimatedMaximumBitrate) Unmarshal(buf []byte) (err error) { + /* + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |V=2|P| FMT=15 | PT=206 | length | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | SSRC of packet sender | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | SSRC of media source | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Unique identifier 'R' 'E' 'M' 'B' | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Num SSRC | BR Exp | BR Mantissa | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | SSRC feedback | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | ... | + */ + + // 20 bytes is the size of the packet with no SSRCs + if len(buf) < 20 { + return errPacketTooShort + } + + // version must be 2 + version := buf[0] >> 6 + if version != 2 { + return fmt.Errorf("%w expected(2) actual(%d)", errBadVersion, version) + } + + // padding must be unset + padding := (buf[0] >> 5) & 1 + if padding != 0 { + return fmt.Errorf("%w expected(0) actual(%d)", errWrongPadding, padding) + } + + // fmt must be 15 + fmtVal := buf[0] & 31 + if fmtVal != 15 { + return fmt.Errorf("%w expected(15) actual(%d)", errWrongFeedbackType, fmtVal) + } + + // Must be payload specific feedback + if buf[1] != 206 { + return fmt.Errorf("%w expected(206) actual(%d)", errWrongPayloadType, buf[1]) + } + + // length is the number of 32-bit words, minus 1 + length := binary.BigEndian.Uint16(buf[2:4]) + size := int((length + 1) * 4) + + // There's not way this could be legit + if size < 20 { + return errHeaderTooSmall + } + + // Make sure the buffer is large enough. + if len(buf) < size { + return errPacketTooShort + } + + // The sender SSRC is 32-bits + p.SenderSSRC = binary.BigEndian.Uint32(buf[4:8]) + + // The destination SSRC must be 0 + media := binary.BigEndian.Uint32(buf[8:12]) + if media != 0 { + return errSSRCMustBeZero + } + + // REMB rules all around me + if !bytes.Equal(buf[12:16], []byte{'R', 'E', 'M', 'B'}) { + return errMissingREMBidentifier + } + + // The next byte is the number of SSRC entries at the end. + num := int(buf[16]) + + // Now we know the expected size, make sure they match. + if size != 20+4*num { + return errSSRCNumAndLengthMismatch + } + + // Get the 6-bit exponent value. + exp := buf[17] >> 2 + + // The remaining 2-bits plus the next 16-bits are the mantissa. + mantissa := uint64(buf[17]&3)<<16 | uint64(buf[18])<<8 | uint64(buf[19]) + + // bitrate = mantissa * 2^exp + + if exp > 46 { + // NOTE: We intentionally truncate values so they fit in a uint64. + // Otherwise we would need a uint82. + // This is 2.3 exabytes per second, which should be good enough. + p.Bitrate = ^uint64(0) + } else { + p.Bitrate = mantissa << exp + } + + // Clear any existing SSRCs + p.SSRCs = nil + + // Loop over and parse the SSRC entires at the end. + // We already verified that size == num * 4 + for n := 20; n < size; n += 4 { + ssrc := binary.BigEndian.Uint32(buf[n : n+4]) + p.SSRCs = append(p.SSRCs, ssrc) + } + + return nil +} + +// Header returns the Header associated with this packet. +func (p *ReceiverEstimatedMaximumBitrate) Header() Header { + return Header{ + Count: FormatREMB, + Type: TypePayloadSpecificFeedback, + Length: uint16((p.MarshalSize() / 4) - 1), + } +} + +// String prints the REMB packet in a human-readable format. +func (p *ReceiverEstimatedMaximumBitrate) String() string { + // Keep a table of powers to units for fast conversion. + bitUnits := []string{"b", "Kb", "Mb", "Gb", "Tb", "Pb", "Eb"} + + // Do some unit conversions because b/s is far too difficult to read. + bitrate := float64(p.Bitrate) + powers := 0 + + // Keep dividing the bitrate until it's under 1000 + for bitrate >= 1000.0 && powers < len(bitUnits) { + bitrate /= 1000.0 + powers++ + } + + unit := bitUnits[powers] + + return fmt.Sprintf("ReceiverEstimatedMaximumBitrate %x %.2f %s/s", p.SenderSSRC, bitrate, unit) +} + +// DestinationSSRC returns an array of SSRC values that this packet refers to. +func (p *ReceiverEstimatedMaximumBitrate) DestinationSSRC() []uint32 { + return p.SSRCs +} diff --git a/vendor/github.com/pion/rtcp/receiver_report.go b/vendor/github.com/pion/rtcp/receiver_report.go new file mode 100644 index 0000000..cf28d39 --- /dev/null +++ b/vendor/github.com/pion/rtcp/receiver_report.go @@ -0,0 +1,193 @@ +package rtcp + +import ( + "encoding/binary" + "fmt" +) + +// A ReceiverReport (RR) packet provides reception quality feedback for an RTP stream +type ReceiverReport struct { + // The synchronization source identifier for the originator of this RR packet. + SSRC uint32 + // Zero or more reception report blocks depending on the number of other + // sources heard by this sender since the last report. Each reception report + // block conveys statistics on the reception of RTP packets from a + // single synchronization source. + Reports []ReceptionReport + // Extension contains additional, payload-specific information that needs to + // be reported regularly about the receiver. + ProfileExtensions []byte +} + +var _ Packet = (*ReceiverReport)(nil) // assert is a Packet + +const ( + ssrcLength = 4 + rrSSRCOffset = headerLength + rrReportOffset = rrSSRCOffset + ssrcLength +) + +// Marshal encodes the ReceiverReport in binary +func (r ReceiverReport) Marshal() ([]byte, error) { + /* + * 0 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * header |V=2|P| RC | PT=RR=201 | length | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | SSRC of packet sender | + * +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ + * report | SSRC_1 (SSRC of first source) | + * block +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * 1 | fraction lost | cumulative number of packets lost | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | extended highest sequence number received | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | interarrival jitter | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | last SR (LSR) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | delay since last SR (DLSR) | + * +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ + * report | SSRC_2 (SSRC of second source) | + * block +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * 2 : ... : + * +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ + * | profile-specific extensions | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ + + rawPacket := make([]byte, r.len()) + packetBody := rawPacket[headerLength:] + + binary.BigEndian.PutUint32(packetBody, r.SSRC) + + for i, rp := range r.Reports { + data, err := rp.Marshal() + if err != nil { + return nil, err + } + offset := ssrcLength + receptionReportLength*i + copy(packetBody[offset:], data) + } + + if len(r.Reports) > countMax { + return nil, errTooManyReports + } + + pe := make([]byte, len(r.ProfileExtensions)) + copy(pe, r.ProfileExtensions) + + // if the length of the profile extensions isn't devisible + // by 4, we need to pad the end. + for (len(pe) & 0x3) != 0 { + pe = append(pe, 0) + } + + rawPacket = append(rawPacket, pe...) + + hData, err := r.Header().Marshal() + if err != nil { + return nil, err + } + copy(rawPacket, hData) + + return rawPacket, nil +} + +// Unmarshal decodes the ReceiverReport from binary +func (r *ReceiverReport) Unmarshal(rawPacket []byte) error { + /* + * 0 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * header |V=2|P| RC | PT=RR=201 | length | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | SSRC of packet sender | + * +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ + * report | SSRC_1 (SSRC of first source) | + * block +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * 1 | fraction lost | cumulative number of packets lost | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | extended highest sequence number received | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | interarrival jitter | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | last SR (LSR) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | delay since last SR (DLSR) | + * +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ + * report | SSRC_2 (SSRC of second source) | + * block +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * 2 : ... : + * +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ + * | profile-specific extensions | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ + + if len(rawPacket) < (headerLength + ssrcLength) { + return errPacketTooShort + } + + var h Header + if err := h.Unmarshal(rawPacket); err != nil { + return err + } + + if h.Type != TypeReceiverReport { + return errWrongType + } + + r.SSRC = binary.BigEndian.Uint32(rawPacket[rrSSRCOffset:]) + + for i := rrReportOffset; i < len(rawPacket) && len(r.Reports) < int(h.Count); i += receptionReportLength { + var rr ReceptionReport + if err := rr.Unmarshal(rawPacket[i:]); err != nil { + return err + } + r.Reports = append(r.Reports, rr) + } + r.ProfileExtensions = rawPacket[rrReportOffset+(len(r.Reports)*receptionReportLength):] + + if uint8(len(r.Reports)) != h.Count { + return errInvalidHeader + } + + return nil +} + +func (r *ReceiverReport) len() int { + repsLength := 0 + for _, rep := range r.Reports { + repsLength += rep.len() + } + return headerLength + ssrcLength + repsLength +} + +// Header returns the Header associated with this packet. +func (r *ReceiverReport) Header() Header { + return Header{ + Count: uint8(len(r.Reports)), + Type: TypeReceiverReport, + Length: uint16((r.len()/4)-1) + uint16(getPadding(len(r.ProfileExtensions))), + } +} + +// DestinationSSRC returns an array of SSRC values that this packet refers to. +func (r *ReceiverReport) DestinationSSRC() []uint32 { + out := make([]uint32, len(r.Reports)) + for i, v := range r.Reports { + out[i] = v.SSRC + } + return out +} + +func (r ReceiverReport) String() string { + out := fmt.Sprintf("ReceiverReport from %x\n", r.SSRC) + out += "\tSSRC \tLost\tLastSequence\n" + for _, i := range r.Reports { + out += fmt.Sprintf("\t%x\t%d/%d\t%d\n", i.SSRC, i.FractionLost, i.TotalLost, i.LastSequenceNumber) + } + out += fmt.Sprintf("\tProfile Extension Data: %v\n", r.ProfileExtensions) + return out +} diff --git a/vendor/github.com/pion/rtcp/reception_report.go b/vendor/github.com/pion/rtcp/reception_report.go new file mode 100644 index 0000000..5bff8f2 --- /dev/null +++ b/vendor/github.com/pion/rtcp/reception_report.go @@ -0,0 +1,130 @@ +package rtcp + +import "encoding/binary" + +// A ReceptionReport block conveys statistics on the reception of RTP packets +// from a single synchronization source. +type ReceptionReport struct { + // The SSRC identifier of the source to which the information in this + // reception report block pertains. + SSRC uint32 + // The fraction of RTP data packets from source SSRC lost since the + // previous SR or RR packet was sent, expressed as a fixed point + // number with the binary point at the left edge of the field. + FractionLost uint8 + // The total number of RTP data packets from source SSRC that have + // been lost since the beginning of reception. + TotalLost uint32 + // The low 16 bits contain the highest sequence number received in an + // RTP data packet from source SSRC, and the most significant 16 + // bits extend that sequence number with the corresponding count of + // sequence number cycles. + LastSequenceNumber uint32 + // An estimate of the statistical variance of the RTP data packet + // interarrival time, measured in timestamp units and expressed as an + // unsigned integer. + Jitter uint32 + // The middle 32 bits out of 64 in the NTP timestamp received as part of + // the most recent RTCP sender report (SR) packet from source SSRC. If no + // SR has been received yet, the field is set to zero. + LastSenderReport uint32 + // The delay, expressed in units of 1/65536 seconds, between receiving the + // last SR packet from source SSRC and sending this reception report block. + // If no SR packet has been received yet from SSRC, the field is set to zero. + Delay uint32 +} + +const ( + receptionReportLength = 24 + fractionLostOffset = 4 + totalLostOffset = 5 + lastSeqOffset = 8 + jitterOffset = 12 + lastSROffset = 16 + delayOffset = 20 +) + +// Marshal encodes the ReceptionReport in binary +func (r ReceptionReport) Marshal() ([]byte, error) { + /* + * 0 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ + * | SSRC | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | fraction lost | cumulative number of packets lost | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | extended highest sequence number received | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | interarrival jitter | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | last SR (LSR) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | delay since last SR (DLSR) | + * +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ + */ + + rawPacket := make([]byte, receptionReportLength) + + binary.BigEndian.PutUint32(rawPacket, r.SSRC) + + rawPacket[fractionLostOffset] = r.FractionLost + + // pack TotalLost into 24 bits + if r.TotalLost >= (1 << 25) { + return nil, errInvalidTotalLost + } + tlBytes := rawPacket[totalLostOffset:] + tlBytes[0] = byte(r.TotalLost >> 16) + tlBytes[1] = byte(r.TotalLost >> 8) + tlBytes[2] = byte(r.TotalLost) + + binary.BigEndian.PutUint32(rawPacket[lastSeqOffset:], r.LastSequenceNumber) + binary.BigEndian.PutUint32(rawPacket[jitterOffset:], r.Jitter) + binary.BigEndian.PutUint32(rawPacket[lastSROffset:], r.LastSenderReport) + binary.BigEndian.PutUint32(rawPacket[delayOffset:], r.Delay) + + return rawPacket, nil +} + +// Unmarshal decodes the ReceptionReport from binary +func (r *ReceptionReport) Unmarshal(rawPacket []byte) error { + if len(rawPacket) < receptionReportLength { + return errPacketTooShort + } + + /* + * 0 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ + * | SSRC | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | fraction lost | cumulative number of packets lost | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | extended highest sequence number received | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | interarrival jitter | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | last SR (LSR) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | delay since last SR (DLSR) | + * +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ + */ + + r.SSRC = binary.BigEndian.Uint32(rawPacket) + r.FractionLost = rawPacket[fractionLostOffset] + + tlBytes := rawPacket[totalLostOffset:] + r.TotalLost = uint32(tlBytes[2]) | uint32(tlBytes[1])<<8 | uint32(tlBytes[0])<<16 + + r.LastSequenceNumber = binary.BigEndian.Uint32(rawPacket[lastSeqOffset:]) + r.Jitter = binary.BigEndian.Uint32(rawPacket[jitterOffset:]) + r.LastSenderReport = binary.BigEndian.Uint32(rawPacket[lastSROffset:]) + r.Delay = binary.BigEndian.Uint32(rawPacket[delayOffset:]) + + return nil +} + +func (r *ReceptionReport) len() int { + return receptionReportLength +} diff --git a/vendor/github.com/pion/rtcp/renovate.json b/vendor/github.com/pion/rtcp/renovate.json new file mode 100644 index 0000000..4400fd9 --- /dev/null +++ b/vendor/github.com/pion/rtcp/renovate.json @@ -0,0 +1,15 @@ +{ + "extends": [ + "config:base" + ], + "postUpdateOptions": [ + "gomodTidy" + ], + "commitBody": "Generated by renovateBot", + "packageRules": [ + { + "packagePatterns": ["^golang.org/x/"], + "schedule": ["on the first day of the month"] + } + ] +} diff --git a/vendor/github.com/pion/rtcp/sender_report.go b/vendor/github.com/pion/rtcp/sender_report.go new file mode 100644 index 0000000..1b16380 --- /dev/null +++ b/vendor/github.com/pion/rtcp/sender_report.go @@ -0,0 +1,260 @@ +package rtcp + +import ( + "encoding/binary" + "fmt" +) + +// A SenderReport (SR) packet provides reception quality feedback for an RTP stream +type SenderReport struct { + // The synchronization source identifier for the originator of this SR packet. + SSRC uint32 + // The wallclock time when this report was sent so that it may be used in + // combination with timestamps returned in reception reports from other + // receivers to measure round-trip propagation to those receivers. + NTPTime uint64 + // Corresponds to the same time as the NTP timestamp (above), but in + // the same units and with the same random offset as the RTP + // timestamps in data packets. This correspondence may be used for + // intra- and inter-media synchronization for sources whose NTP + // timestamps are synchronized, and may be used by media-independent + // receivers to estimate the nominal RTP clock frequency. + RTPTime uint32 + // The total number of RTP data packets transmitted by the sender + // since starting transmission up until the time this SR packet was + // generated. + PacketCount uint32 + // The total number of payload octets (i.e., not including header or + // padding) transmitted in RTP data packets by the sender since + // starting transmission up until the time this SR packet was + // generated. + OctetCount uint32 + // Zero or more reception report blocks depending on the number of other + // sources heard by this sender since the last report. Each reception report + // block conveys statistics on the reception of RTP packets from a + // single synchronization source. + Reports []ReceptionReport + // ProfileExtensions contains additional, payload-specific information that needs to + // be reported regularly about the sender. + ProfileExtensions []byte +} + +var _ Packet = (*SenderReport)(nil) // assert is a Packet + +const ( + srHeaderLength = 24 + srSSRCOffset = 0 + srNTPOffset = srSSRCOffset + ssrcLength + ntpTimeLength = 8 + srRTPOffset = srNTPOffset + ntpTimeLength + rtpTimeLength = 4 + srPacketCountOffset = srRTPOffset + rtpTimeLength + srPacketCountLength = 4 + srOctetCountOffset = srPacketCountOffset + srPacketCountLength + srOctetCountLength = 4 + srReportOffset = srOctetCountOffset + srOctetCountLength +) + +// Marshal encodes the SenderReport in binary +func (r SenderReport) Marshal() ([]byte, error) { + /* + * 0 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * header |V=2|P| RC | PT=SR=200 | length | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | SSRC of sender | + * +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ + * sender | NTP timestamp, most significant word | + * info +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | NTP timestamp, least significant word | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | RTP timestamp | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | sender's packet count | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | sender's octet count | + * +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ + * report | SSRC_1 (SSRC of first source) | + * block +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * 1 | fraction lost | cumulative number of packets lost | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | extended highest sequence number received | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | interarrival jitter | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | last SR (LSR) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | delay since last SR (DLSR) | + * +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ + * report | SSRC_2 (SSRC of second source) | + * block +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * 2 : ... : + * +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ + * | profile-specific extensions | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ + + rawPacket := make([]byte, r.len()) + packetBody := rawPacket[headerLength:] + + binary.BigEndian.PutUint32(packetBody[srSSRCOffset:], r.SSRC) + binary.BigEndian.PutUint64(packetBody[srNTPOffset:], r.NTPTime) + binary.BigEndian.PutUint32(packetBody[srRTPOffset:], r.RTPTime) + binary.BigEndian.PutUint32(packetBody[srPacketCountOffset:], r.PacketCount) + binary.BigEndian.PutUint32(packetBody[srOctetCountOffset:], r.OctetCount) + + offset := srHeaderLength + for _, rp := range r.Reports { + data, err := rp.Marshal() + if err != nil { + return nil, err + } + copy(packetBody[offset:], data) + offset += receptionReportLength + } + + if len(r.Reports) > countMax { + return nil, errTooManyReports + } + + copy(packetBody[offset:], r.ProfileExtensions) + + hData, err := r.Header().Marshal() + if err != nil { + return nil, err + } + copy(rawPacket, hData) + + return rawPacket, nil +} + +// Unmarshal decodes the SenderReport from binary +func (r *SenderReport) Unmarshal(rawPacket []byte) error { + /* + * 0 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * header |V=2|P| RC | PT=SR=200 | length | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | SSRC of sender | + * +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ + * sender | NTP timestamp, most significant word | + * info +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | NTP timestamp, least significant word | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | RTP timestamp | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | sender's packet count | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | sender's octet count | + * +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ + * report | SSRC_1 (SSRC of first source) | + * block +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * 1 | fraction lost | cumulative number of packets lost | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | extended highest sequence number received | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | interarrival jitter | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | last SR (LSR) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | delay since last SR (DLSR) | + * +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ + * report | SSRC_2 (SSRC of second source) | + * block +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * 2 : ... : + * +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ + * | profile-specific extensions | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ + + if len(rawPacket) < (headerLength + srHeaderLength) { + return errPacketTooShort + } + + var h Header + if err := h.Unmarshal(rawPacket); err != nil { + return err + } + + if h.Type != TypeSenderReport { + return errWrongType + } + + packetBody := rawPacket[headerLength:] + + r.SSRC = binary.BigEndian.Uint32(packetBody[srSSRCOffset:]) + r.NTPTime = binary.BigEndian.Uint64(packetBody[srNTPOffset:]) + r.RTPTime = binary.BigEndian.Uint32(packetBody[srRTPOffset:]) + r.PacketCount = binary.BigEndian.Uint32(packetBody[srPacketCountOffset:]) + r.OctetCount = binary.BigEndian.Uint32(packetBody[srOctetCountOffset:]) + + offset := srReportOffset + for i := 0; i < int(h.Count); i++ { + rrEnd := offset + receptionReportLength + if rrEnd > len(packetBody) { + return errPacketTooShort + } + rrBody := packetBody[offset : offset+receptionReportLength] + offset = rrEnd + + var rr ReceptionReport + if err := rr.Unmarshal(rrBody); err != nil { + return err + } + r.Reports = append(r.Reports, rr) + } + + if offset < len(packetBody) { + r.ProfileExtensions = packetBody[offset:] + } + + if uint8(len(r.Reports)) != h.Count { + return errInvalidHeader + } + + return nil +} + +// DestinationSSRC returns an array of SSRC values that this packet refers to. +func (r *SenderReport) DestinationSSRC() []uint32 { + out := make([]uint32, len(r.Reports)+1) + for i, v := range r.Reports { + out[i] = v.SSRC + } + out[len(r.Reports)] = r.SSRC + return out +} + +func (r *SenderReport) len() int { + repsLength := 0 + for _, rep := range r.Reports { + repsLength += rep.len() + } + return headerLength + srHeaderLength + repsLength + len(r.ProfileExtensions) +} + +// Header returns the Header associated with this packet. +func (r *SenderReport) Header() Header { + return Header{ + Count: uint8(len(r.Reports)), + Type: TypeSenderReport, + Length: uint16((r.len() / 4) - 1), + } +} + +func (r SenderReport) String() string { + out := fmt.Sprintf("SenderReport from %x\n", r.SSRC) + out += fmt.Sprintf("\tNTPTime:\t%d\n", r.NTPTime) + out += fmt.Sprintf("\tRTPTIme:\t%d\n", r.RTPTime) + out += fmt.Sprintf("\tPacketCount:\t%d\n", r.PacketCount) + out += fmt.Sprintf("\tOctetCount:\t%d\n", r.OctetCount) + + out += "\tSSRC \tLost\tLastSequence\n" + for _, i := range r.Reports { + out += fmt.Sprintf("\t%x\t%d/%d\t%d\n", i.SSRC, i.FractionLost, i.TotalLost, i.LastSequenceNumber) + } + out += fmt.Sprintf("\tProfile Extension Data: %v\n", r.ProfileExtensions) + return out +} diff --git a/vendor/github.com/pion/rtcp/slice_loss_indication.go b/vendor/github.com/pion/rtcp/slice_loss_indication.go new file mode 100644 index 0000000..7689309 --- /dev/null +++ b/vendor/github.com/pion/rtcp/slice_loss_indication.go @@ -0,0 +1,115 @@ +package rtcp + +import ( + "encoding/binary" + "fmt" + "math" +) + +// SLIEntry represents a single entry to the SLI packet's +// list of lost slices. +type SLIEntry struct { + // ID of first lost slice + First uint16 + + // Number of lost slices + Number uint16 + + // ID of related picture + Picture uint8 +} + +// The SliceLossIndication packet informs the encoder about the loss of a picture slice +type SliceLossIndication struct { + // SSRC of sender + SenderSSRC uint32 + + // SSRC of the media source + MediaSSRC uint32 + + SLI []SLIEntry +} + +var _ Packet = (*SliceLossIndication)(nil) // assert is a Packet + +const ( + sliLength = 2 + sliOffset = 8 +) + +// Marshal encodes the SliceLossIndication in binary +func (p SliceLossIndication) Marshal() ([]byte, error) { + if len(p.SLI)+sliLength > math.MaxUint8 { + return nil, errTooManyReports + } + + rawPacket := make([]byte, sliOffset+(len(p.SLI)*4)) + binary.BigEndian.PutUint32(rawPacket, p.SenderSSRC) + binary.BigEndian.PutUint32(rawPacket[4:], p.MediaSSRC) + for i, s := range p.SLI { + sli := ((uint32(s.First) & 0x1FFF) << 19) | + ((uint32(s.Number) & 0x1FFF) << 6) | + (uint32(s.Picture) & 0x3F) + binary.BigEndian.PutUint32(rawPacket[sliOffset+(4*i):], sli) + } + hData, err := p.Header().Marshal() + if err != nil { + return nil, err + } + + return append(hData, rawPacket...), nil +} + +// Unmarshal decodes the SliceLossIndication from binary +func (p *SliceLossIndication) Unmarshal(rawPacket []byte) error { + if len(rawPacket) < (headerLength + ssrcLength) { + return errPacketTooShort + } + + var h Header + if err := h.Unmarshal(rawPacket); err != nil { + return err + } + + if len(rawPacket) < (headerLength + int(4*h.Length)) { + return errPacketTooShort + } + + if h.Type != TypeTransportSpecificFeedback || h.Count != FormatSLI { + return errWrongType + } + + p.SenderSSRC = binary.BigEndian.Uint32(rawPacket[headerLength:]) + p.MediaSSRC = binary.BigEndian.Uint32(rawPacket[headerLength+ssrcLength:]) + for i := headerLength + sliOffset; i < (headerLength + int(h.Length*4)); i += 4 { + sli := binary.BigEndian.Uint32(rawPacket[i:]) + p.SLI = append(p.SLI, SLIEntry{ + First: uint16((sli >> 19) & 0x1FFF), + Number: uint16((sli >> 6) & 0x1FFF), + Picture: uint8(sli & 0x3F), + }) + } + return nil +} + +func (p *SliceLossIndication) len() int { + return headerLength + sliOffset + (len(p.SLI) * 4) +} + +// Header returns the Header associated with this packet. +func (p *SliceLossIndication) Header() Header { + return Header{ + Count: FormatSLI, + Type: TypeTransportSpecificFeedback, + Length: uint16((p.len() / 4) - 1), + } +} + +func (p *SliceLossIndication) String() string { + return fmt.Sprintf("SliceLossIndication %x %x %+v", p.SenderSSRC, p.MediaSSRC, p.SLI) +} + +// DestinationSSRC returns an array of SSRC values that this packet refers to. +func (p *SliceLossIndication) DestinationSSRC() []uint32 { + return []uint32{p.MediaSSRC} +} diff --git a/vendor/github.com/pion/rtcp/source_description.go b/vendor/github.com/pion/rtcp/source_description.go new file mode 100644 index 0000000..4306b66 --- /dev/null +++ b/vendor/github.com/pion/rtcp/source_description.go @@ -0,0 +1,352 @@ +package rtcp + +import ( + "encoding/binary" + "fmt" +) + +// SDESType is the item type used in the RTCP SDES control packet. +type SDESType uint8 + +// RTP SDES item types registered with IANA. See: https://www.iana.org/assignments/rtp-parameters/rtp-parameters.xhtml#rtp-parameters-5 +const ( + SDESEnd SDESType = iota // end of SDES list RFC 3550, 6.5 + SDESCNAME // canonical name RFC 3550, 6.5.1 + SDESName // user name RFC 3550, 6.5.2 + SDESEmail // user's electronic mail address RFC 3550, 6.5.3 + SDESPhone // user's phone number RFC 3550, 6.5.4 + SDESLocation // geographic user location RFC 3550, 6.5.5 + SDESTool // name of application or tool RFC 3550, 6.5.6 + SDESNote // notice about the source RFC 3550, 6.5.7 + SDESPrivate // private extensions RFC 3550, 6.5.8 (not implemented) +) + +func (s SDESType) String() string { + switch s { + case SDESEnd: + return "END" + case SDESCNAME: + return "CNAME" + case SDESName: + return "NAME" + case SDESEmail: + return "EMAIL" + case SDESPhone: + return "PHONE" + case SDESLocation: + return "LOC" + case SDESTool: + return "TOOL" + case SDESNote: + return "NOTE" + case SDESPrivate: + return "PRIV" + default: + return string(s) + } +} + +const ( + sdesSourceLen = 4 + sdesTypeLen = 1 + sdesTypeOffset = 0 + sdesOctetCountLen = 1 + sdesOctetCountOffset = 1 + sdesMaxOctetCount = (1 << 8) - 1 + sdesTextOffset = 2 +) + +// A SourceDescription (SDES) packet describes the sources in an RTP stream. +type SourceDescription struct { + Chunks []SourceDescriptionChunk +} + +var _ Packet = (*SourceDescription)(nil) // assert is a Packet + +// Marshal encodes the SourceDescription in binary +func (s SourceDescription) Marshal() ([]byte, error) { + /* + * 0 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * header |V=2|P| SC | PT=SDES=202 | length | + * +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ + * chunk | SSRC/CSRC_1 | + * 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | SDES items | + * | ... | + * +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ + * chunk | SSRC/CSRC_2 | + * 2 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | SDES items | + * | ... | + * +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ + */ + + rawPacket := make([]byte, s.len()) + packetBody := rawPacket[headerLength:] + + chunkOffset := 0 + for _, c := range s.Chunks { + data, err := c.Marshal() + if err != nil { + return nil, err + } + copy(packetBody[chunkOffset:], data) + chunkOffset += len(data) + } + + if len(s.Chunks) > countMax { + return nil, errTooManyChunks + } + + hData, err := s.Header().Marshal() + if err != nil { + return nil, err + } + copy(rawPacket, hData) + + return rawPacket, nil +} + +// Unmarshal decodes the SourceDescription from binary +func (s *SourceDescription) Unmarshal(rawPacket []byte) error { + /* + * 0 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * header |V=2|P| SC | PT=SDES=202 | length | + * +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ + * chunk | SSRC/CSRC_1 | + * 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | SDES items | + * | ... | + * +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ + * chunk | SSRC/CSRC_2 | + * 2 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | SDES items | + * | ... | + * +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ + */ + + var h Header + if err := h.Unmarshal(rawPacket); err != nil { + return err + } + + if h.Type != TypeSourceDescription { + return errWrongType + } + + for i := headerLength; i < len(rawPacket); { + var chunk SourceDescriptionChunk + if err := chunk.Unmarshal(rawPacket[i:]); err != nil { + return err + } + s.Chunks = append(s.Chunks, chunk) + + i += chunk.len() + } + + if len(s.Chunks) != int(h.Count) { + return errInvalidHeader + } + + return nil +} + +func (s *SourceDescription) len() int { + chunksLength := 0 + for _, c := range s.Chunks { + chunksLength += c.len() + } + return headerLength + chunksLength +} + +// Header returns the Header associated with this packet. +func (s *SourceDescription) Header() Header { + return Header{ + Count: uint8(len(s.Chunks)), + Type: TypeSourceDescription, + Length: uint16((s.len() / 4) - 1), + } +} + +// A SourceDescriptionChunk contains items describing a single RTP source +type SourceDescriptionChunk struct { + // The source (ssrc) or contributing source (csrc) identifier this packet describes + Source uint32 + Items []SourceDescriptionItem +} + +// Marshal encodes the SourceDescriptionChunk in binary +func (s SourceDescriptionChunk) Marshal() ([]byte, error) { + /* + * +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ + * | SSRC/CSRC_1 | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | SDES items | + * | ... | + * +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ + */ + + rawPacket := make([]byte, sdesSourceLen) + binary.BigEndian.PutUint32(rawPacket, s.Source) + + for _, it := range s.Items { + data, err := it.Marshal() + if err != nil { + return nil, err + } + rawPacket = append(rawPacket, data...) + } + + // The list of items in each chunk MUST be terminated by one or more null octets + rawPacket = append(rawPacket, uint8(SDESEnd)) + + // additional null octets MUST be included if needed to pad until the next 32-bit boundary + rawPacket = append(rawPacket, make([]byte, getPadding(len(rawPacket)))...) + + return rawPacket, nil +} + +// Unmarshal decodes the SourceDescriptionChunk from binary +func (s *SourceDescriptionChunk) Unmarshal(rawPacket []byte) error { + /* + * +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ + * | SSRC/CSRC_1 | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | SDES items | + * | ... | + * +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ + */ + + if len(rawPacket) < (sdesSourceLen + sdesTypeLen) { + return errPacketTooShort + } + + s.Source = binary.BigEndian.Uint32(rawPacket) + + for i := 4; i < len(rawPacket); { + if pktType := SDESType(rawPacket[i]); pktType == SDESEnd { + return nil + } + + var it SourceDescriptionItem + if err := it.Unmarshal(rawPacket[i:]); err != nil { + return err + } + s.Items = append(s.Items, it) + i += it.len() + } + + return errPacketTooShort +} + +func (s SourceDescriptionChunk) len() int { + len := sdesSourceLen + for _, it := range s.Items { + len += it.len() + } + len += sdesTypeLen // for terminating null octet + + // align to 32-bit boundary + len += getPadding(len) + + return len +} + +// A SourceDescriptionItem is a part of a SourceDescription that describes a stream. +type SourceDescriptionItem struct { + // The type identifier for this item. eg, SDESCNAME for canonical name description. + // + // Type zero or SDESEnd is interpreted as the end of an item list and cannot be used. + Type SDESType + // Text is a unicode text blob associated with the item. Its meaning varies based on the item's Type. + Text string +} + +func (s SourceDescriptionItem) len() int { + /* + * 0 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | CNAME=1 | length | user and domain name ... + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ + return sdesTypeLen + sdesOctetCountLen + len([]byte(s.Text)) +} + +// Marshal encodes the SourceDescriptionItem in binary +func (s SourceDescriptionItem) Marshal() ([]byte, error) { + /* + * 0 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | CNAME=1 | length | user and domain name ... + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ + + if s.Type == SDESEnd { + return nil, errSDESMissingType + } + + rawPacket := make([]byte, sdesTypeLen+sdesOctetCountLen) + + rawPacket[sdesTypeOffset] = uint8(s.Type) + + txtBytes := []byte(s.Text) + octetCount := len(txtBytes) + if octetCount > sdesMaxOctetCount { + return nil, errSDESTextTooLong + } + rawPacket[sdesOctetCountOffset] = uint8(octetCount) + + rawPacket = append(rawPacket, txtBytes...) + + return rawPacket, nil +} + +// Unmarshal decodes the SourceDescriptionItem from binary +func (s *SourceDescriptionItem) Unmarshal(rawPacket []byte) error { + /* + * 0 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | CNAME=1 | length | user and domain name ... + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ + + if len(rawPacket) < (sdesTypeLen + sdesOctetCountLen) { + return errPacketTooShort + } + + s.Type = SDESType(rawPacket[sdesTypeOffset]) + + octetCount := int(rawPacket[sdesOctetCountOffset]) + if sdesTextOffset+octetCount > len(rawPacket) { + return errPacketTooShort + } + + txtBytes := rawPacket[sdesTextOffset : sdesTextOffset+octetCount] + s.Text = string(txtBytes) + + return nil +} + +// DestinationSSRC returns an array of SSRC values that this packet refers to. +func (s *SourceDescription) DestinationSSRC() []uint32 { + out := make([]uint32, len(s.Chunks)) + for i, v := range s.Chunks { + out[i] = v.Source + } + return out +} + +func (s *SourceDescription) String() string { + out := "Source Description:\n" + for _, c := range s.Chunks { + out += fmt.Sprintf("\t%x: %s\n", c.Source, c.Items) + } + return out +} diff --git a/vendor/github.com/pion/rtcp/transport_layer_cc.go b/vendor/github.com/pion/rtcp/transport_layer_cc.go new file mode 100644 index 0000000..9606581 --- /dev/null +++ b/vendor/github.com/pion/rtcp/transport_layer_cc.go @@ -0,0 +1,560 @@ +package rtcp + +// Author: adwpc + +import ( + "encoding/binary" + "errors" + "fmt" + "math" +) + +// https://tools.ietf.org/html/draft-holmer-rmcat-transport-wide-cc-extensions-01#page-5 +// 0 1 2 3 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// |V=2|P| FMT=15 | PT=205 | length | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | SSRC of packet sender | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | SSRC of media source | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | base sequence number | packet status count | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | reference time | fb pkt. count | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | packet chunk | packet chunk | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// . . +// . . +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | packet chunk | recv delta | recv delta | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// . . +// . . +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | recv delta | recv delta | zero padding | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +// for packet status chunk +const ( + // type of packet status chunk + TypeTCCRunLengthChunk = 0 + TypeTCCStatusVectorChunk = 1 + + // len of packet status chunk + packetStatusChunkLength = 2 +) + +// type of packet status symbol and recv delta +const ( + // https://tools.ietf.org/html/draft-holmer-rmcat-transport-wide-cc-extensions-01#section-3.1.1 + TypeTCCPacketNotReceived = uint16(iota) + TypeTCCPacketReceivedSmallDelta + TypeTCCPacketReceivedLargeDelta + // https://tools.ietf.org/html/draft-holmer-rmcat-transport-wide-cc-extensions-01#page-7 + // see Example 2: "packet received, w/o recv delta" + TypeTCCPacketReceivedWithoutDelta +) + +// for status vector chunk +const ( + // https://tools.ietf.org/html/draft-holmer-rmcat-transport-wide-cc-extensions-01#section-3.1.4 + TypeTCCSymbolSizeOneBit = 0 + TypeTCCSymbolSizeTwoBit = 1 + + // Notice: RFC is wrong: "packet received" (0) and "packet not received" (1) + // if S == TypeTCCSymbolSizeOneBit, symbol list will be: TypeTCCPacketNotReceived TypeTCCPacketReceivedSmallDelta + // if S == TypeTCCSymbolSizeTwoBit, symbol list will be same as above: +) + +func numOfBitsOfSymbolSize() map[uint16]uint16 { + return map[uint16]uint16{ + TypeTCCSymbolSizeOneBit: 1, + TypeTCCSymbolSizeTwoBit: 2, + } +} + +var _ Packet = (*TransportLayerCC)(nil) // assert is a Packet + +var ( + errPacketStatusChunkLength = errors.New("packet status chunk must be 2 bytes") + errDeltaExceedLimit = errors.New("delta exceed limit") +) + +// PacketStatusChunk has two kinds: +// RunLengthChunk and StatusVectorChunk +type PacketStatusChunk interface { + Marshal() ([]byte, error) + Unmarshal(rawPacket []byte) error +} + +// RunLengthChunk T=TypeTCCRunLengthChunk +// 0 1 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// |T| S | Run Length | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +type RunLengthChunk struct { + PacketStatusChunk + + // T = TypeTCCRunLengthChunk + Type uint16 + + // S: type of packet status + // kind: TypeTCCPacketNotReceived or... + PacketStatusSymbol uint16 + + // RunLength: count of S + RunLength uint16 +} + +// Marshal .. +func (r RunLengthChunk) Marshal() ([]byte, error) { + chunk := make([]byte, 2) + + // append 1 bit '0' + dst, err := setNBitsOfUint16(0, 1, 0, 0) + if err != nil { + return nil, err + } + + // append 2 bit PacketStatusSymbol + dst, err = setNBitsOfUint16(dst, 2, 1, r.PacketStatusSymbol) + if err != nil { + return nil, err + } + + // append 13 bit RunLength + dst, err = setNBitsOfUint16(dst, 13, 3, r.RunLength) + if err != nil { + return nil, err + } + + binary.BigEndian.PutUint16(chunk, dst) + return chunk, nil +} + +// Unmarshal .. +func (r *RunLengthChunk) Unmarshal(rawPacket []byte) error { + if len(rawPacket) != packetStatusChunkLength { + return errPacketStatusChunkLength + } + + // record type + r.Type = TypeTCCRunLengthChunk + + // get PacketStatusSymbol + // r.PacketStatusSymbol = uint16(rawPacket[0] >> 5 & 0x03) + r.PacketStatusSymbol = getNBitsFromByte(rawPacket[0], 1, 2) + + // get RunLength + // r.RunLength = uint16(rawPacket[0]&0x1F)*256 + uint16(rawPacket[1]) + r.RunLength = getNBitsFromByte(rawPacket[0], 3, 5)<<8 + uint16(rawPacket[1]) + return nil +} + +// StatusVectorChunk T=typeStatusVecotrChunk +// 0 1 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// |T|S| symbol list | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +type StatusVectorChunk struct { + PacketStatusChunk + // T = TypeTCCRunLengthChunk + Type uint16 + + // TypeTCCSymbolSizeOneBit or TypeTCCSymbolSizeTwoBit + SymbolSize uint16 + + // when SymbolSize = TypeTCCSymbolSizeOneBit, SymbolList is 14*1bit: + // TypeTCCSymbolListPacketReceived or TypeTCCSymbolListPacketNotReceived + // when SymbolSize = TypeTCCSymbolSizeTwoBit, SymbolList is 7*2bit: + // TypeTCCPacketNotReceived TypeTCCPacketReceivedSmallDelta TypeTCCPacketReceivedLargeDelta or typePacketReserved + SymbolList []uint16 +} + +// Marshal .. +func (r StatusVectorChunk) Marshal() ([]byte, error) { + chunk := make([]byte, 2) + + // set first bit '1' + dst, err := setNBitsOfUint16(0, 1, 0, 1) + if err != nil { + return nil, err + } + + // set second bit SymbolSize + dst, err = setNBitsOfUint16(dst, 1, 1, r.SymbolSize) + if err != nil { + return nil, err + } + + numOfBits := numOfBitsOfSymbolSize()[r.SymbolSize] + // append 14 bit SymbolList + for i, s := range r.SymbolList { + index := numOfBits*uint16(i) + 2 + dst, err = setNBitsOfUint16(dst, numOfBits, index, s) + if err != nil { + return nil, err + } + } + + binary.BigEndian.PutUint16(chunk, dst) + // set SymbolList(bit8-15) + // chunk[1] = uint8(r.SymbolList) & 0x0f + return chunk, nil +} + +// Unmarshal .. +func (r *StatusVectorChunk) Unmarshal(rawPacket []byte) error { + if len(rawPacket) != packetStatusChunkLength { + return errPacketStatusChunkLength + } + + r.Type = TypeTCCStatusVectorChunk + r.SymbolSize = getNBitsFromByte(rawPacket[0], 1, 1) + + if r.SymbolSize == TypeTCCSymbolSizeOneBit { + for i := uint16(0); i < 6; i++ { + r.SymbolList = append(r.SymbolList, getNBitsFromByte(rawPacket[0], 2+i, 1)) + } + for i := uint16(0); i < 8; i++ { + r.SymbolList = append(r.SymbolList, getNBitsFromByte(rawPacket[1], i, 1)) + } + return nil + } + if r.SymbolSize == TypeTCCSymbolSizeTwoBit { + for i := uint16(0); i < 3; i++ { + r.SymbolList = append(r.SymbolList, getNBitsFromByte(rawPacket[0], 2+i*2, 2)) + } + for i := uint16(0); i < 4; i++ { + r.SymbolList = append(r.SymbolList, getNBitsFromByte(rawPacket[1], i*2, 2)) + } + return nil + } + + r.SymbolSize = getNBitsFromByte(rawPacket[0], 2, 6)<<8 + uint16(rawPacket[1]) + return nil +} + +const ( + // TypeTCCDeltaScaleFactor https://tools.ietf.org/html/draft-holmer-rmcat-transport-wide-cc-extensions-01#section-3.1.5 + TypeTCCDeltaScaleFactor = 250 +) + +// RecvDelta are represented as multiples of 250us +// small delta is 1 byte: [0,63.75]ms = [0, 63750]us = [0, 255]*250us +// big delta is 2 bytes: [-8192.0, 8191.75]ms = [-8192000, 8191750]us = [-32768, 32767]*250us +// https://tools.ietf.org/html/draft-holmer-rmcat-transport-wide-cc-extensions-01#section-3.1.5 +type RecvDelta struct { + Type uint16 + // us + Delta int64 +} + +// Marshal .. +func (r RecvDelta) Marshal() ([]byte, error) { + delta := r.Delta / TypeTCCDeltaScaleFactor + + // small delta + if r.Type == TypeTCCPacketReceivedSmallDelta && delta >= 0 && delta <= math.MaxUint8 { + deltaChunk := make([]byte, 1) + deltaChunk[0] = byte(delta) + return deltaChunk, nil + } + + // big delta + if r.Type == TypeTCCPacketReceivedLargeDelta && delta >= math.MinInt16 && delta <= math.MaxInt16 { + deltaChunk := make([]byte, 2) + binary.BigEndian.PutUint16(deltaChunk, uint16(delta)) + return deltaChunk, nil + } + + // overflow + return nil, errDeltaExceedLimit +} + +// Unmarshal .. +func (r *RecvDelta) Unmarshal(rawPacket []byte) error { + chunkLen := len(rawPacket) + + // must be 1 or 2 bytes + if chunkLen != 1 && chunkLen != 2 { + return errDeltaExceedLimit + } + + if chunkLen == 1 { + r.Type = TypeTCCPacketReceivedSmallDelta + r.Delta = TypeTCCDeltaScaleFactor * int64(rawPacket[0]) + return nil + } + + r.Type = TypeTCCPacketReceivedLargeDelta + r.Delta = TypeTCCDeltaScaleFactor * int64(int16(binary.BigEndian.Uint16(rawPacket))) + return nil +} + +const ( + // the offset after header + baseSequenceNumberOffset = 8 + packetStatusCountOffset = 10 + referenceTimeOffset = 12 + fbPktCountOffset = 15 + packetChunkOffset = 16 +) + +// TransportLayerCC for sender-BWE +// https://tools.ietf.org/html/draft-holmer-rmcat-transport-wide-cc-extensions-01#page-5 +type TransportLayerCC struct { + // header + Header Header + + // SSRC of sender + SenderSSRC uint32 + + // SSRC of the media source + MediaSSRC uint32 + + // Transport wide sequence of rtp extension + BaseSequenceNumber uint16 + + // PacketStatusCount + PacketStatusCount uint16 + + // ReferenceTime + ReferenceTime uint32 + + // FbPktCount + FbPktCount uint8 + + // PacketChunks + PacketChunks []PacketStatusChunk + + // RecvDeltas + RecvDeltas []*RecvDelta +} + +// Header returns the Header associated with this packet. +// func (t *TransportLayerCC) Header() Header { +// return t.Header +// return Header{ +// Padding: true, +// Count: FormatTCC, +// Type: TypeTCCTransportSpecificFeedback, +// // https://tools.ietf.org/html/rfc4585#page-33 +// Length: uint16((t.len() / 4) - 1), +// } +// } + +func (t *TransportLayerCC) packetLen() uint16 { + n := uint16(headerLength + packetChunkOffset + len(t.PacketChunks)*2) + for _, d := range t.RecvDeltas { + delta := d.Delta / TypeTCCDeltaScaleFactor + + // small delta + if delta >= 0 && delta <= math.MaxUint8 { + n++ + } else { + n += 2 + } + } + return n +} + +// Len return total bytes with padding +func (t *TransportLayerCC) Len() uint16 { + n := t.packetLen() + // has padding + if n%4 != 0 { + n = (n/4 + 1) * 4 + } + + return n +} + +func (t TransportLayerCC) String() string { + out := fmt.Sprintf("TransportLayerCC:\n\tHeader %v\n", t.Header) + out += fmt.Sprintf("TransportLayerCC:\n\tSender Ssrc %d\n", t.SenderSSRC) + out += fmt.Sprintf("\tMedia Ssrc %d\n", t.MediaSSRC) + out += fmt.Sprintf("\tBase Sequence Number %d\n", t.BaseSequenceNumber) + out += fmt.Sprintf("\tStatus Count %d\n", t.PacketStatusCount) + out += fmt.Sprintf("\tReference Time %d\n", t.ReferenceTime) + out += fmt.Sprintf("\tFeedback Packet Count %d\n", t.FbPktCount) + out += "\tPacketChunks " + for _, chunk := range t.PacketChunks { + out += fmt.Sprintf("%+v ", chunk) + } + out += "\n\tRecvDeltas " + for _, delta := range t.RecvDeltas { + out += fmt.Sprintf("%+v ", delta) + } + out += "\n" + return out +} + +// Marshal encodes the TransportLayerCC in binary +func (t TransportLayerCC) Marshal() ([]byte, error) { + header, err := t.Header.Marshal() + if err != nil { + return nil, err + } + + payload := make([]byte, t.Len()-headerLength) + binary.BigEndian.PutUint32(payload, t.SenderSSRC) + binary.BigEndian.PutUint32(payload[4:], t.MediaSSRC) + binary.BigEndian.PutUint16(payload[baseSequenceNumberOffset:], t.BaseSequenceNumber) + binary.BigEndian.PutUint16(payload[packetStatusCountOffset:], t.PacketStatusCount) + ReferenceTimeAndFbPktCount := appendNBitsToUint32(0, 24, t.ReferenceTime) + ReferenceTimeAndFbPktCount = appendNBitsToUint32(ReferenceTimeAndFbPktCount, 8, uint32(t.FbPktCount)) + binary.BigEndian.PutUint32(payload[referenceTimeOffset:], ReferenceTimeAndFbPktCount) + + for i, chunk := range t.PacketChunks { + b, err := chunk.Marshal() + if err != nil { + return nil, err + } + copy(payload[packetChunkOffset+i*2:], b) + } + + recvDeltaOffset := packetChunkOffset + len(t.PacketChunks)*2 + var i int + for _, delta := range t.RecvDeltas { + b, err := delta.Marshal() + if err == nil { + copy(payload[recvDeltaOffset+i:], b) + i++ + if delta.Type == TypeTCCPacketReceivedLargeDelta { + i++ + } + } + } + + if t.Header.Padding { + payload[len(payload)-1] = uint8(t.Len() - t.packetLen()) + } + + return append(header, payload...), nil +} + +// Unmarshal .. +func (t *TransportLayerCC) Unmarshal(rawPacket []byte) error { //nolint:gocognit + if len(rawPacket) < (headerLength + ssrcLength) { + return errPacketTooShort + } + + if err := t.Header.Unmarshal(rawPacket); err != nil { + return err + } + + // https://tools.ietf.org/html/rfc4585#page-33 + // header's length + payload's length + totalLength := 4 * (t.Header.Length + 1) + + if totalLength <= headerLength+packetChunkOffset { + return errPacketTooShort + } + + if len(rawPacket) < int(totalLength) { + return errPacketTooShort + } + + if t.Header.Type != TypeTransportSpecificFeedback || t.Header.Count != FormatTCC { + return errWrongType + } + + t.SenderSSRC = binary.BigEndian.Uint32(rawPacket[headerLength:]) + t.MediaSSRC = binary.BigEndian.Uint32(rawPacket[headerLength+ssrcLength:]) + t.BaseSequenceNumber = binary.BigEndian.Uint16(rawPacket[headerLength+baseSequenceNumberOffset:]) + t.PacketStatusCount = binary.BigEndian.Uint16(rawPacket[headerLength+packetStatusCountOffset:]) + t.ReferenceTime = get24BitsFromBytes(rawPacket[headerLength+referenceTimeOffset : headerLength+referenceTimeOffset+3]) + t.FbPktCount = rawPacket[headerLength+fbPktCountOffset] + + packetStatusPos := uint16(headerLength + packetChunkOffset) + var processedPacketNum uint16 + for processedPacketNum < t.PacketStatusCount { + if packetStatusPos+packetStatusChunkLength >= totalLength { + return errPacketTooShort + } + typ := getNBitsFromByte(rawPacket[packetStatusPos : packetStatusPos+1][0], 0, 1) + var iPacketStatus PacketStatusChunk + switch typ { + case TypeTCCRunLengthChunk: + packetStatus := &RunLengthChunk{Type: typ} + iPacketStatus = packetStatus + err := packetStatus.Unmarshal(rawPacket[packetStatusPos : packetStatusPos+2]) + if err != nil { + return err + } + + packetNumberToProcess := min(t.PacketStatusCount-processedPacketNum, packetStatus.RunLength) + if packetStatus.PacketStatusSymbol == TypeTCCPacketReceivedSmallDelta || + packetStatus.PacketStatusSymbol == TypeTCCPacketReceivedLargeDelta { + for j := uint16(0); j < packetNumberToProcess; j++ { + t.RecvDeltas = append(t.RecvDeltas, &RecvDelta{Type: packetStatus.PacketStatusSymbol}) + } + } + processedPacketNum += packetNumberToProcess + case TypeTCCStatusVectorChunk: + packetStatus := &StatusVectorChunk{Type: typ} + iPacketStatus = packetStatus + err := packetStatus.Unmarshal(rawPacket[packetStatusPos : packetStatusPos+2]) + if err != nil { + return err + } + if packetStatus.SymbolSize == TypeTCCSymbolSizeOneBit { + for j := 0; j < len(packetStatus.SymbolList); j++ { + if packetStatus.SymbolList[j] == TypeTCCPacketReceivedSmallDelta { + t.RecvDeltas = append(t.RecvDeltas, &RecvDelta{Type: TypeTCCPacketReceivedSmallDelta}) + } + } + } + if packetStatus.SymbolSize == TypeTCCSymbolSizeTwoBit { + for j := 0; j < len(packetStatus.SymbolList); j++ { + if packetStatus.SymbolList[j] == TypeTCCPacketReceivedSmallDelta || packetStatus.SymbolList[j] == TypeTCCPacketReceivedLargeDelta { + t.RecvDeltas = append(t.RecvDeltas, &RecvDelta{Type: packetStatus.SymbolList[j]}) + } + } + } + processedPacketNum += uint16(len(packetStatus.SymbolList)) + } + packetStatusPos += packetStatusChunkLength + t.PacketChunks = append(t.PacketChunks, iPacketStatus) + } + + recvDeltasPos := packetStatusPos + for _, delta := range t.RecvDeltas { + if recvDeltasPos >= totalLength { + return errPacketTooShort + } + if delta.Type == TypeTCCPacketReceivedSmallDelta { + err := delta.Unmarshal(rawPacket[recvDeltasPos : recvDeltasPos+1]) + if err != nil { + return err + } + recvDeltasPos++ + } + if delta.Type == TypeTCCPacketReceivedLargeDelta { + err := delta.Unmarshal(rawPacket[recvDeltasPos : recvDeltasPos+2]) + if err != nil { + return err + } + recvDeltasPos += 2 + } + } + + return nil +} + +// DestinationSSRC returns an array of SSRC values that this packet refers to. +func (t TransportLayerCC) DestinationSSRC() []uint32 { + return []uint32{t.MediaSSRC} +} + +func min(x, y uint16) uint16 { + if x < y { + return x + } + return y +} diff --git a/vendor/github.com/pion/rtcp/transport_layer_nack.go b/vendor/github.com/pion/rtcp/transport_layer_nack.go new file mode 100644 index 0000000..f7ab803 --- /dev/null +++ b/vendor/github.com/pion/rtcp/transport_layer_nack.go @@ -0,0 +1,174 @@ +package rtcp + +import ( + "encoding/binary" + "fmt" + "math" +) + +// PacketBitmap shouldn't be used like a normal integral, +// so it's type is masked here. Access it with PacketList(). +type PacketBitmap uint16 + +// NackPair is a wire-representation of a collection of +// Lost RTP packets +type NackPair struct { + // ID of lost packets + PacketID uint16 + + // Bitmask of following lost packets + LostPackets PacketBitmap +} + +// The TransportLayerNack packet informs the encoder about the loss of a transport packet +// IETF RFC 4585, Section 6.2.1 +// https://tools.ietf.org/html/rfc4585#section-6.2.1 +type TransportLayerNack struct { + // SSRC of sender + SenderSSRC uint32 + + // SSRC of the media source + MediaSSRC uint32 + + Nacks []NackPair +} + +var _ Packet = (*TransportLayerNack)(nil) // assert is a Packet + +// NackPairsFromSequenceNumbers generates a slice of NackPair from a list of SequenceNumbers +// This handles generating the proper values for PacketID/LostPackets +func NackPairsFromSequenceNumbers(sequenceNumbers []uint16) (pairs []NackPair) { + if len(sequenceNumbers) == 0 { + return []NackPair{} + } + + nackPair := &NackPair{PacketID: sequenceNumbers[0]} + for i := 1; i < len(sequenceNumbers); i++ { + m := sequenceNumbers[i] + + if m-nackPair.PacketID > 16 { + pairs = append(pairs, *nackPair) + nackPair = &NackPair{PacketID: m} + continue + } + + nackPair.LostPackets |= 1 << (m - nackPair.PacketID - 1) + } + pairs = append(pairs, *nackPair) + return +} + +// Range calls f sequentially for each sequence number covered by n. +// If f returns false, Range stops the iteration. +func (n *NackPair) Range(f func(seqno uint16) bool) { + more := f(n.PacketID) + if !more { + return + } + + b := n.LostPackets + for i := uint16(0); b != 0; i++ { + if (b & (1 << i)) != 0 { + b &^= (1 << i) + more = f(n.PacketID + i + 1) + if !more { + return + } + } + } +} + +// PacketList returns a list of Nack'd packets that's referenced by a NackPair +func (n *NackPair) PacketList() []uint16 { + out := make([]uint16, 0, 17) + n.Range(func(seqno uint16) bool { + out = append(out, seqno) + return true + }) + return out +} + +const ( + tlnLength = 2 + nackOffset = 8 +) + +// Marshal encodes the TransportLayerNack in binary +func (p TransportLayerNack) Marshal() ([]byte, error) { + if len(p.Nacks)+tlnLength > math.MaxUint8 { + return nil, errTooManyReports + } + + rawPacket := make([]byte, nackOffset+(len(p.Nacks)*4)) + binary.BigEndian.PutUint32(rawPacket, p.SenderSSRC) + binary.BigEndian.PutUint32(rawPacket[4:], p.MediaSSRC) + for i := 0; i < len(p.Nacks); i++ { + binary.BigEndian.PutUint16(rawPacket[nackOffset+(4*i):], p.Nacks[i].PacketID) + binary.BigEndian.PutUint16(rawPacket[nackOffset+(4*i)+2:], uint16(p.Nacks[i].LostPackets)) + } + h := p.Header() + hData, err := h.Marshal() + if err != nil { + return nil, err + } + + return append(hData, rawPacket...), nil +} + +// Unmarshal decodes the TransportLayerNack from binary +func (p *TransportLayerNack) Unmarshal(rawPacket []byte) error { + if len(rawPacket) < (headerLength + ssrcLength) { + return errPacketTooShort + } + + var h Header + if err := h.Unmarshal(rawPacket); err != nil { + return err + } + + if len(rawPacket) < (headerLength + int(4*h.Length)) { + return errPacketTooShort + } + + if h.Type != TypeTransportSpecificFeedback || h.Count != FormatTLN { + return errWrongType + } + + p.SenderSSRC = binary.BigEndian.Uint32(rawPacket[headerLength:]) + p.MediaSSRC = binary.BigEndian.Uint32(rawPacket[headerLength+ssrcLength:]) + for i := headerLength + nackOffset; i < (headerLength + int(h.Length*4)); i += 4 { + p.Nacks = append(p.Nacks, NackPair{ + binary.BigEndian.Uint16(rawPacket[i:]), + PacketBitmap(binary.BigEndian.Uint16(rawPacket[i+2:])), + }) + } + return nil +} + +func (p *TransportLayerNack) len() int { + return headerLength + nackOffset + (len(p.Nacks) * 4) +} + +// Header returns the Header associated with this packet. +func (p *TransportLayerNack) Header() Header { + return Header{ + Count: FormatTLN, + Type: TypeTransportSpecificFeedback, + Length: uint16((p.len() / 4) - 1), + } +} + +func (p TransportLayerNack) String() string { + out := fmt.Sprintf("TransportLayerNack from %x\n", p.SenderSSRC) + out += fmt.Sprintf("\tMedia Ssrc %x\n", p.MediaSSRC) + out += "\tID\tLostPackets\n" + for _, i := range p.Nacks { + out += fmt.Sprintf("\t%d\t%b\n", i.PacketID, i.LostPackets) + } + return out +} + +// DestinationSSRC returns an array of SSRC values that this packet refers to. +func (p *TransportLayerNack) DestinationSSRC() []uint32 { + return []uint32{p.MediaSSRC} +} diff --git a/vendor/github.com/pion/rtcp/util.go b/vendor/github.com/pion/rtcp/util.go new file mode 100644 index 0000000..5702d35 --- /dev/null +++ b/vendor/github.com/pion/rtcp/util.go @@ -0,0 +1,38 @@ +package rtcp + +// getPadding Returns the padding required to make the length a multiple of 4 +func getPadding(len int) int { + if len%4 == 0 { + return 0 + } + return 4 - (len % 4) +} + +// setNBitsOfUint16 will truncate the value to size, left-shift to startIndex position and set +func setNBitsOfUint16(src, size, startIndex, val uint16) (uint16, error) { + if startIndex+size > 16 { + return 0, errInvalidSizeOrStartIndex + } + + // truncate val to size bits + val &= (1 << size) - 1 + + return src | (val << (16 - size - startIndex)), nil +} + +// appendBit32 will left-shift and append n bits of val +func appendNBitsToUint32(src, n, val uint32) uint32 { + return (src << n) | (val & (0xFFFFFFFF >> (32 - n))) +} + +// getNBit get n bits from 1 byte, begin with a position +func getNBitsFromByte(b byte, begin, n uint16) uint16 { + endShift := 8 - (begin + n) + mask := (0xFF >> begin) & uint8(0xFF<<endShift) + return uint16(b&mask) >> endShift +} + +// get24BitFromBytes get 24bits from `[3]byte` slice +func get24BitsFromBytes(b []byte) uint32 { + return uint32(b[0])<<16 + uint32(b[1])<<8 + uint32(b[2]) +} diff --git a/vendor/github.com/pion/rtp/.gitignore b/vendor/github.com/pion/rtp/.gitignore new file mode 100644 index 0000000..83db74b --- /dev/null +++ b/vendor/github.com/pion/rtp/.gitignore @@ -0,0 +1,24 @@ +### JetBrains IDE ### +##################### +.idea/ + +### Emacs Temporary Files ### +############################# +*~ + +### Folders ### +############### +bin/ +vendor/ +node_modules/ + +### Files ### +############# +*.ivf +*.ogg +tags +cover.out +*.sw[poe] +*.wasm +examples/sfu-ws/cert.pem +examples/sfu-ws/key.pem diff --git a/vendor/github.com/pion/rtp/.golangci.yml b/vendor/github.com/pion/rtp/.golangci.yml new file mode 100644 index 0000000..d6162c9 --- /dev/null +++ b/vendor/github.com/pion/rtp/.golangci.yml @@ -0,0 +1,89 @@ +linters-settings: + govet: + check-shadowing: true + misspell: + locale: US + exhaustive: + default-signifies-exhaustive: true + gomodguard: + blocked: + modules: + - github.com/pkg/errors: + recommendations: + - errors + +linters: + enable: + - asciicheck # Simple linter to check that your code does not contain non-ASCII identifiers + - bodyclose # checks whether HTTP response body is closed successfully + - deadcode # Finds unused code + - depguard # Go linter that checks if package imports are in a list of acceptable packages + - dogsled # Checks assignments with too many blank identifiers (e.g. x, _, _, _, := f()) + - dupl # Tool for code clone detection + - errcheck # Errcheck is a program for checking for unchecked errors in go programs. These unchecked errors can be critical bugs in some cases + - exhaustive # check exhaustiveness of enum switch statements + - exportloopref # checks for pointers to enclosing loop variables + - gci # Gci control golang package import order and make it always deterministic. + - gochecknoglobals # Checks that no globals are present in Go code + - gochecknoinits # Checks that no init functions are present in Go code + - gocognit # Computes and checks the cognitive complexity of functions + - goconst # Finds repeated strings that could be replaced by a constant + - gocritic # The most opinionated Go source code linter + - godox # Tool for detection of FIXME, TODO and other comment keywords + - goerr113 # Golang linter to check the errors handling expressions + - gofmt # Gofmt checks whether code was gofmt-ed. By default this tool runs with -s option to check for code simplification + - gofumpt # Gofumpt checks whether code was gofumpt-ed. + - goheader # Checks is file header matches to pattern + - goimports # Goimports does everything that gofmt does. Additionally it checks unused imports + - golint # Golint differs from gofmt. Gofmt reformats Go source code, whereas golint prints out style mistakes + - gomodguard # Allow and block list linter for direct Go module dependencies. This is different from depguard where there are different block types for example version constraints and module recommendations. + - goprintffuncname # Checks that printf-like functions are named with `f` at the end + - gosec # Inspects source code for security problems + - gosimple # Linter for Go source code that specializes in simplifying a code + - govet # Vet examines Go source code and reports suspicious constructs, such as Printf calls whose arguments do not align with the format string + - ineffassign # Detects when assignments to existing variables are not used + - misspell # Finds commonly misspelled English words in comments + - nakedret # Finds naked returns in functions greater than a specified function length + - noctx # noctx finds sending http request without context.Context + - scopelint # Scopelint checks for unpinned variables in go programs + - staticcheck # Staticcheck is a go vet on steroids, applying a ton of static analysis checks + - structcheck # Finds unused struct fields + - stylecheck # Stylecheck is a replacement for golint + - typecheck # Like the front-end of a Go compiler, parses and type-checks Go code + - unconvert # Remove unnecessary type conversions + - unparam # Reports unused function parameters + - unused # Checks Go code for unused constants, variables, functions and types + - varcheck # Finds unused global variables and constants + - whitespace # Tool for detection of leading and trailing whitespace + disable: + - funlen # Tool for detection of long functions + - gocyclo # Computes and checks the cyclomatic complexity of functions + - godot # Check if comments end in a period + - gomnd # An analyzer to detect magic numbers. + - lll # Reports long lines + - maligned # Tool to detect Go structs that would take less memory if their fields were sorted + - nestif # Reports deeply nested if statements + - nlreturn # nlreturn checks for a new line before return and branch statements to increase code clarity + - nolintlint # Reports ill-formed or insufficient nolint directives + - prealloc # Finds slice declarations that could potentially be preallocated + - rowserrcheck # checks whether Err of rows is checked successfully + - sqlclosecheck # Checks that sql.Rows and sql.Stmt are closed. + - testpackage # linter that makes you use a separate _test package + - wsl # Whitespace Linter - Forces you to use empty lines! + +issues: + exclude-use-default: false + exclude-rules: + # Allow complex tests, better to be self contained + - path: _test\.go + linters: + - gocognit + + # Allow complex main function in examples + - path: examples + text: "of func `main` is high" + linters: + - gocognit + +run: + skip-dirs-use-default: false diff --git a/vendor/github.com/pion/rtp/LICENSE b/vendor/github.com/pion/rtp/LICENSE new file mode 100644 index 0000000..ab60297 --- /dev/null +++ b/vendor/github.com/pion/rtp/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/pion/rtp/README.md b/vendor/github.com/pion/rtp/README.md new file mode 100644 index 0000000..b17709e --- /dev/null +++ b/vendor/github.com/pion/rtp/README.md @@ -0,0 +1,53 @@ +<h1 align="center"> + <br> + Pion RTP + <br> +</h1> +<h4 align="center">A Go implementation of RTP</h4> +<p align="center"> + <a href="https://pion.ly"><img src="https://img.shields.io/badge/pion-rtp-gray.svg?longCache=true&colorB=brightgreen" alt="Pion RTP"></a> + <a href="https://sourcegraph.com/github.com/pion/rtp?badge"><img src="https://sourcegraph.com/github.com/pion/rtp/-/badge.svg" alt="Sourcegraph Widget"></a> + <a href="https://pion.ly/slack"><img src="https://img.shields.io/badge/join-us%20on%20slack-gray.svg?longCache=true&logo=slack&colorB=brightgreen" alt="Slack Widget"></a> + <br> + <a href="https://travis-ci.org/pion/rtp"><img src="https://travis-ci.org/pion/rtp.svg?branch=master" alt="Build Status"></a> + <a href="https://pkg.go.dev/github.com/pion/rtp"><img src="https://godoc.org/github.com/pion/rtp?status.svg" alt="GoDoc"></a> + <a href="https://codecov.io/gh/pion/rtp"><img src="https://codecov.io/gh/pion/rtp/branch/master/graph/badge.svg" alt="Coverage Status"></a> + <a href="https://goreportcard.com/report/github.com/pion/rtp"><img src="https://goreportcard.com/badge/github.com/pion/rtp" alt="Go Report Card"></a> + <a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="License: MIT"></a> +</p> +<br> + +### Roadmap +The library is used as a part of our WebRTC implementation. Please refer to that [roadmap](https://github.com/pion/webrtc/issues/9) to track our major milestones. + +### Community +Pion has an active community on the [Golang Slack](https://invite.slack.golangbridge.org/). Sign up and join the **#pion** channel for discussions and support. You can also use [Pion mailing list](https://groups.google.com/forum/#!forum/pion). + +We are always looking to support **your projects**. Please reach out if you have something to build! + +If you need commercial support or don't want to use public methods you can contact us at [team@pion.ly](mailto:team@pion.ly) + +### Contributing +Check out the **[contributing wiki](https://github.com/pion/webrtc/wiki/Contributing)** to join the group of amazing people making this project possible: + +* [John Bradley](https://github.com/kc5nra) - *Original Author* +* [Sean DuBois](https://github.com/Sean-Der) - *Original Author* +* [Woodrow Douglass](https://github.com/wdouglass) *RTCP, RTP improvements, G.722 support, Bugfixes* +* [Michael MacDonald](https://github.com/mjmac) +* [Luke Curley](https://github.com/kixelated) *Performance* +* [Antoine Baché](https://github.com/Antonito) *Fixed crashes* +* [Hugo Arregui](https://github.com/hugoArregui) +* [Raphael Derosso Pereira](https://github.com/raphaelpereira) +* [Atsushi Watanabe](https://github.com/at-wat) +* [adwpc](https://github.com/adwpc) *add transport-cc extension* +* [Bao Nguyen](https://github.com/sysbot) *add VP9 noop, bug fixes. +* [Tarrence van As](https://github.com/tarrencev) *add audio level extension* +* [Simone Gotti](https://github.com/sgotti) +* [Guilherme Souza](https://github.com/gqgs) +* [Rob Lofthouse](https://github.com/roblofthouse) +* [Kazuyuki Honda](https://github.com/hakobera) +* [Haiyang Wang](https://github.com/ocean2811) +* [lxb](https://github.com/lxb531) + +### License +MIT License - see [LICENSE](LICENSE) for full text diff --git a/vendor/github.com/pion/rtp/abssendtimeextension.go b/vendor/github.com/pion/rtp/abssendtimeextension.go new file mode 100644 index 0000000..fc9731d --- /dev/null +++ b/vendor/github.com/pion/rtp/abssendtimeextension.go @@ -0,0 +1,78 @@ +package rtp + +import ( + "time" +) + +const ( + absSendTimeExtensionSize = 3 +) + +// AbsSendTimeExtension is a extension payload format in +// http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time +type AbsSendTimeExtension struct { + Timestamp uint64 +} + +// Marshal serializes the members to buffer. +func (t *AbsSendTimeExtension) Marshal() ([]byte, error) { + return []byte{ + byte(t.Timestamp & 0xFF0000 >> 16), + byte(t.Timestamp & 0xFF00 >> 8), + byte(t.Timestamp & 0xFF), + }, nil +} + +// Unmarshal parses the passed byte slice and stores the result in the members. +func (t *AbsSendTimeExtension) Unmarshal(rawData []byte) error { + if len(rawData) < absSendTimeExtensionSize { + return errTooSmall + } + t.Timestamp = uint64(rawData[0])<<16 | uint64(rawData[1])<<8 | uint64(rawData[2]) + return nil +} + +// Estimate absolute send time according to the receive time. +// Note that if the transmission delay is larger than 64 seconds, estimated time will be wrong. +func (t *AbsSendTimeExtension) Estimate(receive time.Time) time.Time { + receiveNTP := toNtpTime(receive) + ntp := receiveNTP&0xFFFFFFC000000000 | (t.Timestamp&0xFFFFFF)<<14 + if receiveNTP < ntp { + // Receive time must be always later than send time + ntp -= 0x1000000 << 14 + } + + return toTime(ntp) +} + +// NewAbsSendTimeExtension makes new AbsSendTimeExtension from time.Time. +func NewAbsSendTimeExtension(sendTime time.Time) *AbsSendTimeExtension { + return &AbsSendTimeExtension{ + Timestamp: toNtpTime(sendTime) >> 14, + } +} + +func toNtpTime(t time.Time) uint64 { + var s uint64 + var f uint64 + u := uint64(t.UnixNano()) + s = u / 1e9 + s += 0x83AA7E80 // offset in seconds between unix epoch and ntp epoch + f = u % 1e9 + f <<= 32 + f /= 1e9 + s <<= 32 + + return s | f +} + +func toTime(t uint64) time.Time { + s := t >> 32 + f := t & 0xFFFFFFFF + f *= 1e9 + f >>= 32 + s -= 0x83AA7E80 + u := s*1e9 + f + + return time.Unix(0, int64(u)) +} diff --git a/vendor/github.com/pion/rtp/audiolevelextension.go b/vendor/github.com/pion/rtp/audiolevelextension.go new file mode 100644 index 0000000..f8701e1 --- /dev/null +++ b/vendor/github.com/pion/rtp/audiolevelextension.go @@ -0,0 +1,60 @@ +package rtp + +import ( + "errors" +) + +const ( + // audioLevelExtensionSize One byte header size + audioLevelExtensionSize = 1 +) + +var errAudioLevelOverflow = errors.New("audio level overflow") + +// AudioLevelExtension is a extension payload format described in +// https://tools.ietf.org/html/rfc6464 +// +// Implementation based on: +// https://chromium.googlesource.com/external/webrtc/+/e2a017725570ead5946a4ca8235af27470ca0df9/webrtc/modules/rtp_rtcp/source/rtp_header_extensions.cc#49 +// +// One byte format: +// 0 1 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | ID | len=0 |V| level | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// +// Two byte format: +// 0 1 2 3 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | ID | len=1 |V| level | 0 (pad) | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +type AudioLevelExtension struct { + Level uint8 + Voice bool +} + +// Marshal serializes the members to buffer +func (a *AudioLevelExtension) Marshal() ([]byte, error) { + if a.Level > 127 { + return nil, errAudioLevelOverflow + } + voice := uint8(0x00) + if a.Voice { + voice = 0x80 + } + buf := make([]byte, audioLevelExtensionSize) + buf[0] = voice | a.Level + return buf, nil +} + +// Unmarshal parses the passed byte slice and stores the result in the members +func (a *AudioLevelExtension) Unmarshal(rawData []byte) error { + if len(rawData) < audioLevelExtensionSize { + return errTooSmall + } + a.Level = rawData[0] & 0x7F + a.Voice = rawData[0]&0x80 != 0 + return nil +} diff --git a/vendor/github.com/pion/rtp/codecov.yml b/vendor/github.com/pion/rtp/codecov.yml new file mode 100644 index 0000000..085200a --- /dev/null +++ b/vendor/github.com/pion/rtp/codecov.yml @@ -0,0 +1,20 @@ +# +# DO NOT EDIT THIS FILE +# +# It is automatically copied from https://github.com/pion/.goassets repository. +# + +coverage: + status: + project: + default: + # Allow decreasing 2% of total coverage to avoid noise. + threshold: 2% + patch: + default: + target: 70% + only_pulls: true + +ignore: + - "examples/*" + - "examples/**/*" diff --git a/vendor/github.com/pion/rtp/codecs/codecs.go b/vendor/github.com/pion/rtp/codecs/codecs.go new file mode 100644 index 0000000..0e07897 --- /dev/null +++ b/vendor/github.com/pion/rtp/codecs/codecs.go @@ -0,0 +1,2 @@ +// Package codecs implements codec specific RTP payloader/depayloaders +package codecs diff --git a/vendor/github.com/pion/rtp/codecs/common.go b/vendor/github.com/pion/rtp/codecs/common.go new file mode 100644 index 0000000..39336d2 --- /dev/null +++ b/vendor/github.com/pion/rtp/codecs/common.go @@ -0,0 +1,8 @@ +package codecs + +func min(a, b int) int { + if a < b { + return a + } + return b +} diff --git a/vendor/github.com/pion/rtp/codecs/error.go b/vendor/github.com/pion/rtp/codecs/error.go new file mode 100644 index 0000000..38ee907 --- /dev/null +++ b/vendor/github.com/pion/rtp/codecs/error.go @@ -0,0 +1,11 @@ +package codecs + +import "errors" + +var ( + errShortPacket = errors.New("packet is not large enough") + errNilPacket = errors.New("invalid nil packet") + errTooManyPDiff = errors.New("too many PDiff") + errTooManySpatialLayers = errors.New("too many spatial layers") + errUnhandledNALUType = errors.New("NALU Type is unhandled") +) diff --git a/vendor/github.com/pion/rtp/codecs/g711_packet.go b/vendor/github.com/pion/rtp/codecs/g711_packet.go new file mode 100644 index 0000000..a74876f --- /dev/null +++ b/vendor/github.com/pion/rtp/codecs/g711_packet.go @@ -0,0 +1,22 @@ +package codecs + +// G711Payloader payloads G711 packets +type G711Payloader struct{} + +// Payload fragments an G711 packet across one or more byte arrays +func (p *G711Payloader) Payload(mtu int, payload []byte) [][]byte { + var out [][]byte + if payload == nil || mtu <= 0 { + return out + } + + for len(payload) > mtu { + o := make([]byte, mtu) + copy(o, payload[:mtu]) + payload = payload[mtu:] + out = append(out, o) + } + o := make([]byte, len(payload)) + copy(o, payload) + return append(out, o) +} diff --git a/vendor/github.com/pion/rtp/codecs/g722_packet.go b/vendor/github.com/pion/rtp/codecs/g722_packet.go new file mode 100644 index 0000000..70c9883 --- /dev/null +++ b/vendor/github.com/pion/rtp/codecs/g722_packet.go @@ -0,0 +1,22 @@ +package codecs + +// G722Payloader payloads G722 packets +type G722Payloader struct{} + +// Payload fragments an G722 packet across one or more byte arrays +func (p *G722Payloader) Payload(mtu int, payload []byte) [][]byte { + var out [][]byte + if payload == nil || mtu <= 0 { + return out + } + + for len(payload) > mtu { + o := make([]byte, mtu) + copy(o, payload[:mtu]) + payload = payload[mtu:] + out = append(out, o) + } + o := make([]byte, len(payload)) + copy(o, payload) + return append(out, o) +} diff --git a/vendor/github.com/pion/rtp/codecs/h264_packet.go b/vendor/github.com/pion/rtp/codecs/h264_packet.go new file mode 100644 index 0000000..3ee5926 --- /dev/null +++ b/vendor/github.com/pion/rtp/codecs/h264_packet.go @@ -0,0 +1,205 @@ +package codecs + +import ( + "encoding/binary" + "fmt" +) + +// H264Payloader payloads H264 packets +type H264Payloader struct{} + +const ( + stapaNALUType = 24 + fuaNALUType = 28 + + fuaHeaderSize = 2 + stapaHeaderSize = 1 + stapaNALULengthSize = 2 + + naluTypeBitmask = 0x1F + naluRefIdcBitmask = 0x60 + fuaStartBitmask = 0x80 +) + +func annexbNALUStartCode() []byte { return []byte{0x00, 0x00, 0x00, 0x01} } + +func emitNalus(nals []byte, emit func([]byte)) { + nextInd := func(nalu []byte, start int) (indStart int, indLen int) { + zeroCount := 0 + + for i, b := range nalu[start:] { + if b == 0 { + zeroCount++ + continue + } else if b == 1 { + if zeroCount >= 2 { + return start + i - zeroCount, zeroCount + 1 + } + } + zeroCount = 0 + } + return -1, -1 + } + + nextIndStart, nextIndLen := nextInd(nals, 0) + if nextIndStart == -1 { + emit(nals) + } else { + for nextIndStart != -1 { + prevStart := nextIndStart + nextIndLen + nextIndStart, nextIndLen = nextInd(nals, prevStart) + if nextIndStart != -1 { + emit(nals[prevStart:nextIndStart]) + } else { + // Emit until end of stream, no end indicator found + emit(nals[prevStart:]) + } + } + } +} + +// Payload fragments a H264 packet across one or more byte arrays +func (p *H264Payloader) Payload(mtu int, payload []byte) [][]byte { + var payloads [][]byte + if len(payload) == 0 { + return payloads + } + + emitNalus(payload, func(nalu []byte) { + if len(nalu) == 0 { + return + } + + naluType := nalu[0] & naluTypeBitmask + naluRefIdc := nalu[0] & naluRefIdcBitmask + + if naluType == 9 || naluType == 12 { + return + } + + // Single NALU + if len(nalu) <= mtu { + out := make([]byte, len(nalu)) + copy(out, nalu) + payloads = append(payloads, out) + return + } + + // FU-A + maxFragmentSize := mtu - fuaHeaderSize + + // The FU payload consists of fragments of the payload of the fragmented + // NAL unit so that if the fragmentation unit payloads of consecutive + // FUs are sequentially concatenated, the payload of the fragmented NAL + // unit can be reconstructed. The NAL unit type octet of the fragmented + // NAL unit is not included as such in the fragmentation unit payload, + // but rather the information of the NAL unit type octet of the + // fragmented NAL unit is conveyed in the F and NRI fields of the FU + // indicator octet of the fragmentation unit and in the type field of + // the FU header. An FU payload MAY have any number of octets and MAY + // be empty. + + naluData := nalu + // According to the RFC, the first octet is skipped due to redundant information + naluDataIndex := 1 + naluDataLength := len(nalu) - naluDataIndex + naluDataRemaining := naluDataLength + + if min(maxFragmentSize, naluDataRemaining) <= 0 { + return + } + + for naluDataRemaining > 0 { + currentFragmentSize := min(maxFragmentSize, naluDataRemaining) + out := make([]byte, fuaHeaderSize+currentFragmentSize) + + // +---------------+ + // |0|1|2|3|4|5|6|7| + // +-+-+-+-+-+-+-+-+ + // |F|NRI| Type | + // +---------------+ + out[0] = fuaNALUType + out[0] |= naluRefIdc + + // +---------------+ + // |0|1|2|3|4|5|6|7| + // +-+-+-+-+-+-+-+-+ + // |S|E|R| Type | + // +---------------+ + + out[1] = naluType + if naluDataRemaining == naluDataLength { + // Set start bit + out[1] |= 1 << 7 + } else if naluDataRemaining-currentFragmentSize == 0 { + // Set end bit + out[1] |= 1 << 6 + } + + copy(out[fuaHeaderSize:], naluData[naluDataIndex:naluDataIndex+currentFragmentSize]) + payloads = append(payloads, out) + + naluDataRemaining -= currentFragmentSize + naluDataIndex += currentFragmentSize + } + }) + + return payloads +} + +// H264Packet represents the H264 header that is stored in the payload of an RTP Packet +type H264Packet struct { +} + +// Unmarshal parses the passed byte slice and stores the result in the H264Packet this method is called upon +func (p *H264Packet) Unmarshal(payload []byte) ([]byte, error) { + if payload == nil { + return nil, errNilPacket + } else if len(payload) <= 2 { + return nil, fmt.Errorf("%w: %d <= 2", errShortPacket, len(payload)) + } + + // NALU Types + // https://tools.ietf.org/html/rfc6184#section-5.4 + naluType := payload[0] & naluTypeBitmask + switch { + case naluType > 0 && naluType < 24: + return append(annexbNALUStartCode(), payload...), nil + + case naluType == stapaNALUType: + currOffset := int(stapaHeaderSize) + result := []byte{} + for currOffset < len(payload) { + naluSize := int(binary.BigEndian.Uint16(payload[currOffset:])) + currOffset += stapaNALULengthSize + + if len(payload) < currOffset+naluSize { + return nil, fmt.Errorf("%w STAP-A declared size(%d) is larger than buffer(%d)", errShortPacket, naluSize, len(payload)-currOffset) + } + + result = append(result, annexbNALUStartCode()...) + result = append(result, payload[currOffset:currOffset+naluSize]...) + currOffset += naluSize + } + return result, nil + + case naluType == fuaNALUType: + if len(payload) < fuaHeaderSize { + return nil, errShortPacket + } + + if payload[1]&fuaStartBitmask != 0 { + naluRefIdc := payload[0] & naluRefIdcBitmask + fragmentedNaluType := payload[1] & naluTypeBitmask + + // Take a copy of payload since we are mutating it. + payloadCopy := append([]byte{}, payload...) + payloadCopy[fuaHeaderSize-1] = naluRefIdc | fragmentedNaluType + return append(annexbNALUStartCode(), payloadCopy[fuaHeaderSize-1:]...), nil + } + + return payload[fuaHeaderSize:], nil + } + + return nil, fmt.Errorf("%w: %d", errUnhandledNALUType, naluType) +} diff --git a/vendor/github.com/pion/rtp/codecs/opus_packet.go b/vendor/github.com/pion/rtp/codecs/opus_packet.go new file mode 100644 index 0000000..504741f --- /dev/null +++ b/vendor/github.com/pion/rtp/codecs/opus_packet.go @@ -0,0 +1,44 @@ +package codecs + +// OpusPayloader payloads Opus packets +type OpusPayloader struct{} + +// Payload fragments an Opus packet across one or more byte arrays +func (p *OpusPayloader) Payload(mtu int, payload []byte) [][]byte { + if payload == nil { + return [][]byte{} + } + + out := make([]byte, len(payload)) + copy(out, payload) + return [][]byte{out} +} + +// OpusPacket represents the Opus header that is stored in the payload of an RTP Packet +type OpusPacket struct { + Payload []byte +} + +// Unmarshal parses the passed byte slice and stores the result in the OpusPacket this method is called upon +func (p *OpusPacket) Unmarshal(packet []byte) ([]byte, error) { + if packet == nil { + return nil, errNilPacket + } else if len(packet) == 0 { + return nil, errShortPacket + } + + p.Payload = packet + return packet, nil +} + +// OpusPartitionHeadChecker checks Opus partition head +type OpusPartitionHeadChecker struct{} + +// IsPartitionHead checks whether if this is a head of the Opus partition +func (*OpusPartitionHeadChecker) IsPartitionHead(packet []byte) bool { + p := &OpusPacket{} + if _, err := p.Unmarshal(packet); err != nil { + return false + } + return true +} diff --git a/vendor/github.com/pion/rtp/codecs/vp8_packet.go b/vendor/github.com/pion/rtp/codecs/vp8_packet.go new file mode 100644 index 0000000..7ade7da --- /dev/null +++ b/vendor/github.com/pion/rtp/codecs/vp8_packet.go @@ -0,0 +1,143 @@ +package codecs + +// VP8Payloader payloads VP8 packets +type VP8Payloader struct{} + +const ( + vp8HeaderSize = 1 +) + +// Payload fragments a VP8 packet across one or more byte arrays +func (p *VP8Payloader) Payload(mtu int, payload []byte) [][]byte { + /* + * https://tools.ietf.org/html/rfc7741#section-4.2 + * + * 0 1 2 3 4 5 6 7 + * +-+-+-+-+-+-+-+-+ + * |X|R|N|S|R| PID | (REQUIRED) + * +-+-+-+-+-+-+-+-+ + * X: |I|L|T|K| RSV | (OPTIONAL) + * +-+-+-+-+-+-+-+-+ + * I: |M| PictureID | (OPTIONAL) + * +-+-+-+-+-+-+-+-+ + * L: | TL0PICIDX | (OPTIONAL) + * +-+-+-+-+-+-+-+-+ + * T/K: |TID|Y| KEYIDX | (OPTIONAL) + * +-+-+-+-+-+-+-+-+ + * S: Start of VP8 partition. SHOULD be set to 1 when the first payload + * octet of the RTP packet is the beginning of a new VP8 partition, + * and MUST NOT be 1 otherwise. The S bit MUST be set to 1 for the + * first packet of each encoded frame. + */ + + maxFragmentSize := mtu - vp8HeaderSize + + payloadData := payload + payloadDataRemaining := len(payload) + + payloadDataIndex := 0 + var payloads [][]byte + + // Make sure the fragment/payload size is correct + if min(maxFragmentSize, payloadDataRemaining) <= 0 { + return payloads + } + for payloadDataRemaining > 0 { + currentFragmentSize := min(maxFragmentSize, payloadDataRemaining) + out := make([]byte, vp8HeaderSize+currentFragmentSize) + if payloadDataRemaining == len(payload) { + out[0] = 0x10 + } + + copy(out[vp8HeaderSize:], payloadData[payloadDataIndex:payloadDataIndex+currentFragmentSize]) + payloads = append(payloads, out) + + payloadDataRemaining -= currentFragmentSize + payloadDataIndex += currentFragmentSize + } + + return payloads +} + +// VP8Packet represents the VP8 header that is stored in the payload of an RTP Packet +type VP8Packet struct { + // Required Header + X uint8 /* extended controlbits present */ + N uint8 /* (non-reference frame) when set to 1 this frame can be discarded */ + S uint8 /* start of VP8 partition */ + PID uint8 /* partition index */ + + // Optional Header + I uint8 /* 1 if PictureID is present */ + L uint8 /* 1 if TL0PICIDX is present */ + T uint8 /* 1 if TID is present */ + K uint8 /* 1 if KEYIDX is present */ + PictureID uint16 /* 8 or 16 bits, picture ID */ + TL0PICIDX uint8 /* 8 bits temporal level zero index */ + + Payload []byte +} + +// Unmarshal parses the passed byte slice and stores the result in the VP8Packet this method is called upon +func (p *VP8Packet) Unmarshal(payload []byte) ([]byte, error) { + if payload == nil { + return nil, errNilPacket + } + + payloadLen := len(payload) + + if payloadLen < 4 { + return nil, errShortPacket + } + + payloadIndex := 0 + + p.X = (payload[payloadIndex] & 0x80) >> 7 + p.N = (payload[payloadIndex] & 0x20) >> 5 + p.S = (payload[payloadIndex] & 0x10) >> 4 + p.PID = payload[payloadIndex] & 0x07 + + payloadIndex++ + + if p.X == 1 { + p.I = (payload[payloadIndex] & 0x80) >> 7 + p.L = (payload[payloadIndex] & 0x40) >> 6 + p.T = (payload[payloadIndex] & 0x20) >> 5 + p.K = (payload[payloadIndex] & 0x10) >> 4 + payloadIndex++ + } + + if p.I == 1 { // PID present? + if payload[payloadIndex]&0x80 > 0 { // M == 1, PID is 16bit + payloadIndex += 2 + } else { + payloadIndex++ + } + } + + if p.L == 1 { + payloadIndex++ + } + + if p.T == 1 || p.K == 1 { + payloadIndex++ + } + + if payloadIndex >= payloadLen { + return nil, errShortPacket + } + p.Payload = payload[payloadIndex:] + return p.Payload, nil +} + +// VP8PartitionHeadChecker checks VP8 partition head +type VP8PartitionHeadChecker struct{} + +// IsPartitionHead checks whether if this is a head of the VP8 partition +func (*VP8PartitionHeadChecker) IsPartitionHead(packet []byte) bool { + p := &VP8Packet{} + if _, err := p.Unmarshal(packet); err != nil { + return false + } + return p.S == 1 +} diff --git a/vendor/github.com/pion/rtp/codecs/vp9_packet.go b/vendor/github.com/pion/rtp/codecs/vp9_packet.go new file mode 100644 index 0000000..5cb619b --- /dev/null +++ b/vendor/github.com/pion/rtp/codecs/vp9_packet.go @@ -0,0 +1,385 @@ +package codecs + +import ( + "github.com/pion/randutil" +) + +// Use global random generator to properly seed by crypto grade random. +var globalMathRandomGenerator = randutil.NewMathRandomGenerator() // nolint:gochecknoglobals + +// VP9Payloader payloads VP9 packets +type VP9Payloader struct { + pictureID uint16 + initialized bool + + // InitialPictureIDFn is a function that returns random initial picture ID. + InitialPictureIDFn func() uint16 +} + +const ( + vp9HeaderSize = 3 // Flexible mode 15 bit picture ID + maxSpatialLayers = 5 + maxVP9RefPics = 3 +) + +// Payload fragments an VP9 packet across one or more byte arrays +func (p *VP9Payloader) Payload(mtu int, payload []byte) [][]byte { + /* + * https://www.ietf.org/id/draft-ietf-payload-vp9-10.txt + * + * Flexible mode (F=1) + * 0 1 2 3 4 5 6 7 + * +-+-+-+-+-+-+-+-+ + * |I|P|L|F|B|E|V|-| (REQUIRED) + * +-+-+-+-+-+-+-+-+ + * I: |M| PICTURE ID | (REQUIRED) + * +-+-+-+-+-+-+-+-+ + * M: | EXTENDED PID | (RECOMMENDED) + * +-+-+-+-+-+-+-+-+ + * L: | TID |U| SID |D| (CONDITIONALLY RECOMMENDED) + * +-+-+-+-+-+-+-+-+ -\ + * P,F: | P_DIFF |N| (CONDITIONALLY REQUIRED) - up to 3 times + * +-+-+-+-+-+-+-+-+ -/ + * V: | SS | + * | .. | + * +-+-+-+-+-+-+-+-+ + * + * Non-flexible mode (F=0) + * 0 1 2 3 4 5 6 7 + * +-+-+-+-+-+-+-+-+ + * |I|P|L|F|B|E|V|-| (REQUIRED) + * +-+-+-+-+-+-+-+-+ + * I: |M| PICTURE ID | (RECOMMENDED) + * +-+-+-+-+-+-+-+-+ + * M: | EXTENDED PID | (RECOMMENDED) + * +-+-+-+-+-+-+-+-+ + * L: | TID |U| SID |D| (CONDITIONALLY RECOMMENDED) + * +-+-+-+-+-+-+-+-+ + * | TL0PICIDX | (CONDITIONALLY REQUIRED) + * +-+-+-+-+-+-+-+-+ + * V: | SS | + * | .. | + * +-+-+-+-+-+-+-+-+ + */ + + if !p.initialized { + if p.InitialPictureIDFn == nil { + p.InitialPictureIDFn = func() uint16 { + return uint16(globalMathRandomGenerator.Intn(0x7FFF)) + } + } + p.pictureID = p.InitialPictureIDFn() & 0x7FFF + p.initialized = true + } + if payload == nil { + return [][]byte{} + } + + maxFragmentSize := mtu - vp9HeaderSize + payloadDataRemaining := len(payload) + payloadDataIndex := 0 + + if min(maxFragmentSize, payloadDataRemaining) <= 0 { + return [][]byte{} + } + + var payloads [][]byte + for payloadDataRemaining > 0 { + currentFragmentSize := min(maxFragmentSize, payloadDataRemaining) + out := make([]byte, vp9HeaderSize+currentFragmentSize) + + out[0] = 0x90 // F=1 I=1 + if payloadDataIndex == 0 { + out[0] |= 0x08 // B=1 + } + if payloadDataRemaining == currentFragmentSize { + out[0] |= 0x04 // E=1 + } + out[1] = byte(p.pictureID>>8) | 0x80 + out[2] = byte(p.pictureID) + copy(out[vp9HeaderSize:], payload[payloadDataIndex:payloadDataIndex+currentFragmentSize]) + payloads = append(payloads, out) + + payloadDataRemaining -= currentFragmentSize + payloadDataIndex += currentFragmentSize + } + p.pictureID++ + if p.pictureID >= 0x8000 { + p.pictureID = 0 + } + + return payloads +} + +// VP9Packet represents the VP9 header that is stored in the payload of an RTP Packet +type VP9Packet struct { + // Required header + I bool // PictureID is present + P bool // Inter-picture predicted frame + L bool // Layer indices is present + F bool // Flexible mode + B bool // Start of a frame + E bool // End of a frame + V bool // Scalability structure (SS) data present + + // Recommended headers + PictureID uint16 // 7 or 16 bits, picture ID + + // Conditionally recommended headers + TID uint8 // Temporal layer ID + U bool // Switching up point + SID uint8 // Spatial layer ID + D bool // Inter-layer dependency used + + // Conditionally required headers + PDiff []uint8 // Reference index (F=1) + TL0PICIDX uint8 // Temporal layer zero index (F=0) + + // Scalability structure headers + NS uint8 // N_S + 1 indicates the number of spatial layers present in the VP9 stream + Y bool // Each spatial layer's frame resolution present + G bool // PG description present flag. + NG uint8 // N_G indicates the number of pictures in a Picture Group (PG) + Width []uint16 + Height []uint16 + PGTID []uint8 // Temporal layer ID of pictures in a Picture Group + PGU []bool // Switching up point of pictures in a Picture Group + PGPDiff [][]uint8 // Reference indecies of pictures in a Picture Group + + Payload []byte +} + +// Unmarshal parses the passed byte slice and stores the result in the VP9Packet this method is called upon +func (p *VP9Packet) Unmarshal(packet []byte) ([]byte, error) { + if packet == nil { + return nil, errNilPacket + } + if len(packet) < 1 { + return nil, errShortPacket + } + + p.I = packet[0]&0x80 != 0 + p.P = packet[0]&0x40 != 0 + p.L = packet[0]&0x20 != 0 + p.F = packet[0]&0x10 != 0 + p.B = packet[0]&0x08 != 0 + p.E = packet[0]&0x04 != 0 + p.V = packet[0]&0x02 != 0 + + pos := 1 + var err error + + if p.I { + pos, err = p.parsePictureID(packet, pos) + if err != nil { + return nil, err + } + } + + if p.L { + pos, err = p.parseLayerInfo(packet, pos) + if err != nil { + return nil, err + } + } + + if p.F && p.P { + pos, err = p.parseRefIndices(packet, pos) + if err != nil { + return nil, err + } + } + + if p.V { + pos, err = p.parseSSData(packet, pos) + if err != nil { + return nil, err + } + } + + p.Payload = packet[pos:] + return p.Payload, nil +} + +// Picture ID: +// +// +-+-+-+-+-+-+-+-+ +// I: |M| PICTURE ID | M:0 => picture id is 7 bits. +// +-+-+-+-+-+-+-+-+ M:1 => picture id is 15 bits. +// M: | EXTENDED PID | +// +-+-+-+-+-+-+-+-+ +// +func (p *VP9Packet) parsePictureID(packet []byte, pos int) (int, error) { + if len(packet) <= pos { + return pos, errShortPacket + } + + p.PictureID = uint16(packet[pos] & 0x7F) + if packet[pos]&0x80 != 0 { + pos++ + if len(packet) <= pos { + return pos, errShortPacket + } + p.PictureID = p.PictureID<<8 | uint16(packet[pos]) + } + pos++ + return pos, nil +} + +func (p *VP9Packet) parseLayerInfo(packet []byte, pos int) (int, error) { + pos, err := p.parseLayerInfoCommon(packet, pos) + if err != nil { + return pos, err + } + + if p.F { + return pos, nil + } + + return p.parseLayerInfoNonFlexibleMode(packet, pos) +} + +// Layer indices (flexible mode): +// +// +-+-+-+-+-+-+-+-+ +// L: | T |U| S |D| +// +-+-+-+-+-+-+-+-+ +// +func (p *VP9Packet) parseLayerInfoCommon(packet []byte, pos int) (int, error) { + if len(packet) <= pos { + return pos, errShortPacket + } + + p.TID = packet[pos] >> 5 + p.U = packet[pos]&0x10 != 0 + p.SID = (packet[pos] >> 1) & 0x7 + p.D = packet[pos]&0x01 != 0 + + if p.SID >= maxSpatialLayers { + return pos, errTooManySpatialLayers + } + + pos++ + return pos, nil +} + +// Layer indices (non-flexible mode): +// +// +-+-+-+-+-+-+-+-+ +// L: | T |U| S |D| +// +-+-+-+-+-+-+-+-+ +// | TL0PICIDX | +// +-+-+-+-+-+-+-+-+ +// +func (p *VP9Packet) parseLayerInfoNonFlexibleMode(packet []byte, pos int) (int, error) { + if len(packet) <= pos { + return pos, errShortPacket + } + + p.TL0PICIDX = packet[pos] + pos++ + return pos, nil +} + +// Reference indices: +// +// +-+-+-+-+-+-+-+-+ P=1,F=1: At least one reference index +// P,F: | P_DIFF |N| up to 3 times has to be specified. +// +-+-+-+-+-+-+-+-+ N=1: An additional P_DIFF follows +// current P_DIFF. +// +func (p *VP9Packet) parseRefIndices(packet []byte, pos int) (int, error) { + for { + if len(packet) <= pos { + return pos, errShortPacket + } + p.PDiff = append(p.PDiff, packet[pos]>>1) + if packet[pos]&0x01 == 0 { + break + } + if len(p.PDiff) >= maxVP9RefPics { + return pos, errTooManyPDiff + } + pos++ + } + pos++ + + return pos, nil +} + +// Scalability structure (SS): +// +// +-+-+-+-+-+-+-+-+ +// V: | N_S |Y|G|-|-|-| +// +-+-+-+-+-+-+-+-+ -| +// Y: | WIDTH | (OPTIONAL) . +// + + . +// | | (OPTIONAL) . +// +-+-+-+-+-+-+-+-+ . N_S + 1 times +// | HEIGHT | (OPTIONAL) . +// + + . +// | | (OPTIONAL) . +// +-+-+-+-+-+-+-+-+ -| +// G: | N_G | (OPTIONAL) +// +-+-+-+-+-+-+-+-+ -| +// N_G: | T |U| R |-|-| (OPTIONAL) . +// +-+-+-+-+-+-+-+-+ -| . N_G times +// | P_DIFF | (OPTIONAL) . R times . +// +-+-+-+-+-+-+-+-+ -| -| +// +func (p *VP9Packet) parseSSData(packet []byte, pos int) (int, error) { + if len(packet) <= pos { + return pos, errShortPacket + } + + p.NS = packet[pos] >> 5 + p.Y = packet[pos]&0x10 != 0 + p.G = (packet[pos]>>1)&0x7 != 0 + pos++ + + NS := p.NS + 1 + p.NG = 0 + + if p.Y { + p.Width = make([]uint16, NS) + p.Height = make([]uint16, NS) + for i := 0; i < int(NS); i++ { + p.Width[i] = uint16(packet[pos])<<8 | uint16(packet[pos+1]) + pos += 2 + p.Height[i] = uint16(packet[pos])<<8 | uint16(packet[pos+1]) + pos += 2 + } + } + + if p.G { + p.NG = packet[pos] + pos++ + } + + for i := 0; i < int(p.NG); i++ { + p.PGTID = append(p.PGTID, packet[pos]>>5) + p.PGU = append(p.PGU, packet[pos]&0x10 != 0) + R := (packet[pos] >> 2) & 0x3 + pos++ + + p.PGPDiff = append(p.PGPDiff, []uint8{}) + for j := 0; j < int(R); j++ { + p.PGPDiff[i] = append(p.PGPDiff[i], packet[pos]) + pos++ + } + } + + return pos, nil +} + +// VP9PartitionHeadChecker checks VP9 partition head +type VP9PartitionHeadChecker struct{} + +// IsPartitionHead checks whether if this is a head of the VP9 partition +func (*VP9PartitionHeadChecker) IsPartitionHead(packet []byte) bool { + p := &VP9Packet{} + if _, err := p.Unmarshal(packet); err != nil { + return false + } + return p.B +} diff --git a/vendor/github.com/pion/rtp/depacketizer.go b/vendor/github.com/pion/rtp/depacketizer.go new file mode 100644 index 0000000..b8c09da --- /dev/null +++ b/vendor/github.com/pion/rtp/depacketizer.go @@ -0,0 +1,6 @@ +package rtp + +// Depacketizer depacketizes a RTP payload, removing any RTP specific data from the payload +type Depacketizer interface { + Unmarshal(packet []byte) ([]byte, error) +} diff --git a/vendor/github.com/pion/rtp/error.go b/vendor/github.com/pion/rtp/error.go new file mode 100644 index 0000000..5458c6f --- /dev/null +++ b/vendor/github.com/pion/rtp/error.go @@ -0,0 +1,21 @@ +package rtp + +import ( + "errors" +) + +var ( + errHeaderSizeInsufficient = errors.New("RTP header size insufficient") + errHeaderSizeInsufficientForExtension = errors.New("RTP header size insufficient for extension") + errTooSmall = errors.New("buffer too small") + errHeaderExtensionsNotEnabled = errors.New("h.Extension not enabled") + errHeaderExtensionNotFound = errors.New("extension not found") + + errRFC8285OneByteHeaderIDRange = errors.New("header extension id must be between 1 and 14 for RFC 5285 one byte extensions") + errRFC8285OneByteHeaderSize = errors.New("header extension payload must be 16bytes or less for RFC 5285 one byte extensions") + + errRFC8285TwoByteHeaderIDRange = errors.New("header extension id must be between 1 and 255 for RFC 5285 two byte extensions") + errRFC8285TwoByteHeaderSize = errors.New("header extension payload must be 255bytes or less for RFC 5285 two byte extensions") + + errRFC3550HeaderIDRange = errors.New("header extension id must be 0 for non-RFC 5285 extensions") +) diff --git a/vendor/github.com/pion/rtp/go.mod b/vendor/github.com/pion/rtp/go.mod new file mode 100644 index 0000000..412ae63 --- /dev/null +++ b/vendor/github.com/pion/rtp/go.mod @@ -0,0 +1,5 @@ +module github.com/pion/rtp + +go 1.13 + +require github.com/pion/randutil v0.1.0 diff --git a/vendor/github.com/pion/rtp/go.sum b/vendor/github.com/pion/rtp/go.sum new file mode 100644 index 0000000..401b903 --- /dev/null +++ b/vendor/github.com/pion/rtp/go.sum @@ -0,0 +1,2 @@ +github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA= +github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8= diff --git a/vendor/github.com/pion/rtp/packet.go b/vendor/github.com/pion/rtp/packet.go new file mode 100644 index 0000000..b237b0a --- /dev/null +++ b/vendor/github.com/pion/rtp/packet.go @@ -0,0 +1,490 @@ +package rtp + +import ( + "encoding/binary" + "fmt" + "io" +) + +// Extension RTP Header extension +type Extension struct { + id uint8 + payload []byte +} + +// Header represents an RTP packet header +// NOTE: PayloadOffset is populated by Marshal/Unmarshal and should not be modified +type Header struct { + Version uint8 + Padding bool + Extension bool + Marker bool + PayloadOffset int + PayloadType uint8 + SequenceNumber uint16 + Timestamp uint32 + SSRC uint32 + CSRC []uint32 + ExtensionProfile uint16 + Extensions []Extension +} + +// Packet represents an RTP Packet +// NOTE: Raw is populated by Marshal/Unmarshal and should not be modified +type Packet struct { + Header + Raw []byte + Payload []byte +} + +const ( + headerLength = 4 + versionShift = 6 + versionMask = 0x3 + paddingShift = 5 + paddingMask = 0x1 + extensionShift = 4 + extensionMask = 0x1 + extensionProfileOneByte = 0xBEDE + extensionProfileTwoByte = 0x1000 + extensionIDReserved = 0xF + ccMask = 0xF + markerShift = 7 + markerMask = 0x1 + ptMask = 0x7F + seqNumOffset = 2 + seqNumLength = 2 + timestampOffset = 4 + timestampLength = 4 + ssrcOffset = 8 + ssrcLength = 4 + csrcOffset = 12 + csrcLength = 4 +) + +// String helps with debugging by printing packet information in a readable way +func (p Packet) String() string { + out := "RTP PACKET:\n" + + out += fmt.Sprintf("\tVersion: %v\n", p.Version) + out += fmt.Sprintf("\tMarker: %v\n", p.Marker) + out += fmt.Sprintf("\tPayload Type: %d\n", p.PayloadType) + out += fmt.Sprintf("\tSequence Number: %d\n", p.SequenceNumber) + out += fmt.Sprintf("\tTimestamp: %d\n", p.Timestamp) + out += fmt.Sprintf("\tSSRC: %d (%x)\n", p.SSRC, p.SSRC) + out += fmt.Sprintf("\tPayload Length: %d\n", len(p.Payload)) + + return out +} + +// Unmarshal parses the passed byte slice and stores the result in the Header this method is called upon +func (h *Header) Unmarshal(rawPacket []byte) error { //nolint:gocognit + if len(rawPacket) < headerLength { + return fmt.Errorf("%w: %d < %d", errHeaderSizeInsufficient, len(rawPacket), headerLength) + } + + /* + * 0 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * |V=2|P|X| CC |M| PT | sequence number | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | timestamp | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | synchronization source (SSRC) identifier | + * +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ + * | contributing source (CSRC) identifiers | + * | .... | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ + + h.Version = rawPacket[0] >> versionShift & versionMask + h.Padding = (rawPacket[0] >> paddingShift & paddingMask) > 0 + h.Extension = (rawPacket[0] >> extensionShift & extensionMask) > 0 + nCSRC := int(rawPacket[0] & ccMask) + if cap(h.CSRC) < nCSRC || h.CSRC == nil { + h.CSRC = make([]uint32, nCSRC) + } else { + h.CSRC = h.CSRC[:nCSRC] + } + + currOffset := csrcOffset + (nCSRC * csrcLength) + if len(rawPacket) < currOffset { + return fmt.Errorf("size %d < %d: %w", len(rawPacket), currOffset, errHeaderSizeInsufficient) + } + + h.Marker = (rawPacket[1] >> markerShift & markerMask) > 0 + h.PayloadType = rawPacket[1] & ptMask + + h.SequenceNumber = binary.BigEndian.Uint16(rawPacket[seqNumOffset : seqNumOffset+seqNumLength]) + h.Timestamp = binary.BigEndian.Uint32(rawPacket[timestampOffset : timestampOffset+timestampLength]) + h.SSRC = binary.BigEndian.Uint32(rawPacket[ssrcOffset : ssrcOffset+ssrcLength]) + + for i := range h.CSRC { + offset := csrcOffset + (i * csrcLength) + h.CSRC[i] = binary.BigEndian.Uint32(rawPacket[offset:]) + } + + if h.Extensions != nil { + h.Extensions = h.Extensions[:0] + } + + if h.Extension { + if expected := currOffset + 4; len(rawPacket) < expected { + return fmt.Errorf("size %d < %d: %w", + len(rawPacket), expected, + errHeaderSizeInsufficientForExtension, + ) + } + + h.ExtensionProfile = binary.BigEndian.Uint16(rawPacket[currOffset:]) + currOffset += 2 + extensionLength := int(binary.BigEndian.Uint16(rawPacket[currOffset:])) * 4 + currOffset += 2 + + if expected := currOffset + extensionLength; len(rawPacket) < expected { + return fmt.Errorf("size %d < %d: %w", + len(rawPacket), expected, + errHeaderSizeInsufficientForExtension, + ) + } + + switch h.ExtensionProfile { + // RFC 8285 RTP One Byte Header Extension + case extensionProfileOneByte: + end := currOffset + extensionLength + for currOffset < end { + if rawPacket[currOffset] == 0x00 { // padding + currOffset++ + continue + } + + extid := rawPacket[currOffset] >> 4 + len := int(rawPacket[currOffset]&^0xF0 + 1) + currOffset++ + + if extid == extensionIDReserved { + break + } + + extension := Extension{id: extid, payload: rawPacket[currOffset : currOffset+len]} + h.Extensions = append(h.Extensions, extension) + currOffset += len + } + + // RFC 8285 RTP Two Byte Header Extension + case extensionProfileTwoByte: + end := currOffset + extensionLength + for currOffset < end { + if rawPacket[currOffset] == 0x00 { // padding + currOffset++ + continue + } + + extid := rawPacket[currOffset] + currOffset++ + + len := int(rawPacket[currOffset]) + currOffset++ + + extension := Extension{id: extid, payload: rawPacket[currOffset : currOffset+len]} + h.Extensions = append(h.Extensions, extension) + currOffset += len + } + + default: // RFC3550 Extension + if len(rawPacket) < currOffset+extensionLength { + return fmt.Errorf("%w: %d < %d", errHeaderSizeInsufficientForExtension, len(rawPacket), currOffset+extensionLength) + } + + extension := Extension{id: 0, payload: rawPacket[currOffset : currOffset+extensionLength]} + h.Extensions = append(h.Extensions, extension) + currOffset += len(h.Extensions[0].payload) + } + } + + h.PayloadOffset = currOffset + + return nil +} + +// Unmarshal parses the passed byte slice and stores the result in the Packet this method is called upon +func (p *Packet) Unmarshal(rawPacket []byte) error { + if err := p.Header.Unmarshal(rawPacket); err != nil { + return err + } + + p.Payload = rawPacket[p.PayloadOffset:] + p.Raw = rawPacket + return nil +} + +// Marshal serializes the header into bytes. +func (h *Header) Marshal() (buf []byte, err error) { + buf = make([]byte, h.MarshalSize()) + + n, err := h.MarshalTo(buf) + if err != nil { + return nil, err + } + + return buf[:n], nil +} + +// MarshalTo serializes the header and writes to the buffer. +func (h *Header) MarshalTo(buf []byte) (n int, err error) { + /* + * 0 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * |V=2|P|X| CC |M| PT | sequence number | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | timestamp | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | synchronization source (SSRC) identifier | + * +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ + * | contributing source (CSRC) identifiers | + * | .... | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ + + size := h.MarshalSize() + if size > len(buf) { + return 0, io.ErrShortBuffer + } + + // The first byte contains the version, padding bit, extension bit, and csrc size + buf[0] = (h.Version << versionShift) | uint8(len(h.CSRC)) + if h.Padding { + buf[0] |= 1 << paddingShift + } + + if h.Extension { + buf[0] |= 1 << extensionShift + } + + // The second byte contains the marker bit and payload type. + buf[1] = h.PayloadType + if h.Marker { + buf[1] |= 1 << markerShift + } + + binary.BigEndian.PutUint16(buf[2:4], h.SequenceNumber) + binary.BigEndian.PutUint32(buf[4:8], h.Timestamp) + binary.BigEndian.PutUint32(buf[8:12], h.SSRC) + + n = 12 + for _, csrc := range h.CSRC { + binary.BigEndian.PutUint32(buf[n:n+4], csrc) + n += 4 + } + + if h.Extension { + extHeaderPos := n + binary.BigEndian.PutUint16(buf[n+0:n+2], h.ExtensionProfile) + n += 4 + startExtensionsPos := n + + switch h.ExtensionProfile { + // RFC 8285 RTP One Byte Header Extension + case extensionProfileOneByte: + for _, extension := range h.Extensions { + buf[n] = extension.id<<4 | (uint8(len(extension.payload)) - 1) + n++ + n += copy(buf[n:], extension.payload) + } + // RFC 8285 RTP Two Byte Header Extension + case extensionProfileTwoByte: + for _, extension := range h.Extensions { + buf[n] = extension.id + n++ + buf[n] = uint8(len(extension.payload)) + n++ + n += copy(buf[n:], extension.payload) + } + default: // RFC3550 Extension + extlen := len(h.Extensions[0].payload) + if extlen%4 != 0 { + // the payload must be in 32-bit words. + return 0, io.ErrShortBuffer + } + n += copy(buf[n:], h.Extensions[0].payload) + } + + // calculate extensions size and round to 4 bytes boundaries + extSize := n - startExtensionsPos + roundedExtSize := ((extSize + 3) / 4) * 4 + + binary.BigEndian.PutUint16(buf[extHeaderPos+2:extHeaderPos+4], uint16(roundedExtSize/4)) + + // add padding to reach 4 bytes boundaries + for i := 0; i < roundedExtSize-extSize; i++ { + buf[n] = 0 + n++ + } + } + + h.PayloadOffset = n + + return n, nil +} + +// MarshalSize returns the size of the header once marshaled. +func (h *Header) MarshalSize() int { + // NOTE: Be careful to match the MarshalTo() method. + size := 12 + (len(h.CSRC) * csrcLength) + + if h.Extension { + extSize := 4 + + switch h.ExtensionProfile { + // RFC 8285 RTP One Byte Header Extension + case extensionProfileOneByte: + for _, extension := range h.Extensions { + extSize += 1 + len(extension.payload) + } + // RFC 8285 RTP Two Byte Header Extension + case extensionProfileTwoByte: + for _, extension := range h.Extensions { + extSize += 2 + len(extension.payload) + } + default: + extSize += len(h.Extensions[0].payload) + } + + // extensions size must have 4 bytes boundaries + size += ((extSize + 3) / 4) * 4 + } + + return size +} + +// SetExtension sets an RTP header extension +func (h *Header) SetExtension(id uint8, payload []byte) error { //nolint:gocognit + if h.Extension { + switch h.ExtensionProfile { + // RFC 8285 RTP One Byte Header Extension + case extensionProfileOneByte: + if id < 1 || id > 14 { + return fmt.Errorf("%w actual(%d)", errRFC8285OneByteHeaderIDRange, id) + } + if len(payload) > 16 { + return fmt.Errorf("%w actual(%d)", errRFC8285OneByteHeaderSize, len(payload)) + } + // RFC 8285 RTP Two Byte Header Extension + case extensionProfileTwoByte: + if id < 1 || id > 255 { + return fmt.Errorf("%w actual(%d)", errRFC8285TwoByteHeaderIDRange, id) + } + if len(payload) > 255 { + return fmt.Errorf("%w actual(%d)", errRFC8285TwoByteHeaderSize, len(payload)) + } + default: // RFC3550 Extension + if id != 0 { + return fmt.Errorf("%w actual(%d)", errRFC3550HeaderIDRange, id) + } + } + + // Update existing if it exists else add new extension + for i, extension := range h.Extensions { + if extension.id == id { + h.Extensions[i].payload = payload + return nil + } + } + h.Extensions = append(h.Extensions, Extension{id: id, payload: payload}) + return nil + } + + // No existing header extensions + h.Extension = true + + switch len := len(payload); { + case len <= 16: + h.ExtensionProfile = extensionProfileOneByte + case len > 16 && len < 256: + h.ExtensionProfile = extensionProfileTwoByte + } + + h.Extensions = append(h.Extensions, Extension{id: id, payload: payload}) + return nil +} + +// GetExtensionIDs returns an extension id array +func (h *Header) GetExtensionIDs() []uint8 { + if !h.Extension { + return nil + } + + if len(h.Extensions) == 0 { + return nil + } + + ids := make([]uint8, 0, len(h.Extensions)) + for _, extension := range h.Extensions { + ids = append(ids, extension.id) + } + return ids +} + +// GetExtension returns an RTP header extension +func (h *Header) GetExtension(id uint8) []byte { + if !h.Extension { + return nil + } + for _, extension := range h.Extensions { + if extension.id == id { + return extension.payload + } + } + return nil +} + +// DelExtension Removes an RTP Header extension +func (h *Header) DelExtension(id uint8) error { + if !h.Extension { + return errHeaderExtensionsNotEnabled + } + for i, extension := range h.Extensions { + if extension.id == id { + h.Extensions = append(h.Extensions[:i], h.Extensions[i+1:]...) + return nil + } + } + return errHeaderExtensionNotFound +} + +// Marshal serializes the packet into bytes. +func (p *Packet) Marshal() (buf []byte, err error) { + buf = make([]byte, p.MarshalSize()) + + n, err := p.MarshalTo(buf) + if err != nil { + return nil, err + } + + return buf[:n], nil +} + +// MarshalTo serializes the packet and writes to the buffer. +func (p *Packet) MarshalTo(buf []byte) (n int, err error) { + n, err = p.Header.MarshalTo(buf) + if err != nil { + return 0, err + } + + // Make sure the buffer is large enough to hold the packet. + if n+len(p.Payload) > len(buf) { + return 0, io.ErrShortBuffer + } + + m := copy(buf[n:], p.Payload) + p.Raw = buf[:n+m] + + return n + m, nil +} + +// MarshalSize returns the size of the packet once marshaled. +func (p *Packet) MarshalSize() int { + return p.Header.MarshalSize() + len(p.Payload) +} diff --git a/vendor/github.com/pion/rtp/packetizer.go b/vendor/github.com/pion/rtp/packetizer.go new file mode 100644 index 0000000..5a8482f --- /dev/null +++ b/vendor/github.com/pion/rtp/packetizer.go @@ -0,0 +1,91 @@ +package rtp + +import ( + "time" +) + +// Payloader payloads a byte array for use as rtp.Packet payloads +type Payloader interface { + Payload(mtu int, payload []byte) [][]byte +} + +// Packetizer packetizes a payload +type Packetizer interface { + Packetize(payload []byte, samples uint32) []*Packet + EnableAbsSendTime(value int) +} + +type packetizer struct { + MTU int + PayloadType uint8 + SSRC uint32 + Payloader Payloader + Sequencer Sequencer + Timestamp uint32 + ClockRate uint32 + extensionNumbers struct { // put extension numbers in here. If they're 0, the extension is disabled (0 is not a legal extension number) + AbsSendTime int // http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time + } + timegen func() time.Time +} + +// NewPacketizer returns a new instance of a Packetizer for a specific payloader +func NewPacketizer(mtu int, pt uint8, ssrc uint32, payloader Payloader, sequencer Sequencer, clockRate uint32) Packetizer { + return &packetizer{ + MTU: mtu, + PayloadType: pt, + SSRC: ssrc, + Payloader: payloader, + Sequencer: sequencer, + Timestamp: globalMathRandomGenerator.Uint32(), + ClockRate: clockRate, + timegen: time.Now, + } +} + +func (p *packetizer) EnableAbsSendTime(value int) { + p.extensionNumbers.AbsSendTime = value +} + +// Packetize packetizes the payload of an RTP packet and returns one or more RTP packets +func (p *packetizer) Packetize(payload []byte, samples uint32) []*Packet { + // Guard against an empty payload + if len(payload) == 0 { + return nil + } + + payloads := p.Payloader.Payload(p.MTU-12, payload) + packets := make([]*Packet, len(payloads)) + + for i, pp := range payloads { + packets[i] = &Packet{ + Header: Header{ + Version: 2, + Padding: false, + Extension: false, + Marker: i == len(payloads)-1, + PayloadType: p.PayloadType, + SequenceNumber: p.Sequencer.NextSequenceNumber(), + Timestamp: p.Timestamp, // Figure out how to do timestamps + SSRC: p.SSRC, + }, + Payload: pp, + } + } + p.Timestamp += samples + + if len(packets) != 0 && p.extensionNumbers.AbsSendTime != 0 { + sendTime := NewAbsSendTimeExtension(p.timegen()) + // apply http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time + b, err := sendTime.Marshal() + if err != nil { + return nil // never happens + } + err = packets[len(packets)-1].SetExtension(uint8(p.extensionNumbers.AbsSendTime), b) + if err != nil { + return nil // never happens + } + } + + return packets +} diff --git a/vendor/github.com/pion/rtp/partitionheadchecker.go b/vendor/github.com/pion/rtp/partitionheadchecker.go new file mode 100644 index 0000000..6ec2a76 --- /dev/null +++ b/vendor/github.com/pion/rtp/partitionheadchecker.go @@ -0,0 +1,6 @@ +package rtp + +// PartitionHeadChecker is the interface that checks whether the packet is keyframe or not +type PartitionHeadChecker interface { + IsPartitionHead([]byte) bool +} diff --git a/vendor/github.com/pion/rtp/rand.go b/vendor/github.com/pion/rtp/rand.go new file mode 100644 index 0000000..ee85523 --- /dev/null +++ b/vendor/github.com/pion/rtp/rand.go @@ -0,0 +1,8 @@ +package rtp + +import ( + "github.com/pion/randutil" +) + +// Use global random generator to properly seed by crypto grade random. +var globalMathRandomGenerator = randutil.NewMathRandomGenerator() // nolint:gochecknoglobals diff --git a/vendor/github.com/pion/rtp/renovate.json b/vendor/github.com/pion/rtp/renovate.json new file mode 100644 index 0000000..4400fd9 --- /dev/null +++ b/vendor/github.com/pion/rtp/renovate.json @@ -0,0 +1,15 @@ +{ + "extends": [ + "config:base" + ], + "postUpdateOptions": [ + "gomodTidy" + ], + "commitBody": "Generated by renovateBot", + "packageRules": [ + { + "packagePatterns": ["^golang.org/x/"], + "schedule": ["on the first day of the month"] + } + ] +} diff --git a/vendor/github.com/pion/rtp/rtp.go b/vendor/github.com/pion/rtp/rtp.go new file mode 100644 index 0000000..b66b2e4 --- /dev/null +++ b/vendor/github.com/pion/rtp/rtp.go @@ -0,0 +1,2 @@ +// Package rtp provides RTP packetizer and depacketizer +package rtp diff --git a/vendor/github.com/pion/rtp/sequencer.go b/vendor/github.com/pion/rtp/sequencer.go new file mode 100644 index 0000000..2b4a507 --- /dev/null +++ b/vendor/github.com/pion/rtp/sequencer.go @@ -0,0 +1,57 @@ +package rtp + +import ( + "math" + "sync" +) + +// Sequencer generates sequential sequence numbers for building RTP packets +type Sequencer interface { + NextSequenceNumber() uint16 + RollOverCount() uint64 +} + +// NewRandomSequencer returns a new sequencer starting from a random sequence +// number +func NewRandomSequencer() Sequencer { + return &sequencer{ + sequenceNumber: uint16(globalMathRandomGenerator.Intn(math.MaxUint16)), + } +} + +// NewFixedSequencer returns a new sequencer starting from a specific +// sequence number +func NewFixedSequencer(s uint16) Sequencer { + return &sequencer{ + sequenceNumber: s - 1, // -1 because the first sequence number prepends 1 + } +} + +type sequencer struct { + sequenceNumber uint16 + rollOverCount uint64 + mutex sync.Mutex +} + +// NextSequenceNumber increment and returns a new sequence number for +// building RTP packets +func (s *sequencer) NextSequenceNumber() uint16 { + s.mutex.Lock() + defer s.mutex.Unlock() + + s.sequenceNumber++ + if s.sequenceNumber == 0 { + s.rollOverCount++ + } + + return s.sequenceNumber +} + +// RollOverCount returns the amount of times the 16bit sequence number +// has wrapped +func (s *sequencer) RollOverCount() uint64 { + s.mutex.Lock() + defer s.mutex.Unlock() + + return s.rollOverCount +} diff --git a/vendor/github.com/pion/rtp/transportccextension.go b/vendor/github.com/pion/rtp/transportccextension.go new file mode 100644 index 0000000..f9ffe4e --- /dev/null +++ b/vendor/github.com/pion/rtp/transportccextension.go @@ -0,0 +1,39 @@ +package rtp + +import ( + "encoding/binary" +) + +const ( + // transport-wide sequence + transportCCExtensionSize = 2 +) + +// TransportCCExtension is a extension payload format in +// https://tools.ietf.org/html/draft-holmer-rmcat-transport-wide-cc-extensions-01 +// 0 1 2 3 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | 0xBE | 0xDE | length=1 | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | ID | L=1 |transport-wide sequence number | zero padding | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +type TransportCCExtension struct { + TransportSequence uint16 +} + +// Marshal serializes the members to buffer +func (t *TransportCCExtension) Marshal() ([]byte, error) { + buf := make([]byte, transportCCExtensionSize) + binary.BigEndian.PutUint16(buf[0:2], t.TransportSequence) + return buf, nil +} + +// Unmarshal parses the passed byte slice and stores the result in the members +func (t *TransportCCExtension) Unmarshal(rawData []byte) error { + if len(rawData) < transportCCExtensionSize { + return errTooSmall + } + t.TransportSequence = binary.BigEndian.Uint16(rawData[0:2]) + return nil +} diff --git a/vendor/github.com/pion/sctp/.gitignore b/vendor/github.com/pion/sctp/.gitignore new file mode 100644 index 0000000..d39fb86 --- /dev/null +++ b/vendor/github.com/pion/sctp/.gitignore @@ -0,0 +1 @@ +*.sw[poe] diff --git a/vendor/github.com/pion/sctp/.golangci.yml b/vendor/github.com/pion/sctp/.golangci.yml new file mode 100644 index 0000000..4213697 --- /dev/null +++ b/vendor/github.com/pion/sctp/.golangci.yml @@ -0,0 +1,82 @@ +linters-settings: + govet: + check-shadowing: true + misspell: + locale: US + exhaustive: + default-signifies-exhaustive: true + +linters: + enable: + - asciicheck # Simple linter to check that your code does not contain non-ASCII identifiers + - bodyclose # checks whether HTTP response body is closed successfully + - deadcode # Finds unused code + - depguard # Go linter that checks if package imports are in a list of acceptable packages + - dogsled # Checks assignments with too many blank identifiers (e.g. x, _, _, _, := f()) + - dupl # Tool for code clone detection + - errcheck # Errcheck is a program for checking for unchecked errors in go programs. These unchecked errors can be critical bugs in some cases + - exhaustive # check exhaustiveness of enum switch statements + - exportloopref # checks for pointers to enclosing loop variables + - gci # Gci control golang package import order and make it always deterministic. + - gochecknoglobals # Checks that no globals are present in Go code + - gochecknoinits # Checks that no init functions are present in Go code + - gocognit # Computes and checks the cognitive complexity of functions + - goconst # Finds repeated strings that could be replaced by a constant + - gocritic # The most opinionated Go source code linter + - godox # Tool for detection of FIXME, TODO and other comment keywords + - goerr113 # Golang linter to check the errors handling expressions + - gofmt # Gofmt checks whether code was gofmt-ed. By default this tool runs with -s option to check for code simplification + - gofumpt # Gofumpt checks whether code was gofumpt-ed. + - goheader # Checks is file header matches to pattern + - goimports # Goimports does everything that gofmt does. Additionally it checks unused imports + - golint # Golint differs from gofmt. Gofmt reformats Go source code, whereas golint prints out style mistakes + - gomodguard # Allow and block list linter for direct Go module dependencies. This is different from depguard where there are different block types for example version constraints and module recommendations. + - goprintffuncname # Checks that printf-like functions are named with `f` at the end + - gosec # Inspects source code for security problems + - gosimple # Linter for Go source code that specializes in simplifying a code + - govet # Vet examines Go source code and reports suspicious constructs, such as Printf calls whose arguments do not align with the format string + - ineffassign # Detects when assignments to existing variables are not used + - misspell # Finds commonly misspelled English words in comments + - nakedret # Finds naked returns in functions greater than a specified function length + - noctx # noctx finds sending http request without context.Context + - scopelint # Scopelint checks for unpinned variables in go programs + - staticcheck # Staticcheck is a go vet on steroids, applying a ton of static analysis checks + - structcheck # Finds unused struct fields + - stylecheck # Stylecheck is a replacement for golint + - typecheck # Like the front-end of a Go compiler, parses and type-checks Go code + - unconvert # Remove unnecessary type conversions + - unparam # Reports unused function parameters + - unused # Checks Go code for unused constants, variables, functions and types + - varcheck # Finds unused global variables and constants + - whitespace # Tool for detection of leading and trailing whitespace + disable: + - funlen # Tool for detection of long functions + - gocyclo # Computes and checks the cyclomatic complexity of functions + - godot # Check if comments end in a period + - gomnd # An analyzer to detect magic numbers. + - lll # Reports long lines + - maligned # Tool to detect Go structs that would take less memory if their fields were sorted + - nestif # Reports deeply nested if statements + - nlreturn # nlreturn checks for a new line before return and branch statements to increase code clarity + - nolintlint # Reports ill-formed or insufficient nolint directives + - prealloc # Finds slice declarations that could potentially be preallocated + - rowserrcheck # checks whether Err of rows is checked successfully + - sqlclosecheck # Checks that sql.Rows and sql.Stmt are closed. + - testpackage # linter that makes you use a separate _test package + - wsl # Whitespace Linter - Forces you to use empty lines! + +issues: + exclude-rules: + # Allow complex tests, better to be self contained + - path: _test\.go + linters: + - gocognit + + # Allow complex main function in examples + - path: examples + text: "of func `main` is high" + linters: + - gocognit + +run: + skip-dirs-use-default: false diff --git a/vendor/github.com/pion/sctp/DESIGN.md b/vendor/github.com/pion/sctp/DESIGN.md new file mode 100644 index 0000000..02ac161 --- /dev/null +++ b/vendor/github.com/pion/sctp/DESIGN.md @@ -0,0 +1,20 @@ +<h1 align="center"> + Design +</h1> + +### Portable +Pion SCTP is written in Go and extremely portable. Anywhere Golang runs, Pion SCTP should work as well! Instead of dealing with complicated +cross-compiling of multiple libraries, you now can run anywhere with one `go build` + +### Simple API +The API is based on an io.ReadWriteCloser. + +### Readable +If code comes from an RFC we try to make sure everything is commented with a link to the spec. +This makes learning and debugging easier, this library was written to also serve as a guide for others. + +### Tested +Every commit is tested via travis-ci Go provides fantastic facilities for testing, and more will be added as time goes on. + +### Shared libraries +Every pion product is built using shared libraries, allowing others to review and reuse our libraries. diff --git a/vendor/github.com/pion/sctp/LICENSE b/vendor/github.com/pion/sctp/LICENSE new file mode 100644 index 0000000..ab60297 --- /dev/null +++ b/vendor/github.com/pion/sctp/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/pion/sctp/README.md b/vendor/github.com/pion/sctp/README.md new file mode 100644 index 0000000..18b5693 --- /dev/null +++ b/vendor/github.com/pion/sctp/README.md @@ -0,0 +1,54 @@ +<h1 align="center"> + <br> + Pion SCTP + <br> +</h1> +<h4 align="center">A Go implementation of SCTP</h4> +<p align="center"> + <a href="https://pion.ly"><img src="https://img.shields.io/badge/pion-sctp-gray.svg?longCache=true&colorB=brightgreen" alt="Pion SCTP"></a> + <!--<a href="https://sourcegraph.com/github.com/pion/webrtc?badge"><img src="https://sourcegraph.com/github.com/pion/webrtc/-/badge.svg" alt="Sourcegraph Widget"></a>--> + <a href="https://pion.ly/slack"><img src="https://img.shields.io/badge/join-us%20on%20slack-gray.svg?longCache=true&logo=slack&colorB=brightgreen" alt="Slack Widget"></a> + <br> + <a href="https://travis-ci.org/pion/sctp"><img src="https://travis-ci.org/pion/sctp.svg?branch=master" alt="Build Status"></a> + <a href="https://pkg.go.dev/github.com/pion/sctp"><img src="https://godoc.org/github.com/pion/sctp?status.svg" alt="GoDoc"></a> + <a href="https://codecov.io/gh/pion/sctp"><img src="https://codecov.io/gh/pion/sctp/branch/master/graph/badge.svg" alt="Coverage Status"></a> + <a href="https://goreportcard.com/report/github.com/pion/sctp"><img src="https://goreportcard.com/badge/github.com/pion/sctp" alt="Go Report Card"></a> + <!--<a href="https://www.codacy.com/app/Sean-Der/webrtc"><img src="https://api.codacy.com/project/badge/Grade/18f4aec384894e6aac0b94effe51961d" alt="Codacy Badge"></a>--> + <a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="License: MIT"></a> +</p> +<br> + +See [DESIGN.md](DESIGN.md) for an overview of features and future goals. + +### Roadmap +The library is used as a part of our WebRTC implementation. Please refer to that [roadmap](https://github.com/pion/webrtc/issues/9) to track our major milestones. + +### Community +Pion has an active community on the [Golang Slack](https://invite.slack.golangbridge.org/). Sign up and join the **#pion** channel for discussions and support. You can also use [Pion mailing list](https://groups.google.com/forum/#!forum/pion). + +We are always looking to support **your projects**. Please reach out if you have something to build! + +If you need commercial support or don't want to use public methods you can contact us at [team@pion.ly](mailto:team@pion.ly) + +### Contributing +Check out the **[contributing wiki](https://github.com/pion/webrtc/wiki/Contributing)** to join the group of amazing people making this project possible: + +* [John Bradley](https://github.com/kc5nra) - *Original Author* +* [Sean DuBois](https://github.com/Sean-Der) - *Original Author* +* [Michiel De Backker](https://github.com/backkem) - *Public API, Initialization* +* [Konstantin Itskov](https://github.com/trivigy) - *Fix documentation* +* [chenkaiC4](https://github.com/chenkaiC4) - *Fix GolangCI Linter* +* [Ronan J](https://github.com/ronanj) - *Fix PPID* +* [Michael MacDonald](https://github.com/mjmac) - *Fix races* +* [Yutaka Takeda](https://github.com/enobufs) - *PR-SCTP, Retransmissions, Congestion Control* +* [Antoine Baché](https://github.com/Antonito) - *SCTP Profiling* +* [Cecylia Bocovich](https://github.com/cohosh) - *Fix SCTP reads* +* [Hugo Arregui](https://github.com/hugoArregui) +* [Atsushi Watanabe](https://github.com/at-wat) +* [Lukas Herman](https://github.com/lherman-cs) +* [Luke Curley](https://github.com/kixelated) - *Performance* +* [Aaron France](https://github.com/AeroNotix) +* [ZHENK](https://github.com/scorpionknifes) + +### License +MIT License - see [LICENSE](LICENSE) for full text diff --git a/vendor/github.com/pion/sctp/ack_timer.go b/vendor/github.com/pion/sctp/ack_timer.go new file mode 100644 index 0000000..ba23d54 --- /dev/null +++ b/vendor/github.com/pion/sctp/ack_timer.go @@ -0,0 +1,105 @@ +package sctp + +import ( + "sync" + "time" +) + +const ( + ackInterval time.Duration = 200 * time.Millisecond +) + +// ackTimerObserver is the inteface to an ack timer observer. +type ackTimerObserver interface { + onAckTimeout() +} + +// ackTimer provides the retnransmission timer conforms with RFC 4960 Sec 6.3.1 +type ackTimer struct { + observer ackTimerObserver + interval time.Duration + stopFunc stopAckTimerLoop + closed bool + mutex sync.RWMutex +} + +type stopAckTimerLoop func() + +// newAckTimer creates a new acknowledgement timer used to enable delayed ack. +func newAckTimer(observer ackTimerObserver) *ackTimer { + return &ackTimer{ + observer: observer, + interval: ackInterval, + } +} + +// start starts the timer. +func (t *ackTimer) start() bool { + t.mutex.Lock() + defer t.mutex.Unlock() + + // this timer is already closed + if t.closed { + return false + } + + // this is a noop if the timer is already running + if t.stopFunc != nil { + return false + } + + cancelCh := make(chan struct{}) + + go func() { + timer := time.NewTimer(t.interval) + + select { + case <-timer.C: + t.stop() + t.observer.onAckTimeout() + case <-cancelCh: + timer.Stop() + } + }() + + t.stopFunc = func() { + close(cancelCh) + } + + return true +} + +// stops the timer. this is similar to stop() but subsequent start() call +// will fail (the timer is no longer usable) +func (t *ackTimer) stop() { + t.mutex.Lock() + defer t.mutex.Unlock() + + if t.stopFunc != nil { + t.stopFunc() + t.stopFunc = nil + } +} + +// closes the timer. this is similar to stop() but subsequent start() call +// will fail (the timer is no longer usable) +func (t *ackTimer) close() { + t.mutex.Lock() + defer t.mutex.Unlock() + + if t.stopFunc != nil { + t.stopFunc() + t.stopFunc = nil + } + + t.closed = true +} + +// isRunning tests if the timer is running. +// Debug purpose only +func (t *ackTimer) isRunning() bool { + t.mutex.RLock() + defer t.mutex.RUnlock() + + return (t.stopFunc != nil) +} diff --git a/vendor/github.com/pion/sctp/association.go b/vendor/github.com/pion/sctp/association.go new file mode 100644 index 0000000..1393cb8 --- /dev/null +++ b/vendor/github.com/pion/sctp/association.go @@ -0,0 +1,2241 @@ +package sctp + +import ( + "bytes" + "fmt" + "io" + "math" + "net" + "sync" + "sync/atomic" + "time" + + "github.com/pion/logging" + "github.com/pion/randutil" + "github.com/pkg/errors" +) + +// Use global random generator to properly seed by crypto grade random. +var ( + globalMathRandomGenerator = randutil.NewMathRandomGenerator() // nolint:gochecknoglobals + errChunk = errors.New("Abort chunk, with following errors") +) + +const ( + receiveMTU uint32 = 8192 // MTU for inbound packet (from DTLS) + initialMTU uint32 = 1228 // initial MTU for outgoing packets (to DTLS) + initialRecvBufSize uint32 = 1024 * 1024 + commonHeaderSize uint32 = 12 + dataChunkHeaderSize uint32 = 16 + defaultMaxMessageSize uint32 = 65536 +) + +// association state enums +const ( + closed uint32 = iota + cookieWait + cookieEchoed + established + shutdownAckSent + shutdownPending + shutdownReceived + shutdownSent +) + +// retransmission timer IDs +const ( + timerT1Init int = iota + timerT1Cookie + timerT3RTX + timerReconfig +) + +// ack mode (for testing) +const ( + ackModeNormal int = iota + ackModeNoDelay + ackModeAlwaysDelay +) + +// ack transmission state +const ( + ackStateIdle int = iota // ack timer is off + ackStateImmediate // ack timer is on (ack is being delayed) + ackStateDelay // will send ack immediately +) + +// other constants +const ( + acceptChSize = 16 +) + +func getAssociationStateString(a uint32) string { + switch a { + case closed: + return "Closed" + case cookieWait: + return "CookieWait" + case cookieEchoed: + return "CookieEchoed" + case established: + return "Established" + case shutdownPending: + return "ShutdownPending" + case shutdownSent: + return "ShutdownSent" + case shutdownReceived: + return "ShutdownReceived" + case shutdownAckSent: + return "ShutdownAckSent" + default: + return fmt.Sprintf("Invalid association state %d", a) + } +} + +// Association represents an SCTP association +// 13.2. Parameters Necessary per Association (i.e., the TCB) +// Peer : Tag value to be sent in every packet and is received +// Verification: in the INIT or INIT ACK chunk. +// Tag : +// +// My : Tag expected in every inbound packet and sent in the +// Verification: INIT or INIT ACK chunk. +// +// Tag : +// State : A state variable indicating what state the association +// : is in, i.e., COOKIE-WAIT, COOKIE-ECHOED, ESTABLISHED, +// : SHUTDOWN-PENDING, SHUTDOWN-SENT, SHUTDOWN-RECEIVED, +// : SHUTDOWN-ACK-SENT. +// +// Note: No "CLOSED" state is illustrated since if a +// association is "CLOSED" its TCB SHOULD be removed. +type Association struct { + bytesReceived uint64 + bytesSent uint64 + + lock sync.RWMutex + + netConn net.Conn + + peerVerificationTag uint32 + myVerificationTag uint32 + state uint32 + myNextTSN uint32 // nextTSN + peerLastTSN uint32 // lastRcvdTSN + minTSN2MeasureRTT uint32 // for RTT measurement + willSendForwardTSN bool + willRetransmitFast bool + willRetransmitReconfig bool + + // Reconfig + myNextRSN uint32 + reconfigs map[uint32]*chunkReconfig + reconfigRequests map[uint32]*paramOutgoingResetRequest + + // Non-RFC internal data + sourcePort uint16 + destinationPort uint16 + myMaxNumInboundStreams uint16 + myMaxNumOutboundStreams uint16 + myCookie *paramStateCookie + payloadQueue *payloadQueue + inflightQueue *payloadQueue + pendingQueue *pendingQueue + controlQueue *controlQueue + mtu uint32 + maxPayloadSize uint32 // max DATA chunk payload size + cumulativeTSNAckPoint uint32 + advancedPeerTSNAckPoint uint32 + useForwardTSN bool + + // Congestion control parameters + maxReceiveBufferSize uint32 + maxMessageSize uint32 + cwnd uint32 // my congestion window size + rwnd uint32 // calculated peer's receiver windows size + ssthresh uint32 // slow start threshold + partialBytesAcked uint32 + inFastRecovery bool + fastRecoverExitPoint uint32 + + // RTX & Ack timer + rtoMgr *rtoManager + t1Init *rtxTimer + t1Cookie *rtxTimer + t3RTX *rtxTimer + tReconfig *rtxTimer + ackTimer *ackTimer + + // Chunks stored for retransmission + storedInit *chunkInit + storedCookieEcho *chunkCookieEcho + + streams map[uint16]*Stream + acceptCh chan *Stream + readLoopCloseCh chan struct{} + awakeWriteLoopCh chan struct{} + closeWriteLoopCh chan struct{} + handshakeCompletedCh chan error + + closeWriteLoopOnce sync.Once + + // local error + silentError error + + ackState int + ackMode int // for testing + + // stats + stats *associationStats + + // per inbound packet context + delayedAckTriggered bool + immediateAckTriggered bool + + name string + log logging.LeveledLogger +} + +// Config collects the arguments to createAssociation construction into +// a single structure +type Config struct { + NetConn net.Conn + MaxReceiveBufferSize uint32 + MaxMessageSize uint32 + LoggerFactory logging.LoggerFactory +} + +// Server accepts a SCTP stream over a conn +func Server(config Config) (*Association, error) { + a := createAssociation(config) + a.init(false) + + select { + case err := <-a.handshakeCompletedCh: + if err != nil { + return nil, err + } + return a, nil + case <-a.readLoopCloseCh: + return nil, errors.Errorf("association closed before connecting") + } +} + +// Client opens a SCTP stream over a conn +func Client(config Config) (*Association, error) { + a := createAssociation(config) + a.init(true) + + select { + case err := <-a.handshakeCompletedCh: + if err != nil { + return nil, err + } + return a, nil + case <-a.readLoopCloseCh: + return nil, errors.Errorf("association closed before connecting") + } +} + +func createAssociation(config Config) *Association { + var maxReceiveBufferSize uint32 + if config.MaxReceiveBufferSize == 0 { + maxReceiveBufferSize = initialRecvBufSize + } else { + maxReceiveBufferSize = config.MaxReceiveBufferSize + } + + var maxMessageSize uint32 + if config.MaxMessageSize == 0 { + maxMessageSize = defaultMaxMessageSize + } else { + maxMessageSize = config.MaxMessageSize + } + + tsn := globalMathRandomGenerator.Uint32() + a := &Association{ + netConn: config.NetConn, + maxReceiveBufferSize: maxReceiveBufferSize, + maxMessageSize: maxMessageSize, + myMaxNumOutboundStreams: math.MaxUint16, + myMaxNumInboundStreams: math.MaxUint16, + payloadQueue: newPayloadQueue(), + inflightQueue: newPayloadQueue(), + pendingQueue: newPendingQueue(), + controlQueue: newControlQueue(), + mtu: initialMTU, + maxPayloadSize: initialMTU - (commonHeaderSize + dataChunkHeaderSize), + myVerificationTag: globalMathRandomGenerator.Uint32(), + myNextTSN: tsn, + myNextRSN: tsn, + minTSN2MeasureRTT: tsn, + state: closed, + rtoMgr: newRTOManager(), + streams: map[uint16]*Stream{}, + reconfigs: map[uint32]*chunkReconfig{}, + reconfigRequests: map[uint32]*paramOutgoingResetRequest{}, + acceptCh: make(chan *Stream, acceptChSize), + readLoopCloseCh: make(chan struct{}), + awakeWriteLoopCh: make(chan struct{}, 1), + closeWriteLoopCh: make(chan struct{}), + handshakeCompletedCh: make(chan error), + cumulativeTSNAckPoint: tsn - 1, + advancedPeerTSNAckPoint: tsn - 1, + silentError: errors.Errorf("silently discard"), + stats: &associationStats{}, + log: config.LoggerFactory.NewLogger("sctp"), + } + + a.name = fmt.Sprintf("%p", a) + + // RFC 4690 Sec 7.2.1 + // o The initial cwnd before DATA transmission or after a sufficiently + // long idle period MUST be set to min(4*MTU, max (2*MTU, 4380 + // bytes)). + a.cwnd = min32(4*a.mtu, max32(2*a.mtu, 4380)) + a.log.Tracef("[%s] updated cwnd=%d ssthresh=%d inflight=%d (INI)", + a.name, a.cwnd, a.ssthresh, a.inflightQueue.getNumBytes()) + + a.t1Init = newRTXTimer(timerT1Init, a, maxInitRetrans) + a.t1Cookie = newRTXTimer(timerT1Cookie, a, maxInitRetrans) + a.t3RTX = newRTXTimer(timerT3RTX, a, noMaxRetrans) // retransmit forever + a.tReconfig = newRTXTimer(timerReconfig, a, noMaxRetrans) // retransmit forever + a.ackTimer = newAckTimer(a) + + return a +} + +func (a *Association) init(isClient bool) { + a.lock.Lock() + defer a.lock.Unlock() + + go a.readLoop() + go a.writeLoop() + + if isClient { + a.setState(cookieWait) + init := &chunkInit{} + init.initialTSN = a.myNextTSN + init.numOutboundStreams = a.myMaxNumOutboundStreams + init.numInboundStreams = a.myMaxNumInboundStreams + init.initiateTag = a.myVerificationTag + init.advertisedReceiverWindowCredit = a.maxReceiveBufferSize + setSupportedExtensions(&init.chunkInitCommon) + a.storedInit = init + + err := a.sendInit() + if err != nil { + a.log.Errorf("[%s] failed to send init: %s", a.name, err.Error()) + } + + a.t1Init.start(a.rtoMgr.getRTO()) + } +} + +// caller must hold a.lock +func (a *Association) sendInit() error { + a.log.Debugf("[%s] sending INIT", a.name) + if a.storedInit == nil { + return errors.Errorf("the init not stored to send") + } + + outbound := &packet{} + outbound.verificationTag = a.peerVerificationTag + a.sourcePort = 5000 // Spec?? + a.destinationPort = 5000 // Spec?? + outbound.sourcePort = a.sourcePort + outbound.destinationPort = a.destinationPort + + outbound.chunks = []chunk{a.storedInit} + + a.controlQueue.push(outbound) + a.awakeWriteLoop() + + return nil +} + +// caller must hold a.lock +func (a *Association) sendCookieEcho() error { + if a.storedCookieEcho == nil { + return errors.Errorf("cookieEcho not stored to send") + } + + a.log.Debugf("[%s] sending COOKIE-ECHO", a.name) + + outbound := &packet{} + outbound.verificationTag = a.peerVerificationTag + outbound.sourcePort = a.sourcePort + outbound.destinationPort = a.destinationPort + outbound.chunks = []chunk{a.storedCookieEcho} + + a.controlQueue.push(outbound) + a.awakeWriteLoop() + + return nil +} + +// Close ends the SCTP Association and cleans up any state +func (a *Association) Close() error { + a.log.Debugf("[%s] closing association..", a.name) + + a.setState(closed) + + err := a.netConn.Close() + + a.closeAllTimers() + + // awake writeLoop to exit + a.closeWriteLoopOnce.Do(func() { close(a.closeWriteLoopCh) }) + + // Wait for readLoop to end + <-a.readLoopCloseCh + + a.log.Debugf("[%s] association closed", a.name) + a.log.Debugf("[%s] stats nDATAs (in) : %d", a.name, a.stats.getNumDATAs()) + a.log.Debugf("[%s] stats nSACKs (in) : %d", a.name, a.stats.getNumSACKs()) + a.log.Debugf("[%s] stats nT3Timeouts : %d", a.name, a.stats.getNumT3Timeouts()) + a.log.Debugf("[%s] stats nAckTimeouts: %d", a.name, a.stats.getNumAckTimeouts()) + a.log.Debugf("[%s] stats nFastRetrans: %d", a.name, a.stats.getNumFastRetrans()) + return err +} + +func (a *Association) closeAllTimers() { + // Close all retransmission & ack timers + a.t1Init.close() + a.t1Cookie.close() + a.t3RTX.close() + a.tReconfig.close() + a.ackTimer.close() +} + +func (a *Association) readLoop() { + var closeErr error + defer func() { + // also stop writeLoop, otherwise writeLoop can be leaked + // if connection is lost when there is no writing packet. + a.closeWriteLoopOnce.Do(func() { close(a.closeWriteLoopCh) }) + + a.lock.Lock() + for _, s := range a.streams { + a.unregisterStream(s, closeErr) + } + a.lock.Unlock() + close(a.acceptCh) + close(a.readLoopCloseCh) + }() + + a.log.Debugf("[%s] readLoop entered", a.name) + buffer := make([]byte, receiveMTU) + + for { + n, err := a.netConn.Read(buffer) + if err != nil { + closeErr = err + break + } + // Make a buffer sized to what we read, then copy the data we + // read from the underlying transport. We do this because the + // user data is passed to the reassembly queue without + // copying. + inbound := make([]byte, n) + copy(inbound, buffer[:n]) + atomic.AddUint64(&a.bytesReceived, uint64(n)) + if err = a.handleInbound(inbound); err != nil { + closeErr = err + break + } + } + + a.log.Debugf("[%s] readLoop exited %s", a.name, closeErr) +} + +func (a *Association) writeLoop() { + a.log.Debugf("[%s] writeLoop entered", a.name) + +loop: + for { + rawPackets := a.gatherOutbound() + + for _, raw := range rawPackets { + _, err := a.netConn.Write(raw) + if err != nil { + if err != io.EOF { + a.log.Warnf("[%s] failed to write packets on netConn: %v", a.name, err) + } + a.log.Debugf("[%s] writeLoop ended", a.name) + break loop + } + atomic.AddUint64(&a.bytesSent, uint64(len(raw))) + } + + select { + case <-a.awakeWriteLoopCh: + case <-a.closeWriteLoopCh: + break loop + } + } + + a.setState(closed) + a.closeAllTimers() + + a.log.Debugf("[%s] writeLoop exited", a.name) +} + +func (a *Association) awakeWriteLoop() { + select { + case a.awakeWriteLoopCh <- struct{}{}: + default: + } +} + +// unregisterStream un-registers a stream from the association +// The caller should hold the association write lock. +func (a *Association) unregisterStream(s *Stream, err error) { + s.lock.Lock() + defer s.lock.Unlock() + + delete(a.streams, s.streamIdentifier) + s.readErr = err + s.readNotifier.Broadcast() +} + +// handleInbound parses incoming raw packets +func (a *Association) handleInbound(raw []byte) error { + p := &packet{} + if err := p.unmarshal(raw); err != nil { + a.log.Warnf("[%s] unable to parse SCTP packet %s", a.name, err) + return nil + } + + if err := checkPacket(p); err != nil { + a.log.Warnf("[%s] failed validating packet %s", a.name, err) + return nil + } + + a.handleChunkStart() + + for _, c := range p.chunks { + if err := a.handleChunk(p, c); err != nil { + return err + } + } + + a.handleChunkEnd() + + return nil +} + +// The caller should hold the lock +func (a *Association) gatherOutboundDataAndReconfigPackets(rawPackets [][]byte) [][]byte { + for _, p := range a.getDataPacketsToRetransmit() { + raw, err := p.marshal() + if err != nil { + a.log.Warnf("[%s] failed to serialize a DATA packet to be retransmitted", a.name) + continue + } + rawPackets = append(rawPackets, raw) + } + + // Pop unsent data chunks from the pending queue to send as much as + // cwnd and rwnd allow. + chunks, sisToReset := a.popPendingDataChunksToSend() + if len(chunks) > 0 { + // Start timer. (noop if already started) + a.log.Tracef("[%s] T3-rtx timer start (pt1)", a.name) + a.t3RTX.start(a.rtoMgr.getRTO()) + for _, p := range a.bundleDataChunksIntoPackets(chunks) { + raw, err := p.marshal() + if err != nil { + a.log.Warnf("[%s] failed to serialize a DATA packet", a.name) + continue + } + rawPackets = append(rawPackets, raw) + } + } + + if len(sisToReset) > 0 || a.willRetransmitReconfig { + if a.willRetransmitReconfig { + a.willRetransmitReconfig = false + a.log.Debugf("[%s] retransmit %d RECONFIG chunk(s)", a.name, len(a.reconfigs)) + for _, c := range a.reconfigs { + p := a.createPacket([]chunk{c}) + raw, err := p.marshal() + if err != nil { + a.log.Warnf("[%s] failed to serialize a RECONFIG packet to be retransmitted", a.name) + } else { + rawPackets = append(rawPackets, raw) + } + } + } + + if len(sisToReset) > 0 { + rsn := a.generateNextRSN() + tsn := a.myNextTSN - 1 + c := &chunkReconfig{ + paramA: ¶mOutgoingResetRequest{ + reconfigRequestSequenceNumber: rsn, + senderLastTSN: tsn, + streamIdentifiers: sisToReset, + }, + } + a.reconfigs[rsn] = c // store in the map for retransmission + a.log.Debugf("[%s] sending RECONFIG: rsn=%d tsn=%d streams=%v", + a.name, rsn, a.myNextTSN-1, sisToReset) + p := a.createPacket([]chunk{c}) + raw, err := p.marshal() + if err != nil { + a.log.Warnf("[%s] failed to serialize a RECONFIG packet to be transmitted", a.name) + } else { + rawPackets = append(rawPackets, raw) + } + } + + if len(a.reconfigs) > 0 { + a.tReconfig.start(a.rtoMgr.getRTO()) + } + } + + return rawPackets +} + +// The caller should hold the lock +func (a *Association) gatherOutboundFrastRetransmissionPackets(rawPackets [][]byte) [][]byte { + if a.willRetransmitFast { + a.willRetransmitFast = false + + toFastRetrans := []chunk{} + fastRetransSize := commonHeaderSize + + for i := 0; ; i++ { + c, ok := a.inflightQueue.get(a.cumulativeTSNAckPoint + uint32(i) + 1) + if !ok { + break // end of pending data + } + + if c.acked || c.abandoned() { + continue + } + + if c.nSent > 1 || c.missIndicator < 3 { + continue + } + + // RFC 4960 Sec 7.2.4 Fast Retransmit on Gap Reports + // 3) Determine how many of the earliest (i.e., lowest TSN) DATA chunks + // marked for retransmission will fit into a single packet, subject + // to constraint of the path MTU of the destination transport + // address to which the packet is being sent. Call this value K. + // Retransmit those K DATA chunks in a single packet. When a Fast + // Retransmit is being performed, the sender SHOULD ignore the value + // of cwnd and SHOULD NOT delay retransmission for this single + // packet. + + dataChunkSize := dataChunkHeaderSize + uint32(len(c.userData)) + if a.mtu < fastRetransSize+dataChunkSize { + break + } + + fastRetransSize += dataChunkSize + a.stats.incFastRetrans() + c.nSent++ + a.checkPartialReliabilityStatus(c) + toFastRetrans = append(toFastRetrans, c) + a.log.Tracef("[%s] fast-retransmit: tsn=%d sent=%d htna=%d", + a.name, c.tsn, c.nSent, a.fastRecoverExitPoint) + } + + if len(toFastRetrans) > 0 { + raw, err := a.createPacket(toFastRetrans).marshal() + if err != nil { + a.log.Warnf("[%s] failed to serialize a DATA packet to be fast-retransmitted", a.name) + } else { + rawPackets = append(rawPackets, raw) + } + } + } + + return rawPackets +} + +// The caller should hold the lock +func (a *Association) gatherOutboundSackPackets(rawPackets [][]byte) [][]byte { + if a.ackState == ackStateImmediate { + a.ackState = ackStateIdle + sack := a.createSelectiveAckChunk() + a.log.Debugf("[%s] sending SACK: %s", a.name, sack.String()) + raw, err := a.createPacket([]chunk{sack}).marshal() + if err != nil { + a.log.Warnf("[%s] failed to serialize a SACK packet", a.name) + } else { + rawPackets = append(rawPackets, raw) + } + } + + return rawPackets +} + +// The caller should hold the lock +func (a *Association) gatherOutboundForwardTSNPackets(rawPackets [][]byte) [][]byte { + if a.willSendForwardTSN { + a.willSendForwardTSN = false + if sna32GT(a.advancedPeerTSNAckPoint, a.cumulativeTSNAckPoint) { + fwdtsn := a.createForwardTSN() + raw, err := a.createPacket([]chunk{fwdtsn}).marshal() + if err != nil { + a.log.Warnf("[%s] failed to serialize a Forward TSN packet", a.name) + } else { + rawPackets = append(rawPackets, raw) + } + } + } + + return rawPackets +} + +// gatherOutbound gathers outgoing packets +func (a *Association) gatherOutbound() [][]byte { + a.lock.Lock() + defer a.lock.Unlock() + + rawPackets := [][]byte{} + + if a.controlQueue.size() > 0 { + for _, p := range a.controlQueue.popAll() { + raw, err := p.marshal() + if err != nil { + a.log.Warnf("[%s] failed to serialize a control packet", a.name) + continue + } + rawPackets = append(rawPackets, raw) + } + } + + state := a.getState() + + if state == established { + rawPackets = a.gatherOutboundDataAndReconfigPackets(rawPackets) + rawPackets = a.gatherOutboundFrastRetransmissionPackets(rawPackets) + rawPackets = a.gatherOutboundSackPackets(rawPackets) + rawPackets = a.gatherOutboundForwardTSNPackets(rawPackets) + } + + return rawPackets +} + +func checkPacket(p *packet) error { + // All packets must adhere to these rules + + // This is the SCTP sender's port number. It can be used by the + // receiver in combination with the source IP address, the SCTP + // destination port, and possibly the destination IP address to + // identify the association to which this packet belongs. The port + // number 0 MUST NOT be used. + if p.sourcePort == 0 { + return errors.Errorf("sctp packet must not have a source port of 0") + } + + // This is the SCTP port number to which this packet is destined. + // The receiving host will use this port number to de-multiplex the + // SCTP packet to the correct receiving endpoint/application. The + // port number 0 MUST NOT be used. + if p.destinationPort == 0 { + return errors.Errorf("sctp packet must not have a destination port of 0") + } + + // Check values on the packet that are specific to a particular chunk type + for _, c := range p.chunks { + switch c.(type) { // nolint:gocritic + case *chunkInit: + // An INIT or INIT ACK chunk MUST NOT be bundled with any other chunk. + // They MUST be the only chunks present in the SCTP packets that carry + // them. + if len(p.chunks) != 1 { + return errors.Errorf("init chunk must not be bundled with any other chunk") + } + + // A packet containing an INIT chunk MUST have a zero Verification + // Tag. + if p.verificationTag != 0 { + return errors.Errorf("init chunk expects a verification tag of 0 on the packet when out-of-the-blue") + } + } + } + + return nil +} + +func min16(a, b uint16) uint16 { + if a < b { + return a + } + return b +} + +func max32(a, b uint32) uint32 { + if a > b { + return a + } + return b +} + +func min32(a, b uint32) uint32 { + if a < b { + return a + } + return b +} + +// setState atomically sets the state of the Association. +// The caller should hold the lock. +func (a *Association) setState(newState uint32) { + oldState := atomic.SwapUint32(&a.state, newState) + if newState != oldState { + a.log.Debugf("[%s] state change: '%s' => '%s'", + a.name, + getAssociationStateString(oldState), + getAssociationStateString(newState)) + } +} + +// getState atomically returns the state of the Association. +func (a *Association) getState() uint32 { + return atomic.LoadUint32(&a.state) +} + +// BytesSent returns the number of bytes sent +func (a *Association) BytesSent() uint64 { + return atomic.LoadUint64(&a.bytesSent) +} + +// BytesReceived returns the number of bytes received +func (a *Association) BytesReceived() uint64 { + return atomic.LoadUint64(&a.bytesReceived) +} + +func setSupportedExtensions(init *chunkInitCommon) { + // nolint:godox + // TODO RFC5061 https://tools.ietf.org/html/rfc6525#section-5.2 + // An implementation supporting this (Supported Extensions Parameter) + // extension MUST list the ASCONF, the ASCONF-ACK, and the AUTH chunks + // in its INIT and INIT-ACK parameters. + init.params = append(init.params, ¶mSupportedExtensions{ + ChunkTypes: []chunkType{ctReconfig, ctForwardTSN}, + }) +} + +// The caller should hold the lock. +func (a *Association) handleInit(p *packet, i *chunkInit) ([]*packet, error) { + state := a.getState() + a.log.Debugf("[%s] chunkInit received in state '%s'", a.name, getAssociationStateString(state)) + + // https://tools.ietf.org/html/rfc4960#section-5.2.1 + // Upon receipt of an INIT in the COOKIE-WAIT state, an endpoint MUST + // respond with an INIT ACK using the same parameters it sent in its + // original INIT chunk (including its Initiate Tag, unchanged). When + // responding, the endpoint MUST send the INIT ACK back to the same + // address that the original INIT (sent by this endpoint) was sent. + + if state != closed && state != cookieWait && state != cookieEchoed { + // 5.2.2. Unexpected INIT in States Other than CLOSED, COOKIE-ECHOED, + // COOKIE-WAIT, and SHUTDOWN-ACK-SENT + return nil, errors.Errorf("todo: handle Init when in state %s", getAssociationStateString(state)) + } + + // Should we be setting any of these permanently until we've ACKed further? + a.myMaxNumInboundStreams = min16(i.numInboundStreams, a.myMaxNumInboundStreams) + a.myMaxNumOutboundStreams = min16(i.numOutboundStreams, a.myMaxNumOutboundStreams) + a.peerVerificationTag = i.initiateTag + a.sourcePort = p.destinationPort + a.destinationPort = p.sourcePort + + // 13.2 This is the last TSN received in sequence. This value + // is set initially by taking the peer's initial TSN, + // received in the INIT or INIT ACK chunk, and + // subtracting one from it. + a.peerLastTSN = i.initialTSN - 1 + + for _, param := range i.params { + switch v := param.(type) { // nolint:gocritic + case *paramSupportedExtensions: + for _, t := range v.ChunkTypes { + if t == ctForwardTSN { + a.log.Debugf("[%s] use ForwardTSN (on init)\n", a.name) + a.useForwardTSN = true + } + } + } + } + if !a.useForwardTSN { + a.log.Warnf("[%s] not using ForwardTSN (on init)\n", a.name) + } + + outbound := &packet{} + outbound.verificationTag = a.peerVerificationTag + outbound.sourcePort = a.sourcePort + outbound.destinationPort = a.destinationPort + + initAck := &chunkInitAck{} + + initAck.initialTSN = a.myNextTSN + initAck.numOutboundStreams = a.myMaxNumOutboundStreams + initAck.numInboundStreams = a.myMaxNumInboundStreams + initAck.initiateTag = a.myVerificationTag + initAck.advertisedReceiverWindowCredit = a.maxReceiveBufferSize + + if a.myCookie == nil { + var err error + if a.myCookie, err = newRandomStateCookie(); err != nil { + return nil, err + } + } + + initAck.params = []param{a.myCookie} + + setSupportedExtensions(&initAck.chunkInitCommon) + + outbound.chunks = []chunk{initAck} + + return pack(outbound), nil +} + +// The caller should hold the lock. +func (a *Association) handleInitAck(p *packet, i *chunkInitAck) error { + state := a.getState() + a.log.Debugf("[%s] chunkInitAck received in state '%s'", a.name, getAssociationStateString(state)) + if state != cookieWait { + // RFC 4960 + // 5.2.3. Unexpected INIT ACK + // If an INIT ACK is received by an endpoint in any state other than the + // COOKIE-WAIT state, the endpoint should discard the INIT ACK chunk. + // An unexpected INIT ACK usually indicates the processing of an old or + // duplicated INIT chunk. + return nil + } + + a.myMaxNumInboundStreams = min16(i.numInboundStreams, a.myMaxNumInboundStreams) + a.myMaxNumOutboundStreams = min16(i.numOutboundStreams, a.myMaxNumOutboundStreams) + a.peerVerificationTag = i.initiateTag + a.peerLastTSN = i.initialTSN - 1 + if a.sourcePort != p.destinationPort || + a.destinationPort != p.sourcePort { + a.log.Warnf("[%s] handleInitAck: port mismatch", a.name) + return nil + } + + a.rwnd = i.advertisedReceiverWindowCredit + a.log.Debugf("[%s] initial rwnd=%d", a.name, a.rwnd) + + // RFC 4690 Sec 7.2.1 + // o The initial value of ssthresh MAY be arbitrarily high (for + // example, implementations MAY use the size of the receiver + // advertised window). + a.ssthresh = a.rwnd + a.log.Tracef("[%s] updated cwnd=%d ssthresh=%d inflight=%d (INI)", + a.name, a.cwnd, a.ssthresh, a.inflightQueue.getNumBytes()) + + a.t1Init.stop() + a.storedInit = nil + + var cookieParam *paramStateCookie + for _, param := range i.params { + switch v := param.(type) { + case *paramStateCookie: + cookieParam = v + case *paramSupportedExtensions: + for _, t := range v.ChunkTypes { + if t == ctForwardTSN { + a.log.Debugf("[%s] use ForwardTSN (on initAck)\n", a.name) + a.useForwardTSN = true + } + } + } + } + if !a.useForwardTSN { + a.log.Warnf("[%s] not using ForwardTSN (on initAck)\n", a.name) + } + if cookieParam == nil { + return errors.Errorf("no cookie in InitAck") + } + + a.storedCookieEcho = &chunkCookieEcho{} + a.storedCookieEcho.cookie = cookieParam.cookie + + err := a.sendCookieEcho() + if err != nil { + a.log.Errorf("[%s] failed to send init: %s", a.name, err.Error()) + } + + a.t1Cookie.start(a.rtoMgr.getRTO()) + a.setState(cookieEchoed) + return nil +} + +// The caller should hold the lock. +func (a *Association) handleHeartbeat(c *chunkHeartbeat) []*packet { + a.log.Tracef("[%s] chunkHeartbeat", a.name) + hbi, ok := c.params[0].(*paramHeartbeatInfo) + if !ok { + a.log.Warnf("[%s] failed to handle Heartbeat, no ParamHeartbeatInfo", a.name) + } + + return pack(&packet{ + verificationTag: a.peerVerificationTag, + sourcePort: a.sourcePort, + destinationPort: a.destinationPort, + chunks: []chunk{&chunkHeartbeatAck{ + params: []param{ + ¶mHeartbeatInfo{ + heartbeatInformation: hbi.heartbeatInformation, + }, + }, + }}, + }) +} + +// The caller should hold the lock. +func (a *Association) handleCookieEcho(c *chunkCookieEcho) []*packet { + state := a.getState() + a.log.Debugf("[%s] COOKIE-ECHO received in state '%s'", a.name, getAssociationStateString(state)) + switch state { + default: + return nil + case established: + if !bytes.Equal(a.myCookie.cookie, c.cookie) { + return nil + } + case closed, cookieWait, cookieEchoed: + if !bytes.Equal(a.myCookie.cookie, c.cookie) { + return nil + } + + a.t1Init.stop() + a.storedInit = nil + + a.t1Cookie.stop() + a.storedCookieEcho = nil + + a.setState(established) + a.handshakeCompletedCh <- nil + } + + p := &packet{ + verificationTag: a.peerVerificationTag, + sourcePort: a.sourcePort, + destinationPort: a.destinationPort, + chunks: []chunk{&chunkCookieAck{}}, + } + return pack(p) +} + +// The caller should hold the lock. +func (a *Association) handleCookieAck() { + state := a.getState() + a.log.Debugf("[%s] COOKIE-ACK received in state '%s'", a.name, getAssociationStateString(state)) + if state != cookieEchoed { + // RFC 4960 + // 5.2.5. Handle Duplicate COOKIE-ACK. + // At any state other than COOKIE-ECHOED, an endpoint should silently + // discard a received COOKIE ACK chunk. + return + } + + a.t1Cookie.stop() + a.storedCookieEcho = nil + + a.setState(established) + a.handshakeCompletedCh <- nil +} + +// The caller should hold the lock. +func (a *Association) handleData(d *chunkPayloadData) []*packet { + a.log.Tracef("[%s] DATA: tsn=%d immediateSack=%v len=%d", + a.name, d.tsn, d.immediateSack, len(d.userData)) + a.stats.incDATAs() + + canPush := a.payloadQueue.canPush(d, a.peerLastTSN) + if canPush { + s := a.getOrCreateStream(d.streamIdentifier) + if s == nil { + // silentely discard the data. (sender will retry on T3-rtx timeout) + // see pion/sctp#30 + a.log.Debugf("discard %d", d.streamSequenceNumber) + return nil + } + + if a.getMyReceiverWindowCredit() > 0 { + // Pass the new chunk to stream level as soon as it arrives + a.payloadQueue.push(d, a.peerLastTSN) + s.handleData(d) + } else { + // Receive buffer is full + lastTSN, ok := a.payloadQueue.getLastTSNReceived() + if ok && sna32LT(d.tsn, lastTSN) { + a.log.Debugf("[%s] receive buffer full, but accepted as this is a missing chunk with tsn=%d ssn=%d", a.name, d.tsn, d.streamSequenceNumber) + a.payloadQueue.push(d, a.peerLastTSN) + s.handleData(d) + } else { + a.log.Debugf("[%s] receive buffer full. dropping DATA with tsn=%d ssn=%d", a.name, d.tsn, d.streamSequenceNumber) + } + } + } + + return a.handlePeerLastTSNAndAcknowledgement(d.immediateSack) +} + +// A common routine for handleData and handleForwardTSN routines +// The caller should hold the lock. +func (a *Association) handlePeerLastTSNAndAcknowledgement(sackImmediately bool) []*packet { + var reply []*packet + + // Try to advance peerLastTSN + + // From RFC 3758 Sec 3.6: + // .. and then MUST further advance its cumulative TSN point locally + // if possible + // Meaning, if peerLastTSN+1 points to a chunk that is received, + // advance peerLastTSN until peerLastTSN+1 points to unreceived chunk. + for { + if _, popOk := a.payloadQueue.pop(a.peerLastTSN + 1); !popOk { + break + } + a.peerLastTSN++ + + for _, rstReq := range a.reconfigRequests { + resp := a.resetStreamsIfAny(rstReq) + if resp != nil { + a.log.Debugf("[%s] RESET RESPONSE: %+v", a.name, resp) + reply = append(reply, resp) + } + } + } + + hasPacketLoss := (a.payloadQueue.size() > 0) + if hasPacketLoss { + a.log.Tracef("[%s] packetloss: %s", a.name, a.payloadQueue.getGapAckBlocksString(a.peerLastTSN)) + } + + if (a.ackState != ackStateImmediate && !sackImmediately && !hasPacketLoss && a.ackMode == ackModeNormal) || a.ackMode == ackModeAlwaysDelay { + if a.ackState == ackStateIdle { + a.delayedAckTriggered = true + } else { + a.immediateAckTriggered = true + } + } else { + a.immediateAckTriggered = true + } + + return reply +} + +// The caller should hold the lock. +func (a *Association) getMyReceiverWindowCredit() uint32 { + var bytesQueued uint32 + for _, s := range a.streams { + bytesQueued += uint32(s.getNumBytesInReassemblyQueue()) + } + + if bytesQueued >= a.maxReceiveBufferSize { + return 0 + } + return a.maxReceiveBufferSize - bytesQueued +} + +// OpenStream opens a stream +func (a *Association) OpenStream(streamIdentifier uint16, defaultPayloadType PayloadProtocolIdentifier) (*Stream, error) { + a.lock.Lock() + defer a.lock.Unlock() + + if _, ok := a.streams[streamIdentifier]; ok { + return nil, errors.Errorf("there already exists a stream with identifier %d", streamIdentifier) + } + + s := a.createStream(streamIdentifier, false) + s.setDefaultPayloadType(defaultPayloadType) + + return s, nil +} + +// AcceptStream accepts a stream +func (a *Association) AcceptStream() (*Stream, error) { + s, ok := <-a.acceptCh + if !ok { + return nil, io.EOF // no more incoming streams + } + return s, nil +} + +// createStream creates a stream. The caller should hold the lock and check no stream exists for this id. +func (a *Association) createStream(streamIdentifier uint16, accept bool) *Stream { + s := &Stream{ + association: a, + streamIdentifier: streamIdentifier, + reassemblyQueue: newReassemblyQueue(streamIdentifier), + log: a.log, + name: fmt.Sprintf("%d:%s", streamIdentifier, a.name), + } + + s.readNotifier = sync.NewCond(&s.lock) + + if accept { + select { + case a.acceptCh <- s: + a.streams[streamIdentifier] = s + a.log.Debugf("[%s] accepted a new stream (streamIdentifier: %d)", + a.name, streamIdentifier) + default: + a.log.Debugf("[%s] dropped a new stream (acceptCh size: %d)", + a.name, len(a.acceptCh)) + return nil + } + } else { + a.streams[streamIdentifier] = s + } + + return s +} + +// getOrCreateStream gets or creates a stream. The caller should hold the lock. +func (a *Association) getOrCreateStream(streamIdentifier uint16) *Stream { + if s, ok := a.streams[streamIdentifier]; ok { + return s + } + + return a.createStream(streamIdentifier, true) +} + +// The caller should hold the lock. +func (a *Association) processSelectiveAck(d *chunkSelectiveAck) (map[uint16]int, uint32, error) { // nolint:gocognit + bytesAckedPerStream := map[uint16]int{} + + // New ack point, so pop all ACKed packets from inflightQueue + // We add 1 because the "currentAckPoint" has already been popped from the inflight queue + // For the first SACK we take care of this by setting the ackpoint to cumAck - 1 + for i := a.cumulativeTSNAckPoint + 1; sna32LTE(i, d.cumulativeTSNAck); i++ { + c, ok := a.inflightQueue.pop(i) + if !ok { + return nil, 0, errors.Errorf("tsn %v unable to be popped from inflight queue", i) + } + + if !c.acked { + // RFC 4096 sec 6.3.2. Retransmission Timer Rules + // R3) Whenever a SACK is received that acknowledges the DATA chunk + // with the earliest outstanding TSN for that address, restart the + // T3-rtx timer for that address with its current RTO (if there is + // still outstanding data on that address). + if i == a.cumulativeTSNAckPoint+1 { + // T3 timer needs to be reset. Stop it for now. + a.t3RTX.stop() + } + + nBytesAcked := len(c.userData) + + // Sum the number of bytes acknowledged per stream + if amount, ok := bytesAckedPerStream[c.streamIdentifier]; ok { + bytesAckedPerStream[c.streamIdentifier] = amount + nBytesAcked + } else { + bytesAckedPerStream[c.streamIdentifier] = nBytesAcked + } + + // RFC 4960 sec 6.3.1. RTO Calculation + // C4) When data is in flight and when allowed by rule C5 below, a new + // RTT measurement MUST be made each round trip. Furthermore, new + // RTT measurements SHOULD be made no more than once per round trip + // for a given destination transport address. + // C5) Karn's algorithm: RTT measurements MUST NOT be made using + // packets that were retransmitted (and thus for which it is + // ambiguous whether the reply was for the first instance of the + // chunk or for a later instance) + if c.nSent == 1 && sna32GTE(c.tsn, a.minTSN2MeasureRTT) { + a.minTSN2MeasureRTT = a.myNextTSN + rtt := time.Since(c.since).Seconds() * 1000.0 + srtt := a.rtoMgr.setNewRTT(rtt) + a.log.Tracef("[%s] SACK: measured-rtt=%f srtt=%f new-rto=%f", + a.name, rtt, srtt, a.rtoMgr.getRTO()) + } + } + + if a.inFastRecovery && c.tsn == a.fastRecoverExitPoint { + a.log.Debugf("[%s] exit fast-recovery", a.name) + a.inFastRecovery = false + } + } + + htna := d.cumulativeTSNAck + + // Mark selectively acknowledged chunks as "acked" + for _, g := range d.gapAckBlocks { + for i := g.start; i <= g.end; i++ { + tsn := d.cumulativeTSNAck + uint32(i) + c, ok := a.inflightQueue.get(tsn) + if !ok { + return nil, 0, errors.Errorf("requested non-existent TSN %v", tsn) + } + + if !c.acked { + nBytesAcked := a.inflightQueue.markAsAcked(tsn) + + // Sum the number of bytes acknowledged per stream + if amount, ok := bytesAckedPerStream[c.streamIdentifier]; ok { + bytesAckedPerStream[c.streamIdentifier] = amount + nBytesAcked + } else { + bytesAckedPerStream[c.streamIdentifier] = nBytesAcked + } + + a.log.Tracef("[%s] tsn=%d has been sacked", a.name, c.tsn) + + if c.nSent == 1 { + a.minTSN2MeasureRTT = a.myNextTSN + rtt := time.Since(c.since).Seconds() * 1000.0 + srtt := a.rtoMgr.setNewRTT(rtt) + a.log.Tracef("[%s] SACK: measured-rtt=%f srtt=%f new-rto=%f", + a.name, rtt, srtt, a.rtoMgr.getRTO()) + } + + if sna32LT(htna, tsn) { + htna = tsn + } + } + } + } + + return bytesAckedPerStream, htna, nil +} + +// The caller should hold the lock. +func (a *Association) onCumulativeTSNAckPointAdvanced(totalBytesAcked int) { + // RFC 4096, sec 6.3.2. Retransmission Timer Rules + // R2) Whenever all outstanding data sent to an address have been + // acknowledged, turn off the T3-rtx timer of that address. + if a.inflightQueue.size() == 0 { + a.log.Tracef("[%s] SACK: no more packet in-flight (pending=%d)", a.name, a.pendingQueue.size()) + a.t3RTX.stop() + } else { + a.log.Tracef("[%s] T3-rtx timer start (pt2)", a.name) + a.t3RTX.start(a.rtoMgr.getRTO()) + } + + // Update congestion control parameters + if a.cwnd <= a.ssthresh { + // RFC 4096, sec 7.2.1. Slow-Start + // o When cwnd is less than or equal to ssthresh, an SCTP endpoint MUST + // use the slow-start algorithm to increase cwnd only if the current + // congestion window is being fully utilized, an incoming SACK + // advances the Cumulative TSN Ack Point, and the data sender is not + // in Fast Recovery. Only when these three conditions are met can + // the cwnd be increased; otherwise, the cwnd MUST not be increased. + // If these conditions are met, then cwnd MUST be increased by, at + // most, the lesser of 1) the total size of the previously + // outstanding DATA chunk(s) acknowledged, and 2) the destination's + // path MTU. + if !a.inFastRecovery && + a.pendingQueue.size() > 0 { + a.cwnd += min32(uint32(totalBytesAcked), a.cwnd) // TCP way + // a.cwnd += min32(uint32(totalBytesAcked), a.mtu) // SCTP way (slow) + a.log.Tracef("[%s] updated cwnd=%d ssthresh=%d acked=%d (SS)", + a.name, a.cwnd, a.ssthresh, totalBytesAcked) + } else { + a.log.Tracef("[%s] cwnd did not grow: cwnd=%d ssthresh=%d acked=%d FR=%v pending=%d", + a.name, a.cwnd, a.ssthresh, totalBytesAcked, a.inFastRecovery, a.pendingQueue.size()) + } + } else { + // RFC 4096, sec 7.2.2. Congestion Avoidance + // o Whenever cwnd is greater than ssthresh, upon each SACK arrival + // that advances the Cumulative TSN Ack Point, increase + // partial_bytes_acked by the total number of bytes of all new chunks + // acknowledged in that SACK including chunks acknowledged by the new + // Cumulative TSN Ack and by Gap Ack Blocks. + a.partialBytesAcked += uint32(totalBytesAcked) + + // o When partial_bytes_acked is equal to or greater than cwnd and + // before the arrival of the SACK the sender had cwnd or more bytes + // of data outstanding (i.e., before arrival of the SACK, flight size + // was greater than or equal to cwnd), increase cwnd by MTU, and + // reset partial_bytes_acked to (partial_bytes_acked - cwnd). + if a.partialBytesAcked >= a.cwnd && a.pendingQueue.size() > 0 { + a.partialBytesAcked -= a.cwnd + a.cwnd += a.mtu + a.log.Tracef("[%s] updated cwnd=%d ssthresh=%d acked=%d (CA)", + a.name, a.cwnd, a.ssthresh, totalBytesAcked) + } + } +} + +// The caller should hold the lock. +func (a *Association) processFastRetransmission(cumTSNAckPoint, htna uint32, cumTSNAckPointAdvanced bool) error { + // HTNA algorithm - RFC 4960 Sec 7.2.4 + // Increment missIndicator of each chunks that the SACK reported missing + // when either of the following is met: + // a) Not in fast-recovery + // miss indications are incremented only for missing TSNs prior to the + // highest TSN newly acknowledged in the SACK. + // b) In fast-recovery AND the Cumulative TSN Ack Point advanced + // the miss indications are incremented for all TSNs reported missing + // in the SACK. + if !a.inFastRecovery || (a.inFastRecovery && cumTSNAckPointAdvanced) { + var maxTSN uint32 + if !a.inFastRecovery { + // a) increment only for missing TSNs prior to the HTNA + maxTSN = htna + } else { + // b) increment for all TSNs reported missing + maxTSN = cumTSNAckPoint + uint32(a.inflightQueue.size()) + 1 + } + + for tsn := cumTSNAckPoint + 1; sna32LT(tsn, maxTSN); tsn++ { + c, ok := a.inflightQueue.get(tsn) + if !ok { + return errors.Errorf("requested non-existent TSN %v", tsn) + } + if !c.acked && !c.abandoned() && c.missIndicator < 3 { + c.missIndicator++ + if c.missIndicator == 3 { + if !a.inFastRecovery { + // 2) If not in Fast Recovery, adjust the ssthresh and cwnd of the + // destination address(es) to which the missing DATA chunks were + // last sent, according to the formula described in Section 7.2.3. + a.inFastRecovery = true + a.fastRecoverExitPoint = htna + a.ssthresh = max32(a.cwnd/2, 4*a.mtu) + a.cwnd = a.ssthresh + a.partialBytesAcked = 0 + a.willRetransmitFast = true + + a.log.Tracef("[%s] updated cwnd=%d ssthresh=%d inflight=%d (FR)", + a.name, a.cwnd, a.ssthresh, a.inflightQueue.getNumBytes()) + } + } + } + } + } + + if a.inFastRecovery && cumTSNAckPointAdvanced { + a.willRetransmitFast = true + } + + return nil +} + +// The caller should hold the lock. +func (a *Association) handleSack(d *chunkSelectiveAck) error { + a.log.Tracef("[%s] SACK: cumTSN=%d a_rwnd=%d", a.name, d.cumulativeTSNAck, d.advertisedReceiverWindowCredit) + state := a.getState() + if state != established { + return nil + } + + a.stats.incSACKs() + + if sna32GT(a.cumulativeTSNAckPoint, d.cumulativeTSNAck) { + // RFC 4960 sec 6.2.1. Processing a Received SACK + // D) + // i) If Cumulative TSN Ack is less than the Cumulative TSN Ack + // Point, then drop the SACK. Since Cumulative TSN Ack is + // monotonically increasing, a SACK whose Cumulative TSN Ack is + // less than the Cumulative TSN Ack Point indicates an out-of- + // order SACK. + + a.log.Debugf("[%s] SACK Cumulative ACK %v is older than ACK point %v", + a.name, + d.cumulativeTSNAck, + a.cumulativeTSNAckPoint) + + return nil + } + + // Process selective ack + bytesAckedPerStream, htna, err := a.processSelectiveAck(d) + if err != nil { + return err + } + + var totalBytesAcked int + for _, nBytesAcked := range bytesAckedPerStream { + totalBytesAcked += nBytesAcked + } + + cumTSNAckPointAdvanced := false + if sna32LT(a.cumulativeTSNAckPoint, d.cumulativeTSNAck) { + a.log.Tracef("[%s] SACK: cumTSN advanced: %d -> %d", + a.name, + a.cumulativeTSNAckPoint, + d.cumulativeTSNAck) + + a.cumulativeTSNAckPoint = d.cumulativeTSNAck + cumTSNAckPointAdvanced = true + a.onCumulativeTSNAckPointAdvanced(totalBytesAcked) + } + + for si, nBytesAcked := range bytesAckedPerStream { + if s, ok := a.streams[si]; ok { + a.lock.Unlock() + s.onBufferReleased(nBytesAcked) + a.lock.Lock() + } + } + + // New rwnd value + // RFC 4960 sec 6.2.1. Processing a Received SACK + // D) + // ii) Set rwnd equal to the newly received a_rwnd minus the number + // of bytes still outstanding after processing the Cumulative + // TSN Ack and the Gap Ack Blocks. + + // bytes acked were already subtracted by markAsAcked() method + bytesOutstanding := uint32(a.inflightQueue.getNumBytes()) + if bytesOutstanding >= d.advertisedReceiverWindowCredit { + a.rwnd = 0 + } else { + a.rwnd = d.advertisedReceiverWindowCredit - bytesOutstanding + } + + err = a.processFastRetransmission(d.cumulativeTSNAck, htna, cumTSNAckPointAdvanced) + if err != nil { + return err + } + + if a.useForwardTSN { + // RFC 3758 Sec 3.5 C1 + if sna32LT(a.advancedPeerTSNAckPoint, a.cumulativeTSNAckPoint) { + a.advancedPeerTSNAckPoint = a.cumulativeTSNAckPoint + } + + // RFC 3758 Sec 3.5 C2 + for i := a.advancedPeerTSNAckPoint + 1; ; i++ { + c, ok := a.inflightQueue.get(i) + if !ok { + break + } + if !c.abandoned() { + break + } + a.advancedPeerTSNAckPoint = i + } + + // RFC 3758 Sec 3.5 C3 + if sna32GT(a.advancedPeerTSNAckPoint, a.cumulativeTSNAckPoint) { + a.willSendForwardTSN = true + } + a.awakeWriteLoop() + } + + if a.inflightQueue.size() > 0 { + // Start timer. (noop if already started) + a.log.Tracef("[%s] T3-rtx timer start (pt3)", a.name) + a.t3RTX.start(a.rtoMgr.getRTO()) + } + + if cumTSNAckPointAdvanced { + a.awakeWriteLoop() + } + + return nil +} + +// createForwardTSN generates ForwardTSN chunk. +// This method will be be called if useForwardTSN is set to false. +// The caller should hold the lock. +func (a *Association) createForwardTSN() *chunkForwardTSN { + // RFC 3758 Sec 3.5 C4 + streamMap := map[uint16]uint16{} // to report only once per SI + for i := a.cumulativeTSNAckPoint + 1; sna32LTE(i, a.advancedPeerTSNAckPoint); i++ { + c, ok := a.inflightQueue.get(i) + if !ok { + break + } + + ssn, ok := streamMap[c.streamIdentifier] + if !ok { + streamMap[c.streamIdentifier] = c.streamSequenceNumber + } else if sna16LT(ssn, c.streamSequenceNumber) { + // to report only once with greatest SSN + streamMap[c.streamIdentifier] = c.streamSequenceNumber + } + } + + fwdtsn := &chunkForwardTSN{ + newCumulativeTSN: a.advancedPeerTSNAckPoint, + streams: []chunkForwardTSNStream{}, + } + + var streamStr string + for si, ssn := range streamMap { + streamStr += fmt.Sprintf("(si=%d ssn=%d)", si, ssn) + fwdtsn.streams = append(fwdtsn.streams, chunkForwardTSNStream{ + identifier: si, + sequence: ssn, + }) + } + a.log.Tracef("[%s] building fwdtsn: newCumulativeTSN=%d cumTSN=%d - %s", a.name, fwdtsn.newCumulativeTSN, a.cumulativeTSNAckPoint, streamStr) + + return fwdtsn +} + +// createPacket wraps chunks in a packet. +// The caller should hold the read lock. +func (a *Association) createPacket(cs []chunk) *packet { + return &packet{ + verificationTag: a.peerVerificationTag, + sourcePort: a.sourcePort, + destinationPort: a.destinationPort, + chunks: cs, + } +} + +// The caller should hold the lock. +func (a *Association) handleReconfig(c *chunkReconfig) ([]*packet, error) { + a.log.Tracef("[%s] handleReconfig", a.name) + + pp := make([]*packet, 0) + + p, err := a.handleReconfigParam(c.paramA) + if err != nil { + return nil, err + } + if p != nil { + pp = append(pp, p) + } + + if c.paramB != nil { + p, err = a.handleReconfigParam(c.paramB) + if err != nil { + return nil, err + } + if p != nil { + pp = append(pp, p) + } + } + return pp, nil +} + +// The caller should hold the lock. +func (a *Association) handleForwardTSN(c *chunkForwardTSN) []*packet { + a.log.Tracef("[%s] FwdTSN: %s", a.name, c.String()) + + if !a.useForwardTSN { + a.log.Warn("[%s] received FwdTSN but not enabled") + // Return an error chunk + cerr := &chunkError{ + errorCauses: []errorCause{&errorCauseUnrecognizedChunkType{}}, + } + outbound := &packet{} + outbound.verificationTag = a.peerVerificationTag + outbound.sourcePort = a.sourcePort + outbound.destinationPort = a.destinationPort + outbound.chunks = []chunk{cerr} + return []*packet{outbound} + } + + // From RFC 3758 Sec 3.6: + // Note, if the "New Cumulative TSN" value carried in the arrived + // FORWARD TSN chunk is found to be behind or at the current cumulative + // TSN point, the data receiver MUST treat this FORWARD TSN as out-of- + // date and MUST NOT update its Cumulative TSN. The receiver SHOULD + // send a SACK to its peer (the sender of the FORWARD TSN) since such a + // duplicate may indicate the previous SACK was lost in the network. + + a.log.Tracef("[%s] should send ack? newCumTSN=%d peerLastTSN=%d\n", + a.name, c.newCumulativeTSN, a.peerLastTSN) + if sna32LTE(c.newCumulativeTSN, a.peerLastTSN) { + a.log.Tracef("[%s] sending ack on Forward TSN", a.name) + a.ackState = ackStateImmediate + a.ackTimer.stop() + a.awakeWriteLoop() + return nil + } + + // From RFC 3758 Sec 3.6: + // the receiver MUST perform the same TSN handling, including duplicate + // detection, gap detection, SACK generation, cumulative TSN + // advancement, etc. as defined in RFC 2960 [2]---with the following + // exceptions and additions. + + // When a FORWARD TSN chunk arrives, the data receiver MUST first update + // its cumulative TSN point to the value carried in the FORWARD TSN + // chunk, + + // Advance peerLastTSN + for sna32LT(a.peerLastTSN, c.newCumulativeTSN) { + a.payloadQueue.pop(a.peerLastTSN + 1) // may not exist + a.peerLastTSN++ + } + + // Report new peerLastTSN value and abandoned largest SSN value to + // corresponding streams so that the abandoned chunks can be removed + // from the reassemblyQueue. + for _, forwarded := range c.streams { + if s, ok := a.streams[forwarded.identifier]; ok { + s.handleForwardTSNForOrdered(forwarded.sequence) + } + } + + // TSN may be forewared for unordered chunks. ForwardTSN chunk does not + // report which stream identifier it skipped for unordered chunks. + // Therefore, we need to broadcast this event to all existing streams for + // unordered chunks. + // See https://github.com/pion/sctp/issues/106 + for _, s := range a.streams { + s.handleForwardTSNForUnordered(c.newCumulativeTSN) + } + + return a.handlePeerLastTSNAndAcknowledgement(false) +} + +func (a *Association) sendResetRequest(streamIdentifier uint16) error { + a.lock.Lock() + defer a.lock.Unlock() + + state := a.getState() + if state != established { + return errors.Errorf("sending reset packet in non-established state: state=%s", + getAssociationStateString(state)) + } + + // Create DATA chunk which only contains valid stream identifier with + // nil userData and use it as a EOS from the stream. + c := &chunkPayloadData{ + streamIdentifier: streamIdentifier, + beginningFragment: true, + endingFragment: true, + userData: nil, + } + + a.pendingQueue.push(c) + a.awakeWriteLoop() + return nil +} + +// The caller should hold the lock. +func (a *Association) handleReconfigParam(raw param) (*packet, error) { + switch p := raw.(type) { + case *paramOutgoingResetRequest: + a.reconfigRequests[p.reconfigRequestSequenceNumber] = p + resp := a.resetStreamsIfAny(p) + if resp != nil { + return resp, nil + } + return nil, nil + + case *paramReconfigResponse: + delete(a.reconfigs, p.reconfigResponseSequenceNumber) + if len(a.reconfigs) == 0 { + a.tReconfig.stop() + } + return nil, nil + default: + return nil, errors.Errorf("unexpected parameter type %T", p) + } +} + +// The caller should hold the lock. +func (a *Association) resetStreamsIfAny(p *paramOutgoingResetRequest) *packet { + result := reconfigResultSuccessPerformed + if sna32LTE(p.senderLastTSN, a.peerLastTSN) { + a.log.Debugf("[%s] resetStream(): senderLastTSN=%d <= peerLastTSN=%d", + a.name, p.senderLastTSN, a.peerLastTSN) + for _, id := range p.streamIdentifiers { + s, ok := a.streams[id] + if !ok { + continue + } + a.unregisterStream(s, io.EOF) + } + delete(a.reconfigRequests, p.reconfigRequestSequenceNumber) + } else { + a.log.Debugf("[%s] resetStream(): senderLastTSN=%d > peerLastTSN=%d", + a.name, p.senderLastTSN, a.peerLastTSN) + result = reconfigResultInProgress + } + + return a.createPacket([]chunk{&chunkReconfig{ + paramA: ¶mReconfigResponse{ + reconfigResponseSequenceNumber: p.reconfigRequestSequenceNumber, + result: result, + }, + }}) +} + +// Move the chunk peeked with a.pendingQueue.peek() to the inflightQueue. +// The caller should hold the lock. +func (a *Association) movePendingDataChunkToInflightQueue(c *chunkPayloadData) { + if err := a.pendingQueue.pop(c); err != nil { + a.log.Errorf("[%s] failed to pop from pending queue: %s", a.name, err.Error()) + } + + // Mark all fragements are in-flight now + if c.endingFragment { + c.setAllInflight() + } + + // Assign TSN + c.tsn = a.generateNextTSN() + + c.since = time.Now() // use to calculate RTT and also for maxPacketLifeTime + c.nSent = 1 // being sent for the first time + + a.checkPartialReliabilityStatus(c) + + a.log.Tracef("[%s] sending ppi=%d tsn=%d ssn=%d sent=%d len=%d (%v,%v)", + a.name, c.payloadType, c.tsn, c.streamSequenceNumber, c.nSent, len(c.userData), c.beginningFragment, c.endingFragment) + + // Push it into the inflightQueue + a.inflightQueue.pushNoCheck(c) +} + +// popPendingDataChunksToSend pops chunks from the pending queues as many as +// the cwnd and rwnd allows to send. +// The caller should hold the lock. +func (a *Association) popPendingDataChunksToSend() ([]*chunkPayloadData, []uint16) { + chunks := []*chunkPayloadData{} + var sisToReset []uint16 // stream identifieres to reset + + if a.pendingQueue.size() > 0 { + // RFC 4960 sec 6.1. Transmission of DATA Chunks + // A) At any given time, the data sender MUST NOT transmit new data to + // any destination transport address if its peer's rwnd indicates + // that the peer has no buffer space (i.e., rwnd is 0; see Section + // 6.2.1). However, regardless of the value of rwnd (including if it + // is 0), the data sender can always have one DATA chunk in flight to + // the receiver if allowed by cwnd (see rule B, below). + + for { + c := a.pendingQueue.peek() + if c == nil { + break // no more pending data + } + + dataLen := uint32(len(c.userData)) + if dataLen == 0 { + sisToReset = append(sisToReset, c.streamIdentifier) + err := a.pendingQueue.pop(c) + if err != nil { + a.log.Errorf("failed to pop from pending queue: %s", err.Error()) + } + continue + } + + if uint32(a.inflightQueue.getNumBytes())+dataLen > a.cwnd { + break // would exceeds cwnd + } + + if dataLen > a.rwnd { + break // no more rwnd + } + + a.rwnd -= dataLen + + a.movePendingDataChunkToInflightQueue(c) + chunks = append(chunks, c) + } + + // the data sender can always have one DATA chunk in flight to the receiver + if len(chunks) == 0 && a.inflightQueue.size() == 0 { + // Send zero window probe + c := a.pendingQueue.peek() + if c != nil { + a.movePendingDataChunkToInflightQueue(c) + chunks = append(chunks, c) + } + } + } + + return chunks, sisToReset +} + +// bundleDataChunksIntoPackets packs DATA chunks into packets. It tries to bundle +// DATA chunks into a packet so long as the resulting packet size does not exceed +// the path MTU. +// The caller should hold the lock. +func (a *Association) bundleDataChunksIntoPackets(chunks []*chunkPayloadData) []*packet { + packets := []*packet{} + chunksToSend := []chunk{} + bytesInPacket := int(commonHeaderSize) + + for _, c := range chunks { + // RFC 4960 sec 6.1. Transmission of DATA Chunks + // Multiple DATA chunks committed for transmission MAY be bundled in a + // single packet. Furthermore, DATA chunks being retransmitted MAY be + // bundled with new DATA chunks, as long as the resulting packet size + // does not exceed the path MTU. + if bytesInPacket+len(c.userData) > int(a.mtu) { + packets = append(packets, a.createPacket(chunksToSend)) + chunksToSend = []chunk{} + bytesInPacket = int(commonHeaderSize) + } + + chunksToSend = append(chunksToSend, c) + bytesInPacket += int(dataChunkHeaderSize) + len(c.userData) + } + + if len(chunksToSend) > 0 { + packets = append(packets, a.createPacket(chunksToSend)) + } + + return packets +} + +// sendPayloadData sends the data chunks. +func (a *Association) sendPayloadData(chunks []*chunkPayloadData) error { + a.lock.Lock() + defer a.lock.Unlock() + + state := a.getState() + if state != established { + return errors.Errorf("sending payload data in non-established state: state=%s", + getAssociationStateString(state)) + } + + // Push the chunks into the pending queue first. + for _, c := range chunks { + a.pendingQueue.push(c) + } + + a.awakeWriteLoop() + return nil +} + +// The caller should hold the lock. +func (a *Association) checkPartialReliabilityStatus(c *chunkPayloadData) { + if !a.useForwardTSN { + return + } + + // draft-ietf-rtcweb-data-protocol-09.txt section 6 + // 6. Procedures + // All Data Channel Establishment Protocol messages MUST be sent using + // ordered delivery and reliable transmission. + // + if c.payloadType == PayloadTypeWebRTCDCEP { + return + } + + // PR-SCTP + if s, ok := a.streams[c.streamIdentifier]; ok { + s.lock.RLock() + if s.reliabilityType == ReliabilityTypeRexmit { + if c.nSent >= s.reliabilityValue { + c.setAbandoned(true) + a.log.Tracef("[%s] marked as abandoned: tsn=%d ppi=%d (remix: %d)", a.name, c.tsn, c.payloadType, c.nSent) + } + } else if s.reliabilityType == ReliabilityTypeTimed { + elapsed := int64(time.Since(c.since).Seconds() * 1000) + if elapsed >= int64(s.reliabilityValue) { + c.setAbandoned(true) + a.log.Tracef("[%s] marked as abandoned: tsn=%d ppi=%d (timed: %d)", a.name, c.tsn, c.payloadType, elapsed) + } + } + s.lock.RUnlock() + } else { + a.log.Errorf("[%s] stream %d not found)", a.name, c.streamIdentifier) + } +} + +// getDataPacketsToRetransmit is called when T3-rtx is timed out and retransmit outstanding data chunks +// that are not acked or abandoned yet. +// The caller should hold the lock. +func (a *Association) getDataPacketsToRetransmit() []*packet { + awnd := min32(a.cwnd, a.rwnd) + chunks := []*chunkPayloadData{} + var bytesToSend int + var done bool + + for i := 0; !done; i++ { + c, ok := a.inflightQueue.get(a.cumulativeTSNAckPoint + uint32(i) + 1) + if !ok { + break // end of pending data + } + + if !c.retransmit { + continue + } + + if i == 0 && int(a.rwnd) < len(c.userData) { + // Send it as a zero window probe + done = true + } else if bytesToSend+len(c.userData) > int(awnd) { + break + } + + // reset the retransmit flag not to retransmit again before the next + // t3-rtx timer fires + c.retransmit = false + bytesToSend += len(c.userData) + + c.nSent++ + + a.checkPartialReliabilityStatus(c) + + a.log.Tracef("[%s] retransmitting tsn=%d ssn=%d sent=%d", a.name, c.tsn, c.streamSequenceNumber, c.nSent) + + chunks = append(chunks, c) + } + + return a.bundleDataChunksIntoPackets(chunks) +} + +// generateNextTSN returns the myNextTSN and increases it. The caller should hold the lock. +// The caller should hold the lock. +func (a *Association) generateNextTSN() uint32 { + tsn := a.myNextTSN + a.myNextTSN++ + return tsn +} + +// generateNextRSN returns the myNextRSN and increases it. The caller should hold the lock. +// The caller should hold the lock. +func (a *Association) generateNextRSN() uint32 { + rsn := a.myNextRSN + a.myNextRSN++ + return rsn +} + +func (a *Association) createSelectiveAckChunk() *chunkSelectiveAck { + sack := &chunkSelectiveAck{} + sack.cumulativeTSNAck = a.peerLastTSN + sack.advertisedReceiverWindowCredit = a.getMyReceiverWindowCredit() + sack.duplicateTSN = a.payloadQueue.popDuplicates() + sack.gapAckBlocks = a.payloadQueue.getGapAckBlocks(a.peerLastTSN) + return sack +} + +func pack(p *packet) []*packet { + return []*packet{p} +} + +func (a *Association) handleChunkStart() { + a.lock.Lock() + defer a.lock.Unlock() + + a.delayedAckTriggered = false + a.immediateAckTriggered = false +} + +func (a *Association) handleChunkEnd() { + a.lock.Lock() + defer a.lock.Unlock() + + if a.immediateAckTriggered { + // Send SACK now! + a.ackState = ackStateImmediate + a.ackTimer.stop() + a.awakeWriteLoop() + } else if a.delayedAckTriggered { + // Will send delayed ack in the next ack timeout + a.ackState = ackStateDelay + a.ackTimer.start() + } +} + +func (a *Association) handleChunk(p *packet, c chunk) error { + a.lock.Lock() + defer a.lock.Unlock() + + var packets []*packet + var err error + + if _, err = c.check(); err != nil { + a.log.Errorf("[ %s ] failed validating chunk: %s ", a.name, err) + return nil + } + + switch c := c.(type) { + case *chunkInit: + packets, err = a.handleInit(p, c) + + case *chunkInitAck: + err = a.handleInitAck(p, c) + + case *chunkAbort: + var errStr string + for _, e := range c.errorCauses { + errStr += fmt.Sprintf("(%s)", e) + } + return fmt.Errorf("[%s] %w: %s", a.name, errChunk, errStr) + + case *chunkError: + var errStr string + for _, e := range c.errorCauses { + errStr += fmt.Sprintf("(%s)", e) + } + a.log.Debugf("[%s] Error chunk, with following errors: %s", a.name, errStr) + + case *chunkHeartbeat: + packets = a.handleHeartbeat(c) + + case *chunkCookieEcho: + packets = a.handleCookieEcho(c) + + case *chunkCookieAck: + a.handleCookieAck() + + case *chunkPayloadData: + packets = a.handleData(c) + + case *chunkSelectiveAck: + err = a.handleSack(c) + + case *chunkReconfig: + packets, err = a.handleReconfig(c) + + case *chunkForwardTSN: + packets = a.handleForwardTSN(c) + + default: + err = errors.Errorf("unhandled chunk type") + } + + // Log and return, the only condition that is fatal is a ABORT chunk + if err != nil { + a.log.Errorf("Failed to handle chunk: %v", err) + return nil + } + + if len(packets) > 0 { + a.controlQueue.pushAll(packets) + a.awakeWriteLoop() + } + + return nil +} + +func (a *Association) onRetransmissionTimeout(id int, nRtos uint) { + a.lock.Lock() + defer a.lock.Unlock() + + if id == timerT1Init { + err := a.sendInit() + if err != nil { + a.log.Debugf("[%s] failed to retransmit init (nRtos=%d): %v", a.name, nRtos, err) + } + return + } + + if id == timerT1Cookie { + err := a.sendCookieEcho() + if err != nil { + a.log.Debugf("[%s] failed to retransmit cookie-echo (nRtos=%d): %v", a.name, nRtos, err) + } + return + } + + if id == timerT3RTX { + a.stats.incT3Timeouts() + + // RFC 4960 sec 6.3.3 + // E1) For the destination address for which the timer expires, adjust + // its ssthresh with rules defined in Section 7.2.3 and set the + // cwnd <- MTU. + // RFC 4960 sec 7.2.3 + // When the T3-rtx timer expires on an address, SCTP should perform slow + // start by: + // ssthresh = max(cwnd/2, 4*MTU) + // cwnd = 1*MTU + + a.ssthresh = max32(a.cwnd/2, 4*a.mtu) + a.cwnd = a.mtu + a.log.Tracef("[%s] updated cwnd=%d ssthresh=%d inflight=%d (RTO)", + a.name, a.cwnd, a.ssthresh, a.inflightQueue.getNumBytes()) + + // RFC 3758 sec 3.5 + // A5) Any time the T3-rtx timer expires, on any destination, the sender + // SHOULD try to advance the "Advanced.Peer.Ack.Point" by following + // the procedures outlined in C2 - C5. + if a.useForwardTSN { + // RFC 3758 Sec 3.5 C2 + for i := a.advancedPeerTSNAckPoint + 1; ; i++ { + c, ok := a.inflightQueue.get(i) + if !ok { + break + } + if !c.abandoned() { + break + } + a.advancedPeerTSNAckPoint = i + } + + // RFC 3758 Sec 3.5 C3 + if sna32GT(a.advancedPeerTSNAckPoint, a.cumulativeTSNAckPoint) { + a.willSendForwardTSN = true + } + } + + a.log.Debugf("[%s] T3-rtx timed out: nRtos=%d cwnd=%d ssthresh=%d", a.name, nRtos, a.cwnd, a.ssthresh) + + /* + a.log.Debugf(" - advancedPeerTSNAckPoint=%d", a.advancedPeerTSNAckPoint) + a.log.Debugf(" - cumulativeTSNAckPoint=%d", a.cumulativeTSNAckPoint) + a.inflightQueue.updateSortedKeys() + for i, tsn := range a.inflightQueue.sorted { + if c, ok := a.inflightQueue.get(tsn); ok { + a.log.Debugf(" - [%d] tsn=%d acked=%v abandoned=%v (%v,%v) len=%d", + i, c.tsn, c.acked, c.abandoned(), c.beginningFragment, c.endingFragment, len(c.userData)) + } + } + */ + + a.inflightQueue.markAllToRetrasmit() + a.awakeWriteLoop() + + return + } + + if id == timerReconfig { + a.willRetransmitReconfig = true + a.awakeWriteLoop() + } +} + +func (a *Association) onRetransmissionFailure(id int) { + a.lock.Lock() + defer a.lock.Unlock() + + if id == timerT1Init { + a.log.Errorf("[%s] retransmission failure: T1-init", a.name) + a.handshakeCompletedCh <- errors.Errorf("handshake failed (INIT ACK)") + return + } + + if id == timerT1Cookie { + a.log.Errorf("[%s] retransmission failure: T1-cookie", a.name) + a.handshakeCompletedCh <- errors.Errorf("handshake failed (COOKIE ECHO)") + return + } + + if id == timerT3RTX { + // T3-rtx timer will not fail by design + // Justifications: + // * ICE would fail if the connectivity is lost + // * WebRTC spec is not clear how this incident should be reported to ULP + a.log.Errorf("[%s] retransmission failure: T3-rtx (DATA)", a.name) + return + } +} + +func (a *Association) onAckTimeout() { + a.lock.Lock() + defer a.lock.Unlock() + + a.log.Tracef("[%s] ack timed out (ackState: %d)", a.name, a.ackState) + a.stats.incAckTimeouts() + + a.ackState = ackStateImmediate + a.awakeWriteLoop() +} + +// bufferedAmount returns total amount (in bytes) of currently buffered user data. +// This is used only by testing. +func (a *Association) bufferedAmount() int { + a.lock.RLock() + defer a.lock.RUnlock() + + return a.pendingQueue.getNumBytes() + a.inflightQueue.getNumBytes() +} + +// MaxMessageSize returns the maximum message size you can send. +func (a *Association) MaxMessageSize() uint32 { + return atomic.LoadUint32(&a.maxMessageSize) +} + +// SetMaxMessageSize sets the maximum message size you can send. +func (a *Association) SetMaxMessageSize(maxMsgSize uint32) { + atomic.StoreUint32(&a.maxMessageSize, maxMsgSize) +} diff --git a/vendor/github.com/pion/sctp/association_stats.go b/vendor/github.com/pion/sctp/association_stats.go new file mode 100644 index 0000000..4ccb7be --- /dev/null +++ b/vendor/github.com/pion/sctp/association_stats.go @@ -0,0 +1,61 @@ +package sctp + +import ( + "sync/atomic" +) + +type associationStats struct { + nDATAs uint64 + nSACKs uint64 + nT3Timeouts uint64 + nAckTimeouts uint64 + nFastRetrans uint64 +} + +func (s *associationStats) incDATAs() { + atomic.AddUint64(&s.nDATAs, 1) +} + +func (s *associationStats) getNumDATAs() uint64 { + return atomic.LoadUint64(&s.nDATAs) +} + +func (s *associationStats) incSACKs() { + atomic.AddUint64(&s.nSACKs, 1) +} + +func (s *associationStats) getNumSACKs() uint64 { + return atomic.LoadUint64(&s.nSACKs) +} + +func (s *associationStats) incT3Timeouts() { + atomic.AddUint64(&s.nT3Timeouts, 1) +} + +func (s *associationStats) getNumT3Timeouts() uint64 { + return atomic.LoadUint64(&s.nT3Timeouts) +} + +func (s *associationStats) incAckTimeouts() { + atomic.AddUint64(&s.nAckTimeouts, 1) +} + +func (s *associationStats) getNumAckTimeouts() uint64 { + return atomic.LoadUint64(&s.nAckTimeouts) +} + +func (s *associationStats) incFastRetrans() { + atomic.AddUint64(&s.nFastRetrans, 1) +} + +func (s *associationStats) getNumFastRetrans() uint64 { + return atomic.LoadUint64(&s.nFastRetrans) +} + +func (s *associationStats) reset() { + atomic.StoreUint64(&s.nDATAs, 0) + atomic.StoreUint64(&s.nSACKs, 0) + atomic.StoreUint64(&s.nT3Timeouts, 0) + atomic.StoreUint64(&s.nAckTimeouts, 0) + atomic.StoreUint64(&s.nFastRetrans, 0) +} diff --git a/vendor/github.com/pion/sctp/chunk.go b/vendor/github.com/pion/sctp/chunk.go new file mode 100644 index 0000000..ec47da1 --- /dev/null +++ b/vendor/github.com/pion/sctp/chunk.go @@ -0,0 +1,9 @@ +package sctp + +type chunk interface { + unmarshal(raw []byte) error + marshal() ([]byte, error) + check() (bool, error) + + valueLength() int +} diff --git a/vendor/github.com/pion/sctp/chunk_abort.go b/vendor/github.com/pion/sctp/chunk_abort.go new file mode 100644 index 0000000..9fd4692 --- /dev/null +++ b/vendor/github.com/pion/sctp/chunk_abort.go @@ -0,0 +1,88 @@ +package sctp // nolint:dupl + +import ( + "fmt" + + "github.com/pkg/errors" +) + +/* +Abort represents an SCTP Chunk of type ABORT + +The ABORT chunk is sent to the peer of an association to close the +association. The ABORT chunk may contain Cause Parameters to inform +the receiver about the reason of the abort. DATA chunks MUST NOT be +bundled with ABORT. Control chunks (except for INIT, INIT ACK, and +SHUTDOWN COMPLETE) MAY be bundled with an ABORT, but they MUST be +placed before the ABORT in the SCTP packet or they will be ignored by +the receiver. + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| Type = 6 |Reserved |T| Length | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| | +| zero or more Error Causes | +| | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +*/ +type chunkAbort struct { + chunkHeader + errorCauses []errorCause +} + +func (a *chunkAbort) unmarshal(raw []byte) error { + if err := a.chunkHeader.unmarshal(raw); err != nil { + return err + } + + if a.typ != ctAbort { + return errors.Errorf("ChunkType is not of type ABORT, actually is %s", a.typ.String()) + } + + offset := chunkHeaderSize + for { + if len(raw)-offset < 4 { + break + } + + e, err := buildErrorCause(raw[offset:]) + if err != nil { + return errors.Wrap(err, "Failed build Abort Chunk") + } + + offset += int(e.length()) + a.errorCauses = append(a.errorCauses, e) + } + return nil +} + +func (a *chunkAbort) marshal() ([]byte, error) { + a.chunkHeader.typ = ctAbort + a.flags = 0x00 + a.raw = []byte{} + for _, ec := range a.errorCauses { + raw, err := ec.marshal() + if err != nil { + return nil, err + } + a.raw = append(a.raw, raw...) + } + return a.chunkHeader.marshal() +} + +func (a *chunkAbort) check() (abort bool, err error) { + return false, nil +} + +// String makes chunkAbort printable +func (a *chunkAbort) String() string { + res := a.chunkHeader.String() + + for _, cause := range a.errorCauses { + res += fmt.Sprintf("\n - %s", cause) + } + + return res +} diff --git a/vendor/github.com/pion/sctp/chunk_cookie_ack.go b/vendor/github.com/pion/sctp/chunk_cookie_ack.go new file mode 100644 index 0000000..83b6f71 --- /dev/null +++ b/vendor/github.com/pion/sctp/chunk_cookie_ack.go @@ -0,0 +1,44 @@ +package sctp + +import ( + "github.com/pkg/errors" +) + +/* +chunkCookieAck represents an SCTP Chunk of type chunkCookieAck + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| Type = 11 |Chunk Flags | Length = 4 | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +*/ +type chunkCookieAck struct { + chunkHeader +} + +func (c *chunkCookieAck) unmarshal(raw []byte) error { + if err := c.chunkHeader.unmarshal(raw); err != nil { + return err + } + + if c.typ != ctCookieAck { + return errors.Errorf("ChunkType is not of type COOKIEACK, actually is %s", c.typ.String()) + } + + return nil +} + +func (c *chunkCookieAck) marshal() ([]byte, error) { + c.chunkHeader.typ = ctCookieAck + return c.chunkHeader.marshal() +} + +func (c *chunkCookieAck) check() (abort bool, err error) { + return false, nil +} + +// String makes chunkCookieAck printable +func (c *chunkCookieAck) String() string { + return c.chunkHeader.String() +} diff --git a/vendor/github.com/pion/sctp/chunk_cookie_echo.go b/vendor/github.com/pion/sctp/chunk_cookie_echo.go new file mode 100644 index 0000000..3f0ed36 --- /dev/null +++ b/vendor/github.com/pion/sctp/chunk_cookie_echo.go @@ -0,0 +1,46 @@ +package sctp + +import ( + "github.com/pkg/errors" +) + +/* +CookieEcho represents an SCTP Chunk of type CookieEcho + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| Type = 10 |Chunk Flags | Length | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| Cookie | +| | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +*/ +type chunkCookieEcho struct { + chunkHeader + cookie []byte +} + +func (c *chunkCookieEcho) unmarshal(raw []byte) error { + if err := c.chunkHeader.unmarshal(raw); err != nil { + return err + } + + if c.typ != ctCookieEcho { + return errors.Errorf("ChunkType is not of type COOKIEECHO, actually is %s", c.typ.String()) + } + c.cookie = c.raw + + return nil +} + +func (c *chunkCookieEcho) marshal() ([]byte, error) { + c.chunkHeader.typ = ctCookieEcho + c.chunkHeader.raw = c.cookie + return c.chunkHeader.marshal() +} + +func (c *chunkCookieEcho) check() (abort bool, err error) { + return false, nil +} diff --git a/vendor/github.com/pion/sctp/chunk_error.go b/vendor/github.com/pion/sctp/chunk_error.go new file mode 100644 index 0000000..eb29324 --- /dev/null +++ b/vendor/github.com/pion/sctp/chunk_error.go @@ -0,0 +1,95 @@ +package sctp // nolint:dupl + +import ( + "fmt" + + "github.com/pkg/errors" +) + +/* + Operation Error (ERROR) (9) + + An endpoint sends this chunk to its peer endpoint to notify it of + certain error conditions. It contains one or more error causes. An + Operation Error is not considered fatal in and of itself, but may be + used with an ERROR chunk to report a fatal condition. It has the + following parameters: + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Type = 9 | Chunk Flags | Length | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + \ \ + / one or more Error Causes / + \ \ + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + Chunk Flags: 8 bits + + Set to 0 on transmit and ignored on receipt. + + Length: 16 bits (unsigned integer) + + Set to the size of the chunk in bytes, including the chunk header + and all the Error Cause fields present. +*/ +type chunkError struct { + chunkHeader + errorCauses []errorCause +} + +func (a *chunkError) unmarshal(raw []byte) error { + if err := a.chunkHeader.unmarshal(raw); err != nil { + return err + } + + if a.typ != ctError { + return errors.Errorf("ChunkType is not of type ctError, actually is %s", a.typ.String()) + } + + offset := chunkHeaderSize + for { + if len(raw)-offset < 4 { + break + } + + e, err := buildErrorCause(raw[offset:]) + if err != nil { + return errors.Wrap(err, "Failed build Error Chunk") + } + + offset += int(e.length()) + a.errorCauses = append(a.errorCauses, e) + } + return nil +} + +func (a *chunkError) marshal() ([]byte, error) { + a.chunkHeader.typ = ctError + a.flags = 0x00 + a.raw = []byte{} + for _, ec := range a.errorCauses { + raw, err := ec.marshal() + if err != nil { + return nil, err + } + a.raw = append(a.raw, raw...) + } + return a.chunkHeader.marshal() +} + +func (a *chunkError) check() (abort bool, err error) { + return false, nil +} + +// String makes chunkError printable +func (a *chunkError) String() string { + res := a.chunkHeader.String() + + for _, cause := range a.errorCauses { + res += fmt.Sprintf("\n - %s", cause) + } + + return res +} diff --git a/vendor/github.com/pion/sctp/chunk_forward_tsn.go b/vendor/github.com/pion/sctp/chunk_forward_tsn.go new file mode 100644 index 0000000..f6ff357 --- /dev/null +++ b/vendor/github.com/pion/sctp/chunk_forward_tsn.go @@ -0,0 +1,145 @@ +package sctp + +import ( + "encoding/binary" + "fmt" + + "github.com/pkg/errors" +) + +// This chunk shall be used by the data sender to inform the data +// receiver to adjust its cumulative received TSN point forward because +// some missing TSNs are associated with data chunks that SHOULD NOT be +// transmitted or retransmitted by the sender. +// +// 0 1 2 3 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | Type = 192 | Flags = 0x00 | Length = Variable | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | New Cumulative TSN | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | Stream-1 | Stream Sequence-1 | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// \ / +// / \ +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | Stream-N | Stream Sequence-N | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +type chunkForwardTSN struct { + chunkHeader + + // This indicates the new cumulative TSN to the data receiver. Upon + // the reception of this value, the data receiver MUST consider + // any missing TSNs earlier than or equal to this value as received, + // and stop reporting them as gaps in any subsequent SACKs. + newCumulativeTSN uint32 + + streams []chunkForwardTSNStream +} + +const ( + newCumulativeTSNLength = 4 + forwardTSNStreamLength = 4 +) + +var errMarshalStreamFailed = errors.New("failed to marshal stream") + +func (c *chunkForwardTSN) unmarshal(raw []byte) error { + if err := c.chunkHeader.unmarshal(raw); err != nil { + return err + } + + if len(c.raw) < newCumulativeTSNLength { + return errors.New("chunk to short") + } + + c.newCumulativeTSN = binary.BigEndian.Uint32(c.raw[0:]) + + offset := newCumulativeTSNLength + remaining := len(c.raw) - offset + for remaining > 0 { + s := chunkForwardTSNStream{} + + if err := s.unmarshal(c.raw[offset:]); err != nil { + return fmt.Errorf("failed to unmarshal stream: %w", err) + } + + c.streams = append(c.streams, s) + + offset += s.length() + remaining -= s.length() + } + + return nil +} + +func (c *chunkForwardTSN) marshal() ([]byte, error) { + out := make([]byte, newCumulativeTSNLength) + binary.BigEndian.PutUint32(out[0:], c.newCumulativeTSN) + + for _, s := range c.streams { + b, err := s.marshal() + if err != nil { + return nil, fmt.Errorf("%w: %v", errMarshalStreamFailed, err) + } + out = append(out, b...) + } + + c.typ = ctForwardTSN + c.raw = out + return c.chunkHeader.marshal() +} + +func (c *chunkForwardTSN) check() (abort bool, err error) { + return true, nil +} + +// String makes chunkForwardTSN printable +func (c *chunkForwardTSN) String() string { + res := fmt.Sprintf("New Cumulative TSN: %d\n", c.newCumulativeTSN) + for _, s := range c.streams { + res += fmt.Sprintf(" - si=%d, ssn=%d\n", s.identifier, s.sequence) + } + return res +} + +type chunkForwardTSNStream struct { + // This field holds a stream number that was skipped by this + // FWD-TSN. + identifier uint16 + + // This field holds the sequence number associated with the stream + // that was skipped. The stream sequence field holds the largest + // stream sequence number in this stream being skipped. The receiver + // of the FWD-TSN's can use the Stream-N and Stream Sequence-N fields + // to enable delivery of any stranded TSN's that remain on the stream + // re-ordering queues. This field MUST NOT report TSN's corresponding + // to DATA chunks that are marked as unordered. For ordered DATA + // chunks this field MUST be filled in. + sequence uint16 +} + +func (s *chunkForwardTSNStream) length() int { + return forwardTSNStreamLength +} + +func (s *chunkForwardTSNStream) unmarshal(raw []byte) error { + if len(raw) < forwardTSNStreamLength { + return errors.New("stream to short") + } + s.identifier = binary.BigEndian.Uint16(raw[0:]) + s.sequence = binary.BigEndian.Uint16(raw[2:]) + + return nil +} + +func (s *chunkForwardTSNStream) marshal() ([]byte, error) { // nolint:unparam + out := make([]byte, forwardTSNStreamLength) + + binary.BigEndian.PutUint16(out[0:], s.identifier) + binary.BigEndian.PutUint16(out[2:], s.sequence) + + return out, nil +} diff --git a/vendor/github.com/pion/sctp/chunk_heartbeat.go b/vendor/github.com/pion/sctp/chunk_heartbeat.go new file mode 100644 index 0000000..f2026a4 --- /dev/null +++ b/vendor/github.com/pion/sctp/chunk_heartbeat.go @@ -0,0 +1,75 @@ +package sctp + +import ( + "github.com/pkg/errors" +) + +/* +chunkHeartbeat represents an SCTP Chunk of type HEARTBEAT + +An endpoint should send this chunk to its peer endpoint to probe the +reachability of a particular destination transport address defined in +the present association. + +The parameter field contains the Heartbeat Information, which is a +variable-length opaque data structure understood only by the sender. + + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| Type = 4 | Chunk Flags | Heartbeat Length | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| | +| Heartbeat Information TLV (Variable-Length) | +| | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +Defined as a variable-length parameter using the format described +in Section 3.2.1, i.e.: + +Variable Parameters Status Type Value +------------------------------------------------------------- +heartbeat Info Mandatory 1 + +*/ +type chunkHeartbeat struct { + chunkHeader + params []param +} + +func (h *chunkHeartbeat) unmarshal(raw []byte) error { + if err := h.chunkHeader.unmarshal(raw); err != nil { + return err + } else if h.typ != ctHeartbeat { + return errors.Errorf("ChunkType is not of type HEARTBEAT, actually is %s", h.typ.String()) + } + + if len(raw) <= chunkHeaderSize { + return errors.Errorf("Heartbeat is not long enough to contain Heartbeat Info %d", len(raw)) + } + + pType, err := parseParamType(raw[chunkHeaderSize:]) + if err != nil { + return errors.Wrap(err, "failed to parse param type") + } + if pType != heartbeatInfo { + return errors.Errorf("Heartbeat should only have HEARTBEAT param, instead have %s", pType.String()) + } + + p, err := buildParam(pType, raw[chunkHeaderSize:]) + if err != nil { + return errors.Wrap(err, "Failed unmarshalling param in Heartbeat Chunk") + } + h.params = append(h.params, p) + + return nil +} + +func (h *chunkHeartbeat) Marshal() ([]byte, error) { + return nil, errors.Errorf("Unimplemented") +} + +func (h *chunkHeartbeat) check() (abort bool, err error) { + return false, nil +} diff --git a/vendor/github.com/pion/sctp/chunk_heartbeat_ack.go b/vendor/github.com/pion/sctp/chunk_heartbeat_ack.go new file mode 100644 index 0000000..ff84090 --- /dev/null +++ b/vendor/github.com/pion/sctp/chunk_heartbeat_ack.go @@ -0,0 +1,86 @@ +package sctp + +import ( + "github.com/pkg/errors" +) + +/* +chunkHeartbeatAck represents an SCTP Chunk of type HEARTBEAT ACK + +An endpoint should send this chunk to its peer endpoint as a response +to a HEARTBEAT chunk (see Section 8.3). A HEARTBEAT ACK is always +sent to the source IP address of the IP datagram containing the +HEARTBEAT chunk to which this ack is responding. + +The parameter field contains a variable-length opaque data structure. + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| Type = 5 | Chunk Flags | Heartbeat Ack Length | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| | +| Heartbeat Information TLV (Variable-Length) | +| | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + +Defined as a variable-length parameter using the format described +in Section 3.2.1, i.e.: + +Variable Parameters Status Type Value +------------------------------------------------------------- +Heartbeat Info Mandatory 1 + +*/ +type chunkHeartbeatAck struct { + chunkHeader + params []param +} + +func (h *chunkHeartbeatAck) unmarshal(raw []byte) error { + return errors.Errorf("Unimplemented") +} + +func (h *chunkHeartbeatAck) marshal() ([]byte, error) { + if len(h.params) != 1 { + return nil, errors.Errorf("Heartbeat Ack must have one param") + } + + switch h.params[0].(type) { + case *paramHeartbeatInfo: + // ParamHeartbeatInfo is valid + default: + return nil, errors.Errorf("Heartbeat Ack must have one param, and it should be a HeartbeatInfo") + } + + out := make([]byte, 0) + for idx, p := range h.params { + pp, err := p.marshal() + if err != nil { + return nil, errors.Wrap(err, "Unable to marshal parameter for Heartbeat Ack") + } + + out = append(out, pp...) + + // Chunks (including Type, Length, and Value fields) are padded out + // by the sender with all zero bytes to be a multiple of 4 bytes + // long. This padding MUST NOT be more than 3 bytes in total. The + // Chunk Length value does not include terminating padding of the + // chunk. *However, it does include padding of any variable-length + // parameter except the last parameter in the chunk.* The receiver + // MUST ignore the padding. + if idx != len(h.params)-1 { + out = padByte(out, getPadding(len(pp))) + } + } + + h.chunkHeader.typ = ctHeartbeatAck + h.chunkHeader.raw = out + + return h.chunkHeader.marshal() +} + +func (h *chunkHeartbeatAck) check() (abort bool, err error) { + return false, nil +} diff --git a/vendor/github.com/pion/sctp/chunk_init.go b/vendor/github.com/pion/sctp/chunk_init.go new file mode 100644 index 0000000..65b9b1a --- /dev/null +++ b/vendor/github.com/pion/sctp/chunk_init.go @@ -0,0 +1,123 @@ +package sctp // nolint:dupl + +import ( + "fmt" + + "github.com/pkg/errors" +) + +/* +Init represents an SCTP Chunk of type INIT + +See chunkInitCommon for the fixed headers + +Variable Parameters Status Type Value +------------------------------------------------------------- +IPv4 IP (Note 1) Optional 5 +IPv6 IP (Note 1) Optional 6 +Cookie Preservative Optional 9 +Reserved for ECN Capable (Note 2) Optional 32768 (0x8000) +Host Name IP (Note 3) Optional 11 +Supported IP Types (Note 4) Optional 12 +*/ +type chunkInit struct { + chunkHeader + chunkInitCommon +} + +func (i *chunkInit) unmarshal(raw []byte) error { + if err := i.chunkHeader.unmarshal(raw); err != nil { + return err + } + + if i.typ != ctInit { + return errors.Errorf("ChunkType is not of type INIT, actually is %s", i.typ.String()) + } else if len(i.raw) < initChunkMinLength { + return errors.Errorf("Chunk Value isn't long enough for mandatory parameters exp: %d actual: %d", initChunkMinLength, len(i.raw)) + } + + // The Chunk Flags field in INIT is reserved, and all bits in it should + // be set to 0 by the sender and ignored by the receiver. The sequence + // of parameters within an INIT can be processed in any order. + if i.flags != 0 { + return errors.New("ChunkType of type INIT flags must be all 0") + } + + if err := i.chunkInitCommon.unmarshal(i.raw); err != nil { + return errors.Wrap(err, "Failed to unmarshal INIT body") + } + + return nil +} + +func (i *chunkInit) marshal() ([]byte, error) { + initShared, err := i.chunkInitCommon.marshal() + if err != nil { + return nil, errors.Wrap(err, "Failed marshaling INIT common data") + } + + i.chunkHeader.typ = ctInit + i.chunkHeader.raw = initShared + return i.chunkHeader.marshal() +} + +func (i *chunkInit) check() (abort bool, err error) { + // The receiver of the INIT (the responding end) records the value of + // the Initiate Tag parameter. This value MUST be placed into the + // Verification Tag field of every SCTP packet that the receiver of + // the INIT transmits within this association. + // + // The Initiate Tag is allowed to have any value except 0. See + // Section 5.3.1 for more on the selection of the tag value. + // + // If the value of the Initiate Tag in a received INIT chunk is found + // to be 0, the receiver MUST treat it as an error and close the + // association by transmitting an ABORT. + if i.initiateTag == 0 { + abort = true + return abort, errors.New("ChunkType of type INIT InitiateTag must not be 0") + } + + // Defines the maximum number of streams the sender of this INIT + // chunk allows the peer end to create in this association. The + // value 0 MUST NOT be used. + // + // Note: There is no negotiation of the actual number of streams but + // instead the two endpoints will use the min(requested, offered). + // See Section 5.1.1 for details. + // + // Note: A receiver of an INIT with the MIS value of 0 SHOULD abort + // the association. + if i.numInboundStreams == 0 { + abort = true + return abort, errors.New("INIT inbound stream request must be > 0") + } + + // Defines the number of outbound streams the sender of this INIT + // chunk wishes to create in this association. The value of 0 MUST + // NOT be used. + // + // Note: A receiver of an INIT with the OS value set to 0 SHOULD + // abort the association. + + if i.numOutboundStreams == 0 { + abort = true + return abort, errors.New("INIT outbound stream request must be > 0") + } + + // An SCTP receiver MUST be able to receive a minimum of 1500 bytes in + // one SCTP packet. This means that an SCTP endpoint MUST NOT indicate + // less than 1500 bytes in its initial a_rwnd sent in the INIT or INIT + // ACK. + if i.advertisedReceiverWindowCredit < 1500 { + abort = true + return abort, errors.New("INIT Advertised Receiver Window Credit (a_rwnd) must be >= 1500") + } + + return false, nil +} + +// String makes chunkInit printable +func (i *chunkInit) String() string { + return fmt.Sprintf("%s\n%s", i.chunkHeader, i.chunkInitCommon) +} diff --git a/vendor/github.com/pion/sctp/chunk_init_ack.go b/vendor/github.com/pion/sctp/chunk_init_ack.go new file mode 100644 index 0000000..551bcea --- /dev/null +++ b/vendor/github.com/pion/sctp/chunk_init_ack.go @@ -0,0 +1,126 @@ +package sctp // nolint:dupl + +import ( + "fmt" + + "github.com/pkg/errors" +) + +/* +chunkInitAck represents an SCTP Chunk of type INIT ACK + +See chunkInitCommon for the fixed headers + +Variable Parameters Status Type Value +------------------------------------------------------------- +State Cookie Mandatory 7 +IPv4 IP (Note 1) Optional 5 +IPv6 IP (Note 1) Optional 6 +Unrecognized Parameter Optional 8 +Reserved for ECN Capable (Note 2) Optional 32768 (0x8000) +Host Name IP (Note 3) Optional 11<Paste> + +*/ +type chunkInitAck struct { + chunkHeader + chunkInitCommon +} + +func (i *chunkInitAck) unmarshal(raw []byte) error { + if err := i.chunkHeader.unmarshal(raw); err != nil { + return err + } + + if i.typ != ctInitAck { + return errors.Errorf("ChunkType is not of type INIT ACK, actually is %s", i.typ.String()) + } else if len(i.raw) < initChunkMinLength { + return errors.Errorf("Chunk Value isn't long enough for mandatory parameters exp: %d actual: %d", initChunkMinLength, len(i.raw)) + } + + // The Chunk Flags field in INIT is reserved, and all bits in it should + // be set to 0 by the sender and ignored by the receiver. The sequence + // of parameters within an INIT can be processed in any order. + if i.flags != 0 { + return errors.New("ChunkType of type INIT ACK flags must be all 0") + } + + if err := i.chunkInitCommon.unmarshal(i.raw); err != nil { + return errors.Wrap(err, "Failed to unmarshal INIT body") + } + + return nil +} + +func (i *chunkInitAck) marshal() ([]byte, error) { + initShared, err := i.chunkInitCommon.marshal() + if err != nil { + return nil, errors.Wrap(err, "Failed marshaling INIT common data") + } + + i.chunkHeader.typ = ctInitAck + i.chunkHeader.raw = initShared + return i.chunkHeader.marshal() +} + +func (i *chunkInitAck) check() (abort bool, err error) { + // The receiver of the INIT ACK records the value of the Initiate Tag + // parameter. This value MUST be placed into the Verification Tag + // field of every SCTP packet that the INIT ACK receiver transmits + // within this association. + // + // The Initiate Tag MUST NOT take the value 0. See Section 5.3.1 for + // more on the selection of the Initiate Tag value. + // + // If the value of the Initiate Tag in a received INIT ACK chunk is + // found to be 0, the receiver MUST destroy the association + // discarding its TCB. The receiver MAY send an ABORT for debugging + // purpose. + if i.initiateTag == 0 { + abort = true + return abort, errors.New("ChunkType of type INIT ACK InitiateTag must not be 0") + } + + // Defines the maximum number of streams the sender of this INIT ACK + // chunk allows the peer end to create in this association. The + // value 0 MUST NOT be used. + // + // Note: There is no negotiation of the actual number of streams but + // instead the two endpoints will use the min(requested, offered). + // See Section 5.1.1 for details. + // + // Note: A receiver of an INIT ACK with the MIS value set to 0 SHOULD + // destroy the association discarding its TCB. + if i.numInboundStreams == 0 { + abort = true + return abort, errors.New("INIT ACK inbound stream request must be > 0") + } + + // Defines the number of outbound streams the sender of this INIT ACK + // chunk wishes to create in this association. The value of 0 MUST + // NOT be used, and the value MUST NOT be greater than the MIS value + // sent in the INIT chunk. + // + // Note: A receiver of an INIT ACK with the OS value set to 0 SHOULD + // destroy the association discarding its TCB. + + if i.numOutboundStreams == 0 { + abort = true + return abort, errors.New("INIT ACK outbound stream request must be > 0") + } + + // An SCTP receiver MUST be able to receive a minimum of 1500 bytes in + // one SCTP packet. This means that an SCTP endpoint MUST NOT indicate + // less than 1500 bytes in its initial a_rwnd sent in the INIT or INIT + // ACK. + if i.advertisedReceiverWindowCredit < 1500 { + abort = true + return abort, errors.New("INIT ACK Advertised Receiver Window Credit (a_rwnd) must be >= 1500") + } + + return false, nil +} + +// String makes chunkInitAck printable +func (i *chunkInitAck) String() string { + return fmt.Sprintf("%s\n%s", i.chunkHeader, i.chunkInitCommon) +} diff --git a/vendor/github.com/pion/sctp/chunk_init_common.go b/vendor/github.com/pion/sctp/chunk_init_common.go new file mode 100644 index 0000000..f64be78 --- /dev/null +++ b/vendor/github.com/pion/sctp/chunk_init_common.go @@ -0,0 +1,155 @@ +package sctp + +import ( + "encoding/binary" + "fmt" + + "github.com/pkg/errors" +) + +/* +chunkInitCommon represents an SCTP Chunk body of type INIT and INIT ACK + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| Type = 1 | Chunk Flags | Chunk Length | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| Initiate Tag | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| Advertised Receiver Window Credit (a_rwnd) | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| Number of Outbound Streams | Number of Inbound Streams | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| Initial TSN | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| | +| Optional/Variable-Length Parameters | +| | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +The INIT chunk contains the following parameters. Unless otherwise +noted, each parameter MUST only be included once in the INIT chunk. + +Fixed Parameters Status +---------------------------------------------- +Initiate Tag Mandatory +Advertised Receiver Window Credit Mandatory +Number of Outbound Streams Mandatory +Number of Inbound Streams Mandatory +Initial TSN Mandatory +*/ + +type chunkInitCommon struct { + initiateTag uint32 + advertisedReceiverWindowCredit uint32 + numOutboundStreams uint16 + numInboundStreams uint16 + initialTSN uint32 + params []param +} + +const ( + initChunkMinLength = 16 + initOptionalVarHeaderLength = 4 +) + +func (i *chunkInitCommon) unmarshal(raw []byte) error { + i.initiateTag = binary.BigEndian.Uint32(raw[0:]) + i.advertisedReceiverWindowCredit = binary.BigEndian.Uint32(raw[4:]) + i.numOutboundStreams = binary.BigEndian.Uint16(raw[8:]) + i.numInboundStreams = binary.BigEndian.Uint16(raw[10:]) + i.initialTSN = binary.BigEndian.Uint32(raw[12:]) + + // https://tools.ietf.org/html/rfc4960#section-3.2.1 + // + // Chunk values of SCTP control chunks consist of a chunk-type-specific + // header of required fields, followed by zero or more parameters. The + // optional and variable-length parameters contained in a chunk are + // defined in a Type-Length-Value format as shown below. + // + // 0 1 2 3 + // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // | Parameter Type | Parameter Length | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // | | + // | Parameter Value | + // | | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + offset := initChunkMinLength + remaining := len(raw) - offset + for remaining > 0 { + if remaining > initOptionalVarHeaderLength { + pType, err := parseParamType(raw[offset:]) + if err != nil { + return errors.Wrap(err, "failed to parse param type") + } + p, err := buildParam(pType, raw[offset:]) + if err != nil { + return errors.Wrap(err, "Failed unmarshalling param in Init Chunk") + } + i.params = append(i.params, p) + padding := getPadding(p.length()) + offset += p.length() + padding + remaining -= p.length() + padding + } else { + break + } + } + + return nil +} + +func (i *chunkInitCommon) marshal() ([]byte, error) { + out := make([]byte, initChunkMinLength) + binary.BigEndian.PutUint32(out[0:], i.initiateTag) + binary.BigEndian.PutUint32(out[4:], i.advertisedReceiverWindowCredit) + binary.BigEndian.PutUint16(out[8:], i.numOutboundStreams) + binary.BigEndian.PutUint16(out[10:], i.numInboundStreams) + binary.BigEndian.PutUint32(out[12:], i.initialTSN) + for idx, p := range i.params { + pp, err := p.marshal() + if err != nil { + return nil, errors.Wrap(err, "Unable to marshal parameter for INIT/INITACK") + } + + out = append(out, pp...) + + // Chunks (including Type, Length, and Value fields) are padded out + // by the sender with all zero bytes to be a multiple of 4 bytes + // long. This padding MUST NOT be more than 3 bytes in total. The + // Chunk Length value does not include terminating padding of the + // chunk. *However, it does include padding of any variable-length + // parameter except the last parameter in the chunk.* The receiver + // MUST ignore the padding. + if idx != len(i.params)-1 { + out = padByte(out, getPadding(len(pp))) + } + } + + return out, nil +} + +// String makes chunkInitCommon printable +func (i chunkInitCommon) String() string { + format := `initiateTag: %d + advertisedReceiverWindowCredit: %d + numOutboundStreams: %d + numInboundStreams: %d + initialTSN: %d` + + res := fmt.Sprintf(format, + i.initiateTag, + i.advertisedReceiverWindowCredit, + i.numOutboundStreams, + i.numInboundStreams, + i.initialTSN, + ) + + for i, param := range i.params { + res += fmt.Sprintf("Param %d:\n %s", i, param) + } + return res +} diff --git a/vendor/github.com/pion/sctp/chunk_payload_data.go b/vendor/github.com/pion/sctp/chunk_payload_data.go new file mode 100644 index 0000000..3e5e346 --- /dev/null +++ b/vendor/github.com/pion/sctp/chunk_payload_data.go @@ -0,0 +1,195 @@ +package sctp + +import ( + "encoding/binary" + "fmt" + "time" +) + +/* +chunkPayloadData represents an SCTP Chunk of type DATA + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| Type = 0 | Reserved|U|B|E| Length | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| TSN | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| Stream Identifier S | Stream Sequence Number n | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| Payload Protocol Identifier | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| | +| User Data (seq n of Stream S) | +| | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + +An unfragmented user message shall have both the B and E bits set to +'1'. Setting both B and E bits to '0' indicates a middle fragment of +a multi-fragment user message, as summarized in the following table: + B E Description +============================================================ +| 1 0 | First piece of a fragmented user message | ++----------------------------------------------------------+ +| 0 0 | Middle piece of a fragmented user message | ++----------------------------------------------------------+ +| 0 1 | Last piece of a fragmented user message | ++----------------------------------------------------------+ +| 1 1 | Unfragmented message | +============================================================ +| Table 1: Fragment Description Flags | +============================================================ +*/ +type chunkPayloadData struct { + chunkHeader + + unordered bool + beginningFragment bool + endingFragment bool + immediateSack bool + + tsn uint32 + streamIdentifier uint16 + streamSequenceNumber uint16 + payloadType PayloadProtocolIdentifier + userData []byte + + // Whether this data chunk was acknowledged (received by peer) + acked bool + missIndicator uint32 + + // Partial-reliability parameters used only by sender + since time.Time + nSent uint32 // number of transmission made for this chunk + _abandoned bool + _allInflight bool // valid only with the first fragment + + // Retransmission flag set when T1-RTX timeout occurred and this + // chunk is still in the inflight queue + retransmit bool + + head *chunkPayloadData // link to the head of the fragment +} + +const ( + payloadDataEndingFragmentBitmask = 1 + payloadDataBeginingFragmentBitmask = 2 + payloadDataUnorderedBitmask = 4 + payloadDataImmediateSACK = 8 + + payloadDataHeaderSize = 12 +) + +// PayloadProtocolIdentifier is an enum for DataChannel payload types +type PayloadProtocolIdentifier uint32 + +// PayloadProtocolIdentifier enums +// https://www.iana.org/assignments/sctp-parameters/sctp-parameters.xhtml#sctp-parameters-25 +const ( + PayloadTypeWebRTCDCEP PayloadProtocolIdentifier = 50 + PayloadTypeWebRTCString PayloadProtocolIdentifier = 51 + PayloadTypeWebRTCBinary PayloadProtocolIdentifier = 53 + PayloadTypeWebRTCStringEmpty PayloadProtocolIdentifier = 56 + PayloadTypeWebRTCBinaryEmpty PayloadProtocolIdentifier = 57 +) + +func (p PayloadProtocolIdentifier) String() string { + switch p { + case PayloadTypeWebRTCDCEP: + return "WebRTC DCEP" + case PayloadTypeWebRTCString: + return "WebRTC String" + case PayloadTypeWebRTCBinary: + return "WebRTC Binary" + case PayloadTypeWebRTCStringEmpty: + return "WebRTC String (Empty)" + case PayloadTypeWebRTCBinaryEmpty: + return "WebRTC Binary (Empty)" + default: + return fmt.Sprintf("Unknown Payload Protocol Identifier: %d", p) + } +} + +func (p *chunkPayloadData) unmarshal(raw []byte) error { + if err := p.chunkHeader.unmarshal(raw); err != nil { + return err + } + + p.immediateSack = p.flags&payloadDataImmediateSACK != 0 + p.unordered = p.flags&payloadDataUnorderedBitmask != 0 + p.beginningFragment = p.flags&payloadDataBeginingFragmentBitmask != 0 + p.endingFragment = p.flags&payloadDataEndingFragmentBitmask != 0 + + p.tsn = binary.BigEndian.Uint32(p.raw[0:]) + p.streamIdentifier = binary.BigEndian.Uint16(p.raw[4:]) + p.streamSequenceNumber = binary.BigEndian.Uint16(p.raw[6:]) + p.payloadType = PayloadProtocolIdentifier(binary.BigEndian.Uint32(p.raw[8:])) + p.userData = p.raw[payloadDataHeaderSize:] + + return nil +} + +func (p *chunkPayloadData) marshal() ([]byte, error) { + payRaw := make([]byte, payloadDataHeaderSize+len(p.userData)) + + binary.BigEndian.PutUint32(payRaw[0:], p.tsn) + binary.BigEndian.PutUint16(payRaw[4:], p.streamIdentifier) + binary.BigEndian.PutUint16(payRaw[6:], p.streamSequenceNumber) + binary.BigEndian.PutUint32(payRaw[8:], uint32(p.payloadType)) + copy(payRaw[payloadDataHeaderSize:], p.userData) + + flags := uint8(0) + if p.endingFragment { + flags = 1 + } + if p.beginningFragment { + flags |= 1 << 1 + } + if p.unordered { + flags |= 1 << 2 + } + if p.immediateSack { + flags |= 1 << 3 + } + + p.chunkHeader.flags = flags + p.chunkHeader.typ = ctPayloadData + p.chunkHeader.raw = payRaw + return p.chunkHeader.marshal() +} + +func (p *chunkPayloadData) check() (abort bool, err error) { + return false, nil +} + +// String makes chunkPayloadData printable +func (p *chunkPayloadData) String() string { + return fmt.Sprintf("%s\n%d", p.chunkHeader, p.tsn) +} + +func (p *chunkPayloadData) abandoned() bool { + if p.head != nil { + return p.head._abandoned && p.head._allInflight + } + return p._abandoned && p._allInflight +} + +func (p *chunkPayloadData) setAbandoned(abandoned bool) { + if p.head != nil { + p.head._abandoned = abandoned + return + } + p._abandoned = abandoned +} + +func (p *chunkPayloadData) setAllInflight() { + if p.endingFragment { + if p.head != nil { + p.head._allInflight = true + } else { + p._allInflight = true + } + } +} diff --git a/vendor/github.com/pion/sctp/chunk_reconfig.go b/vendor/github.com/pion/sctp/chunk_reconfig.go new file mode 100644 index 0000000..a53ad5e --- /dev/null +++ b/vendor/github.com/pion/sctp/chunk_reconfig.go @@ -0,0 +1,99 @@ +package sctp + +import ( + "fmt" + + "github.com/pkg/errors" +) + +// https://tools.ietf.org/html/rfc6525#section-3.1 +// chunkReconfig represents an SCTP Chunk used to reconfigure streams. +// +// 0 1 2 3 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | Type = 130 | Chunk Flags | Chunk Length | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// \ \ +// / Re-configuration Parameter / +// \ \ +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// \ \ +// / Re-configuration Parameter (optional) / +// \ \ +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +type chunkReconfig struct { + chunkHeader + paramA param + paramB param +} + +func (c *chunkReconfig) unmarshal(raw []byte) error { + if err := c.chunkHeader.unmarshal(raw); err != nil { + return err + } + pType, err := parseParamType(c.raw) + if err != nil { + return errors.Wrap(err, "failed to parse param type") + } + a, err := buildParam(pType, c.raw) + if err != nil { + return err + } + c.paramA = a + + padding := getPadding(a.length()) + offset := a.length() + padding + if len(c.raw) > offset { + pType, err := parseParamType(c.raw[offset:]) + if err != nil { + return errors.Wrap(err, "failed to parse param type") + } + b, err := buildParam(pType, c.raw[offset:]) + if err != nil { + return err + } + c.paramB = b + } + + return nil +} + +func (c *chunkReconfig) marshal() ([]byte, error) { + out, err := c.paramA.marshal() + if err != nil { + return nil, errors.Wrap(err, "Unable to marshal parameter A for reconfig") + } + if c.paramB != nil { + // Pad param A + out = padByte(out, getPadding(len(out))) + + outB, err := c.paramB.marshal() + if err != nil { + return nil, errors.Wrap(err, "Unable to marshal parameter B for reconfig") + } + + out = append(out, outB...) + } + + c.typ = ctReconfig + c.raw = out + return c.chunkHeader.marshal() +} + +func (c *chunkReconfig) check() (abort bool, err error) { + // nolint:godox + // TODO: check allowed combinations: + // https://tools.ietf.org/html/rfc6525#section-3.1 + return true, nil +} + +// String makes chunkReconfig printable +func (c *chunkReconfig) String() string { + res := fmt.Sprintf("Param A:\n %s", c.paramA) + if c.paramB != nil { + res += fmt.Sprintf("Param B:\n %s", c.paramB) + } + return res +} diff --git a/vendor/github.com/pion/sctp/chunk_selective_ack.go b/vendor/github.com/pion/sctp/chunk_selective_ack.go new file mode 100644 index 0000000..920d562 --- /dev/null +++ b/vendor/github.com/pion/sctp/chunk_selective_ack.go @@ -0,0 +1,142 @@ +package sctp + +import ( + "encoding/binary" + "fmt" + + "github.com/pkg/errors" +) + +/* +chunkSelectiveAck represents an SCTP Chunk of type SACK + +This chunk is sent to the peer endpoint to acknowledge received DATA +chunks and to inform the peer endpoint of gaps in the received +subsequences of DATA chunks as represented by their TSNs. +0 1 2 3 +0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| Type = 3 |Chunk Flags | Chunk Length | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| Cumulative TSN Ack | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| Advertised Receiver Window Credit (a_rwnd) | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| Number of Gap Ack Blocks = N | Number of Duplicate TSNs = X | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| Gap Ack Block #1 Start | Gap Ack Block #1 End | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +/ / +\ ... \ +/ / ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| Gap Ack Block #N Start | Gap Ack Block #N End | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| Duplicate TSN 1 | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +/ / +\ ... \ +/ / ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| Duplicate TSN X | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +*/ + +type gapAckBlock struct { + start uint16 + end uint16 +} + +// String makes gapAckBlock printable +func (g gapAckBlock) String() string { + return fmt.Sprintf("%d - %d", g.start, g.end) +} + +type chunkSelectiveAck struct { + chunkHeader + cumulativeTSNAck uint32 + advertisedReceiverWindowCredit uint32 + gapAckBlocks []gapAckBlock + duplicateTSN []uint32 +} + +const ( + selectiveAckHeaderSize = 12 +) + +func (s *chunkSelectiveAck) unmarshal(raw []byte) error { + if err := s.chunkHeader.unmarshal(raw); err != nil { + return err + } + + if s.typ != ctSack { + return errors.Errorf("ChunkType is not of type SACK, actually is %s", s.typ.String()) + } + + if len(s.raw) < selectiveAckHeaderSize { + return errors.Errorf("SACK Chunk size is not large enough to contain header (%v remaining, needs %v bytes)", + len(s.raw), selectiveAckHeaderSize) + } + + s.cumulativeTSNAck = binary.BigEndian.Uint32(s.raw[0:]) + s.advertisedReceiverWindowCredit = binary.BigEndian.Uint32(s.raw[4:]) + s.gapAckBlocks = make([]gapAckBlock, binary.BigEndian.Uint16(s.raw[8:])) + s.duplicateTSN = make([]uint32, binary.BigEndian.Uint16(s.raw[10:])) + + if len(s.raw) != selectiveAckHeaderSize+(4*len(s.gapAckBlocks)+(4*len(s.duplicateTSN))) { + return errors.Errorf("SACK Chunk size does not match predicted amount from header values") + } + + offset := selectiveAckHeaderSize + for i := range s.gapAckBlocks { + s.gapAckBlocks[i].start = binary.BigEndian.Uint16(s.raw[offset:]) + s.gapAckBlocks[i].end = binary.BigEndian.Uint16(s.raw[offset+2:]) + offset += 4 + } + for i := range s.duplicateTSN { + s.duplicateTSN[i] = binary.BigEndian.Uint32(s.raw[offset:]) + offset += 4 + } + + return nil +} + +func (s *chunkSelectiveAck) marshal() ([]byte, error) { + sackRaw := make([]byte, selectiveAckHeaderSize+(4*len(s.gapAckBlocks)+(4*len(s.duplicateTSN)))) + binary.BigEndian.PutUint32(sackRaw[0:], s.cumulativeTSNAck) + binary.BigEndian.PutUint32(sackRaw[4:], s.advertisedReceiverWindowCredit) + binary.BigEndian.PutUint16(sackRaw[8:], uint16(len(s.gapAckBlocks))) + binary.BigEndian.PutUint16(sackRaw[10:], uint16(len(s.duplicateTSN))) + offset := selectiveAckHeaderSize + for _, g := range s.gapAckBlocks { + binary.BigEndian.PutUint16(sackRaw[offset:], g.start) + binary.BigEndian.PutUint16(sackRaw[offset+2:], g.end) + offset += 4 + } + for _, t := range s.duplicateTSN { + binary.BigEndian.PutUint32(sackRaw[offset:], t) + offset += 4 + } + + s.chunkHeader.typ = ctSack + s.chunkHeader.raw = sackRaw + return s.chunkHeader.marshal() +} + +func (s *chunkSelectiveAck) check() (abort bool, err error) { + return false, nil +} + +// String makes chunkSelectiveAck printable +func (s *chunkSelectiveAck) String() string { + res := fmt.Sprintf("SACK cumTsnAck=%d arwnd=%d dupTsn=%d", + s.cumulativeTSNAck, + s.advertisedReceiverWindowCredit, + s.duplicateTSN) + + for _, gap := range s.gapAckBlocks { + res = fmt.Sprintf("%s\n gap ack: %s", res, gap) + } + + return res +} diff --git a/vendor/github.com/pion/sctp/chunkheader.go b/vendor/github.com/pion/sctp/chunkheader.go new file mode 100644 index 0000000..a4a78db --- /dev/null +++ b/vendor/github.com/pion/sctp/chunkheader.go @@ -0,0 +1,90 @@ +package sctp + +import ( + "encoding/binary" + + "github.com/pkg/errors" +) + +/* +chunkHeader represents a SCTP Chunk header, defined in https://tools.ietf.org/html/rfc4960#section-3.2 +The figure below illustrates the field format for the chunks to be +transmitted in the SCTP packet. Each chunk is formatted with a Chunk +Type field, a chunk-specific Flag field, a Chunk Length field, and a +Value field. + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| Chunk Type | Chunk Flags | Chunk Length | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| | +| Chunk Value | +| | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +*/ +type chunkHeader struct { + typ chunkType + flags byte + raw []byte +} + +const ( + chunkHeaderSize = 4 +) + +func (c *chunkHeader) unmarshal(raw []byte) error { + if len(raw) < chunkHeaderSize { + return errors.Errorf("raw only %d bytes, %d is the minimum length for a SCTP chunk", len(raw), chunkHeaderSize) + } + + c.typ = chunkType(raw[0]) + c.flags = raw[1] + length := binary.BigEndian.Uint16(raw[2:]) + + // Length includes Chunk header + valueLength := int(length - chunkHeaderSize) + lengthAfterValue := len(raw) - (chunkHeaderSize + valueLength) + + if lengthAfterValue < 0 { + return errors.Errorf("Not enough data left in SCTP packet to satisfy requested length remain %d req %d ", valueLength, len(raw)-chunkHeaderSize) + } else if lengthAfterValue < 4 { + // https://tools.ietf.org/html/rfc4960#section-3.2 + // The Chunk Length field does not count any chunk padding. + // Chunks (including Type, Length, and Value fields) are padded out + // by the sender with all zero bytes to be a multiple of 4 bytes + // long. This padding MUST NOT be more than 3 bytes in total. The + // Chunk Length value does not include terminating padding of the + // chunk. However, it does include padding of any variable-length + // parameter except the last parameter in the chunk. The receiver + // MUST ignore the padding. + for i := lengthAfterValue; i > 0; i-- { + paddingOffset := chunkHeaderSize + valueLength + (i - 1) + if raw[paddingOffset] != 0 { + return errors.Errorf("Chunk padding is non-zero at offset %d ", paddingOffset) + } + } + } + + c.raw = raw[chunkHeaderSize : chunkHeaderSize+valueLength] + return nil +} + +func (c *chunkHeader) marshal() ([]byte, error) { + raw := make([]byte, 4+len(c.raw)) + + raw[0] = uint8(c.typ) + raw[1] = c.flags + binary.BigEndian.PutUint16(raw[2:], uint16(len(c.raw)+chunkHeaderSize)) + copy(raw[4:], c.raw) + return raw, nil +} + +func (c *chunkHeader) valueLength() int { + return len(c.raw) +} + +// String makes chunkHeader printable +func (c chunkHeader) String() string { + return c.typ.String() +} diff --git a/vendor/github.com/pion/sctp/chunktype.go b/vendor/github.com/pion/sctp/chunktype.go new file mode 100644 index 0000000..65b57a4 --- /dev/null +++ b/vendor/github.com/pion/sctp/chunktype.go @@ -0,0 +1,67 @@ +package sctp + +import "fmt" + +// chunkType is an enum for SCTP Chunk Type field +// This field identifies the type of information contained in the +// Chunk Value field. +type chunkType uint8 + +// List of known chunkType enums +const ( + ctPayloadData chunkType = 0 + ctInit chunkType = 1 + ctInitAck chunkType = 2 + ctSack chunkType = 3 + ctHeartbeat chunkType = 4 + ctHeartbeatAck chunkType = 5 + ctAbort chunkType = 6 + ctShutdown chunkType = 7 + ctShutdownAck chunkType = 8 + ctError chunkType = 9 + ctCookieEcho chunkType = 10 + ctCookieAck chunkType = 11 + ctCWR chunkType = 13 + ctShutdownComplete chunkType = 14 + ctReconfig chunkType = 130 + ctForwardTSN chunkType = 192 +) + +func (c chunkType) String() string { + switch c { + case ctPayloadData: + return "DATA" + case ctInit: + return "INIT" + case ctInitAck: + return "INIT-ACK" + case ctSack: + return "SACK" + case ctHeartbeat: + return "HEARTBEAT" + case ctHeartbeatAck: + return "HEARTBEAT-ACK" + case ctAbort: + return "ABORT" + case ctShutdown: + return "SHUTDOWN" + case ctShutdownAck: + return "SHUTDOWN-ACK" + case ctError: + return "ERROR" + case ctCookieEcho: + return "COOKIE-ECHO" + case ctCookieAck: + return "COOKIE-ACK" + case ctCWR: + return "ECNE" // Explicit Congestion Notification Echo + case ctShutdownComplete: + return "SHUTDOWN-COMPLETE" + case ctReconfig: + return "RECONFIG" // Re-configuration + case ctForwardTSN: + return "FORWARD-TSN" + default: + return fmt.Sprintf("Unknown ChunkType: %d", c) + } +} diff --git a/vendor/github.com/pion/sctp/codecov.yml b/vendor/github.com/pion/sctp/codecov.yml new file mode 100644 index 0000000..085200a --- /dev/null +++ b/vendor/github.com/pion/sctp/codecov.yml @@ -0,0 +1,20 @@ +# +# DO NOT EDIT THIS FILE +# +# It is automatically copied from https://github.com/pion/.goassets repository. +# + +coverage: + status: + project: + default: + # Allow decreasing 2% of total coverage to avoid noise. + threshold: 2% + patch: + default: + target: 70% + only_pulls: true + +ignore: + - "examples/*" + - "examples/**/*" diff --git a/vendor/github.com/pion/sctp/control_queue.go b/vendor/github.com/pion/sctp/control_queue.go new file mode 100644 index 0000000..7e13469 --- /dev/null +++ b/vendor/github.com/pion/sctp/control_queue.go @@ -0,0 +1,29 @@ +package sctp + +// control queue + +type controlQueue struct { + queue []*packet +} + +func newControlQueue() *controlQueue { + return &controlQueue{queue: []*packet{}} +} + +func (q *controlQueue) push(c *packet) { + q.queue = append(q.queue, c) +} + +func (q *controlQueue) pushAll(packets []*packet) { + q.queue = append(q.queue, packets...) +} + +func (q *controlQueue) popAll() []*packet { + packets := q.queue + q.queue = []*packet{} + return packets +} + +func (q *controlQueue) size() int { + return len(q.queue) +} diff --git a/vendor/github.com/pion/sctp/error_cause.go b/vendor/github.com/pion/sctp/error_cause.go new file mode 100644 index 0000000..e94cc5c --- /dev/null +++ b/vendor/github.com/pion/sctp/error_cause.go @@ -0,0 +1,91 @@ +package sctp + +import ( + "encoding/binary" + "fmt" + + "github.com/pkg/errors" +) + +// errorCauseCode is a cause code that appears in either a ERROR or ABORT chunk +type errorCauseCode uint16 + +type errorCause interface { + unmarshal([]byte) error + marshal() ([]byte, error) + length() uint16 + String() string + + errorCauseCode() errorCauseCode +} + +// buildErrorCause delegates the building of a error cause from raw bytes to the correct structure +func buildErrorCause(raw []byte) (errorCause, error) { + var e errorCause + + c := errorCauseCode(binary.BigEndian.Uint16(raw[0:])) + switch c { + case invalidMandatoryParameter: + e = &errorCauseInvalidMandatoryParameter{} + case unrecognizedChunkType: + e = &errorCauseUnrecognizedChunkType{} + case protocolViolation: + e = &errorCauseProtocolViolation{} + default: + return nil, errors.Errorf("BuildErrorCause does not handle %s", c.String()) + } + + if err := e.unmarshal(raw); err != nil { + return nil, err + } + return e, nil +} + +const ( + invalidStreamIdentifier errorCauseCode = 1 + missingMandatoryParameter errorCauseCode = 2 + staleCookieError errorCauseCode = 3 + outOfResource errorCauseCode = 4 + unresolvableAddress errorCauseCode = 5 + unrecognizedChunkType errorCauseCode = 6 + invalidMandatoryParameter errorCauseCode = 7 + unrecognizedParameters errorCauseCode = 8 + noUserData errorCauseCode = 9 + cookieReceivedWhileShuttingDown errorCauseCode = 10 + restartOfAnAssociationWithNewAddresses errorCauseCode = 11 + userInitiatedAbort errorCauseCode = 12 + protocolViolation errorCauseCode = 13 +) + +func (e errorCauseCode) String() string { + switch e { + case invalidStreamIdentifier: + return "Invalid Stream Identifier" + case missingMandatoryParameter: + return "Missing Mandatory Parameter" + case staleCookieError: + return "Stale Cookie Error" + case outOfResource: + return "Out Of Resource" + case unresolvableAddress: + return "Unresolvable IP" + case unrecognizedChunkType: + return "Unrecognized Chunk Type" + case invalidMandatoryParameter: + return "Invalid Mandatory Parameter" + case unrecognizedParameters: + return "Unrecognized Parameters" + case noUserData: + return "No User Data" + case cookieReceivedWhileShuttingDown: + return "Cookie Received While Shutting Down" + case restartOfAnAssociationWithNewAddresses: + return "Restart Of An Association With New Addresses" + case userInitiatedAbort: + return "User Initiated Abort" + case protocolViolation: + return "Protocol Violation" + default: + return fmt.Sprintf("Unknown CauseCode: %d", e) + } +} diff --git a/vendor/github.com/pion/sctp/error_cause_header.go b/vendor/github.com/pion/sctp/error_cause_header.go new file mode 100644 index 0000000..1ad6e1d --- /dev/null +++ b/vendor/github.com/pion/sctp/error_cause_header.go @@ -0,0 +1,47 @@ +package sctp + +import ( + "encoding/binary" +) + +// errorCauseHeader represents the shared header that is shared by all error causes +type errorCauseHeader struct { + code errorCauseCode + len uint16 + raw []byte +} + +const ( + errorCauseHeaderLength = 4 +) + +func (e *errorCauseHeader) marshal() ([]byte, error) { + e.len = uint16(len(e.raw)) + uint16(errorCauseHeaderLength) + raw := make([]byte, e.len) + binary.BigEndian.PutUint16(raw[0:], uint16(e.code)) + binary.BigEndian.PutUint16(raw[2:], e.len) + copy(raw[errorCauseHeaderLength:], e.raw) + + return raw, nil +} + +func (e *errorCauseHeader) unmarshal(raw []byte) error { + e.code = errorCauseCode(binary.BigEndian.Uint16(raw[0:])) + e.len = binary.BigEndian.Uint16(raw[2:]) + valueLength := e.len - errorCauseHeaderLength + e.raw = raw[errorCauseHeaderLength : errorCauseHeaderLength+valueLength] + return nil +} + +func (e *errorCauseHeader) length() uint16 { + return e.len +} + +func (e *errorCauseHeader) errorCauseCode() errorCauseCode { + return e.code +} + +// String makes errorCauseHeader printable +func (e errorCauseHeader) String() string { + return e.code.String() +} diff --git a/vendor/github.com/pion/sctp/error_cause_invalid_mandatory_parameter.go b/vendor/github.com/pion/sctp/error_cause_invalid_mandatory_parameter.go new file mode 100644 index 0000000..3da8b47 --- /dev/null +++ b/vendor/github.com/pion/sctp/error_cause_invalid_mandatory_parameter.go @@ -0,0 +1,19 @@ +package sctp + +// errorCauseInvalidMandatoryParameter represents an SCTP error cause +type errorCauseInvalidMandatoryParameter struct { + errorCauseHeader +} + +func (e *errorCauseInvalidMandatoryParameter) marshal() ([]byte, error) { + return e.errorCauseHeader.marshal() +} + +func (e *errorCauseInvalidMandatoryParameter) unmarshal(raw []byte) error { + return e.errorCauseHeader.unmarshal(raw) +} + +// String makes errorCauseInvalidMandatoryParameter printable +func (e *errorCauseInvalidMandatoryParameter) String() string { + return e.errorCauseHeader.String() +} diff --git a/vendor/github.com/pion/sctp/error_cause_protocol_violation.go b/vendor/github.com/pion/sctp/error_cause_protocol_violation.go new file mode 100644 index 0000000..8b457f4 --- /dev/null +++ b/vendor/github.com/pion/sctp/error_cause_protocol_violation.go @@ -0,0 +1,50 @@ +package sctp + +import ( + "fmt" + + "github.com/pkg/errors" +) + +/* + This error cause MAY be included in ABORT chunks that are sent + because an SCTP endpoint detects a protocol violation of the peer + that is not covered by the error causes described in Section 3.3.10.1 + to Section 3.3.10.12. An implementation MAY provide additional + information specifying what kind of protocol violation has been + detected. + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Cause Code=13 | Cause Length=Variable | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + / Additional Information / + \ \ + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +*/ +type errorCauseProtocolViolation struct { + errorCauseHeader + additionalInformation []byte +} + +func (e *errorCauseProtocolViolation) marshal() ([]byte, error) { + e.raw = e.additionalInformation + return e.errorCauseHeader.marshal() +} + +func (e *errorCauseProtocolViolation) unmarshal(raw []byte) error { + err := e.errorCauseHeader.unmarshal(raw) + if err != nil { + return errors.Wrap(err, "Unable to unmarshal Protocol Violation error") + } + + e.additionalInformation = e.raw + + return nil +} + +// String makes errorCauseProtocolViolation printable +func (e *errorCauseProtocolViolation) String() string { + return fmt.Sprintf("%s: %s", e.errorCauseHeader, e.additionalInformation) +} diff --git a/vendor/github.com/pion/sctp/error_cause_unrecognized_chunk_type.go b/vendor/github.com/pion/sctp/error_cause_unrecognized_chunk_type.go new file mode 100644 index 0000000..fee9a36 --- /dev/null +++ b/vendor/github.com/pion/sctp/error_cause_unrecognized_chunk_type.go @@ -0,0 +1,28 @@ +package sctp + +// errorCauseUnrecognizedChunkType represents an SCTP error cause +type errorCauseUnrecognizedChunkType struct { + errorCauseHeader + unrecognizedChunk []byte +} + +func (e *errorCauseUnrecognizedChunkType) marshal() ([]byte, error) { + e.code = unrecognizedChunkType + e.errorCauseHeader.raw = e.unrecognizedChunk + return e.errorCauseHeader.marshal() +} + +func (e *errorCauseUnrecognizedChunkType) unmarshal(raw []byte) error { + err := e.errorCauseHeader.unmarshal(raw) + if err != nil { + return err + } + + e.unrecognizedChunk = e.errorCauseHeader.raw + return nil +} + +// String makes errorCauseUnrecognizedChunkType printable +func (e *errorCauseUnrecognizedChunkType) String() string { + return e.errorCauseHeader.String() +} diff --git a/vendor/github.com/pion/sctp/go.mod b/vendor/github.com/pion/sctp/go.mod new file mode 100644 index 0000000..022e2fc --- /dev/null +++ b/vendor/github.com/pion/sctp/go.mod @@ -0,0 +1,14 @@ +module github.com/pion/sctp + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/kr/pretty v0.1.0 // indirect + github.com/pion/logging v0.2.2 + github.com/pion/randutil v0.1.0 + github.com/pion/transport v0.10.1 + github.com/pkg/errors v0.9.1 + github.com/stretchr/testify v1.6.1 + gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect +) + +go 1.13 diff --git a/vendor/github.com/pion/sctp/go.sum b/vendor/github.com/pion/sctp/go.sum new file mode 100644 index 0000000..3744262 --- /dev/null +++ b/vendor/github.com/pion/sctp/go.sum @@ -0,0 +1,36 @@ +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY= +github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms= +github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA= +github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8= +github.com/pion/transport v0.10.1 h1:2W+yJT+0mOQ160ThZYUx5Zp2skzshiNgxrNE9GUfhJM= +github.com/pion/transport v0.10.1/go.mod h1:PBis1stIILMiis0PewDw91WJeLJkyIMcEk+DwKOzf4A= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20200625001655-4c5254603344 h1:vGXIOMxbNfDTk/aXCmfdLgkrSV+Z2tcbze+pEc3v5W4= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/vendor/github.com/pion/sctp/packet.go b/vendor/github.com/pion/sctp/packet.go new file mode 100644 index 0000000..dcc650b --- /dev/null +++ b/vendor/github.com/pion/sctp/packet.go @@ -0,0 +1,178 @@ +package sctp + +import ( + "encoding/binary" + "fmt" + "hash/crc32" + + "github.com/pkg/errors" +) + +// Create the crc32 table we'll use for the checksum +var castagnoliTable = crc32.MakeTable(crc32.Castagnoli) // nolint:gochecknoglobals + +// Allocate and zero this data once. +// We need to use it for the checksum and don't want to allocate/clear each time. +var fourZeroes [4]byte // nolint:gochecknoglobals + +/* +Packet represents an SCTP packet, defined in https://tools.ietf.org/html/rfc4960#section-3 +An SCTP packet is composed of a common header and chunks. A chunk +contains either control information or user data. + + + SCTP Packet Format + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| Common Header | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| Chunk #1 | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| ... | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| Chunk #n | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + + SCTP Common Header Format + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| Source Value Number | Destination Value Number | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| Verification Tag | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| Checksum | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + +*/ +type packet struct { + sourcePort uint16 + destinationPort uint16 + verificationTag uint32 + chunks []chunk +} + +const ( + packetHeaderSize = 12 +) + +func (p *packet) unmarshal(raw []byte) error { + if len(raw) < packetHeaderSize { + return errors.Errorf("raw only %d bytes, %d is the minimum length for a SCTP packet", len(raw), packetHeaderSize) + } + + p.sourcePort = binary.BigEndian.Uint16(raw[0:]) + p.destinationPort = binary.BigEndian.Uint16(raw[2:]) + p.verificationTag = binary.BigEndian.Uint32(raw[4:]) + + offset := packetHeaderSize + for { + // Exact match, no more chunks + if offset == len(raw) { + break + } else if offset+chunkHeaderSize > len(raw) { + return errors.Errorf("Unable to parse SCTP chunk, not enough data for complete header: offset %d remaining %d", offset, len(raw)) + } + + var c chunk + switch chunkType(raw[offset]) { + case ctInit: + c = &chunkInit{} + case ctInitAck: + c = &chunkInitAck{} + case ctAbort: + c = &chunkAbort{} + case ctCookieEcho: + c = &chunkCookieEcho{} + case ctCookieAck: + c = &chunkCookieAck{} + case ctHeartbeat: + c = &chunkHeartbeat{} + case ctPayloadData: + c = &chunkPayloadData{} + case ctSack: + c = &chunkSelectiveAck{} + case ctReconfig: + c = &chunkReconfig{} + case ctForwardTSN: + c = &chunkForwardTSN{} + case ctError: + c = &chunkError{} + default: + return errors.Errorf("Failed to unmarshal, contains unknown chunk type %s", chunkType(raw[offset]).String()) + } + + if err := c.unmarshal(raw[offset:]); err != nil { + return err + } + + p.chunks = append(p.chunks, c) + chunkValuePadding := getPadding(c.valueLength()) + offset += chunkHeaderSize + c.valueLength() + chunkValuePadding + } + theirChecksum := binary.LittleEndian.Uint32(raw[8:]) + ourChecksum := generatePacketChecksum(raw) + if theirChecksum != ourChecksum { + return errors.Errorf("Checksum mismatch theirs: %d ours: %d", theirChecksum, ourChecksum) + } + return nil +} + +func (p *packet) marshal() ([]byte, error) { + raw := make([]byte, packetHeaderSize) + + // Populate static headers + // 8-12 is Checksum which will be populated when packet is complete + binary.BigEndian.PutUint16(raw[0:], p.sourcePort) + binary.BigEndian.PutUint16(raw[2:], p.destinationPort) + binary.BigEndian.PutUint32(raw[4:], p.verificationTag) + + // Populate chunks + for _, c := range p.chunks { + chunkRaw, err := c.marshal() + if err != nil { + return nil, err + } + raw = append(raw, chunkRaw...) + + paddingNeeded := getPadding(len(raw)) + if paddingNeeded != 0 { + raw = append(raw, make([]byte, paddingNeeded)...) + } + } + + // Checksum is already in BigEndian + // Using LittleEndian.PutUint32 stops it from being flipped + binary.LittleEndian.PutUint32(raw[8:], generatePacketChecksum(raw)) + return raw, nil +} + +func generatePacketChecksum(raw []byte) (sum uint32) { + // Fastest way to do a crc32 without allocating. + sum = crc32.Update(sum, castagnoliTable, raw[0:8]) + sum = crc32.Update(sum, castagnoliTable, fourZeroes[:]) + sum = crc32.Update(sum, castagnoliTable, raw[12:]) + return sum +} + +// String makes packet printable +func (p *packet) String() string { + format := `Packet: + sourcePort: %d + destinationPort: %d + verificationTag: %d + ` + res := fmt.Sprintf(format, + p.sourcePort, + p.destinationPort, + p.verificationTag, + ) + for i, chunk := range p.chunks { + res += fmt.Sprintf("Chunk %d:\n %s", i, chunk) + } + return res +} diff --git a/vendor/github.com/pion/sctp/param.go b/vendor/github.com/pion/sctp/param.go new file mode 100644 index 0000000..08e46d1 --- /dev/null +++ b/vendor/github.com/pion/sctp/param.go @@ -0,0 +1,35 @@ +package sctp + +import ( + "github.com/pkg/errors" +) + +type param interface { + marshal() ([]byte, error) + length() int +} + +func buildParam(t paramType, rawParam []byte) (param, error) { + switch t { + case forwardTSNSupp: + return (¶mForwardTSNSupported{}).unmarshal(rawParam) + case supportedExt: + return (¶mSupportedExtensions{}).unmarshal(rawParam) + case random: + return (¶mRandom{}).unmarshal(rawParam) + case reqHMACAlgo: + return (¶mRequestedHMACAlgorithm{}).unmarshal(rawParam) + case chunkList: + return (¶mChunkList{}).unmarshal(rawParam) + case stateCookie: + return (¶mStateCookie{}).unmarshal(rawParam) + case heartbeatInfo: + return (¶mHeartbeatInfo{}).unmarshal(rawParam) + case outSSNResetReq: + return (¶mOutgoingResetRequest{}).unmarshal(rawParam) + case reconfigResp: + return (¶mReconfigResponse{}).unmarshal(rawParam) + default: + return nil, errors.Errorf("Unhandled ParamType %v", t) + } +} diff --git a/vendor/github.com/pion/sctp/param_chunk_list.go b/vendor/github.com/pion/sctp/param_chunk_list.go new file mode 100644 index 0000000..4ea484c --- /dev/null +++ b/vendor/github.com/pion/sctp/param_chunk_list.go @@ -0,0 +1,28 @@ +package sctp + +type paramChunkList struct { + paramHeader + chunkTypes []chunkType +} + +func (c *paramChunkList) marshal() ([]byte, error) { + c.typ = chunkList + c.raw = make([]byte, len(c.chunkTypes)) + for i, t := range c.chunkTypes { + c.raw[i] = byte(t) + } + + return c.paramHeader.marshal() +} + +func (c *paramChunkList) unmarshal(raw []byte) (param, error) { + err := c.paramHeader.unmarshal(raw) + if err != nil { + return nil, err + } + for _, t := range c.raw { + c.chunkTypes = append(c.chunkTypes, chunkType(t)) + } + + return c, nil +} diff --git a/vendor/github.com/pion/sctp/param_forward_tsn_supported.go b/vendor/github.com/pion/sctp/param_forward_tsn_supported.go new file mode 100644 index 0000000..62de155 --- /dev/null +++ b/vendor/github.com/pion/sctp/param_forward_tsn_supported.go @@ -0,0 +1,28 @@ +package sctp + +// At the initialization of the association, the sender of the INIT or +// INIT ACK chunk MAY include this OPTIONAL parameter to inform its peer +// that it is able to support the Forward TSN chunk +// +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | Parameter Type = 49152 | Parameter Length = 4 | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +type paramForwardTSNSupported struct { + paramHeader +} + +func (f *paramForwardTSNSupported) marshal() ([]byte, error) { + f.typ = forwardTSNSupp + f.raw = []byte{} + return f.paramHeader.marshal() +} + +func (f *paramForwardTSNSupported) unmarshal(raw []byte) (param, error) { + err := f.paramHeader.unmarshal(raw) + if err != nil { + return nil, err + } + return f, nil +} diff --git a/vendor/github.com/pion/sctp/param_heartbeat_info.go b/vendor/github.com/pion/sctp/param_heartbeat_info.go new file mode 100644 index 0000000..47f64eb --- /dev/null +++ b/vendor/github.com/pion/sctp/param_heartbeat_info.go @@ -0,0 +1,21 @@ +package sctp + +type paramHeartbeatInfo struct { + paramHeader + heartbeatInformation []byte +} + +func (h *paramHeartbeatInfo) marshal() ([]byte, error) { + h.typ = heartbeatInfo + h.raw = h.heartbeatInformation + return h.paramHeader.marshal() +} + +func (h *paramHeartbeatInfo) unmarshal(raw []byte) (param, error) { + err := h.paramHeader.unmarshal(raw) + if err != nil { + return nil, err + } + h.heartbeatInformation = h.raw + return h, nil +} diff --git a/vendor/github.com/pion/sctp/param_outgoing_reset_request.go b/vendor/github.com/pion/sctp/param_outgoing_reset_request.go new file mode 100644 index 0000000..ceae178 --- /dev/null +++ b/vendor/github.com/pion/sctp/param_outgoing_reset_request.go @@ -0,0 +1,88 @@ +package sctp + +import ( + "encoding/binary" + "errors" +) + +const ( + paramOutgoingResetRequestStreamIdentifiersOffset = 12 +) + +// This parameter is used by the sender to request the reset of some or +// all outgoing streams. +// 0 1 2 3 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | Parameter Type = 13 | Parameter Length = 16 + 2 * N | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | Re-configuration Request Sequence Number | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | Re-configuration Response Sequence Number | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | Sender's Last Assigned TSN | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | Stream Number 1 (optional) | Stream Number 2 (optional) | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// / ...... / +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | Stream Number N-1 (optional) | Stream Number N (optional) | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +type paramOutgoingResetRequest struct { + paramHeader + // reconfigRequestSequenceNumber is used to identify the request. It is a monotonically + // increasing number that is initialized to the same value as the + // initial TSN. It is increased by 1 whenever sending a new Re- + // configuration Request Parameter. + reconfigRequestSequenceNumber uint32 + // When this Outgoing SSN Reset Request Parameter is sent in response + // to an Incoming SSN Reset Request Parameter, this parameter is also + // an implicit response to the incoming request. This field then + // holds the Re-configuration Request Sequence Number of the incoming + // request. In other cases, it holds the next expected + // Re-configuration Request Sequence Number minus 1. + reconfigResponseSequenceNumber uint32 + // This value holds the next TSN minus 1 -- in other words, the last + // TSN that this sender assigned. + senderLastTSN uint32 + // This optional field, if included, is used to indicate specific + // streams that are to be reset. If no streams are listed, then all + // streams are to be reset. + streamIdentifiers []uint16 +} + +var errSSNResetRequestParamTooShort = errors.New("outgoing SSN reset request parameter too short") + +func (r *paramOutgoingResetRequest) marshal() ([]byte, error) { + r.typ = outSSNResetReq + r.raw = make([]byte, paramOutgoingResetRequestStreamIdentifiersOffset+2*len(r.streamIdentifiers)) + binary.BigEndian.PutUint32(r.raw, r.reconfigRequestSequenceNumber) + binary.BigEndian.PutUint32(r.raw[4:], r.reconfigResponseSequenceNumber) + binary.BigEndian.PutUint32(r.raw[8:], r.senderLastTSN) + for i, sID := range r.streamIdentifiers { + binary.BigEndian.PutUint16(r.raw[paramOutgoingResetRequestStreamIdentifiersOffset+2*i:], sID) + } + return r.paramHeader.marshal() +} + +func (r *paramOutgoingResetRequest) unmarshal(raw []byte) (param, error) { + err := r.paramHeader.unmarshal(raw) + if err != nil { + return nil, err + } + if len(r.raw) < paramOutgoingResetRequestStreamIdentifiersOffset { + return nil, errSSNResetRequestParamTooShort + } + r.reconfigRequestSequenceNumber = binary.BigEndian.Uint32(r.raw) + r.reconfigResponseSequenceNumber = binary.BigEndian.Uint32(r.raw[4:]) + r.senderLastTSN = binary.BigEndian.Uint32(r.raw[8:]) + + lim := (len(r.raw) - paramOutgoingResetRequestStreamIdentifiersOffset) / 2 + r.streamIdentifiers = make([]uint16, lim) + for i := 0; i < lim; i++ { + r.streamIdentifiers[i] = binary.BigEndian.Uint16(r.raw[paramOutgoingResetRequestStreamIdentifiersOffset+2*i:]) + } + + return r, nil +} diff --git a/vendor/github.com/pion/sctp/param_random.go b/vendor/github.com/pion/sctp/param_random.go new file mode 100644 index 0000000..dc454b3 --- /dev/null +++ b/vendor/github.com/pion/sctp/param_random.go @@ -0,0 +1,21 @@ +package sctp + +type paramRandom struct { + paramHeader + randomData []byte +} + +func (r *paramRandom) marshal() ([]byte, error) { + r.typ = random + r.raw = r.randomData + return r.paramHeader.marshal() +} + +func (r *paramRandom) unmarshal(raw []byte) (param, error) { + err := r.paramHeader.unmarshal(raw) + if err != nil { + return nil, err + } + r.randomData = r.raw + return r, nil +} diff --git a/vendor/github.com/pion/sctp/param_reconfig_response.go b/vendor/github.com/pion/sctp/param_reconfig_response.go new file mode 100644 index 0000000..d9eab55 --- /dev/null +++ b/vendor/github.com/pion/sctp/param_reconfig_response.go @@ -0,0 +1,92 @@ +package sctp + +import ( + "encoding/binary" + "errors" + "fmt" +) + +// This parameter is used by the receiver of a Re-configuration Request +// Parameter to respond to the request. +// +// 0 1 2 3 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | Parameter Type = 16 | Parameter Length | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | Re-configuration Response Sequence Number | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | Result | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | Sender's Next TSN (optional) | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | Receiver's Next TSN (optional) | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +type paramReconfigResponse struct { + paramHeader + // This value is copied from the request parameter and is used by the + // receiver of the Re-configuration Response Parameter to tie the + // response to the request. + reconfigResponseSequenceNumber uint32 + // This value describes the result of the processing of the request. + result reconfigResult +} + +type reconfigResult uint32 + +const ( + reconfigResultSuccessNOP reconfigResult = 0 + reconfigResultSuccessPerformed reconfigResult = 1 + reconfigResultDenied reconfigResult = 2 + reconfigResultErrorWrongSSN reconfigResult = 3 + reconfigResultErrorRequestAlreadyInProgress reconfigResult = 4 + reconfigResultErrorBadSequenceNumber reconfigResult = 5 + reconfigResultInProgress reconfigResult = 6 +) + +var errReconfigRespParamTooShort = errors.New("reconfig response parameter too short") + +func (t reconfigResult) String() string { + switch t { + case reconfigResultSuccessNOP: + return "0: Success - Nothing to do" + case reconfigResultSuccessPerformed: + return "1: Success - Performed" + case reconfigResultDenied: + return "2: Denied" + case reconfigResultErrorWrongSSN: + return "3: Error - Wrong SSN" + case reconfigResultErrorRequestAlreadyInProgress: + return "4: Error - Request already in progress" + case reconfigResultErrorBadSequenceNumber: + return "5: Error - Bad Sequence Number" + case reconfigResultInProgress: + return "6: In progress" + default: + return fmt.Sprintf("Unknown reconfigResult: %d", t) + } +} + +func (r *paramReconfigResponse) marshal() ([]byte, error) { + r.typ = reconfigResp + r.raw = make([]byte, 8) + binary.BigEndian.PutUint32(r.raw, r.reconfigResponseSequenceNumber) + binary.BigEndian.PutUint32(r.raw[4:], uint32(r.result)) + + return r.paramHeader.marshal() +} + +func (r *paramReconfigResponse) unmarshal(raw []byte) (param, error) { + err := r.paramHeader.unmarshal(raw) + if err != nil { + return nil, err + } + if len(r.raw) < 8 { + return nil, errReconfigRespParamTooShort + } + r.reconfigResponseSequenceNumber = binary.BigEndian.Uint32(r.raw) + r.result = reconfigResult(binary.BigEndian.Uint32(r.raw[4:])) + + return r, nil +} diff --git a/vendor/github.com/pion/sctp/param_requested_hmac_algorithm.go b/vendor/github.com/pion/sctp/param_requested_hmac_algorithm.go new file mode 100644 index 0000000..b520fe3 --- /dev/null +++ b/vendor/github.com/pion/sctp/param_requested_hmac_algorithm.go @@ -0,0 +1,73 @@ +package sctp + +import ( + "encoding/binary" + "fmt" + + "github.com/pkg/errors" +) + +type hmacAlgorithm uint16 + +const ( + hmacResv1 hmacAlgorithm = 0 + hmacSHA128 = 1 + hmacResv2 hmacAlgorithm = 2 + hmacSHA256 hmacAlgorithm = 3 +) + +func (c hmacAlgorithm) String() string { + switch c { + case hmacResv1: + return "HMAC Reserved (0x00)" + case hmacSHA128: + return "HMAC SHA-128" + case hmacResv2: + return "HMAC Reserved (0x02)" + case hmacSHA256: + return "HMAC SHA-256" + default: + return fmt.Sprintf("Unknown HMAC Algorithm type: %d", c) + } +} + +type paramRequestedHMACAlgorithm struct { + paramHeader + availableAlgorithms []hmacAlgorithm +} + +func (r *paramRequestedHMACAlgorithm) marshal() ([]byte, error) { + r.typ = reqHMACAlgo + r.raw = make([]byte, len(r.availableAlgorithms)*2) + i := 0 + for _, a := range r.availableAlgorithms { + binary.BigEndian.PutUint16(r.raw[i:], uint16(a)) + i += 2 + } + + return r.paramHeader.marshal() +} + +func (r *paramRequestedHMACAlgorithm) unmarshal(raw []byte) (param, error) { + err := r.paramHeader.unmarshal(raw) + if err != nil { + return nil, err + } + + i := 0 + for i < len(r.raw) { + a := hmacAlgorithm(binary.BigEndian.Uint16(r.raw[i:])) + switch a { + case hmacSHA128: + fallthrough + case hmacSHA256: + r.availableAlgorithms = append(r.availableAlgorithms, a) + default: + return nil, errors.Errorf("Invalid algorithm type '%v'", a) + } + + i += 2 + } + + return r, nil +} diff --git a/vendor/github.com/pion/sctp/param_state_cookie.go b/vendor/github.com/pion/sctp/param_state_cookie.go new file mode 100644 index 0000000..9681267 --- /dev/null +++ b/vendor/github.com/pion/sctp/param_state_cookie.go @@ -0,0 +1,46 @@ +package sctp + +import ( + "crypto/rand" + "fmt" +) + +type paramStateCookie struct { + paramHeader + cookie []byte +} + +func newRandomStateCookie() (*paramStateCookie, error) { + randCookie := make([]byte, 32) + _, err := rand.Read(randCookie) + // crypto/rand.Read returns n == len(b) if and only if err == nil. + if err != nil { + return nil, err + } + + s := ¶mStateCookie{ + cookie: randCookie, + } + + return s, nil +} + +func (s *paramStateCookie) marshal() ([]byte, error) { + s.typ = stateCookie + s.raw = s.cookie + return s.paramHeader.marshal() +} + +func (s *paramStateCookie) unmarshal(raw []byte) (param, error) { + err := s.paramHeader.unmarshal(raw) + if err != nil { + return nil, err + } + s.cookie = s.raw + return s, nil +} + +// String makes paramStateCookie printable +func (s *paramStateCookie) String() string { + return fmt.Sprintf("%s: %s", s.paramHeader, s.cookie) +} diff --git a/vendor/github.com/pion/sctp/param_supported_extensions.go b/vendor/github.com/pion/sctp/param_supported_extensions.go new file mode 100644 index 0000000..2935524 --- /dev/null +++ b/vendor/github.com/pion/sctp/param_supported_extensions.go @@ -0,0 +1,29 @@ +package sctp + +type paramSupportedExtensions struct { + paramHeader + ChunkTypes []chunkType +} + +func (s *paramSupportedExtensions) marshal() ([]byte, error) { + s.typ = supportedExt + s.raw = make([]byte, len(s.ChunkTypes)) + for i, c := range s.ChunkTypes { + s.raw[i] = byte(c) + } + + return s.paramHeader.marshal() +} + +func (s *paramSupportedExtensions) unmarshal(raw []byte) (param, error) { + err := s.paramHeader.unmarshal(raw) + if err != nil { + return nil, err + } + + for _, t := range s.raw { + s.ChunkTypes = append(s.ChunkTypes, chunkType(t)) + } + + return s, nil +} diff --git a/vendor/github.com/pion/sctp/paramheader.go b/vendor/github.com/pion/sctp/paramheader.go new file mode 100644 index 0000000..d88642b --- /dev/null +++ b/vendor/github.com/pion/sctp/paramheader.go @@ -0,0 +1,63 @@ +package sctp + +import ( + "encoding/binary" + "encoding/hex" + "fmt" + + "github.com/pkg/errors" +) + +type paramHeader struct { + typ paramType + len int + raw []byte +} + +const ( + paramHeaderLength = 4 +) + +func (p *paramHeader) marshal() ([]byte, error) { + paramLengthPlusHeader := paramHeaderLength + len(p.raw) + + rawParam := make([]byte, paramLengthPlusHeader) + binary.BigEndian.PutUint16(rawParam[0:], uint16(p.typ)) + binary.BigEndian.PutUint16(rawParam[2:], uint16(paramLengthPlusHeader)) + copy(rawParam[paramHeaderLength:], p.raw) + + return rawParam, nil +} + +func (p *paramHeader) unmarshal(raw []byte) error { + if len(raw) < paramHeaderLength { + return errors.New("param header too short") + } + + paramLengthPlusHeader := binary.BigEndian.Uint16(raw[2:]) + if int(paramLengthPlusHeader) < paramHeaderLength { + return errors.Errorf("param self reported length (%d) smaller than header length (%d)", int(paramLengthPlusHeader), paramHeaderLength) + } + if len(raw) < int(paramLengthPlusHeader) { + return errors.Errorf("param length (%d) shorter than its self reported length (%d)", len(raw), int(paramLengthPlusHeader)) + } + + typ, err := parseParamType(raw[0:]) + if err != nil { + return errors.Wrap(err, "failed to parse param type") + } + p.typ = typ + p.raw = raw[paramHeaderLength:paramLengthPlusHeader] + p.len = int(paramLengthPlusHeader) + + return nil +} + +func (p *paramHeader) length() int { + return p.len +} + +// String makes paramHeader printable +func (p paramHeader) String() string { + return fmt.Sprintf("%s (%d): %s", p.typ, p.len, hex.Dump(p.raw)) +} diff --git a/vendor/github.com/pion/sctp/paramtype.go b/vendor/github.com/pion/sctp/paramtype.go new file mode 100644 index 0000000..bb0ee82 --- /dev/null +++ b/vendor/github.com/pion/sctp/paramtype.go @@ -0,0 +1,106 @@ +package sctp + +import ( + "encoding/binary" + "fmt" + + "github.com/pkg/errors" +) + +// paramType represents a SCTP INIT/INITACK parameter +type paramType uint16 + +const ( + heartbeatInfo paramType = 1 // Heartbeat Info [RFC4960] + ipV4Addr paramType = 5 // IPv4 IP [RFC4960] + ipV6Addr paramType = 6 // IPv6 IP [RFC4960] + stateCookie paramType = 7 // State Cookie [RFC4960] + unrecognizedParam paramType = 8 // Unrecognized Parameters [RFC4960] + cookiePreservative paramType = 9 // Cookie Preservative [RFC4960] + hostNameAddr paramType = 11 // Host Name IP [RFC4960] + supportedAddrTypes paramType = 12 // Supported IP Types [RFC4960] + outSSNResetReq paramType = 13 // Outgoing SSN Reset Request Parameter [RFC6525] + incSSNResetReq paramType = 14 // Incoming SSN Reset Request Parameter [RFC6525] + ssnTSNResetReq paramType = 15 // SSN/TSN Reset Request Parameter [RFC6525] + reconfigResp paramType = 16 // Re-configuration Response Parameter [RFC6525] + addOutStreamsReq paramType = 17 // Add Outgoing Streams Request Parameter [RFC6525] + addIncStreamsReq paramType = 18 // Add Incoming Streams Request Parameter [RFC6525] + random paramType = 32770 // Random (0x8002) [RFC4805] + chunkList paramType = 32771 // Chunk List (0x8003) [RFC4895] + reqHMACAlgo paramType = 32772 // Requested HMAC Algorithm Parameter (0x8004) [RFC4895] + padding paramType = 32773 // Padding (0x8005) + supportedExt paramType = 32776 // Supported Extensions (0x8008) [RFC5061] + forwardTSNSupp paramType = 49152 // Forward TSN supported (0xC000) [RFC3758] + addIPAddr paramType = 49153 // Add IP IP (0xC001) [RFC5061] + delIPAddr paramType = 49154 // Delete IP IP (0xC002) [RFC5061] + errClauseInd paramType = 49155 // Error Cause Indication (0xC003) [RFC5061] + setPriAddr paramType = 49156 // Set Primary IP (0xC004) [RFC5061] + successInd paramType = 49157 // Success Indication (0xC005) [RFC5061] + adaptLayerInd paramType = 49158 // Adaptation Layer Indication (0xC006) [RFC5061] +) + +func parseParamType(raw []byte) (paramType, error) { + if len(raw) < 2 { + return paramType(0), errors.New("packet to short") + } + return paramType(binary.BigEndian.Uint16(raw)), nil +} + +func (p paramType) String() string { + switch p { + case heartbeatInfo: + return "Heartbeat Info" + case ipV4Addr: + return "IPv4 IP" + case ipV6Addr: + return "IPv6 IP" + case stateCookie: + return "State Cookie" + case unrecognizedParam: + return "Unrecognized Parameters" + case cookiePreservative: + return "Cookie Preservative" + case hostNameAddr: + return "Host Name IP" + case supportedAddrTypes: + return "Supported IP Types" + case outSSNResetReq: + return "Outgoing SSN Reset Request Parameter" + case incSSNResetReq: + return "Incoming SSN Reset Request Parameter" + case ssnTSNResetReq: + return "SSN/TSN Reset Request Parameter" + case reconfigResp: + return "Re-configuration Response Parameter" + case addOutStreamsReq: + return "Add Outgoing Streams Request Parameter" + case addIncStreamsReq: + return "Add Incoming Streams Request Parameter" + case random: + return "Random" + case chunkList: + return "Chunk List" + case reqHMACAlgo: + return "Requested HMAC Algorithm Parameter" + case padding: + return "Padding" + case supportedExt: + return "Supported Extensions" + case forwardTSNSupp: + return "Forward TSN supported" + case addIPAddr: + return "Add IP IP" + case delIPAddr: + return "Delete IP IP" + case errClauseInd: + return "Error Cause Indication" + case setPriAddr: + return "Set Primary IP" + case successInd: + return "Success Indication" + case adaptLayerInd: + return "Adaptation Layer Indication" + default: + return fmt.Sprintf("Unknown ParamType: %d", p) + } +} diff --git a/vendor/github.com/pion/sctp/payload_queue.go b/vendor/github.com/pion/sctp/payload_queue.go new file mode 100644 index 0000000..2d1a35a --- /dev/null +++ b/vendor/github.com/pion/sctp/payload_queue.go @@ -0,0 +1,179 @@ +package sctp + +import ( + "fmt" + "sort" +) + +type payloadQueue struct { + chunkMap map[uint32]*chunkPayloadData + sorted []uint32 + dupTSN []uint32 + nBytes int +} + +func newPayloadQueue() *payloadQueue { + return &payloadQueue{chunkMap: map[uint32]*chunkPayloadData{}} +} + +func (q *payloadQueue) updateSortedKeys() { + if q.sorted != nil { + return + } + + q.sorted = make([]uint32, len(q.chunkMap)) + i := 0 + for k := range q.chunkMap { + q.sorted[i] = k + i++ + } + + sort.Slice(q.sorted, func(i, j int) bool { + return sna32LT(q.sorted[i], q.sorted[j]) + }) +} + +func (q *payloadQueue) canPush(p *chunkPayloadData, cumulativeTSN uint32) bool { + _, ok := q.chunkMap[p.tsn] + if ok || sna32LTE(p.tsn, cumulativeTSN) { + return false + } + return true +} + +func (q *payloadQueue) pushNoCheck(p *chunkPayloadData) { + q.chunkMap[p.tsn] = p + q.nBytes += len(p.userData) + q.sorted = nil +} + +// push pushes a payload data. If the payload data is already in our queue or +// older than our cumulativeTSN marker, it will be recored as duplications, +// which can later be retrieved using popDuplicates. +func (q *payloadQueue) push(p *chunkPayloadData, cumulativeTSN uint32) bool { + _, ok := q.chunkMap[p.tsn] + if ok || sna32LTE(p.tsn, cumulativeTSN) { + // Found the packet, log in dups + q.dupTSN = append(q.dupTSN, p.tsn) + return false + } + + q.chunkMap[p.tsn] = p + q.nBytes += len(p.userData) + q.sorted = nil + return true +} + +// pop pops only if the oldest chunk's TSN matches the given TSN. +func (q *payloadQueue) pop(tsn uint32) (*chunkPayloadData, bool) { + q.updateSortedKeys() + + if len(q.chunkMap) > 0 && tsn == q.sorted[0] { + q.sorted = q.sorted[1:] + if c, ok := q.chunkMap[tsn]; ok { + delete(q.chunkMap, tsn) + q.nBytes -= len(c.userData) + return c, true + } + } + + return nil, false +} + +// get returns reference to chunkPayloadData with the given TSN value. +func (q *payloadQueue) get(tsn uint32) (*chunkPayloadData, bool) { + c, ok := q.chunkMap[tsn] + return c, ok +} + +// popDuplicates returns an array of TSN values that were found duplicate. +func (q *payloadQueue) popDuplicates() []uint32 { + dups := q.dupTSN + q.dupTSN = []uint32{} + return dups +} + +func (q *payloadQueue) getGapAckBlocks(cumulativeTSN uint32) (gapAckBlocks []gapAckBlock) { + var b gapAckBlock + + if len(q.chunkMap) == 0 { + return []gapAckBlock{} + } + + q.updateSortedKeys() + + for i, tsn := range q.sorted { + if i == 0 { + b.start = uint16(tsn - cumulativeTSN) + b.end = b.start + continue + } + diff := uint16(tsn - cumulativeTSN) + if b.end+1 == diff { + b.end++ + } else { + gapAckBlocks = append(gapAckBlocks, gapAckBlock{ + start: b.start, + end: b.end, + }) + b.start = diff + b.end = diff + } + } + + gapAckBlocks = append(gapAckBlocks, gapAckBlock{ + start: b.start, + end: b.end, + }) + + return gapAckBlocks +} + +func (q *payloadQueue) getGapAckBlocksString(cumulativeTSN uint32) string { + gapAckBlocks := q.getGapAckBlocks(cumulativeTSN) + str := fmt.Sprintf("cumTSN=%d", cumulativeTSN) + for _, b := range gapAckBlocks { + str += fmt.Sprintf(",%d-%d", b.start, b.end) + } + return str +} + +func (q *payloadQueue) markAsAcked(tsn uint32) int { + var nBytesAcked int + if c, ok := q.chunkMap[tsn]; ok { + c.acked = true + c.retransmit = false + nBytesAcked = len(c.userData) + q.nBytes -= nBytesAcked + c.userData = []byte{} + } + + return nBytesAcked +} + +func (q *payloadQueue) getLastTSNReceived() (uint32, bool) { + q.updateSortedKeys() + + qlen := len(q.sorted) + if qlen == 0 { + return 0, false + } + return q.sorted[qlen-1], true +} + +func (q *payloadQueue) markAllToRetrasmit() { + for _, c := range q.chunkMap { + if c.acked || c.abandoned() { + continue + } + c.retransmit = true + } +} + +func (q *payloadQueue) getNumBytes() int { + return q.nBytes +} + +func (q *payloadQueue) size() int { + return len(q.chunkMap) +} diff --git a/vendor/github.com/pion/sctp/pending_queue.go b/vendor/github.com/pion/sctp/pending_queue.go new file mode 100644 index 0000000..5f204d3 --- /dev/null +++ b/vendor/github.com/pion/sctp/pending_queue.go @@ -0,0 +1,138 @@ +package sctp + +import ( + "github.com/pkg/errors" +) + +// pendingBaseQueue + +type pendingBaseQueue struct { + queue []*chunkPayloadData +} + +func newPendingBaseQueue() *pendingBaseQueue { + return &pendingBaseQueue{queue: []*chunkPayloadData{}} +} + +func (q *pendingBaseQueue) push(c *chunkPayloadData) { + q.queue = append(q.queue, c) +} + +func (q *pendingBaseQueue) pop() *chunkPayloadData { + if len(q.queue) == 0 { + return nil + } + c := q.queue[0] + q.queue = q.queue[1:] + return c +} + +func (q *pendingBaseQueue) get(i int) *chunkPayloadData { + if len(q.queue) == 0 || i < 0 || i >= len(q.queue) { + return nil + } + return q.queue[i] +} + +func (q *pendingBaseQueue) size() int { + return len(q.queue) +} + +// pendingQueue + +type pendingQueue struct { + unorderedQueue *pendingBaseQueue + orderedQueue *pendingBaseQueue + nBytes int + selected bool + unorderedIsSelected bool +} + +var ( + errUnexpectedChuckPoppedUnordered = errors.New("unexpected chunk popped (unordered)") + errUnexpectedChuckPoppedOrdered = errors.New("unexpected chunk popped (ordered)") + errUnexpectedQState = errors.New("unexpected q state (should've been selected)") +) + +func newPendingQueue() *pendingQueue { + return &pendingQueue{ + unorderedQueue: newPendingBaseQueue(), + orderedQueue: newPendingBaseQueue(), + } +} + +func (q *pendingQueue) push(c *chunkPayloadData) { + if c.unordered { + q.unorderedQueue.push(c) + } else { + q.orderedQueue.push(c) + } + q.nBytes += len(c.userData) +} + +func (q *pendingQueue) peek() *chunkPayloadData { + if q.selected { + if q.unorderedIsSelected { + return q.unorderedQueue.get(0) + } + return q.orderedQueue.get(0) + } + + if c := q.unorderedQueue.get(0); c != nil { + return c + } + return q.orderedQueue.get(0) +} + +func (q *pendingQueue) pop(c *chunkPayloadData) error { + if q.selected { + var popped *chunkPayloadData + if q.unorderedIsSelected { + popped = q.unorderedQueue.pop() + if popped != c { + return errUnexpectedChuckPoppedUnordered + } + } else { + popped = q.orderedQueue.pop() + if popped != c { + return errUnexpectedChuckPoppedOrdered + } + } + if popped.endingFragment { + q.selected = false + } + } else { + if !c.beginningFragment { + return errUnexpectedQState + } + if c.unordered { + popped := q.unorderedQueue.pop() + if popped != c { + return errUnexpectedChuckPoppedUnordered + } + if !popped.endingFragment { + q.selected = true + q.unorderedIsSelected = true + } + } else { + popped := q.orderedQueue.pop() + if popped != c { + return errUnexpectedChuckPoppedOrdered + } + if !popped.endingFragment { + q.selected = true + q.unorderedIsSelected = false + } + } + } + q.nBytes -= len(c.userData) + return nil +} + +func (q *pendingQueue) getNumBytes() int { + return q.nBytes +} + +func (q *pendingQueue) size() int { + return q.unorderedQueue.size() + q.orderedQueue.size() +} diff --git a/vendor/github.com/pion/sctp/reassembly_queue.go b/vendor/github.com/pion/sctp/reassembly_queue.go new file mode 100644 index 0000000..f71d59c --- /dev/null +++ b/vendor/github.com/pion/sctp/reassembly_queue.go @@ -0,0 +1,353 @@ +package sctp + +import ( + "io" + "sort" + "sync/atomic" + + "github.com/pkg/errors" +) + +func sortChunksByTSN(a []*chunkPayloadData) { + sort.Slice(a, func(i, j int) bool { + return sna32LT(a[i].tsn, a[j].tsn) + }) +} + +func sortChunksBySSN(a []*chunkSet) { + sort.Slice(a, func(i, j int) bool { + return sna16LT(a[i].ssn, a[j].ssn) + }) +} + +// chunkSet is a set of chunks that share the same SSN +type chunkSet struct { + ssn uint16 // used only with the ordered chunks + ppi PayloadProtocolIdentifier + chunks []*chunkPayloadData +} + +func newChunkSet(ssn uint16, ppi PayloadProtocolIdentifier) *chunkSet { + return &chunkSet{ + ssn: ssn, + ppi: ppi, + chunks: []*chunkPayloadData{}, + } +} + +func (set *chunkSet) push(chunk *chunkPayloadData) bool { + // check if dup + for _, c := range set.chunks { + if c.tsn == chunk.tsn { + return false + } + } + + // append and sort + set.chunks = append(set.chunks, chunk) + sortChunksByTSN(set.chunks) + + // Check if we now have a complete set + complete := set.isComplete() + return complete +} + +func (set *chunkSet) isComplete() bool { + // Condition for complete set + // 0. Has at least one chunk. + // 1. Begins with beginningFragment set to true + // 2. Ends with endingFragment set to true + // 3. TSN monotinically increase by 1 from beginning to end + + // 0. + nChunks := len(set.chunks) + if nChunks == 0 { + return false + } + + // 1. + if !set.chunks[0].beginningFragment { + return false + } + + // 2. + if !set.chunks[nChunks-1].endingFragment { + return false + } + + // 3. + var lastTSN uint32 + for i, c := range set.chunks { + if i > 0 { + // Fragments must have contiguous TSN + // From RFC 4960 Section 3.3.1: + // When a user message is fragmented into multiple chunks, the TSNs are + // used by the receiver to reassemble the message. This means that the + // TSNs for each fragment of a fragmented user message MUST be strictly + // sequential. + if c.tsn != lastTSN+1 { + // mid or end fragment is missing + return false + } + } + + lastTSN = c.tsn + } + + return true +} + +type reassemblyQueue struct { + si uint16 + nextSSN uint16 // expected SSN for next ordered chunk + ordered []*chunkSet + unordered []*chunkSet + unorderedChunks []*chunkPayloadData + nBytes uint64 +} + +var errTryAgain = errors.New("try again") + +func newReassemblyQueue(si uint16) *reassemblyQueue { + // From RFC 4960 Sec 6.5: + // The Stream Sequence Number in all the streams MUST start from 0 when + // the association is established. Also, when the Stream Sequence + // Number reaches the value 65535 the next Stream Sequence Number MUST + // be set to 0. + return &reassemblyQueue{ + si: si, + nextSSN: 0, // From RFC 4960 Sec 6.5: + ordered: make([]*chunkSet, 0), + unordered: make([]*chunkSet, 0), + } +} + +func (r *reassemblyQueue) push(chunk *chunkPayloadData) bool { + var cset *chunkSet + + if chunk.streamIdentifier != r.si { + return false + } + + if chunk.unordered { + // First, insert into unorderedChunks array + r.unorderedChunks = append(r.unorderedChunks, chunk) + atomic.AddUint64(&r.nBytes, uint64(len(chunk.userData))) + sortChunksByTSN(r.unorderedChunks) + + // Scan unorderedChunks that are contiguous (in TSN) + cset = r.findCompleteUnorderedChunkSet() + + // If found, append the complete set to the unordered array + if cset != nil { + r.unordered = append(r.unordered, cset) + return true + } + + return false + } + + // This is an ordered chunk + + if sna16LT(chunk.streamSequenceNumber, r.nextSSN) { + return false + } + + // Check if a chunkSet with the SSN already exists + for _, set := range r.ordered { + if set.ssn == chunk.streamSequenceNumber { + cset = set + break + } + } + + // If not found, create a new chunkSet + if cset == nil { + cset = newChunkSet(chunk.streamSequenceNumber, chunk.payloadType) + r.ordered = append(r.ordered, cset) + if !chunk.unordered { + sortChunksBySSN(r.ordered) + } + } + + atomic.AddUint64(&r.nBytes, uint64(len(chunk.userData))) + + return cset.push(chunk) +} + +func (r *reassemblyQueue) findCompleteUnorderedChunkSet() *chunkSet { + startIdx := -1 + nChunks := 0 + var lastTSN uint32 + var found bool + + for i, c := range r.unorderedChunks { + // seek beigining + if c.beginningFragment { + startIdx = i + nChunks = 1 + lastTSN = c.tsn + + if c.endingFragment { + found = true + break + } + continue + } + + if startIdx < 0 { + continue + } + + // Check if contiguous in TSN + if c.tsn != lastTSN+1 { + startIdx = -1 + continue + } + + lastTSN = c.tsn + nChunks++ + + if c.endingFragment { + found = true + break + } + } + + if !found { + return nil + } + + // Extract the range of chunks + var chunks []*chunkPayloadData + chunks = append(chunks, r.unorderedChunks[startIdx:startIdx+nChunks]...) + + r.unorderedChunks = append( + r.unorderedChunks[:startIdx], + r.unorderedChunks[startIdx+nChunks:]...) + + chunkSet := newChunkSet(0, chunks[0].payloadType) + chunkSet.chunks = chunks + + return chunkSet +} + +func (r *reassemblyQueue) isReadable() bool { + // Check unordered first + if len(r.unordered) > 0 { + // The chunk sets in r.unordered should all be complete. + return true + } + + // Check ordered sets + if len(r.ordered) > 0 { + cset := r.ordered[0] + if cset.isComplete() { + if sna16LTE(cset.ssn, r.nextSSN) { + return true + } + } + } + return false +} + +func (r *reassemblyQueue) read(buf []byte) (int, PayloadProtocolIdentifier, error) { + var cset *chunkSet + // Check unordered first + switch { + case len(r.unordered) > 0: + cset = r.unordered[0] + r.unordered = r.unordered[1:] + case len(r.ordered) > 0: + // Now, check ordered + cset = r.ordered[0] + if !cset.isComplete() { + return 0, 0, errTryAgain + } + if sna16GT(cset.ssn, r.nextSSN) { + return 0, 0, errTryAgain + } + r.ordered = r.ordered[1:] + if cset.ssn == r.nextSSN { + r.nextSSN++ + } + default: + return 0, 0, errTryAgain + } + + // Concat all fragments into the buffer + nWritten := 0 + ppi := cset.ppi + var err error + for _, c := range cset.chunks { + toCopy := len(c.userData) + r.subtractNumBytes(toCopy) + if err == nil { + n := copy(buf[nWritten:], c.userData) + nWritten += n + if n < toCopy { + err = io.ErrShortBuffer + } + } + } + + return nWritten, ppi, err +} + +func (r *reassemblyQueue) forwardTSNForOrdered(lastSSN uint16) { + // Use lastSSN to locate a chunkSet then remove it if the set has + // not been complete + keep := []*chunkSet{} + for _, set := range r.ordered { + if sna16LTE(set.ssn, lastSSN) { + if !set.isComplete() { + // drop the set + for _, c := range set.chunks { + r.subtractNumBytes(len(c.userData)) + } + continue + } + } + keep = append(keep, set) + } + r.ordered = keep + + // Finally, forward nextSSN + if sna16LTE(r.nextSSN, lastSSN) { + r.nextSSN = lastSSN + 1 + } +} + +func (r *reassemblyQueue) forwardTSNForUnordered(newCumulativeTSN uint32) { + // Remove all fragments in the unordered sets that contains chunks + // equal to or older than `newCumulativeTSN`. + // We know all sets in the r.unordered are complete ones. + // Just remove chunks that are equal to or older than newCumulativeTSN + // from the unorderedChunks + lastIdx := -1 + for i, c := range r.unorderedChunks { + if sna32GT(c.tsn, newCumulativeTSN) { + break + } + lastIdx = i + } + if lastIdx >= 0 { + for _, c := range r.unorderedChunks[0 : lastIdx+1] { + r.subtractNumBytes(len(c.userData)) + } + r.unorderedChunks = r.unorderedChunks[lastIdx+1:] + } +} + +func (r *reassemblyQueue) subtractNumBytes(nBytes int) { + cur := atomic.LoadUint64(&r.nBytes) + if int(cur) >= nBytes { + atomic.AddUint64(&r.nBytes, -uint64(nBytes)) + } else { + atomic.StoreUint64(&r.nBytes, 0) + } +} + +func (r *reassemblyQueue) getNumBytes() int { + return int(atomic.LoadUint64(&r.nBytes)) +} diff --git a/vendor/github.com/pion/sctp/renovate.json b/vendor/github.com/pion/sctp/renovate.json new file mode 100644 index 0000000..4400fd9 --- /dev/null +++ b/vendor/github.com/pion/sctp/renovate.json @@ -0,0 +1,15 @@ +{ + "extends": [ + "config:base" + ], + "postUpdateOptions": [ + "gomodTidy" + ], + "commitBody": "Generated by renovateBot", + "packageRules": [ + { + "packagePatterns": ["^golang.org/x/"], + "schedule": ["on the first day of the month"] + } + ] +} diff --git a/vendor/github.com/pion/sctp/rtx_timer.go b/vendor/github.com/pion/sctp/rtx_timer.go new file mode 100644 index 0000000..14bc39d --- /dev/null +++ b/vendor/github.com/pion/sctp/rtx_timer.go @@ -0,0 +1,219 @@ +package sctp + +import ( + "math" + "sync" + "time" +) + +const ( + rtoInitial float64 = 3.0 * 1000 // msec + rtoMin float64 = 1.0 * 1000 // msec + rtoMax float64 = 60.0 * 1000 // msec + rtoAlpha float64 = 0.125 + rtoBeta float64 = 0.25 + maxInitRetrans uint = 8 + pathMaxRetrans uint = 5 + noMaxRetrans uint = 0 +) + +// rtoManager manages Rtx timeout values. +// This is an implementation of RFC 4960 sec 6.3.1. +type rtoManager struct { + srtt float64 + rttvar float64 + rto float64 + noUpdate bool + mutex sync.RWMutex +} + +// newRTOManager creates a new rtoManager. +func newRTOManager() *rtoManager { + return &rtoManager{ + rto: rtoInitial, + } +} + +// setNewRTT takes a newly measured RTT then adjust the RTO in msec. +func (m *rtoManager) setNewRTT(rtt float64) float64 { + m.mutex.Lock() + defer m.mutex.Unlock() + + if m.noUpdate { + return m.srtt + } + + if m.srtt == 0 { + // First measurement + m.srtt = rtt + m.rttvar = rtt / 2 + } else { + // Subsequent rtt measurement + m.rttvar = (1-rtoBeta)*m.rttvar + rtoBeta*(math.Abs(m.srtt-rtt)) + m.srtt = (1-rtoAlpha)*m.srtt + rtoAlpha*rtt + } + m.rto = math.Min(math.Max(m.srtt+4*m.rttvar, rtoMin), rtoMax) + return m.srtt +} + +// getRTO simply returns the current RTO in msec. +func (m *rtoManager) getRTO() float64 { + m.mutex.RLock() + defer m.mutex.RUnlock() + + return m.rto +} + +// reset resets the RTO variables to the initial values. +func (m *rtoManager) reset() { + m.mutex.Lock() + defer m.mutex.Unlock() + + if m.noUpdate { + return + } + + m.srtt = 0 + m.rttvar = 0 + m.rto = rtoInitial +} + +// set RTO value for testing +func (m *rtoManager) setRTO(rto float64, noUpdate bool) { + m.mutex.Lock() + defer m.mutex.Unlock() + + m.rto = rto + m.noUpdate = noUpdate +} + +// rtxTimerObserver is the inteface to a timer observer. +// NOTE: Observers MUST NOT call start() or stop() method on rtxTimer +// from within these callbacks. +type rtxTimerObserver interface { + onRetransmissionTimeout(timerID int, n uint) + onRetransmissionFailure(timerID int) +} + +// rtxTimer provides the retnransmission timer conforms with RFC 4960 Sec 6.3.1 +type rtxTimer struct { + id int + observer rtxTimerObserver + maxRetrans uint + stopFunc stopTimerLoop + closed bool + mutex sync.RWMutex +} + +type stopTimerLoop func() + +// newRTXTimer creates a new retransmission timer. +// if maxRetrans is set to 0, it will keep retransmitting until stop() is called. +// (it will never make onRetransmissionFailure() callback. +func newRTXTimer(id int, observer rtxTimerObserver, maxRetrans uint) *rtxTimer { + return &rtxTimer{ + id: id, + observer: observer, + maxRetrans: maxRetrans, + } +} + +// start starts the timer. +func (t *rtxTimer) start(rto float64) bool { + t.mutex.Lock() + defer t.mutex.Unlock() + + // this timer is already closed + if t.closed { + return false + } + + // this is a noop if the timer is always running + if t.stopFunc != nil { + return false + } + + // Note: rto value is intentionally not capped by RTO.Min to allow + // fast timeout for the tests. Non-test code should pass in the + // rto generated by rtoManager getRTO() method which caps the + // value at RTO.Min or at RTO.Max. + var nRtos uint + + cancelCh := make(chan struct{}) + + go func() { + canceling := false + + for !canceling { + timeout := calculateNextTimeout(rto, nRtos) + timer := time.NewTimer(time.Duration(timeout) * time.Millisecond) + + select { + case <-timer.C: + nRtos++ + if t.maxRetrans == 0 || nRtos <= t.maxRetrans { + t.observer.onRetransmissionTimeout(t.id, nRtos) + } else { + t.stop() + t.observer.onRetransmissionFailure(t.id) + } + case <-cancelCh: + canceling = true + timer.Stop() + } + } + }() + + t.stopFunc = func() { + close(cancelCh) + } + + return true +} + +// stop stops the timer. +func (t *rtxTimer) stop() { + t.mutex.Lock() + defer t.mutex.Unlock() + + if t.stopFunc != nil { + t.stopFunc() + t.stopFunc = nil + } +} + +// closes the timer. this is similar to stop() but subsequent start() call +// will fail (the timer is no longer usable) +func (t *rtxTimer) close() { + t.mutex.Lock() + defer t.mutex.Unlock() + + if t.stopFunc != nil { + t.stopFunc() + t.stopFunc = nil + } + + t.closed = true +} + +// isRunning tests if the timer is running. +// Debug purpose only +func (t *rtxTimer) isRunning() bool { + t.mutex.RLock() + defer t.mutex.RUnlock() + + return (t.stopFunc != nil) +} + +func calculateNextTimeout(rto float64, nRtos uint) float64 { + // RFC 4096 sec 6.3.3. Handle T3-rtx Expiration + // E2) For the destination address for which the timer expires, set RTO + // <- RTO * 2 ("back off the timer"). The maximum value discussed + // in rule C7 above (RTO.max) may be used to provide an upper bound + // to this doubling operation. + if nRtos < 31 { + m := 1 << nRtos + return math.Min(rto*float64(m), rtoMax) + } + return rtoMax +} diff --git a/vendor/github.com/pion/sctp/sctp.go b/vendor/github.com/pion/sctp/sctp.go new file mode 100644 index 0000000..e601342 --- /dev/null +++ b/vendor/github.com/pion/sctp/sctp.go @@ -0,0 +1,2 @@ +// Package sctp implements the SCTP spec +package sctp diff --git a/vendor/github.com/pion/sctp/stream.go b/vendor/github.com/pion/sctp/stream.go new file mode 100644 index 0000000..8a17b97 --- /dev/null +++ b/vendor/github.com/pion/sctp/stream.go @@ -0,0 +1,357 @@ +package sctp + +import ( + "io" + "math" + "sync" + + "github.com/pion/logging" + "github.com/pkg/errors" +) + +const ( + // ReliabilityTypeReliable is used for reliable transmission + ReliabilityTypeReliable byte = 0 + // ReliabilityTypeRexmit is used for partial reliability by retransmission count + ReliabilityTypeRexmit byte = 1 + // ReliabilityTypeTimed is used for partial reliability by retransmission duration + ReliabilityTypeTimed byte = 2 +) + +// Stream represents an SCTP stream +type Stream struct { + association *Association + lock sync.RWMutex + streamIdentifier uint16 + defaultPayloadType PayloadProtocolIdentifier + reassemblyQueue *reassemblyQueue + sequenceNumber uint16 + readNotifier *sync.Cond + readErr error + writeErr error + unordered bool + reliabilityType byte + reliabilityValue uint32 + bufferedAmount uint64 + bufferedAmountLow uint64 + onBufferedAmountLow func() + log logging.LeveledLogger + name string +} + +// StreamIdentifier returns the Stream identifier associated to the stream. +func (s *Stream) StreamIdentifier() uint16 { + s.lock.RLock() + defer s.lock.RUnlock() + return s.streamIdentifier +} + +// SetDefaultPayloadType sets the default payload type used by Write. +func (s *Stream) SetDefaultPayloadType(defaultPayloadType PayloadProtocolIdentifier) { + s.lock.Lock() + defer s.lock.Unlock() + + s.setDefaultPayloadType(defaultPayloadType) +} + +// setDefaultPayloadType sets the defaultPayloadType. The caller should hold the lock. +func (s *Stream) setDefaultPayloadType(defaultPayloadType PayloadProtocolIdentifier) { + s.defaultPayloadType = defaultPayloadType +} + +// SetReliabilityParams sets reliability parameters for this stream. +func (s *Stream) SetReliabilityParams(unordered bool, relType byte, relVal uint32) { + s.lock.Lock() + defer s.lock.Unlock() + + s.setReliabilityParams(unordered, relType, relVal) +} + +// setReliabilityParams sets reliability parameters for this stream. +// The caller should hold the lock. +func (s *Stream) setReliabilityParams(unordered bool, relType byte, relVal uint32) { + s.log.Debugf("[%s] reliability params: ordered=%v type=%d value=%d", + s.name, !unordered, relType, relVal) + s.unordered = unordered + s.reliabilityType = relType + s.reliabilityValue = relVal +} + +// Read reads a packet of len(p) bytes, dropping the Payload Protocol Identifier. +// Returns EOF when the stream is reset or an error if the stream is closed +// otherwise. +func (s *Stream) Read(p []byte) (int, error) { + n, _, err := s.ReadSCTP(p) + return n, err +} + +// ReadSCTP reads a packet of len(p) bytes and returns the associated Payload +// Protocol Identifier. +// Returns EOF when the stream is reset or an error if the stream is closed +// otherwise. +func (s *Stream) ReadSCTP(p []byte) (int, PayloadProtocolIdentifier, error) { + s.lock.Lock() + defer s.lock.Unlock() + + for { + n, ppi, err := s.reassemblyQueue.read(p) + if err == nil { + return n, ppi, nil + } else if errors.Is(err, io.ErrShortBuffer) { + return 0, PayloadProtocolIdentifier(0), err + } + + err = s.readErr + if err != nil { + return 0, PayloadProtocolIdentifier(0), err + } + + s.readNotifier.Wait() + } +} + +func (s *Stream) handleData(pd *chunkPayloadData) { + s.lock.Lock() + defer s.lock.Unlock() + + var readable bool + if s.reassemblyQueue.push(pd) { + readable = s.reassemblyQueue.isReadable() + s.log.Debugf("[%s] reassemblyQueue readable=%v", s.name, readable) + if readable { + s.log.Debugf("[%s] readNotifier.signal()", s.name) + s.readNotifier.Signal() + s.log.Debugf("[%s] readNotifier.signal() done", s.name) + } + } +} + +func (s *Stream) handleForwardTSNForOrdered(ssn uint16) { + var readable bool + + func() { + s.lock.Lock() + defer s.lock.Unlock() + + if s.unordered { + return // unordered chunks are handled by handleForwardUnordered method + } + + // Remove all chunks older than or equal to the new TSN from + // the reassemblyQueue. + s.reassemblyQueue.forwardTSNForOrdered(ssn) + readable = s.reassemblyQueue.isReadable() + }() + + // Notify the reader asynchronously if there's a data chunk to read. + if readable { + s.readNotifier.Signal() + } +} + +func (s *Stream) handleForwardTSNForUnordered(newCumulativeTSN uint32) { + var readable bool + + func() { + s.lock.Lock() + defer s.lock.Unlock() + + if !s.unordered { + return // ordered chunks are handled by handleForwardTSNOrdered method + } + + // Remove all chunks older than or equal to the new TSN from + // the reassemblyQueue. + s.reassemblyQueue.forwardTSNForUnordered(newCumulativeTSN) + readable = s.reassemblyQueue.isReadable() + }() + + // Notify the reader asynchronously if there's a data chunk to read. + if readable { + s.readNotifier.Signal() + } +} + +// Write writes len(p) bytes from p with the default Payload Protocol Identifier +func (s *Stream) Write(p []byte) (n int, err error) { + return s.WriteSCTP(p, s.defaultPayloadType) +} + +// WriteSCTP writes len(p) bytes from p to the DTLS connection +func (s *Stream) WriteSCTP(p []byte, ppi PayloadProtocolIdentifier) (n int, err error) { + maxMessageSize := s.association.MaxMessageSize() + if len(p) > int(maxMessageSize) { + return 0, errors.Errorf("Outbound packet larger than maximum message size %v", math.MaxUint16) + } + + s.lock.RLock() + err = s.writeErr + s.lock.RUnlock() + if err != nil { + return 0, err + } + + chunks := s.packetize(p, ppi) + + return len(p), s.association.sendPayloadData(chunks) +} + +func (s *Stream) packetize(raw []byte, ppi PayloadProtocolIdentifier) []*chunkPayloadData { + s.lock.Lock() + defer s.lock.Unlock() + + i := uint32(0) + remaining := uint32(len(raw)) + + // From draft-ietf-rtcweb-data-protocol-09, section 6: + // All Data Channel Establishment Protocol messages MUST be sent using + // ordered delivery and reliable transmission. + unordered := ppi != PayloadTypeWebRTCDCEP && s.unordered + + var chunks []*chunkPayloadData + var head *chunkPayloadData + for remaining != 0 { + fragmentSize := min32(s.association.maxPayloadSize, remaining) + + // Copy the userdata since we'll have to store it until acked + // and the caller may re-use the buffer in the mean time + userData := make([]byte, fragmentSize) + copy(userData, raw[i:i+fragmentSize]) + + chunk := &chunkPayloadData{ + streamIdentifier: s.streamIdentifier, + userData: userData, + unordered: unordered, + beginningFragment: i == 0, + endingFragment: remaining-fragmentSize == 0, + immediateSack: false, + payloadType: ppi, + streamSequenceNumber: s.sequenceNumber, + head: head, + } + + if head == nil { + head = chunk + } + + chunks = append(chunks, chunk) + + remaining -= fragmentSize + i += fragmentSize + } + + // RFC 4960 Sec 6.6 + // Note: When transmitting ordered and unordered data, an endpoint does + // not increment its Stream Sequence Number when transmitting a DATA + // chunk with U flag set to 1. + if !unordered { + s.sequenceNumber++ + } + + s.bufferedAmount += uint64(len(raw)) + s.log.Tracef("[%s] bufferedAmount = %d", s.name, s.bufferedAmount) + + return chunks +} + +// Close closes the write-direction of the stream. +// Future calls to Write are not permitted after calling Close. +func (s *Stream) Close() error { + if sid, isOpen := func() (uint16, bool) { + s.lock.Lock() + defer s.lock.Unlock() + + isOpen := true + if s.writeErr == nil { + s.writeErr = errors.New("Stream closed") + } else { + isOpen = false + } + + if s.readErr == nil { + s.readErr = io.EOF + } else { + isOpen = false + } + s.readNotifier.Broadcast() // broadcast regardless + + return s.streamIdentifier, isOpen + }(); isOpen { + // Reset the outgoing stream + // https://tools.ietf.org/html/rfc6525 + return s.association.sendResetRequest(sid) + } + + return nil +} + +// BufferedAmount returns the number of bytes of data currently queued to be sent over this stream. +func (s *Stream) BufferedAmount() uint64 { + s.lock.RLock() + defer s.lock.RUnlock() + + return s.bufferedAmount +} + +// BufferedAmountLowThreshold returns the number of bytes of buffered outgoing data that is +// considered "low." Defaults to 0. +func (s *Stream) BufferedAmountLowThreshold() uint64 { + s.lock.RLock() + defer s.lock.RUnlock() + + return s.bufferedAmountLow +} + +// SetBufferedAmountLowThreshold is used to update the threshold. +// See BufferedAmountLowThreshold(). +func (s *Stream) SetBufferedAmountLowThreshold(th uint64) { + s.lock.Lock() + defer s.lock.Unlock() + + s.bufferedAmountLow = th +} + +// OnBufferedAmountLow sets the callback handler which would be called when the number of +// bytes of outgoing data buffered is lower than the threshold. +func (s *Stream) OnBufferedAmountLow(f func()) { + s.lock.Lock() + defer s.lock.Unlock() + + s.onBufferedAmountLow = f +} + +// This method is called by association's readLoop (go-)routine to notify this stream +// of the specified amount of outgoing data has been delivered to the peer. +func (s *Stream) onBufferReleased(nBytesReleased int) { + if nBytesReleased <= 0 { + return + } + + s.lock.Lock() + + fromAmount := s.bufferedAmount + + if s.bufferedAmount < uint64(nBytesReleased) { + s.bufferedAmount = 0 + s.log.Errorf("[%s] released buffer size %d should be <= %d", + s.name, nBytesReleased, s.bufferedAmount) + } else { + s.bufferedAmount -= uint64(nBytesReleased) + } + + s.log.Tracef("[%s] bufferedAmount = %d", s.name, s.bufferedAmount) + + if s.onBufferedAmountLow != nil && fromAmount > s.bufferedAmountLow && s.bufferedAmount <= s.bufferedAmountLow { + f := s.onBufferedAmountLow + s.lock.Unlock() + f() + return + } + + s.lock.Unlock() +} + +func (s *Stream) getNumBytesInReassemblyQueue() int { + // No lock is required as it reads the size with atomic load function. + return s.reassemblyQueue.getNumBytes() +} diff --git a/vendor/github.com/pion/sctp/util.go b/vendor/github.com/pion/sctp/util.go new file mode 100644 index 0000000..e2e54ab --- /dev/null +++ b/vendor/github.com/pion/sctp/util.go @@ -0,0 +1,58 @@ +package sctp + +const ( + paddingMultiple = 4 +) + +func getPadding(len int) int { + return (paddingMultiple - (len % paddingMultiple)) % paddingMultiple +} + +func padByte(in []byte, cnt int) []byte { + if cnt < 0 { + cnt = 0 + } + padding := make([]byte, cnt) + return append(in, padding...) +} + +// Serial Number Arithmetic (RFC 1982) +func sna32LT(i1, i2 uint32) bool { + return (i1 < i2 && i2-i1 < 1<<31) || (i1 > i2 && i1-i2 > 1<<31) +} + +func sna32LTE(i1, i2 uint32) bool { + return i1 == i2 || sna32LT(i1, i2) +} + +func sna32GT(i1, i2 uint32) bool { + return (i1 < i2 && (i2-i1) >= 1<<31) || (i1 > i2 && (i1-i2) <= 1<<31) +} + +func sna32GTE(i1, i2 uint32) bool { + return i1 == i2 || sna32GT(i1, i2) +} + +func sna32EQ(i1, i2 uint32) bool { + return i1 == i2 +} + +func sna16LT(i1, i2 uint16) bool { + return (i1 < i2 && (i2-i1) < 1<<15) || (i1 > i2 && (i1-i2) > 1<<15) +} + +func sna16LTE(i1, i2 uint16) bool { + return i1 == i2 || sna16LT(i1, i2) +} + +func sna16GT(i1, i2 uint16) bool { + return (i1 < i2 && (i2-i1) >= 1<<15) || (i1 > i2 && (i1-i2) <= 1<<15) +} + +func sna16GTE(i1, i2 uint16) bool { + return i1 == i2 || sna16GT(i1, i2) +} + +func sna16EQ(i1, i2 uint16) bool { + return i1 == i2 +} diff --git a/vendor/github.com/pion/sdp/v3/.gitignore b/vendor/github.com/pion/sdp/v3/.gitignore new file mode 100644 index 0000000..83db74b --- /dev/null +++ b/vendor/github.com/pion/sdp/v3/.gitignore @@ -0,0 +1,24 @@ +### JetBrains IDE ### +##################### +.idea/ + +### Emacs Temporary Files ### +############################# +*~ + +### Folders ### +############### +bin/ +vendor/ +node_modules/ + +### Files ### +############# +*.ivf +*.ogg +tags +cover.out +*.sw[poe] +*.wasm +examples/sfu-ws/cert.pem +examples/sfu-ws/key.pem diff --git a/vendor/github.com/pion/sdp/v3/.golangci.yml b/vendor/github.com/pion/sdp/v3/.golangci.yml new file mode 100644 index 0000000..d6162c9 --- /dev/null +++ b/vendor/github.com/pion/sdp/v3/.golangci.yml @@ -0,0 +1,89 @@ +linters-settings: + govet: + check-shadowing: true + misspell: + locale: US + exhaustive: + default-signifies-exhaustive: true + gomodguard: + blocked: + modules: + - github.com/pkg/errors: + recommendations: + - errors + +linters: + enable: + - asciicheck # Simple linter to check that your code does not contain non-ASCII identifiers + - bodyclose # checks whether HTTP response body is closed successfully + - deadcode # Finds unused code + - depguard # Go linter that checks if package imports are in a list of acceptable packages + - dogsled # Checks assignments with too many blank identifiers (e.g. x, _, _, _, := f()) + - dupl # Tool for code clone detection + - errcheck # Errcheck is a program for checking for unchecked errors in go programs. These unchecked errors can be critical bugs in some cases + - exhaustive # check exhaustiveness of enum switch statements + - exportloopref # checks for pointers to enclosing loop variables + - gci # Gci control golang package import order and make it always deterministic. + - gochecknoglobals # Checks that no globals are present in Go code + - gochecknoinits # Checks that no init functions are present in Go code + - gocognit # Computes and checks the cognitive complexity of functions + - goconst # Finds repeated strings that could be replaced by a constant + - gocritic # The most opinionated Go source code linter + - godox # Tool for detection of FIXME, TODO and other comment keywords + - goerr113 # Golang linter to check the errors handling expressions + - gofmt # Gofmt checks whether code was gofmt-ed. By default this tool runs with -s option to check for code simplification + - gofumpt # Gofumpt checks whether code was gofumpt-ed. + - goheader # Checks is file header matches to pattern + - goimports # Goimports does everything that gofmt does. Additionally it checks unused imports + - golint # Golint differs from gofmt. Gofmt reformats Go source code, whereas golint prints out style mistakes + - gomodguard # Allow and block list linter for direct Go module dependencies. This is different from depguard where there are different block types for example version constraints and module recommendations. + - goprintffuncname # Checks that printf-like functions are named with `f` at the end + - gosec # Inspects source code for security problems + - gosimple # Linter for Go source code that specializes in simplifying a code + - govet # Vet examines Go source code and reports suspicious constructs, such as Printf calls whose arguments do not align with the format string + - ineffassign # Detects when assignments to existing variables are not used + - misspell # Finds commonly misspelled English words in comments + - nakedret # Finds naked returns in functions greater than a specified function length + - noctx # noctx finds sending http request without context.Context + - scopelint # Scopelint checks for unpinned variables in go programs + - staticcheck # Staticcheck is a go vet on steroids, applying a ton of static analysis checks + - structcheck # Finds unused struct fields + - stylecheck # Stylecheck is a replacement for golint + - typecheck # Like the front-end of a Go compiler, parses and type-checks Go code + - unconvert # Remove unnecessary type conversions + - unparam # Reports unused function parameters + - unused # Checks Go code for unused constants, variables, functions and types + - varcheck # Finds unused global variables and constants + - whitespace # Tool for detection of leading and trailing whitespace + disable: + - funlen # Tool for detection of long functions + - gocyclo # Computes and checks the cyclomatic complexity of functions + - godot # Check if comments end in a period + - gomnd # An analyzer to detect magic numbers. + - lll # Reports long lines + - maligned # Tool to detect Go structs that would take less memory if their fields were sorted + - nestif # Reports deeply nested if statements + - nlreturn # nlreturn checks for a new line before return and branch statements to increase code clarity + - nolintlint # Reports ill-formed or insufficient nolint directives + - prealloc # Finds slice declarations that could potentially be preallocated + - rowserrcheck # checks whether Err of rows is checked successfully + - sqlclosecheck # Checks that sql.Rows and sql.Stmt are closed. + - testpackage # linter that makes you use a separate _test package + - wsl # Whitespace Linter - Forces you to use empty lines! + +issues: + exclude-use-default: false + exclude-rules: + # Allow complex tests, better to be self contained + - path: _test\.go + linters: + - gocognit + + # Allow complex main function in examples + - path: examples + text: "of func `main` is high" + linters: + - gocognit + +run: + skip-dirs-use-default: false diff --git a/vendor/github.com/pion/sdp/v3/LICENSE b/vendor/github.com/pion/sdp/v3/LICENSE new file mode 100644 index 0000000..ab60297 --- /dev/null +++ b/vendor/github.com/pion/sdp/v3/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/pion/sdp/v3/README.md b/vendor/github.com/pion/sdp/v3/README.md new file mode 100644 index 0000000..e7d7ff6 --- /dev/null +++ b/vendor/github.com/pion/sdp/v3/README.md @@ -0,0 +1,59 @@ +<h1 align="center"> + <br> + Pion SDP + <br> +</h1> +<h4 align="center">A Go implementation of the SDP</h4> +<p align="center"> + <a href="https://pion.ly"><img src="https://img.shields.io/badge/pion-sdp-gray.svg?longCache=true&colorB=brightgreen" alt="Pion SDP"></a> + <a href="https://sourcegraph.com/github.com/pion/sdp?badge"><img src="https://sourcegraph.com/github.com/pion/sdp/-/badge.svg" alt="Sourcegraph Widget"></a> + <a href="https://pion.ly/slack"><img src="https://img.shields.io/badge/join-us%20on%20slack-gray.svg?longCache=true&logo=slack&colorB=brightgreen" alt="Slack Widget"></a> + <br> + <a href="https://travis-ci.org/pion/sdp"><img src="https://travis-ci.org/pion/sdp.svg?branch=master" alt="Build Status"></a> + <a href="https://pkg.go.dev/github.com/pion/sdp/v2"><img src="https://godoc.org/github.com/pion/sdp?status.svg" alt="GoDoc"></a> + <a href="https://codecov.io/gh/pion/sdp"><img src="https://codecov.io/gh/pion/sdp/branch/master/graph/badge.svg" alt="Coverage Status"></a> + <a href="https://goreportcard.com/report/github.com/pion/sdp"><img src="https://goreportcard.com/badge/github.com/pion/sdp" alt="Go Report Card"></a> + <a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="License: MIT"></a> +</p> +<br> + +See [DESIGN.md](DESIGN.md) for an overview of features and future goals. + +### Roadmap +The library is used as a part of our WebRTC implementation. Please refer to that [roadmap](https://github.com/pion/webrtc/issues/9) to track our major milestones. + +### Community +Pion has an active community on the [Golang Slack](https://invite.slack.golangbridge.org/). Sign up and join the **#pion** channel for discussions and support. You can also use [Pion mailing list](https://groups.google.com/forum/#!forum/pion). + +We are always looking to support **your projects**. Please reach out if you have something to build! + +If you need commercial support or don't want to use public methods you can contact us at [team@pion.ly](mailto:team@pion.ly) + +### Contributing +Check out the **[contributing wiki](https://github.com/pion/webrtc/wiki/Contributing)** to join the group of amazing people making this project possible: + +* [John Bradley](https://github.com/kc5nra) - *Original Author* +* [Sean DuBois](https://github.com/Sean-Der) - *Original Author* +* [Michiel De Backker](https://github.com/backkem) - *Public API, Initialization* +* [Konstantin Itskov](https://github.com/trivigy) - *Fix documentation* +* [chenkaiC4](https://github.com/chenkaiC4) - *Fix GolangCI Linter* +* [Woodrow Douglass](https://github.com/wdouglass) *RTCP, RTP improvements, G.722 support, Bugfixes* +* [Michael MacDonald](https://github.com/mjmac) +* [Max Hawkins](https://github.com/maxhawkins) +* [mchlrhw](https://github.com/mchlrhw) +* [Hugo Arregui](https://github.com/hugoArregui) +* [Guilherme Souza](https://github.com/gqgs) +* [adwpc](https://github.com/adwpc) - *extmap add transport-cc* +* [Atsushi Watanabe](https://github.com/at-wat) +* [Luke S](https://github.com/encounter) +* [Jerko Steiner](https://github.com/jeremija) +* [Roman Romanenko](https://github.com/r-novel) +* [Jason Brady](https://github.com/jbrady42) +* [Kory Miller](https://github.com/jbrady42/korymiller1489) +* [ZHENK](https://github.com/scorpionknifes) +* [Tarrence van As](https://github.com/tarrencev) +* [Maxim Oransky](https://github.com/sdfsdhgjkbmnmxc) +* [Graham King](https://github.com/grahamking/) + +### License +MIT License - see [LICENSE](LICENSE) for full text diff --git a/vendor/github.com/pion/sdp/v3/base_lexer.go b/vendor/github.com/pion/sdp/v3/base_lexer.go new file mode 100644 index 0000000..45fe799 --- /dev/null +++ b/vendor/github.com/pion/sdp/v3/base_lexer.go @@ -0,0 +1,231 @@ +package sdp + +import ( + "errors" + "fmt" + "io" +) + +var errDocumentStart = errors.New("already on document start") + +type syntaxError struct { + s string + i int +} + +func (e syntaxError) Error() string { + if e.i < 0 { + e.i = 0 + } + head, middle, tail := e.s[:e.i], e.s[e.i:e.i+1], e.s[e.i+1:] + return fmt.Sprintf("%s --> %s <-- %s", head, middle, tail) +} + +type baseLexer struct { + value []byte + pos int +} + +func (l baseLexer) syntaxError() error { + return syntaxError{s: string(l.value), i: l.pos - 1} +} + +func (l *baseLexer) unreadByte() error { + if l.pos <= 0 { + return errDocumentStart + } + l.pos-- + return nil +} + +func (l *baseLexer) readByte() (byte, error) { + if l.pos >= len(l.value) { + return byte(0), io.EOF + } + ch := l.value[l.pos] + l.pos++ + return ch, nil +} + +func (l *baseLexer) nextLine() error { + for { + ch, err := l.readByte() + if err == io.EOF { + return nil + } else if err != nil { + return err + } + if !isNewline(ch) { + return l.unreadByte() + } + } +} + +func (l *baseLexer) readWhitespace() error { + for { + ch, err := l.readByte() + if err == io.EOF { + return nil + } else if err != nil { + return err + } + if !isWhitespace(ch) { + return l.unreadByte() + } + } +} + +func (l *baseLexer) readUint64Field() (i uint64, err error) { + for { + ch, err := l.readByte() + if err == io.EOF && i > 0 { + break + } else if err != nil { + return i, err + } + + if isNewline(ch) { + if err := l.unreadByte(); err != nil { + return i, err + } + break + } + + if isWhitespace(ch) { + if err := l.readWhitespace(); err != nil { + return i, err + } + break + } + + switch ch { + case '0': + i *= 10 + case '1': + i = i*10 + 1 + case '2': + i = i*10 + 2 + case '3': + i = i*10 + 3 + case '4': + i = i*10 + 4 + case '5': + i = i*10 + 5 + case '6': + i = i*10 + 6 + case '7': + i = i*10 + 7 + case '8': + i = i*10 + 8 + case '9': + i = i*10 + 9 + default: + return i, l.syntaxError() + } + } + + return i, nil +} + +// Returns next field on this line or empty string if no more fields on line +func (l *baseLexer) readField() (string, error) { + start := l.pos + stop := start + for { + stop = l.pos + ch, err := l.readByte() + if err == io.EOF && stop > start { + break + } else if err != nil { + return "", err + } + + if isNewline(ch) { + if err := l.unreadByte(); err != nil { + return "", err + } + break + } + + if isWhitespace(ch) { + if err := l.readWhitespace(); err != nil { + return "", err + } + break + } + } + return string(l.value[start:stop]), nil +} + +// Returns symbols until line end +func (l *baseLexer) readLine() (string, error) { + start := l.pos + trim := 1 + for { + ch, err := l.readByte() + if err != nil { + return "", err + } + if ch == '\r' { + trim++ + } + if ch == '\n' { + return string(l.value[start : l.pos-trim]), nil + } + } +} + +func (l *baseLexer) readString(until byte) (string, error) { + start := l.pos + for { + ch, err := l.readByte() + if err != nil { + return "", err + } + if ch == until { + return string(l.value[start:l.pos]), nil + } + } +} + +func (l *baseLexer) readType() (string, error) { + for { + b, err := l.readByte() + if err != nil { + return "", err + } + + if isNewline(b) { + continue + } + + err = l.unreadByte() + if err != nil { + return "", err + } + + key, err := l.readString('=') + if err != nil { + return key, err + } + + if len(key) == 2 { + return key, nil + } + + return key, l.syntaxError() + } +} + +func isNewline(ch byte) bool { return ch == '\n' || ch == '\r' } + +func isWhitespace(ch byte) bool { return ch == ' ' || ch == '\t' } + +func anyOf(element string, data ...string) bool { + for _, v := range data { + if element == v { + return true + } + } + return false +} diff --git a/vendor/github.com/pion/sdp/v3/codecov.yml b/vendor/github.com/pion/sdp/v3/codecov.yml new file mode 100644 index 0000000..085200a --- /dev/null +++ b/vendor/github.com/pion/sdp/v3/codecov.yml @@ -0,0 +1,20 @@ +# +# DO NOT EDIT THIS FILE +# +# It is automatically copied from https://github.com/pion/.goassets repository. +# + +coverage: + status: + project: + default: + # Allow decreasing 2% of total coverage to avoid noise. + threshold: 2% + patch: + default: + target: 70% + only_pulls: true + +ignore: + - "examples/*" + - "examples/**/*" diff --git a/vendor/github.com/pion/sdp/v3/common_description.go b/vendor/github.com/pion/sdp/v3/common_description.go new file mode 100644 index 0000000..1174be4 --- /dev/null +++ b/vendor/github.com/pion/sdp/v3/common_description.go @@ -0,0 +1,110 @@ +package sdp + +import ( + "strconv" + "strings" +) + +// Information describes the "i=" field which provides textual information +// about the session. +type Information string + +func (i Information) String() string { + return string(i) +} + +// ConnectionInformation defines the representation for the "c=" field +// containing connection data. +type ConnectionInformation struct { + NetworkType string + AddressType string + Address *Address +} + +func (c ConnectionInformation) String() string { + parts := []string{c.NetworkType, c.AddressType} + if c.Address != nil && c.Address.String() != "" { + parts = append(parts, c.Address.String()) + } + return strings.Join(parts, " ") +} + +// Address desribes a structured address token from within the "c=" field. +type Address struct { + Address string + TTL *int + Range *int +} + +func (c *Address) String() string { + var parts []string + parts = append(parts, c.Address) + if c.TTL != nil { + parts = append(parts, strconv.Itoa(*c.TTL)) + } + + if c.Range != nil { + parts = append(parts, strconv.Itoa(*c.Range)) + } + + return strings.Join(parts, "/") +} + +// Bandwidth describes an optional field which denotes the proposed bandwidth +// to be used by the session or media. +type Bandwidth struct { + Experimental bool + Type string + Bandwidth uint64 +} + +func (b Bandwidth) String() string { + var output string + if b.Experimental { + output += "X-" + } + output += b.Type + ":" + strconv.FormatUint(b.Bandwidth, 10) + return output +} + +// EncryptionKey describes the "k=" which conveys encryption key information. +type EncryptionKey string + +func (s EncryptionKey) String() string { + return string(s) +} + +// Attribute describes the "a=" field which represents the primary means for +// extending SDP. +type Attribute struct { + Key string + Value string +} + +// NewPropertyAttribute constructs a new attribute +func NewPropertyAttribute(key string) Attribute { + return Attribute{ + Key: key, + } +} + +// NewAttribute constructs a new attribute +func NewAttribute(key, value string) Attribute { + return Attribute{ + Key: key, + Value: value, + } +} + +func (a Attribute) String() string { + output := a.Key + if len(a.Value) > 0 { + output += ":" + a.Value + } + return output +} + +// IsICECandidate returns true if the attribute key equals "candidate". +func (a Attribute) IsICECandidate() bool { + return a.Key == "candidate" +} diff --git a/vendor/github.com/pion/sdp/v3/direction.go b/vendor/github.com/pion/sdp/v3/direction.go new file mode 100644 index 0000000..19ea92d --- /dev/null +++ b/vendor/github.com/pion/sdp/v3/direction.go @@ -0,0 +1,59 @@ +package sdp + +import "errors" + +// Direction is a marker for transmission directon of an endpoint +type Direction int + +const ( + // DirectionSendRecv is for bidirectional communication + DirectionSendRecv Direction = iota + 1 + // DirectionSendOnly is for outgoing communication + DirectionSendOnly + // DirectionRecvOnly is for incoming communication + DirectionRecvOnly + // DirectionInactive is for no communication + DirectionInactive +) + +const ( + directionSendRecvStr = "sendrecv" + directionSendOnlyStr = "sendonly" + directionRecvOnlyStr = "recvonly" + directionInactiveStr = "inactive" + directionUnknownStr = "" +) + +var errDirectionString = errors.New("invalid direction string") + +// NewDirection defines a procedure for creating a new direction from a raw +// string. +func NewDirection(raw string) (Direction, error) { + switch raw { + case directionSendRecvStr: + return DirectionSendRecv, nil + case directionSendOnlyStr: + return DirectionSendOnly, nil + case directionRecvOnlyStr: + return DirectionRecvOnly, nil + case directionInactiveStr: + return DirectionInactive, nil + default: + return Direction(unknown), errDirectionString + } +} + +func (t Direction) String() string { + switch t { + case DirectionSendRecv: + return directionSendRecvStr + case DirectionSendOnly: + return directionSendOnlyStr + case DirectionRecvOnly: + return directionRecvOnlyStr + case DirectionInactive: + return directionInactiveStr + default: + return directionUnknownStr + } +} diff --git a/vendor/github.com/pion/sdp/v3/extmap.go b/vendor/github.com/pion/sdp/v3/extmap.go new file mode 100644 index 0000000..1242a8c --- /dev/null +++ b/vendor/github.com/pion/sdp/v3/extmap.go @@ -0,0 +1,108 @@ +package sdp + +import ( + "fmt" + "net/url" + "strconv" + "strings" +) + +// Default ext values +const ( + DefExtMapValueABSSendTime = 1 + DefExtMapValueTransportCC = 2 + DefExtMapValueSDESMid = 3 + DefExtMapValueSDESRTPStreamID = 4 + + ABSSendTimeURI = "http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time" + TransportCCURI = "http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01" + SDESMidURI = "urn:ietf:params:rtp-hdrext:sdes:mid" + SDESRTPStreamIDURI = "urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id" + AudioLevelURI = "urn:ietf:params:rtp-hdrext:ssrc-audio-level" +) + +// ExtMap represents the activation of a single RTP header extension +type ExtMap struct { + Value int + Direction Direction + URI *url.URL + ExtAttr *string +} + +// Clone converts this object to an Attribute +func (e *ExtMap) Clone() Attribute { + return Attribute{Key: "extmap", Value: e.string()} +} + +// Unmarshal creates an Extmap from a string +func (e *ExtMap) Unmarshal(raw string) error { + parts := strings.SplitN(raw, ":", 2) + if len(parts) != 2 { + return fmt.Errorf("%w: %v", errSyntaxError, raw) + } + + fields := strings.Fields(parts[1]) + if len(fields) < 2 { + return fmt.Errorf("%w: %v", errSyntaxError, raw) + } + + valdir := strings.Split(fields[0], "/") + value, err := strconv.ParseInt(valdir[0], 10, 64) + if (value < 1) || (value > 246) { + return fmt.Errorf("%w: %v -- extmap key must be in the range 1-256", errSyntaxError, valdir[0]) + } + if err != nil { + return fmt.Errorf("%w: %v", errSyntaxError, valdir[0]) + } + + var direction Direction + if len(valdir) == 2 { + direction, err = NewDirection(valdir[1]) + if err != nil { + return err + } + } + + uri, err := url.Parse(fields[1]) + if err != nil { + return err + } + + if len(fields) == 3 { + tmp := fields[2] + e.ExtAttr = &tmp + } + + e.Value = int(value) + e.Direction = direction + e.URI = uri + return nil +} + +// Marshal creates a string from an ExtMap +func (e *ExtMap) Marshal() string { + return e.Name() + ":" + e.string() +} + +func (e *ExtMap) string() string { + output := fmt.Sprintf("%d", e.Value) + dirstring := e.Direction.String() + if dirstring != directionUnknownStr { + output += "/" + dirstring + } + + if e.URI != nil { + output += " " + e.URI.String() + } + + if e.ExtAttr != nil { + output += " " + *e.ExtAttr + } + + return output +} + +// Name returns the constant name of this object +func (e *ExtMap) Name() string { + return "extmap" +} diff --git a/vendor/github.com/pion/sdp/v3/fuzz.go b/vendor/github.com/pion/sdp/v3/fuzz.go new file mode 100644 index 0000000..f41ef78 --- /dev/null +++ b/vendor/github.com/pion/sdp/v3/fuzz.go @@ -0,0 +1,31 @@ +// +build gofuzz + +package sdp + +// Fuzz implements a randomized fuzz test of the sdp +// parser using go-fuzz. +// +// To run the fuzzer, first download go-fuzz: +// `go get github.com/dvyukov/go-fuzz/...` +// +// Then build the testing package: +// `go-fuzz-build` +// +// And run the fuzzer on the corpus: +// `go-fuzz` +func Fuzz(data []byte) int { + // Check that unmarshalling any byte slice does not panic. + var sd SessionDescription + if err := sd.Unmarshal(data); err != nil { + return 0 + } + // Check that we can marshal anything we unmarshalled. + _, err := sd.Marshal() + if err != nil { + panic("failed to marshal") // nolint + } + // It'd be nice to check that if we round trip Marshal then Unmarshal, + // we get the original back. Right now, though, we frequently don't, + // and we'd need to fix that first. + return 1 +} diff --git a/vendor/github.com/pion/sdp/v3/go.mod b/vendor/github.com/pion/sdp/v3/go.mod new file mode 100644 index 0000000..da7ceaa --- /dev/null +++ b/vendor/github.com/pion/sdp/v3/go.mod @@ -0,0 +1,8 @@ +module github.com/pion/sdp/v3 + +go 1.13 + +require ( + github.com/pion/randutil v0.1.0 + github.com/stretchr/testify v1.6.1 +) diff --git a/vendor/github.com/pion/sdp/v3/go.sum b/vendor/github.com/pion/sdp/v3/go.sum new file mode 100644 index 0000000..43316cb --- /dev/null +++ b/vendor/github.com/pion/sdp/v3/go.sum @@ -0,0 +1,13 @@ +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA= +github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/vendor/github.com/pion/sdp/v3/jsep.go b/vendor/github.com/pion/sdp/v3/jsep.go new file mode 100644 index 0000000..efdef03 --- /dev/null +++ b/vendor/github.com/pion/sdp/v3/jsep.go @@ -0,0 +1,205 @@ +package sdp + +import ( + "fmt" + "net/url" + "strconv" + "time" +) + +// Constants for SDP attributes used in JSEP +const ( + AttrKeyCandidate = "candidate" + AttrKeyEndOfCandidates = "end-of-candidates" + AttrKeyIdentity = "identity" + AttrKeyGroup = "group" + AttrKeySSRC = "ssrc" + AttrKeySSRCGroup = "ssrc-group" + AttrKeyMsid = "msid" + AttrKeyMsidSemantic = "msid-semantic" + AttrKeyConnectionSetup = "setup" + AttrKeyMID = "mid" + AttrKeyICELite = "ice-lite" + AttrKeyRTCPMux = "rtcp-mux" + AttrKeyRTCPRsize = "rtcp-rsize" + AttrKeyInactive = "inactive" + AttrKeyRecvOnly = "recvonly" + AttrKeySendOnly = "sendonly" + AttrKeySendRecv = "sendrecv" + AttrKeyExtMap = "extmap" +) + +// Constants for semantic tokens used in JSEP +const ( + SemanticTokenLipSynchronization = "LS" + SemanticTokenFlowIdentification = "FID" + SemanticTokenForwardErrorCorrection = "FEC" + SemanticTokenWebRTCMediaStreams = "WMS" +) + +// Constants for extmap key +const ( + ExtMapValueTransportCC = 3 +) + +func extMapURI() map[int]string { + return map[int]string{ + ExtMapValueTransportCC: "http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01", + } +} + +// API to match draft-ietf-rtcweb-jsep +// Move to webrtc or its own package? + +// NewJSEPSessionDescription creates a new SessionDescription with +// some settings that are required by the JSEP spec. +// +// Note: Since v2.4.0, session ID has been fixed to use crypto random according to +// JSEP spec, so that NewJSEPSessionDescription now returns error as a second +// return value. +func NewJSEPSessionDescription(identity bool) (*SessionDescription, error) { + sid, err := newSessionID() + if err != nil { + return nil, err + } + d := &SessionDescription{ + Version: 0, + Origin: Origin{ + Username: "-", + SessionID: sid, + SessionVersion: uint64(time.Now().Unix()), + NetworkType: "IN", + AddressType: "IP4", + UnicastAddress: "0.0.0.0", + }, + SessionName: "-", + TimeDescriptions: []TimeDescription{ + { + Timing: Timing{ + StartTime: 0, + StopTime: 0, + }, + RepeatTimes: nil, + }, + }, + Attributes: []Attribute{ + // "Attribute(ice-options:trickle)", // TODO: implement trickle ICE + }, + } + + if identity { + d.WithPropertyAttribute(AttrKeyIdentity) + } + + return d, nil +} + +// WithPropertyAttribute adds a property attribute 'a=key' to the session description +func (s *SessionDescription) WithPropertyAttribute(key string) *SessionDescription { + s.Attributes = append(s.Attributes, NewPropertyAttribute(key)) + return s +} + +// WithValueAttribute adds a value attribute 'a=key:value' to the session description +func (s *SessionDescription) WithValueAttribute(key, value string) *SessionDescription { + s.Attributes = append(s.Attributes, NewAttribute(key, value)) + return s +} + +// WithFingerprint adds a fingerprint to the session description +func (s *SessionDescription) WithFingerprint(algorithm, value string) *SessionDescription { + return s.WithValueAttribute("fingerprint", algorithm+" "+value) +} + +// WithMedia adds a media description to the session description +func (s *SessionDescription) WithMedia(md *MediaDescription) *SessionDescription { + s.MediaDescriptions = append(s.MediaDescriptions, md) + return s +} + +// NewJSEPMediaDescription creates a new MediaName with +// some settings that are required by the JSEP spec. +func NewJSEPMediaDescription(codecType string, codecPrefs []string) *MediaDescription { + return &MediaDescription{ + MediaName: MediaName{ + Media: codecType, + Port: RangedPort{Value: 9}, + Protos: []string{"UDP", "TLS", "RTP", "SAVPF"}, + }, + ConnectionInformation: &ConnectionInformation{ + NetworkType: "IN", + AddressType: "IP4", + Address: &Address{ + Address: "0.0.0.0", + }, + }, + } +} + +// WithPropertyAttribute adds a property attribute 'a=key' to the media description +func (d *MediaDescription) WithPropertyAttribute(key string) *MediaDescription { + d.Attributes = append(d.Attributes, NewPropertyAttribute(key)) + return d +} + +// WithValueAttribute adds a value attribute 'a=key:value' to the media description +func (d *MediaDescription) WithValueAttribute(key, value string) *MediaDescription { + d.Attributes = append(d.Attributes, NewAttribute(key, value)) + return d +} + +// WithFingerprint adds a fingerprint to the media description +func (d *MediaDescription) WithFingerprint(algorithm, value string) *MediaDescription { + return d.WithValueAttribute("fingerprint", algorithm+" "+value) +} + +// WithICECredentials adds ICE credentials to the media description +func (d *MediaDescription) WithICECredentials(username, password string) *MediaDescription { + return d. + WithValueAttribute("ice-ufrag", username). + WithValueAttribute("ice-pwd", password) +} + +// WithCodec adds codec information to the media description +func (d *MediaDescription) WithCodec(payloadType uint8, name string, clockrate uint32, channels uint16, fmtp string) *MediaDescription { + d.MediaName.Formats = append(d.MediaName.Formats, strconv.Itoa(int(payloadType))) + rtpmap := fmt.Sprintf("%d %s/%d", payloadType, name, clockrate) + if channels > 0 { + rtpmap += fmt.Sprintf("/%d", channels) + } + d.WithValueAttribute("rtpmap", rtpmap) + if fmtp != "" { + d.WithValueAttribute("fmtp", fmt.Sprintf("%d %s", payloadType, fmtp)) + } + return d +} + +// WithMediaSource adds media source information to the media description +func (d *MediaDescription) WithMediaSource(ssrc uint32, cname, streamLabel, label string) *MediaDescription { + return d. + WithValueAttribute("ssrc", fmt.Sprintf("%d cname:%s", ssrc, cname)). // Deprecated but not phased out? + WithValueAttribute("ssrc", fmt.Sprintf("%d msid:%s %s", ssrc, streamLabel, label)). + WithValueAttribute("ssrc", fmt.Sprintf("%d mslabel:%s", ssrc, streamLabel)). // Deprecated but not phased out? + WithValueAttribute("ssrc", fmt.Sprintf("%d label:%s", ssrc, label)) // Deprecated but not phased out? +} + +// WithCandidate adds an ICE candidate to the media description +// Deprecated: use WithICECandidate instead +func (d *MediaDescription) WithCandidate(value string) *MediaDescription { + return d.WithValueAttribute("candidate", value) +} + +// WithExtMap adds an extmap to the media description +func (d *MediaDescription) WithExtMap(e ExtMap) *MediaDescription { + return d.WithPropertyAttribute(e.Marshal()) +} + +// WithTransportCCExtMap adds an extmap to the media description +func (d *MediaDescription) WithTransportCCExtMap() *MediaDescription { + uri, _ := url.Parse(extMapURI()[ExtMapValueTransportCC]) + e := ExtMap{ + Value: ExtMapValueTransportCC, + URI: uri, + } + return d.WithExtMap(e) +} diff --git a/vendor/github.com/pion/sdp/v3/marshal.go b/vendor/github.com/pion/sdp/v3/marshal.go new file mode 100644 index 0000000..f029c4e --- /dev/null +++ b/vendor/github.com/pion/sdp/v3/marshal.go @@ -0,0 +1,136 @@ +package sdp + +import ( + "strings" +) + +// Marshal takes a SDP struct to text +// https://tools.ietf.org/html/rfc4566#section-5 +// Session description +// v= (protocol version) +// o= (originator and session identifier) +// s= (session name) +// i=* (session information) +// u=* (URI of description) +// e=* (email address) +// p=* (phone number) +// c=* (connection information -- not required if included in +// all media) +// b=* (zero or more bandwidth information lines) +// One or more time descriptions ("t=" and "r=" lines; see below) +// z=* (time zone adjustments) +// k=* (encryption key) +// a=* (zero or more session attribute lines) +// Zero or more media descriptions +// +// Time description +// t= (time the session is active) +// r=* (zero or more repeat times) +// +// Media description, if present +// m= (media name and transport address) +// i=* (media title) +// c=* (connection information -- optional if included at +// session level) +// b=* (zero or more bandwidth information lines) +// k=* (encryption key) +// a=* (zero or more media attribute lines) +func (s *SessionDescription) Marshal() ([]byte, error) { + m := make(marshaller, 0, 1024) + + m.addKeyValue("v=", s.Version.String()) + m.addKeyValue("o=", s.Origin.String()) + m.addKeyValue("s=", s.SessionName.String()) + + if s.SessionInformation != nil { + m.addKeyValue("i=", s.SessionInformation.String()) + } + + if s.URI != nil { + m.addKeyValue("u=", s.URI.String()) + } + + if s.EmailAddress != nil { + m.addKeyValue("e=", s.EmailAddress.String()) + } + + if s.PhoneNumber != nil { + m.addKeyValue("p=", s.PhoneNumber.String()) + } + + if s.ConnectionInformation != nil { + m.addKeyValue("c=", s.ConnectionInformation.String()) + } + + for _, b := range s.Bandwidth { + m.addKeyValue("b=", b.String()) + } + + for _, td := range s.TimeDescriptions { + m.addKeyValue("t=", td.Timing.String()) + for _, r := range td.RepeatTimes { + m.addKeyValue("r=", r.String()) + } + } + + if len(s.TimeZones) > 0 { + var b strings.Builder + for i, z := range s.TimeZones { + if i > 0 { + b.WriteString(" ") + } + b.WriteString(z.String()) + } + m.addKeyValue("z=", b.String()) + } + + if s.EncryptionKey != nil { + m.addKeyValue("k=", s.EncryptionKey.String()) + } + + for _, a := range s.Attributes { + m.addKeyValue("a=", a.String()) + } + + for _, md := range s.MediaDescriptions { + m.addKeyValue("m=", md.MediaName.String()) + + if md.MediaTitle != nil { + m.addKeyValue("i=", md.MediaTitle.String()) + } + + if md.ConnectionInformation != nil { + m.addKeyValue("c=", md.ConnectionInformation.String()) + } + + for _, b := range md.Bandwidth { + m.addKeyValue("b=", b.String()) + } + + if md.EncryptionKey != nil { + m.addKeyValue("k=", md.EncryptionKey.String()) + } + + for _, a := range md.Attributes { + m.addKeyValue("a=", a.String()) + } + } + + return m.bytes(), nil +} + +// marshaller contains state during marshaling. +type marshaller []byte + +func (m *marshaller) addKeyValue(key, value string) { + if value == "" { + return + } + *m = append(*m, key...) + *m = append(*m, value...) + *m = append(*m, "\r\n"...) +} + +func (m *marshaller) bytes() []byte { + return *m +} diff --git a/vendor/github.com/pion/sdp/v3/media_description.go b/vendor/github.com/pion/sdp/v3/media_description.go new file mode 100644 index 0000000..c088507 --- /dev/null +++ b/vendor/github.com/pion/sdp/v3/media_description.go @@ -0,0 +1,80 @@ +package sdp + +import ( + "strconv" + "strings" +) + +// MediaDescription represents a media type. +// https://tools.ietf.org/html/rfc4566#section-5.14 +type MediaDescription struct { + // m=<media> <port>/<number of ports> <proto> <fmt> ... + // https://tools.ietf.org/html/rfc4566#section-5.14 + MediaName MediaName + + // i=<session description> + // https://tools.ietf.org/html/rfc4566#section-5.4 + MediaTitle *Information + + // c=<nettype> <addrtype> <connection-address> + // https://tools.ietf.org/html/rfc4566#section-5.7 + ConnectionInformation *ConnectionInformation + + // b=<bwtype>:<bandwidth> + // https://tools.ietf.org/html/rfc4566#section-5.8 + Bandwidth []Bandwidth + + // k=<method> + // k=<method>:<encryption key> + // https://tools.ietf.org/html/rfc4566#section-5.12 + EncryptionKey *EncryptionKey + + // a=<attribute> + // a=<attribute>:<value> + // https://tools.ietf.org/html/rfc4566#section-5.13 + Attributes []Attribute +} + +// Attribute returns the value of an attribute and if it exists +func (d *MediaDescription) Attribute(key string) (string, bool) { + for _, a := range d.Attributes { + if a.Key == key { + return a.Value, true + } + } + return "", false +} + +// RangedPort supports special format for the media field "m=" port value. If +// it may be necessary to specify multiple transport ports, the protocol allows +// to write it as: <port>/<number of ports> where number of ports is a an +// offsetting range. +type RangedPort struct { + Value int + Range *int +} + +func (p *RangedPort) String() string { + output := strconv.Itoa(p.Value) + if p.Range != nil { + output += "/" + strconv.Itoa(*p.Range) + } + return output +} + +// MediaName describes the "m=" field storage structure. +type MediaName struct { + Media string + Port RangedPort + Protos []string + Formats []string +} + +func (m MediaName) String() string { + return strings.Join([]string{ + m.Media, + m.Port.String(), + strings.Join(m.Protos, "/"), + strings.Join(m.Formats, " "), + }, " ") +} diff --git a/vendor/github.com/pion/sdp/v3/renovate.json b/vendor/github.com/pion/sdp/v3/renovate.json new file mode 100644 index 0000000..4400fd9 --- /dev/null +++ b/vendor/github.com/pion/sdp/v3/renovate.json @@ -0,0 +1,15 @@ +{ + "extends": [ + "config:base" + ], + "postUpdateOptions": [ + "gomodTidy" + ], + "commitBody": "Generated by renovateBot", + "packageRules": [ + { + "packagePatterns": ["^golang.org/x/"], + "schedule": ["on the first day of the month"] + } + ] +} diff --git a/vendor/github.com/pion/sdp/v3/sdp.go b/vendor/github.com/pion/sdp/v3/sdp.go new file mode 100644 index 0000000..c92ac1a --- /dev/null +++ b/vendor/github.com/pion/sdp/v3/sdp.go @@ -0,0 +1,2 @@ +// Package sdp implements Session Description Protocol (SDP) +package sdp diff --git a/vendor/github.com/pion/sdp/v3/session_description.go b/vendor/github.com/pion/sdp/v3/session_description.go new file mode 100644 index 0000000..c4aaf5f --- /dev/null +++ b/vendor/github.com/pion/sdp/v3/session_description.go @@ -0,0 +1,146 @@ +package sdp + +import ( + "fmt" + "net/url" + "strconv" +) + +// SessionDescription is a a well-defined format for conveying sufficient +// information to discover and participate in a multimedia session. +type SessionDescription struct { + // v=0 + // https://tools.ietf.org/html/rfc4566#section-5.1 + Version Version + + // o=<username> <sess-id> <sess-version> <nettype> <addrtype> <unicast-address> + // https://tools.ietf.org/html/rfc4566#section-5.2 + Origin Origin + + // s=<session name> + // https://tools.ietf.org/html/rfc4566#section-5.3 + SessionName SessionName + + // i=<session description> + // https://tools.ietf.org/html/rfc4566#section-5.4 + SessionInformation *Information + + // u=<uri> + // https://tools.ietf.org/html/rfc4566#section-5.5 + URI *url.URL + + // e=<email-address> + // https://tools.ietf.org/html/rfc4566#section-5.6 + EmailAddress *EmailAddress + + // p=<phone-number> + // https://tools.ietf.org/html/rfc4566#section-5.6 + PhoneNumber *PhoneNumber + + // c=<nettype> <addrtype> <connection-address> + // https://tools.ietf.org/html/rfc4566#section-5.7 + ConnectionInformation *ConnectionInformation + + // b=<bwtype>:<bandwidth> + // https://tools.ietf.org/html/rfc4566#section-5.8 + Bandwidth []Bandwidth + + // https://tools.ietf.org/html/rfc4566#section-5.9 + // https://tools.ietf.org/html/rfc4566#section-5.10 + TimeDescriptions []TimeDescription + + // z=<adjustment time> <offset> <adjustment time> <offset> ... + // https://tools.ietf.org/html/rfc4566#section-5.11 + TimeZones []TimeZone + + // k=<method> + // k=<method>:<encryption key> + // https://tools.ietf.org/html/rfc4566#section-5.12 + EncryptionKey *EncryptionKey + + // a=<attribute> + // a=<attribute>:<value> + // https://tools.ietf.org/html/rfc4566#section-5.13 + Attributes []Attribute + + // https://tools.ietf.org/html/rfc4566#section-5.14 + MediaDescriptions []*MediaDescription +} + +// Attribute returns the value of an attribute and if it exists +func (s *SessionDescription) Attribute(key string) (string, bool) { + for _, a := range s.Attributes { + if a.Key == key { + return a.Value, true + } + } + return "", false +} + +// Version describes the value provided by the "v=" field which gives +// the version of the Session Description Protocol. +type Version int + +func (v Version) String() string { + return strconv.Itoa(int(v)) +} + +// Origin defines the structure for the "o=" field which provides the +// originator of the session plus a session identifier and version number. +type Origin struct { + Username string + SessionID uint64 + SessionVersion uint64 + NetworkType string + AddressType string + UnicastAddress string +} + +func (o Origin) String() string { + return fmt.Sprintf( + "%v %d %d %v %v %v", + o.Username, + o.SessionID, + o.SessionVersion, + o.NetworkType, + o.AddressType, + o.UnicastAddress, + ) +} + +// SessionName describes a structured representations for the "s=" field +// and is the textual session name. +type SessionName string + +func (s SessionName) String() string { + return string(s) +} + +// EmailAddress describes a structured representations for the "e=" line +// which specifies email contact information for the person responsible for +// the conference. +type EmailAddress string + +func (e EmailAddress) String() string { + return string(e) +} + +// PhoneNumber describes a structured representations for the "p=" line +// specify phone contact information for the person responsible for the +// conference. +type PhoneNumber string + +func (p PhoneNumber) String() string { + return string(p) +} + +// TimeZone defines the structured object for "z=" line which describes +// repeated sessions scheduling. +type TimeZone struct { + AdjustmentTime uint64 + Offset int64 +} + +func (z TimeZone) String() string { + return strconv.FormatUint(z.AdjustmentTime, 10) + " " + strconv.FormatInt(z.Offset, 10) +} diff --git a/vendor/github.com/pion/sdp/v3/time_description.go b/vendor/github.com/pion/sdp/v3/time_description.go new file mode 100644 index 0000000..8b24e13 --- /dev/null +++ b/vendor/github.com/pion/sdp/v3/time_description.go @@ -0,0 +1,51 @@ +package sdp + +import ( + "strconv" + "strings" +) + +// TimeDescription describes "t=", "r=" fields of the session description +// which are used to specify the start and stop times for a session as well as +// repeat intervals and durations for the scheduled session. +type TimeDescription struct { + // t=<start-time> <stop-time> + // https://tools.ietf.org/html/rfc4566#section-5.9 + Timing Timing + + // r=<repeat interval> <active duration> <offsets from start-time> + // https://tools.ietf.org/html/rfc4566#section-5.10 + RepeatTimes []RepeatTime +} + +// Timing defines the "t=" field's structured representation for the start and +// stop times. +type Timing struct { + StartTime uint64 + StopTime uint64 +} + +func (t Timing) String() string { + output := strconv.FormatUint(t.StartTime, 10) + output += " " + strconv.FormatUint(t.StopTime, 10) + return output +} + +// RepeatTime describes the "r=" fields of the session description which +// represents the intervals and durations for repeated scheduled sessions. +type RepeatTime struct { + Interval int64 + Duration int64 + Offsets []int64 +} + +func (r RepeatTime) String() string { + fields := make([]string, 0) + fields = append(fields, strconv.FormatInt(r.Interval, 10)) + fields = append(fields, strconv.FormatInt(r.Duration, 10)) + for _, value := range r.Offsets { + fields = append(fields, strconv.FormatInt(value, 10)) + } + + return strings.Join(fields, " ") +} diff --git a/vendor/github.com/pion/sdp/v3/unmarshal.go b/vendor/github.com/pion/sdp/v3/unmarshal.go new file mode 100644 index 0000000..92e0dcf --- /dev/null +++ b/vendor/github.com/pion/sdp/v3/unmarshal.go @@ -0,0 +1,907 @@ +package sdp + +import ( + "errors" + "fmt" + "net/url" + "strconv" + "strings" +) + +var ( + errSDPInvalidSyntax = errors.New("sdp: invalid syntax") + errSDPInvalidNumericValue = errors.New("sdp: invalid numeric value") + errSDPInvalidValue = errors.New("sdp: invalid value") + errSDPInvalidPortValue = errors.New("sdp: invalid port value") +) + +// Unmarshal is the primary function that deserializes the session description +// message and stores it inside of a structured SessionDescription object. +// +// The States Transition Table describes the computation flow between functions +// (namely s1, s2, s3, ...) for a parsing procedure that complies with the +// specifications laid out by the rfc4566#section-5 as well as by JavaScript +// Session Establishment Protocol draft. Links: +// https://tools.ietf.org/html/rfc4566#section-5 +// https://tools.ietf.org/html/draft-ietf-rtcweb-jsep-24 +// +// https://tools.ietf.org/html/rfc4566#section-5 +// Session description +// v= (protocol version) +// o= (originator and session identifier) +// s= (session name) +// i=* (session information) +// u=* (URI of description) +// e=* (email address) +// p=* (phone number) +// c=* (connection information -- not required if included in +// all media) +// b=* (zero or more bandwidth information lines) +// One or more time descriptions ("t=" and "r=" lines; see below) +// z=* (time zone adjustments) +// k=* (encryption key) +// a=* (zero or more session attribute lines) +// Zero or more media descriptions +// +// Time description +// t= (time the session is active) +// r=* (zero or more repeat times) +// +// Media description, if present +// m= (media name and transport address) +// i=* (media title) +// c=* (connection information -- optional if included at +// session level) +// b=* (zero or more bandwidth information lines) +// k=* (encryption key) +// a=* (zero or more media attribute lines) +// +// In order to generate the following state table and draw subsequent +// deterministic finite-state automota ("DFA") the following regex was used to +// derive the DFA: +// vosi?u?e?p?c?b*(tr*)+z?k?a*(mi?c?b*k?a*)* +// possible place and state to exit: +// ** * * * ** * * * * +// 99 1 1 1 11 1 1 1 1 +// 3 1 1 26 5 5 4 4 +// +// Please pay close attention to the `k`, and `a` parsing states. In the table +// below in order to distinguish between the states belonging to the media +// description as opposed to the session description, the states are marked +// with an asterisk ("a*", "k*"). +// +--------+----+-------+----+-----+----+-----+---+----+----+---+---+-----+---+---+----+---+----+ +// | STATES | a* | a*,k* | a | a,k | b | b,c | e | i | m | o | p | r,t | s | t | u | v | z | +// +--------+----+-------+----+-----+----+-----+---+----+----+---+---+-----+---+---+----+---+----+ +// | s1 | | | | | | | | | | | | | | | | 2 | | +// | s2 | | | | | | | | | | 3 | | | | | | | | +// | s3 | | | | | | | | | | | | | 4 | | | | | +// | s4 | | | | | | 5 | 6 | 7 | | | 8 | | | 9 | 10 | | | +// | s5 | | | | | 5 | | | | | | | | | 9 | | | | +// | s6 | | | | | | 5 | | | | | 8 | | | 9 | | | | +// | s7 | | | | | | 5 | 6 | | | | 8 | | | 9 | 10 | | | +// | s8 | | | | | | 5 | | | | | | | | 9 | | | | +// | s9 | | | | 11 | | | | | 12 | | | 9 | | | | | 13 | +// | s10 | | | | | | 5 | 6 | | | | 8 | | | 9 | | | | +// | s11 | | | 11 | | | | | | 12 | | | | | | | | | +// | s12 | | 14 | | | | 15 | | 16 | 12 | | | | | | | | | +// | s13 | | | | 11 | | | | | 12 | | | | | | | | | +// | s14 | 14 | | | | | | | | 12 | | | | | | | | | +// | s15 | | 14 | | | 15 | | | | 12 | | | | | | | | | +// | s16 | | 14 | | | | 15 | | | 12 | | | | | | | | | +// +--------+----+-------+----+-----+----+-----+---+----+----+---+---+-----+---+---+----+---+----+ +func (s *SessionDescription) Unmarshal(value []byte) error { + l := new(lexer) + l.desc = s + l.value = value + for state := s1; state != nil; { + var err error + state, err = state(l) + if err != nil { + return err + } + } + return nil +} + +func s1(l *lexer) (stateFn, error) { + return l.handleType(func(key string) stateFn { + if key == "v=" { + return unmarshalProtocolVersion + } + return nil + }) +} + +func s2(l *lexer) (stateFn, error) { + return l.handleType(func(key string) stateFn { + if key == "o=" { + return unmarshalOrigin + } + return nil + }) +} + +func s3(l *lexer) (stateFn, error) { + return l.handleType(func(key string) stateFn { + if key == "s=" { + return unmarshalSessionName + } + return nil + }) +} + +func s4(l *lexer) (stateFn, error) { + return l.handleType(func(key string) stateFn { + switch key { + case "i=": + return unmarshalSessionInformation + case "u=": + return unmarshalURI + case "e=": + return unmarshalEmail + case "p=": + return unmarshalPhone + case "c=": + return unmarshalSessionConnectionInformation + case "b=": + return unmarshalSessionBandwidth + case "t=": + return unmarshalTiming + } + return nil + }) +} + +func s5(l *lexer) (stateFn, error) { + return l.handleType(func(key string) stateFn { + switch key { + case "b=": + return unmarshalSessionBandwidth + case "t=": + return unmarshalTiming + } + return nil + }) +} + +func s6(l *lexer) (stateFn, error) { + return l.handleType(func(key string) stateFn { + switch key { + case "p=": + return unmarshalPhone + case "c=": + return unmarshalSessionConnectionInformation + case "b=": + return unmarshalSessionBandwidth + case "t=": + return unmarshalTiming + } + return nil + }) +} + +func s7(l *lexer) (stateFn, error) { + return l.handleType(func(key string) stateFn { + switch key { + case "u=": + return unmarshalURI + case "e=": + return unmarshalEmail + case "p=": + return unmarshalPhone + case "c=": + return unmarshalSessionConnectionInformation + case "b=": + return unmarshalSessionBandwidth + case "t=": + return unmarshalTiming + } + return nil + }) +} + +func s8(l *lexer) (stateFn, error) { + return l.handleType(func(key string) stateFn { + switch key { + case "c=": + return unmarshalSessionConnectionInformation + case "b=": + return unmarshalSessionBandwidth + case "t=": + return unmarshalTiming + } + return nil + }) +} + +func s9(l *lexer) (stateFn, error) { + return l.handleType(func(key string) stateFn { + switch key { + case "z=": + return unmarshalTimeZones + case "k=": + return unmarshalSessionEncryptionKey + case "a=": + return unmarshalSessionAttribute + case "r=": + return unmarshalRepeatTimes + case "t=": + return unmarshalTiming + case "m=": + return unmarshalMediaDescription + } + return nil + }) +} + +func s10(l *lexer) (stateFn, error) { + return l.handleType(func(key string) stateFn { + switch key { + case "e=": + return unmarshalEmail + case "p=": + return unmarshalPhone + case "c=": + return unmarshalSessionConnectionInformation + case "b=": + return unmarshalSessionBandwidth + case "t=": + return unmarshalTiming + } + return nil + }) +} + +func s11(l *lexer) (stateFn, error) { + return l.handleType(func(key string) stateFn { + switch key { + case "a=": + return unmarshalSessionAttribute + case "m=": + return unmarshalMediaDescription + } + return nil + }) +} + +func s12(l *lexer) (stateFn, error) { + return l.handleType(func(key string) stateFn { + switch key { + case "a=": + return unmarshalMediaAttribute + case "k=": + return unmarshalMediaEncryptionKey + case "b=": + return unmarshalMediaBandwidth + case "c=": + return unmarshalMediaConnectionInformation + case "i=": + return unmarshalMediaTitle + case "m=": + return unmarshalMediaDescription + } + return nil + }) +} + +func s13(l *lexer) (stateFn, error) { + return l.handleType(func(key string) stateFn { + switch key { + case "a=": + return unmarshalSessionAttribute + case "k=": + return unmarshalSessionEncryptionKey + case "m=": + return unmarshalMediaDescription + } + return nil + }) +} + +func s14(l *lexer) (stateFn, error) { + return l.handleType(func(key string) stateFn { + switch key { + case "a=": + return unmarshalMediaAttribute + case "k=": + // Non-spec ordering + return unmarshalMediaEncryptionKey + case "b=": + // Non-spec ordering + return unmarshalMediaBandwidth + case "c=": + // Non-spec ordering + return unmarshalMediaConnectionInformation + case "i=": + // Non-spec ordering + return unmarshalMediaTitle + case "m=": + return unmarshalMediaDescription + } + return nil + }) +} + +func s15(l *lexer) (stateFn, error) { + return l.handleType(func(key string) stateFn { + switch key { + case "a=": + return unmarshalMediaAttribute + case "k=": + return unmarshalMediaEncryptionKey + case "b=": + return unmarshalMediaBandwidth + case "c=": + return unmarshalMediaConnectionInformation + case "i=": + // Non-spec ordering + return unmarshalMediaTitle + case "m=": + return unmarshalMediaDescription + } + return nil + }) +} + +func s16(l *lexer) (stateFn, error) { + return l.handleType(func(key string) stateFn { + switch key { + case "a=": + return unmarshalMediaAttribute + case "k=": + return unmarshalMediaEncryptionKey + case "c=": + return unmarshalMediaConnectionInformation + case "b=": + return unmarshalMediaBandwidth + case "i=": + // Non-spec ordering + return unmarshalMediaTitle + case "m=": + return unmarshalMediaDescription + } + return nil + }) +} + +func unmarshalProtocolVersion(l *lexer) (stateFn, error) { + version, err := l.readUint64Field() + if err != nil { + return nil, err + } + + // As off the latest draft of the rfc this value is required to be 0. + // https://tools.ietf.org/html/draft-ietf-rtcweb-jsep-24#section-5.8.1 + if version != 0 { + return nil, fmt.Errorf("%w `%v`", errSDPInvalidValue, version) + } + + if err := l.nextLine(); err != nil { + return nil, err + } + + return s2, nil +} + +func unmarshalOrigin(l *lexer) (stateFn, error) { + var err error + + l.desc.Origin.Username, err = l.readField() + if err != nil { + return nil, err + } + + l.desc.Origin.SessionID, err = l.readUint64Field() + if err != nil { + return nil, err + } + + l.desc.Origin.SessionVersion, err = l.readUint64Field() + if err != nil { + return nil, err + } + + l.desc.Origin.NetworkType, err = l.readField() + if err != nil { + return nil, err + } + + // Set according to currently registered with IANA + // https://tools.ietf.org/html/rfc4566#section-8.2.6 + if !anyOf(l.desc.Origin.NetworkType, "IN") { + return nil, fmt.Errorf("%w `%v`", errSDPInvalidValue, l.desc.Origin.NetworkType) + } + + l.desc.Origin.AddressType, err = l.readField() + if err != nil { + return nil, err + } + + // Set according to currently registered with IANA + // https://tools.ietf.org/html/rfc4566#section-8.2.7 + if !anyOf(l.desc.Origin.AddressType, "IP4", "IP6") { + return nil, fmt.Errorf("%w `%v`", errSDPInvalidValue, l.desc.Origin.AddressType) + } + + l.desc.Origin.UnicastAddress, err = l.readField() + if err != nil { + return nil, err + } + + if err := l.nextLine(); err != nil { + return nil, err + } + + return s3, nil +} + +func unmarshalSessionName(l *lexer) (stateFn, error) { + value, err := l.readLine() + if err != nil { + return nil, err + } + + l.desc.SessionName = SessionName(value) + return s4, nil +} + +func unmarshalSessionInformation(l *lexer) (stateFn, error) { + value, err := l.readLine() + if err != nil { + return nil, err + } + + sessionInformation := Information(value) + l.desc.SessionInformation = &sessionInformation + return s7, nil +} + +func unmarshalURI(l *lexer) (stateFn, error) { + value, err := l.readLine() + if err != nil { + return nil, err + } + + l.desc.URI, err = url.Parse(value) + if err != nil { + return nil, err + } + + return s10, nil +} + +func unmarshalEmail(l *lexer) (stateFn, error) { + value, err := l.readLine() + if err != nil { + return nil, err + } + + emailAddress := EmailAddress(value) + l.desc.EmailAddress = &emailAddress + return s6, nil +} + +func unmarshalPhone(l *lexer) (stateFn, error) { + value, err := l.readLine() + if err != nil { + return nil, err + } + + phoneNumber := PhoneNumber(value) + l.desc.PhoneNumber = &phoneNumber + return s8, nil +} + +func unmarshalSessionConnectionInformation(l *lexer) (stateFn, error) { + var err error + l.desc.ConnectionInformation, err = l.unmarshalConnectionInformation() + if err != nil { + return nil, err + } + return s5, nil +} + +func (l *lexer) unmarshalConnectionInformation() (*ConnectionInformation, error) { + var err error + var c ConnectionInformation + + c.NetworkType, err = l.readField() + if err != nil { + return nil, err + } + + // Set according to currently registered with IANA + // https://tools.ietf.org/html/rfc4566#section-8.2.6 + if !anyOf(c.NetworkType, "IN") { + return nil, fmt.Errorf("%w `%v`", errSDPInvalidValue, c.NetworkType) + } + + c.AddressType, err = l.readField() + if err != nil { + return nil, err + } + + // Set according to currently registered with IANA + // https://tools.ietf.org/html/rfc4566#section-8.2.7 + if !anyOf(c.AddressType, "IP4", "IP6") { + return nil, fmt.Errorf("%w `%v`", errSDPInvalidValue, c.AddressType) + } + + address, err := l.readField() + if err != nil { + return nil, err + } + + if address != "" { + c.Address = new(Address) + c.Address.Address = address + } + + if err := l.nextLine(); err != nil { + return nil, err + } + + return &c, nil +} + +func unmarshalSessionBandwidth(l *lexer) (stateFn, error) { + value, err := l.readLine() + if err != nil { + return nil, err + } + + bandwidth, err := unmarshalBandwidth(value) + if err != nil { + return nil, fmt.Errorf("%w `b=%v`", errSDPInvalidValue, value) + } + l.desc.Bandwidth = append(l.desc.Bandwidth, *bandwidth) + + return s5, nil +} + +func unmarshalBandwidth(value string) (*Bandwidth, error) { + parts := strings.Split(value, ":") + if len(parts) != 2 { + return nil, fmt.Errorf("%w `b=%v`", errSDPInvalidValue, parts) + } + + experimental := strings.HasPrefix(parts[0], "X-") + if experimental { + parts[0] = strings.TrimPrefix(parts[0], "X-") + } else if !anyOf(parts[0], "CT", "AS") { + // Set according to currently registered with IANA + // https://tools.ietf.org/html/rfc4566#section-5.8 + return nil, fmt.Errorf("%w `%v`", errSDPInvalidValue, parts[0]) + } + + bandwidth, err := strconv.ParseUint(parts[1], 10, 64) + if err != nil { + return nil, fmt.Errorf("%w `%v`", errSDPInvalidNumericValue, parts[1]) + } + + return &Bandwidth{ + Experimental: experimental, + Type: parts[0], + Bandwidth: bandwidth, + }, nil +} + +func unmarshalTiming(l *lexer) (stateFn, error) { + var err error + var td TimeDescription + + td.Timing.StartTime, err = l.readUint64Field() + if err != nil { + return nil, err + } + + td.Timing.StopTime, err = l.readUint64Field() + if err != nil { + return nil, err + } + + if err := l.nextLine(); err != nil { + return nil, err + } + + l.desc.TimeDescriptions = append(l.desc.TimeDescriptions, td) + return s9, nil +} + +func unmarshalRepeatTimes(l *lexer) (stateFn, error) { + var err error + var newRepeatTime RepeatTime + + latestTimeDesc := &l.desc.TimeDescriptions[len(l.desc.TimeDescriptions)-1] + + field, err := l.readField() + if err != nil { + return nil, err + } + + newRepeatTime.Interval, err = parseTimeUnits(field) + if err != nil { + return nil, fmt.Errorf("%w `%v`", errSDPInvalidValue, field) + } + + field, err = l.readField() + if err != nil { + return nil, err + } + + newRepeatTime.Duration, err = parseTimeUnits(field) + if err != nil { + return nil, fmt.Errorf("%w `%v`", errSDPInvalidValue, field) + } + + for { + field, err := l.readField() + if err != nil { + return nil, err + } + if field == "" { + break + } + offset, err := parseTimeUnits(field) + if err != nil { + return nil, fmt.Errorf("%w `%v`", errSDPInvalidValue, field) + } + newRepeatTime.Offsets = append(newRepeatTime.Offsets, offset) + } + + if err := l.nextLine(); err != nil { + return nil, err + } + + latestTimeDesc.RepeatTimes = append(latestTimeDesc.RepeatTimes, newRepeatTime) + return s9, nil +} + +func unmarshalTimeZones(l *lexer) (stateFn, error) { + // These fields are transimitted in pairs + // z=<adjustment time> <offset> <adjustment time> <offset> .... + // so we are making sure that there are actually multiple of 2 total. + for { + var err error + var timeZone TimeZone + + timeZone.AdjustmentTime, err = l.readUint64Field() + if err != nil { + return nil, err + } + + offset, err := l.readField() + if err != nil { + return nil, err + } + + if offset == "" { + break + } + + timeZone.Offset, err = parseTimeUnits(offset) + if err != nil { + return nil, err + } + + l.desc.TimeZones = append(l.desc.TimeZones, timeZone) + } + + if err := l.nextLine(); err != nil { + return nil, err + } + + return s13, nil +} + +func unmarshalSessionEncryptionKey(l *lexer) (stateFn, error) { + value, err := l.readLine() + if err != nil { + return nil, err + } + + encryptionKey := EncryptionKey(value) + l.desc.EncryptionKey = &encryptionKey + return s11, nil +} + +func unmarshalSessionAttribute(l *lexer) (stateFn, error) { + value, err := l.readLine() + if err != nil { + return nil, err + } + + i := strings.IndexRune(value, ':') + var a Attribute + if i > 0 { + a = NewAttribute(value[:i], value[i+1:]) + } else { + a = NewPropertyAttribute(value) + } + + l.desc.Attributes = append(l.desc.Attributes, a) + return s11, nil +} + +func unmarshalMediaDescription(l *lexer) (stateFn, error) { + var newMediaDesc MediaDescription + + // <media> + field, err := l.readField() + if err != nil { + return nil, err + } + + // Set according to currently registered with IANA + // https://tools.ietf.org/html/rfc4566#section-5.14 + if !anyOf(field, "audio", "video", "text", "application", "message") { + return nil, fmt.Errorf("%w `%v`", errSDPInvalidValue, field) + } + newMediaDesc.MediaName.Media = field + + // <port> + field, err = l.readField() + if err != nil { + return nil, err + } + parts := strings.Split(field, "/") + newMediaDesc.MediaName.Port.Value, err = parsePort(parts[0]) + if err != nil { + return nil, fmt.Errorf("%w `%v`", errSDPInvalidPortValue, parts[0]) + } + + if len(parts) > 1 { + var portRange int + portRange, err = strconv.Atoi(parts[1]) + if err != nil { + return nil, fmt.Errorf("%w `%v`", errSDPInvalidValue, parts) + } + newMediaDesc.MediaName.Port.Range = &portRange + } + + // <proto> + field, err = l.readField() + if err != nil { + return nil, err + } + + // Set according to currently registered with IANA + // https://tools.ietf.org/html/rfc4566#section-5.14 + for _, proto := range strings.Split(field, "/") { + if !anyOf(proto, "UDP", "RTP", "AVP", "SAVP", "SAVPF", "TLS", "DTLS", "SCTP", "AVPF") { + return nil, fmt.Errorf("%w `%v`", errSDPInvalidNumericValue, field) + } + newMediaDesc.MediaName.Protos = append(newMediaDesc.MediaName.Protos, proto) + } + + // <fmt>... + for { + field, err = l.readField() + if err != nil { + return nil, err + } + if field == "" { + break + } + newMediaDesc.MediaName.Formats = append(newMediaDesc.MediaName.Formats, field) + } + + if err := l.nextLine(); err != nil { + return nil, err + } + + l.desc.MediaDescriptions = append(l.desc.MediaDescriptions, &newMediaDesc) + return s12, nil +} + +func unmarshalMediaTitle(l *lexer) (stateFn, error) { + value, err := l.readLine() + if err != nil { + return nil, err + } + + latestMediaDesc := l.desc.MediaDescriptions[len(l.desc.MediaDescriptions)-1] + mediaTitle := Information(value) + latestMediaDesc.MediaTitle = &mediaTitle + return s16, nil +} + +func unmarshalMediaConnectionInformation(l *lexer) (stateFn, error) { + var err error + latestMediaDesc := l.desc.MediaDescriptions[len(l.desc.MediaDescriptions)-1] + latestMediaDesc.ConnectionInformation, err = l.unmarshalConnectionInformation() + if err != nil { + return nil, err + } + return s15, nil +} + +func unmarshalMediaBandwidth(l *lexer) (stateFn, error) { + value, err := l.readLine() + if err != nil { + return nil, err + } + + latestMediaDesc := l.desc.MediaDescriptions[len(l.desc.MediaDescriptions)-1] + bandwidth, err := unmarshalBandwidth(value) + if err != nil { + return nil, fmt.Errorf("%w `b=%v`", errSDPInvalidSyntax, value) + } + latestMediaDesc.Bandwidth = append(latestMediaDesc.Bandwidth, *bandwidth) + return s15, nil +} + +func unmarshalMediaEncryptionKey(l *lexer) (stateFn, error) { + value, err := l.readLine() + if err != nil { + return nil, err + } + + latestMediaDesc := l.desc.MediaDescriptions[len(l.desc.MediaDescriptions)-1] + encryptionKey := EncryptionKey(value) + latestMediaDesc.EncryptionKey = &encryptionKey + return s14, nil +} + +func unmarshalMediaAttribute(l *lexer) (stateFn, error) { + value, err := l.readLine() + if err != nil { + return nil, err + } + + i := strings.IndexRune(value, ':') + var a Attribute + if i > 0 { + a = NewAttribute(value[:i], value[i+1:]) + } else { + a = NewPropertyAttribute(value) + } + + latestMediaDesc := l.desc.MediaDescriptions[len(l.desc.MediaDescriptions)-1] + latestMediaDesc.Attributes = append(latestMediaDesc.Attributes, a) + return s14, nil +} + +func parseTimeUnits(value string) (num int64, err error) { + k := timeShorthand(value[len(value)-1]) + if k > 0 { + num, err = strconv.ParseInt(value[:len(value)-1], 10, 64) + } else { + k = 1 + num, err = strconv.ParseInt(value, 10, 64) + } + if err != nil { + return 0, fmt.Errorf("%w `%v`", errSDPInvalidValue, value) + } + return num * k, nil +} + +func timeShorthand(b byte) int64 { + // Some time offsets in the protocol can be provided with a shorthand + // notation. This code ensures to convert it to NTP timestamp format. + switch b { + case 'd': // days + return 86400 + case 'h': // hours + return 3600 + case 'm': // minutes + return 60 + case 's': // seconds (allowed for completeness) + return 1 + default: + return 0 + } +} + +func parsePort(value string) (int, error) { + port, err := strconv.Atoi(value) + if err != nil { + return 0, fmt.Errorf("%w `%v`", errSDPInvalidPortValue, port) + } + + if port < 0 || port > 65536 { + return 0, fmt.Errorf("%w -- out of range `%v`", errSDPInvalidPortValue, port) + } + + return port, nil +} diff --git a/vendor/github.com/pion/sdp/v3/util.go b/vendor/github.com/pion/sdp/v3/util.go new file mode 100644 index 0000000..74f47ed --- /dev/null +++ b/vendor/github.com/pion/sdp/v3/util.go @@ -0,0 +1,318 @@ +package sdp + +import ( + "errors" + "fmt" + "io" + "sort" + "strconv" + "strings" + + "github.com/pion/randutil" +) + +const ( + attributeKey = "a=" +) + +var ( + errExtractCodecRtpmap = errors.New("could not extract codec from rtpmap") + errExtractCodecFmtp = errors.New("could not extract codec from fmtp") + errExtractCodecRtcpFb = errors.New("could not extract codec from rtcp-fb") + errPayloadTypeNotFound = errors.New("payload type not found") + errCodecNotFound = errors.New("codec not found") + errSyntaxError = errors.New("SyntaxError") +) + +// ConnectionRole indicates which of the end points should initiate the connection establishment +type ConnectionRole int + +const ( + // ConnectionRoleActive indicates the endpoint will initiate an outgoing connection. + ConnectionRoleActive ConnectionRole = iota + 1 + + // ConnectionRolePassive indicates the endpoint will accept an incoming connection. + ConnectionRolePassive + + // ConnectionRoleActpass indicates the endpoint is willing to accept an incoming connection or to initiate an outgoing connection. + ConnectionRoleActpass + + // ConnectionRoleHoldconn indicates the endpoint does not want the connection to be established for the time being. + ConnectionRoleHoldconn +) + +func (t ConnectionRole) String() string { + switch t { + case ConnectionRoleActive: + return "active" + case ConnectionRolePassive: + return "passive" + case ConnectionRoleActpass: + return "actpass" + case ConnectionRoleHoldconn: + return "holdconn" + default: + return "Unknown" + } +} + +func newSessionID() (uint64, error) { + // https://tools.ietf.org/html/draft-ietf-rtcweb-jsep-26#section-5.2.1 + // Session ID is recommended to be constructed by generating a 64-bit + // quantity with the highest bit set to zero and the remaining 63-bits + // being cryptographically random. + id, err := randutil.CryptoUint64() + return id & (^(uint64(1) << 63)), err +} + +// Codec represents a codec +type Codec struct { + PayloadType uint8 + Name string + ClockRate uint32 + EncodingParameters string + Fmtp string + RTCPFeedback []string +} + +const ( + unknown = iota +) + +func (c Codec) String() string { + return fmt.Sprintf("%d %s/%d/%s (%s) [%s]", c.PayloadType, c.Name, c.ClockRate, c.EncodingParameters, c.Fmtp, strings.Join(c.RTCPFeedback, ", ")) +} + +func parseRtpmap(rtpmap string) (Codec, error) { + var codec Codec + parsingFailed := errExtractCodecRtpmap + + // a=rtpmap:<payload type> <encoding name>/<clock rate>[/<encoding parameters>] + split := strings.Split(rtpmap, " ") + if len(split) != 2 { + return codec, parsingFailed + } + + ptSplit := strings.Split(split[0], ":") + if len(ptSplit) != 2 { + return codec, parsingFailed + } + + ptInt, err := strconv.Atoi(ptSplit[1]) + if err != nil { + return codec, parsingFailed + } + + codec.PayloadType = uint8(ptInt) + + split = strings.Split(split[1], "/") + codec.Name = split[0] + parts := len(split) + if parts > 1 { + rate, err := strconv.Atoi(split[1]) + if err != nil { + return codec, parsingFailed + } + codec.ClockRate = uint32(rate) + } + if parts > 2 { + codec.EncodingParameters = split[2] + } + + return codec, nil +} + +func parseFmtp(fmtp string) (Codec, error) { + var codec Codec + parsingFailed := errExtractCodecFmtp + + // a=fmtp:<format> <format specific parameters> + split := strings.Split(fmtp, " ") + if len(split) != 2 { + return codec, parsingFailed + } + + formatParams := split[1] + + split = strings.Split(split[0], ":") + if len(split) != 2 { + return codec, parsingFailed + } + + ptInt, err := strconv.Atoi(split[1]) + if err != nil { + return codec, parsingFailed + } + + codec.PayloadType = uint8(ptInt) + codec.Fmtp = formatParams + + return codec, nil +} + +func parseRtcpFb(rtcpFb string) (Codec, error) { + var codec Codec + parsingFailed := errExtractCodecRtcpFb + + // a=ftcp-fb:<payload type> <RTCP feedback type> [<RTCP feedback parameter>] + split := strings.SplitN(rtcpFb, " ", 2) + if len(split) != 2 { + return codec, parsingFailed + } + + ptSplit := strings.Split(split[0], ":") + if len(ptSplit) != 2 { + return codec, parsingFailed + } + + ptInt, err := strconv.Atoi(ptSplit[1]) + if err != nil { + return codec, parsingFailed + } + + codec.PayloadType = uint8(ptInt) + codec.RTCPFeedback = append(codec.RTCPFeedback, split[1]) + + return codec, nil +} + +func mergeCodecs(codec Codec, codecs map[uint8]Codec) { + savedCodec := codecs[codec.PayloadType] + + if savedCodec.PayloadType == 0 { + savedCodec.PayloadType = codec.PayloadType + } + if savedCodec.Name == "" { + savedCodec.Name = codec.Name + } + if savedCodec.ClockRate == 0 { + savedCodec.ClockRate = codec.ClockRate + } + if savedCodec.EncodingParameters == "" { + savedCodec.EncodingParameters = codec.EncodingParameters + } + if savedCodec.Fmtp == "" { + savedCodec.Fmtp = codec.Fmtp + } + savedCodec.RTCPFeedback = append(savedCodec.RTCPFeedback, codec.RTCPFeedback...) + + codecs[savedCodec.PayloadType] = savedCodec +} + +func (s *SessionDescription) buildCodecMap() map[uint8]Codec { + codecs := make(map[uint8]Codec) + + for _, m := range s.MediaDescriptions { + for _, a := range m.Attributes { + attr := a.String() + switch { + case strings.HasPrefix(attr, "rtpmap:"): + codec, err := parseRtpmap(attr) + if err == nil { + mergeCodecs(codec, codecs) + } + case strings.HasPrefix(attr, "fmtp:"): + codec, err := parseFmtp(attr) + if err == nil { + mergeCodecs(codec, codecs) + } + case strings.HasPrefix(attr, "rtcp-fb:"): + codec, err := parseRtcpFb(attr) + if err == nil { + mergeCodecs(codec, codecs) + } + } + } + } + + return codecs +} + +func equivalentFmtp(want, got string) bool { + wantSplit := strings.Split(want, ";") + gotSplit := strings.Split(got, ";") + + if len(wantSplit) != len(gotSplit) { + return false + } + + sort.Strings(wantSplit) + sort.Strings(gotSplit) + + for i, wantPart := range wantSplit { + wantPart = strings.TrimSpace(wantPart) + gotPart := strings.TrimSpace(gotSplit[i]) + if gotPart != wantPart { + return false + } + } + + return true +} + +func codecsMatch(wanted, got Codec) bool { + if wanted.Name != "" && !strings.EqualFold(wanted.Name, got.Name) { + return false + } + if wanted.ClockRate != 0 && wanted.ClockRate != got.ClockRate { + return false + } + if wanted.EncodingParameters != "" && wanted.EncodingParameters != got.EncodingParameters { + return false + } + if wanted.Fmtp != "" && !equivalentFmtp(wanted.Fmtp, got.Fmtp) { + return false + } + + return true +} + +// GetCodecForPayloadType scans the SessionDescription for the given payload type and returns the codec +func (s *SessionDescription) GetCodecForPayloadType(payloadType uint8) (Codec, error) { + codecs := s.buildCodecMap() + + codec, ok := codecs[payloadType] + if ok { + return codec, nil + } + + return codec, errPayloadTypeNotFound +} + +// GetPayloadTypeForCodec scans the SessionDescription for a codec that matches the provided codec +// as closely as possible and returns its payload type +func (s *SessionDescription) GetPayloadTypeForCodec(wanted Codec) (uint8, error) { + codecs := s.buildCodecMap() + + for payloadType, codec := range codecs { + if codecsMatch(wanted, codec) { + return payloadType, nil + } + } + + return 0, errCodecNotFound +} + +type stateFn func(*lexer) (stateFn, error) + +type lexer struct { + desc *SessionDescription + baseLexer +} + +type keyToState func(key string) stateFn + +func (l *lexer) handleType(fn keyToState) (stateFn, error) { + key, err := l.readType() + if err == io.EOF && key == "" { + return nil, nil + } else if err != nil { + return nil, err + } + + if res := fn(key); res != nil { + return res, nil + } + + return nil, l.syntaxError() +} diff --git a/vendor/github.com/pion/srtp/v2/.gitignore b/vendor/github.com/pion/srtp/v2/.gitignore new file mode 100644 index 0000000..83db74b --- /dev/null +++ b/vendor/github.com/pion/srtp/v2/.gitignore @@ -0,0 +1,24 @@ +### JetBrains IDE ### +##################### +.idea/ + +### Emacs Temporary Files ### +############################# +*~ + +### Folders ### +############### +bin/ +vendor/ +node_modules/ + +### Files ### +############# +*.ivf +*.ogg +tags +cover.out +*.sw[poe] +*.wasm +examples/sfu-ws/cert.pem +examples/sfu-ws/key.pem diff --git a/vendor/github.com/pion/srtp/v2/.golangci.yml b/vendor/github.com/pion/srtp/v2/.golangci.yml new file mode 100644 index 0000000..d6162c9 --- /dev/null +++ b/vendor/github.com/pion/srtp/v2/.golangci.yml @@ -0,0 +1,89 @@ +linters-settings: + govet: + check-shadowing: true + misspell: + locale: US + exhaustive: + default-signifies-exhaustive: true + gomodguard: + blocked: + modules: + - github.com/pkg/errors: + recommendations: + - errors + +linters: + enable: + - asciicheck # Simple linter to check that your code does not contain non-ASCII identifiers + - bodyclose # checks whether HTTP response body is closed successfully + - deadcode # Finds unused code + - depguard # Go linter that checks if package imports are in a list of acceptable packages + - dogsled # Checks assignments with too many blank identifiers (e.g. x, _, _, _, := f()) + - dupl # Tool for code clone detection + - errcheck # Errcheck is a program for checking for unchecked errors in go programs. These unchecked errors can be critical bugs in some cases + - exhaustive # check exhaustiveness of enum switch statements + - exportloopref # checks for pointers to enclosing loop variables + - gci # Gci control golang package import order and make it always deterministic. + - gochecknoglobals # Checks that no globals are present in Go code + - gochecknoinits # Checks that no init functions are present in Go code + - gocognit # Computes and checks the cognitive complexity of functions + - goconst # Finds repeated strings that could be replaced by a constant + - gocritic # The most opinionated Go source code linter + - godox # Tool for detection of FIXME, TODO and other comment keywords + - goerr113 # Golang linter to check the errors handling expressions + - gofmt # Gofmt checks whether code was gofmt-ed. By default this tool runs with -s option to check for code simplification + - gofumpt # Gofumpt checks whether code was gofumpt-ed. + - goheader # Checks is file header matches to pattern + - goimports # Goimports does everything that gofmt does. Additionally it checks unused imports + - golint # Golint differs from gofmt. Gofmt reformats Go source code, whereas golint prints out style mistakes + - gomodguard # Allow and block list linter for direct Go module dependencies. This is different from depguard where there are different block types for example version constraints and module recommendations. + - goprintffuncname # Checks that printf-like functions are named with `f` at the end + - gosec # Inspects source code for security problems + - gosimple # Linter for Go source code that specializes in simplifying a code + - govet # Vet examines Go source code and reports suspicious constructs, such as Printf calls whose arguments do not align with the format string + - ineffassign # Detects when assignments to existing variables are not used + - misspell # Finds commonly misspelled English words in comments + - nakedret # Finds naked returns in functions greater than a specified function length + - noctx # noctx finds sending http request without context.Context + - scopelint # Scopelint checks for unpinned variables in go programs + - staticcheck # Staticcheck is a go vet on steroids, applying a ton of static analysis checks + - structcheck # Finds unused struct fields + - stylecheck # Stylecheck is a replacement for golint + - typecheck # Like the front-end of a Go compiler, parses and type-checks Go code + - unconvert # Remove unnecessary type conversions + - unparam # Reports unused function parameters + - unused # Checks Go code for unused constants, variables, functions and types + - varcheck # Finds unused global variables and constants + - whitespace # Tool for detection of leading and trailing whitespace + disable: + - funlen # Tool for detection of long functions + - gocyclo # Computes and checks the cyclomatic complexity of functions + - godot # Check if comments end in a period + - gomnd # An analyzer to detect magic numbers. + - lll # Reports long lines + - maligned # Tool to detect Go structs that would take less memory if their fields were sorted + - nestif # Reports deeply nested if statements + - nlreturn # nlreturn checks for a new line before return and branch statements to increase code clarity + - nolintlint # Reports ill-formed or insufficient nolint directives + - prealloc # Finds slice declarations that could potentially be preallocated + - rowserrcheck # checks whether Err of rows is checked successfully + - sqlclosecheck # Checks that sql.Rows and sql.Stmt are closed. + - testpackage # linter that makes you use a separate _test package + - wsl # Whitespace Linter - Forces you to use empty lines! + +issues: + exclude-use-default: false + exclude-rules: + # Allow complex tests, better to be self contained + - path: _test\.go + linters: + - gocognit + + # Allow complex main function in examples + - path: examples + text: "of func `main` is high" + linters: + - gocognit + +run: + skip-dirs-use-default: false diff --git a/vendor/github.com/pion/srtp/v2/DESIGN.md b/vendor/github.com/pion/srtp/v2/DESIGN.md new file mode 100644 index 0000000..8742540 --- /dev/null +++ b/vendor/github.com/pion/srtp/v2/DESIGN.md @@ -0,0 +1,20 @@ +<h1 align="center"> + Design +</h1> + +### Portable +Pion SRTP is written in Go and extremely portable. Anywhere Golang runs, Pion SRTP should work as well! Instead of dealing with complicated +cross-compiling of multiple libraries, you now can run anywhere with one `go build` + +### Simple API +The API is based on an io.ReadWriteCloser. + +### Readable +If code comes from an RFC we try to make sure everything is commented with a link to the spec. +This makes learning and debugging easier, this library was written to also serve as a guide for others. + +### Tested +Every commit is tested via travis-ci Go provides fantastic facilities for testing, and more will be added as time goes on. + +### Shared libraries +Every pion product is built using shared libraries, allowing others to review and reuse our libraries. diff --git a/vendor/github.com/pion/srtp/v2/LICENSE b/vendor/github.com/pion/srtp/v2/LICENSE new file mode 100644 index 0000000..ab60297 --- /dev/null +++ b/vendor/github.com/pion/srtp/v2/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/pion/srtp/v2/README.md b/vendor/github.com/pion/srtp/v2/README.md new file mode 100644 index 0000000..19551e3 --- /dev/null +++ b/vendor/github.com/pion/srtp/v2/README.md @@ -0,0 +1,54 @@ +<h1 align="center"> + <br> + Pion SRTP + <br> +</h1> +<h4 align="center">A Go implementation of SRTP</h4> +<p align="center"> + <a href="https://pion.ly"><img src="https://img.shields.io/badge/pion-srtp-gray.svg?longCache=true&colorB=brightgreen" alt="Pion SRTP"></a> + <a href="https://sourcegraph.com/github.com/pion/srtp?badge"><img src="https://sourcegraph.com/github.com/pion/srtp/-/badge.svg" alt="Sourcegraph Widget"></a> + <a href="https://pion.ly/slack"><img src="https://img.shields.io/badge/join-us%20on%20slack-gray.svg?longCache=true&logo=slack&colorB=brightgreen" alt="Slack Widget"></a> + <br> + <a href="https://travis-ci.org/pion/srtp"><img src="https://travis-ci.org/pion/srtp.svg?branch=master" alt="Build Status"></a> + <a href="https://pkg.go.dev/github.com/pion/srtp"><img src="https://godoc.org/github.com/pion/srtp?status.svg" alt="GoDoc"></a> + <a href="https://codecov.io/gh/pion/srtp"><img src="https://codecov.io/gh/pion/srtp/branch/master/graph/badge.svg" alt="Coverage Status"></a> + <a href="https://goreportcard.com/report/github.com/pion/srtp"><img src="https://goreportcard.com/badge/github.com/pion/srtp" alt="Go Report Card"></a> + <a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="License: MIT"></a> +</p> +<br> + +See [DESIGN.md](DESIGN.md) for an overview of features and future goals. + +### Roadmap +The library is used as a part of our WebRTC implementation. Please refer to that [roadmap](https://github.com/pion/webrtc/issues/9) to track our major milestones. + +### Community +Pion has an active community on the [Golang Slack](https://invite.slack.golangbridge.org/). Sign up and join the **#pion** channel for discussions and support. You can also use [Pion mailing list](https://groups.google.com/forum/#!forum/pion). + +We are always looking to support **your projects**. Please reach out if you have something to build! + +If you need commercial support or don't want to use public methods you can contact us at [team@pion.ly](mailto:team@pion.ly) + +### Contributing +Check out the **[contributing wiki](https://github.com/pion/webrtc/wiki/Contributing)** to join the group of amazing people making this project possible: + +* [Sean DuBois](https://github.com/Sean-Der) - *Original Author* +* [Michiel De Backker](https://github.com/backkem) - *io.Writer interfaces* +* [Tobias Fridén](https://github.com/tobiasfriden) *SRTP authentication verification* +* [chenkaiC4](https://github.com/chenkaiC4) - *Fix GolangCI Linter* +* [Luke Curley](https://github.com/kixelated) - *Performance* +* [Chris Hiszpanski](https://github.com/thinkski) - *Fix out-of-bounds access* +* [Yutaka Takeda](https://github.com/enobufs) - *Fix log messages* +* [Max Hawkins](https://github.com/maxhawkins) +* [Woodrow Douglass](https://github.com/wdouglass) +* [Hugo Arregui](https://github.com/hugoArregui) +* [Atsushi Watanabe](https://github.com/at-wat) +* [Novel Corpse](https://github.com/NovelCorpse) +* [Jerko Steiner](https://github.com/jeremija) +* [Juliusz Chroboczek](https://github.com/jech) +* [Mission Liao](https://github.com/mission-liao) +* [Orlando](https://github.com/OrlandoCo) +* [Tarrence van As](https://github.com/tarrencev) + +### License +MIT License - see [LICENSE](LICENSE) for full text diff --git a/vendor/github.com/pion/srtp/v2/codecov.yml b/vendor/github.com/pion/srtp/v2/codecov.yml new file mode 100644 index 0000000..085200a --- /dev/null +++ b/vendor/github.com/pion/srtp/v2/codecov.yml @@ -0,0 +1,20 @@ +# +# DO NOT EDIT THIS FILE +# +# It is automatically copied from https://github.com/pion/.goassets repository. +# + +coverage: + status: + project: + default: + # Allow decreasing 2% of total coverage to avoid noise. + threshold: 2% + patch: + default: + target: 70% + only_pulls: true + +ignore: + - "examples/*" + - "examples/**/*" diff --git a/vendor/github.com/pion/srtp/v2/context.go b/vendor/github.com/pion/srtp/v2/context.go new file mode 100644 index 0000000..63566de --- /dev/null +++ b/vendor/github.com/pion/srtp/v2/context.go @@ -0,0 +1,196 @@ +package srtp + +import ( + "fmt" + + "github.com/pion/transport/replaydetector" +) + +const ( + labelSRTPEncryption = 0x00 + labelSRTPAuthenticationTag = 0x01 + labelSRTPSalt = 0x02 + + labelSRTCPEncryption = 0x03 + labelSRTCPAuthenticationTag = 0x04 + labelSRTCPSalt = 0x05 + + maxROCDisorder = 100 + maxSequenceNumber = 65535 + + srtcpIndexSize = 4 +) + +// Encrypt/Decrypt state for a single SRTP SSRC +type srtpSSRCState struct { + ssrc uint32 + rolloverCounter uint32 + rolloverHasProcessed bool + lastSequenceNumber uint16 + replayDetector replaydetector.ReplayDetector +} + +// Encrypt/Decrypt state for a single SRTCP SSRC +type srtcpSSRCState struct { + srtcpIndex uint32 + ssrc uint32 + replayDetector replaydetector.ReplayDetector +} + +// Context represents a SRTP cryptographic context. +// Context can only be used for one-way operations. +// it must either used ONLY for encryption or ONLY for decryption. +type Context struct { + cipher srtpCipher + + srtpSSRCStates map[uint32]*srtpSSRCState + srtcpSSRCStates map[uint32]*srtcpSSRCState + + newSRTCPReplayDetector func() replaydetector.ReplayDetector + newSRTPReplayDetector func() replaydetector.ReplayDetector +} + +// CreateContext creates a new SRTP Context. +// +// CreateContext receives variable number of ContextOption-s. +// Passing multiple options which set the same parameter let the last one valid. +// Following example create SRTP Context with replay protection with window size of 256. +// +// decCtx, err := srtp.CreateContext(key, salt, profile, srtp.SRTPReplayProtection(256)) +// +func CreateContext(masterKey, masterSalt []byte, profile ProtectionProfile, opts ...ContextOption) (c *Context, err error) { + keyLen, err := profile.keyLen() + if err != nil { + return nil, err + } + + saltLen, err := profile.saltLen() + if err != nil { + return nil, err + } + + if masterKeyLen := len(masterKey); masterKeyLen != keyLen { + return c, fmt.Errorf("%w expected(%d) actual(%d)", errShortSrtpMasterKey, masterKey, keyLen) + } else if masterSaltLen := len(masterSalt); masterSaltLen != saltLen { + return c, fmt.Errorf("%w expected(%d) actual(%d)", errShortSrtpMasterSalt, saltLen, masterSaltLen) + } + + c = &Context{ + srtpSSRCStates: map[uint32]*srtpSSRCState{}, + srtcpSSRCStates: map[uint32]*srtcpSSRCState{}, + } + + switch profile { + case ProtectionProfileAeadAes128Gcm: + c.cipher, err = newSrtpCipherAeadAesGcm(masterKey, masterSalt) + case ProtectionProfileAes128CmHmacSha1_80: + c.cipher, err = newSrtpCipherAesCmHmacSha1(masterKey, masterSalt) + default: + return nil, fmt.Errorf("%w: %#v", errNoSuchSRTPProfile, profile) + } + if err != nil { + return nil, err + } + + for _, o := range append( + []ContextOption{ // Default options + SRTPNoReplayProtection(), + SRTCPNoReplayProtection(), + }, + opts..., // User specified options + ) { + if errOpt := o(c); errOpt != nil { + return nil, errOpt + } + } + + return c, nil +} + +// https://tools.ietf.org/html/rfc3550#appendix-A.1 +func (s *srtpSSRCState) nextRolloverCount(sequenceNumber uint16) (uint32, func()) { + roc := s.rolloverCounter + + switch { + case !s.rolloverHasProcessed: + case sequenceNumber == 0: // We exactly hit the rollover count + // Only update rolloverCounter if lastSequenceNumber is greater then maxROCDisorder + // otherwise we already incremented for disorder + if s.lastSequenceNumber > maxROCDisorder { + roc++ + } + case s.lastSequenceNumber < maxROCDisorder && + sequenceNumber > (maxSequenceNumber-maxROCDisorder): + // Our last sequence number incremented because we crossed 0, but then our current number was within maxROCDisorder of the max + // So we fell behind, drop to account for jitter + roc-- + case sequenceNumber < maxROCDisorder && + s.lastSequenceNumber > (maxSequenceNumber-maxROCDisorder): + // our current is within a maxROCDisorder of 0 + // and our last sequence number was a high sequence number, increment to account for jitter + roc++ + } + return roc, func() { + s.rolloverHasProcessed = true + s.lastSequenceNumber = sequenceNumber + s.rolloverCounter = roc + } +} + +func (c *Context) getSRTPSSRCState(ssrc uint32) *srtpSSRCState { + s, ok := c.srtpSSRCStates[ssrc] + if ok { + return s + } + + s = &srtpSSRCState{ + ssrc: ssrc, + replayDetector: c.newSRTPReplayDetector(), + } + c.srtpSSRCStates[ssrc] = s + return s +} + +func (c *Context) getSRTCPSSRCState(ssrc uint32) *srtcpSSRCState { + s, ok := c.srtcpSSRCStates[ssrc] + if ok { + return s + } + + s = &srtcpSSRCState{ + ssrc: ssrc, + replayDetector: c.newSRTCPReplayDetector(), + } + c.srtcpSSRCStates[ssrc] = s + return s +} + +// ROC returns SRTP rollover counter value of specified SSRC. +func (c *Context) ROC(ssrc uint32) (uint32, bool) { + s, ok := c.srtpSSRCStates[ssrc] + if !ok { + return 0, false + } + return s.rolloverCounter, true +} + +// SetROC sets SRTP rollover counter value of specified SSRC. +func (c *Context) SetROC(ssrc uint32, roc uint32) { + s := c.getSRTPSSRCState(ssrc) + s.rolloverCounter = roc +} + +// Index returns SRTCP index value of specified SSRC. +func (c *Context) Index(ssrc uint32) (uint32, bool) { + s, ok := c.srtcpSSRCStates[ssrc] + if !ok { + return 0, false + } + return s.srtcpIndex, true +} + +// SetIndex sets SRTCP index value of specified SSRC. +func (c *Context) SetIndex(ssrc uint32, index uint32) { + s := c.getSRTCPSSRCState(ssrc) + s.srtcpIndex = index % (maxSRTCPIndex + 1) +} diff --git a/vendor/github.com/pion/srtp/v2/errors.go b/vendor/github.com/pion/srtp/v2/errors.go new file mode 100644 index 0000000..a702621 --- /dev/null +++ b/vendor/github.com/pion/srtp/v2/errors.go @@ -0,0 +1,40 @@ +package srtp + +import ( + "errors" + "fmt" +) + +var ( + errDuplicated = errors.New("duplicated packet") + errShortSrtpMasterKey = errors.New("SRTP master key is not long enough") + errShortSrtpMasterSalt = errors.New("SRTP master salt is not long enough") + errNoSuchSRTPProfile = errors.New("no such SRTP Profile") + errNonZeroKDRNotSupported = errors.New("indexOverKdr > 0 is not supported yet") + errExporterWrongLabel = errors.New("exporter called with wrong label") + errNoConfig = errors.New("no config provided") + errNoConn = errors.New("no conn provided") + errFailedToVerifyAuthTag = errors.New("failed to verify auth tag") + errTooShortRTCP = errors.New("packet is too short to be rtcp packet") + errPayloadDiffers = errors.New("payload differs") + errStartedChannelUsedIncorrectly = errors.New("started channel used incorrectly, should only be closed") + + errStreamNotInited = errors.New("stream has not been inited, unable to close") + errStreamAlreadyClosed = errors.New("stream is already closed") + errStreamAlreadyInited = errors.New("stream is already inited") + errFailedTypeAssertion = errors.New("failed to cast child") +) + +type errorDuplicated struct { + Proto string // srtp or srtcp + SSRC uint32 + Index uint32 // sequence number or index +} + +func (e *errorDuplicated) Error() string { + return fmt.Sprintf("%s ssrc=%d index=%d: %v", e.Proto, e.SSRC, e.Index, errDuplicated) +} + +func (e *errorDuplicated) Unwrap() error { + return errDuplicated +} diff --git a/vendor/github.com/pion/srtp/v2/go.mod b/vendor/github.com/pion/srtp/v2/go.mod new file mode 100644 index 0000000..bc04f63 --- /dev/null +++ b/vendor/github.com/pion/srtp/v2/go.mod @@ -0,0 +1,11 @@ +module github.com/pion/srtp/v2 + +go 1.14 + +require ( + github.com/pion/logging v0.2.2 + github.com/pion/rtcp v1.2.6 + github.com/pion/rtp v1.6.2 + github.com/pion/transport v0.12.2 + github.com/stretchr/testify v1.7.0 +) diff --git a/vendor/github.com/pion/srtp/v2/go.sum b/vendor/github.com/pion/srtp/v2/go.sum new file mode 100644 index 0000000..6066df9 --- /dev/null +++ b/vendor/github.com/pion/srtp/v2/go.sum @@ -0,0 +1,34 @@ +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY= +github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms= +github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA= +github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8= +github.com/pion/rtcp v1.2.6 h1:1zvwBbyd0TeEuuWftrd/4d++m+/kZSeiguxU61LFWpo= +github.com/pion/rtcp v1.2.6/go.mod h1:52rMNPWFsjr39z9B9MhnkqhPLoeHTv1aN63o/42bWE0= +github.com/pion/rtp v1.6.2 h1:iGBerLX6JiDjB9NXuaPzHyxHFG9JsIEdgwTC0lp5n/U= +github.com/pion/rtp v1.6.2/go.mod h1:bDb5n+BFZxXx0Ea7E5qe+klMuqiBrP+w8XSjiWtCUko= +github.com/pion/transport v0.12.2 h1:WYEjhloRHt1R86LhUKjC5y+P52Y11/QqEUalvtzVoys= +github.com/pion/transport v0.12.2/go.mod h1:N3+vZQD9HlDP5GWkZ85LohxNsDcNgofQmyL6ojX5d8Q= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20201201195509-5d6afe98e0b7 h1:3uJsdck53FDIpWwLeAXlia9p4C8j0BO2xZrqzKpL0D8= +golang.org/x/net v0.0.0-20201201195509-5d6afe98e0b7/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/vendor/github.com/pion/srtp/v2/key_derivation.go b/vendor/github.com/pion/srtp/v2/key_derivation.go new file mode 100644 index 0000000..5bbf3aa --- /dev/null +++ b/vendor/github.com/pion/srtp/v2/key_derivation.go @@ -0,0 +1,63 @@ +package srtp + +import ( + "crypto/aes" + "encoding/binary" +) + +func aesCmKeyDerivation(label byte, masterKey, masterSalt []byte, indexOverKdr int, outLen int) ([]byte, error) { + if indexOverKdr != 0 { + // 24-bit "index DIV kdr" must be xored to prf input. + return nil, errNonZeroKDRNotSupported + } + + // https://tools.ietf.org/html/rfc3711#appendix-B.3 + // The input block for AES-CM is generated by exclusive-oring the master salt with the + // concatenation of the encryption key label 0x00 with (index DIV kdr), + // - index is 'rollover count' and DIV is 'divided by' + + nMasterKey := len(masterKey) + nMasterSalt := len(masterSalt) + + prfIn := make([]byte, nMasterKey) + copy(prfIn[:nMasterSalt], masterSalt) + + prfIn[7] ^= label + + // The resulting value is then AES encrypted using the master key to get the cipher key. + block, err := aes.NewCipher(masterKey) + if err != nil { + return nil, err + } + + out := make([]byte, ((outLen+nMasterKey)/nMasterKey)*nMasterKey) + var i uint16 + for n := 0; n < outLen; n += nMasterKey { + binary.BigEndian.PutUint16(prfIn[nMasterKey-2:], i) + block.Encrypt(out[n:n+nMasterKey], prfIn) + i++ + } + return out[:outLen], nil +} + +// Generate IV https://tools.ietf.org/html/rfc3711#section-4.1.1 +// where the 128-bit integer value IV SHALL be defined by the SSRC, the +// SRTP packet index i, and the SRTP session salting key k_s, as below. +// - ROC = a 32-bit unsigned rollover counter (ROC), which records how many +// - times the 16-bit RTP sequence number has been reset to zero after +// - passing through 65,535 +// i = 2^16 * ROC + SEQ +// IV = (salt*2 ^ 16) | (ssrc*2 ^ 64) | (i*2 ^ 16) +func generateCounter(sequenceNumber uint16, rolloverCounter uint32, ssrc uint32, sessionSalt []byte) []byte { + counter := make([]byte, 16) + + binary.BigEndian.PutUint32(counter[4:], ssrc) + binary.BigEndian.PutUint32(counter[8:], rolloverCounter) + binary.BigEndian.PutUint32(counter[12:], uint32(sequenceNumber)<<16) + + for i := range sessionSalt { + counter[i] ^= sessionSalt[i] + } + + return counter +} diff --git a/vendor/github.com/pion/srtp/v2/keying.go b/vendor/github.com/pion/srtp/v2/keying.go new file mode 100644 index 0000000..82fd4d9 --- /dev/null +++ b/vendor/github.com/pion/srtp/v2/keying.go @@ -0,0 +1,54 @@ +package srtp + +const labelExtractorDtlsSrtp = "EXTRACTOR-dtls_srtp" + +// KeyingMaterialExporter allows package SRTP to extract keying material +type KeyingMaterialExporter interface { + ExportKeyingMaterial(label string, context []byte, length int) ([]byte, error) +} + +// ExtractSessionKeysFromDTLS allows setting the Config SessionKeys by +// extracting them from DTLS. This behavior is defined in RFC5764: +// https://tools.ietf.org/html/rfc5764 +func (c *Config) ExtractSessionKeysFromDTLS(exporter KeyingMaterialExporter, isClient bool) error { + keyLen, err := c.Profile.keyLen() + if err != nil { + return err + } + + saltLen, err := c.Profile.saltLen() + if err != nil { + return err + } + + keyingMaterial, err := exporter.ExportKeyingMaterial(labelExtractorDtlsSrtp, nil, (keyLen*2)+(saltLen*2)) + if err != nil { + return err + } + + offset := 0 + clientWriteKey := append([]byte{}, keyingMaterial[offset:offset+keyLen]...) + offset += keyLen + + serverWriteKey := append([]byte{}, keyingMaterial[offset:offset+keyLen]...) + offset += keyLen + + clientWriteKey = append(clientWriteKey, keyingMaterial[offset:offset+saltLen]...) + offset += saltLen + + serverWriteKey = append(serverWriteKey, keyingMaterial[offset:offset+saltLen]...) + + if isClient { + c.Keys.LocalMasterKey = clientWriteKey[0:keyLen] + c.Keys.LocalMasterSalt = clientWriteKey[keyLen:] + c.Keys.RemoteMasterKey = serverWriteKey[0:keyLen] + c.Keys.RemoteMasterSalt = serverWriteKey[keyLen:] + return nil + } + + c.Keys.LocalMasterKey = serverWriteKey[0:keyLen] + c.Keys.LocalMasterSalt = serverWriteKey[keyLen:] + c.Keys.RemoteMasterKey = clientWriteKey[0:keyLen] + c.Keys.RemoteMasterSalt = clientWriteKey[keyLen:] + return nil +} diff --git a/vendor/github.com/pion/srtp/v2/option.go b/vendor/github.com/pion/srtp/v2/option.go new file mode 100644 index 0000000..d6159f1 --- /dev/null +++ b/vendor/github.com/pion/srtp/v2/option.go @@ -0,0 +1,54 @@ +package srtp + +import ( + "github.com/pion/transport/replaydetector" +) + +// ContextOption represents option of Context using the functional options pattern. +type ContextOption func(*Context) error + +// SRTPReplayProtection sets SRTP replay protection window size. +func SRTPReplayProtection(windowSize uint) ContextOption { // nolint:golint + return func(c *Context) error { + c.newSRTPReplayDetector = func() replaydetector.ReplayDetector { + return replaydetector.WithWrap(windowSize, maxSequenceNumber) + } + return nil + } +} + +// SRTCPReplayProtection sets SRTCP replay protection window size. +func SRTCPReplayProtection(windowSize uint) ContextOption { + return func(c *Context) error { + c.newSRTCPReplayDetector = func() replaydetector.ReplayDetector { + return replaydetector.WithWrap(windowSize, maxSRTCPIndex) + } + return nil + } +} + +// SRTPNoReplayProtection disables SRTP replay protection. +func SRTPNoReplayProtection() ContextOption { // nolint:golint + return func(c *Context) error { + c.newSRTPReplayDetector = func() replaydetector.ReplayDetector { + return &nopReplayDetector{} + } + return nil + } +} + +// SRTCPNoReplayProtection disables SRTCP replay protection. +func SRTCPNoReplayProtection() ContextOption { + return func(c *Context) error { + c.newSRTCPReplayDetector = func() replaydetector.ReplayDetector { + return &nopReplayDetector{} + } + return nil + } +} + +type nopReplayDetector struct{} + +func (s *nopReplayDetector) Check(uint64) (func(), bool) { + return func() {}, true +} diff --git a/vendor/github.com/pion/srtp/v2/protection_profile.go b/vendor/github.com/pion/srtp/v2/protection_profile.go new file mode 100644 index 0000000..94476ad --- /dev/null +++ b/vendor/github.com/pion/srtp/v2/protection_profile.go @@ -0,0 +1,67 @@ +package srtp + +import "fmt" + +// ProtectionProfile specifies Cipher and AuthTag details, similar to TLS cipher suite +type ProtectionProfile uint16 + +// Supported protection profiles +const ( + ProtectionProfileAes128CmHmacSha1_80 ProtectionProfile = 0x0001 + ProtectionProfileAeadAes128Gcm ProtectionProfile = 0x0007 +) + +func (p ProtectionProfile) keyLen() (int, error) { + switch p { + case ProtectionProfileAes128CmHmacSha1_80: + fallthrough + case ProtectionProfileAeadAes128Gcm: + return 16, nil + default: + return 0, fmt.Errorf("%w: %#v", errNoSuchSRTPProfile, p) + } +} + +func (p ProtectionProfile) saltLen() (int, error) { + switch p { + case ProtectionProfileAes128CmHmacSha1_80: + return 14, nil + case ProtectionProfileAeadAes128Gcm: + return 12, nil + default: + return 0, fmt.Errorf("%w: %#v", errNoSuchSRTPProfile, p) + } +} + +func (p ProtectionProfile) authTagLen() (int, error) { + switch p { + case ProtectionProfileAes128CmHmacSha1_80: + return (&srtpCipherAesCmHmacSha1{}).authTagLen(), nil + case ProtectionProfileAeadAes128Gcm: + return (&srtpCipherAeadAesGcm{}).authTagLen(), nil + default: + return 0, fmt.Errorf("%w: %#v", errNoSuchSRTPProfile, p) + } +} + +func (p ProtectionProfile) aeadAuthTagLen() (int, error) { + switch p { + case ProtectionProfileAes128CmHmacSha1_80: + return (&srtpCipherAesCmHmacSha1{}).aeadAuthTagLen(), nil + case ProtectionProfileAeadAes128Gcm: + return (&srtpCipherAeadAesGcm{}).aeadAuthTagLen(), nil + default: + return 0, fmt.Errorf("%w: %#v", errNoSuchSRTPProfile, p) + } +} + +func (p ProtectionProfile) authKeyLen() (int, error) { + switch p { + case ProtectionProfileAes128CmHmacSha1_80: + return 20, nil + case ProtectionProfileAeadAes128Gcm: + return 0, nil + default: + return 0, fmt.Errorf("%w: %#v", errNoSuchSRTPProfile, p) + } +} diff --git a/vendor/github.com/pion/srtp/v2/renovate.json b/vendor/github.com/pion/srtp/v2/renovate.json new file mode 100644 index 0000000..4400fd9 --- /dev/null +++ b/vendor/github.com/pion/srtp/v2/renovate.json @@ -0,0 +1,15 @@ +{ + "extends": [ + "config:base" + ], + "postUpdateOptions": [ + "gomodTidy" + ], + "commitBody": "Generated by renovateBot", + "packageRules": [ + { + "packagePatterns": ["^golang.org/x/"], + "schedule": ["on the first day of the month"] + } + ] +} diff --git a/vendor/github.com/pion/srtp/v2/session.go b/vendor/github.com/pion/srtp/v2/session.go new file mode 100644 index 0000000..95520f7 --- /dev/null +++ b/vendor/github.com/pion/srtp/v2/session.go @@ -0,0 +1,150 @@ +package srtp + +import ( + "io" + "net" + "sync" + + "github.com/pion/logging" + "github.com/pion/transport/packetio" +) + +type streamSession interface { + Close() error + write([]byte) (int, error) + decrypt([]byte) error +} + +type session struct { + localContextMutex sync.Mutex + localContext, remoteContext *Context + localOptions, remoteOptions []ContextOption + + newStream chan readStream + + started chan interface{} + closed chan interface{} + + readStreamsClosed bool + readStreams map[uint32]readStream + readStreamsLock sync.Mutex + + log logging.LeveledLogger + bufferFactory func(packetType packetio.BufferPacketType, ssrc uint32) io.ReadWriteCloser + + nextConn net.Conn +} + +// Config is used to configure a session. +// You can provide either a KeyingMaterialExporter to export keys +// or directly pass the keys themselves. +// After a Config is passed to a session it must not be modified. +type Config struct { + Keys SessionKeys + Profile ProtectionProfile + BufferFactory func(packetType packetio.BufferPacketType, ssrc uint32) io.ReadWriteCloser + LoggerFactory logging.LoggerFactory + + // List of local/remote context options. + // ReplayProtection is enabled on remote context by default. + // Default replay protection window size is 64. + LocalOptions, RemoteOptions []ContextOption +} + +// SessionKeys bundles the keys required to setup an SRTP session +type SessionKeys struct { + LocalMasterKey []byte + LocalMasterSalt []byte + RemoteMasterKey []byte + RemoteMasterSalt []byte +} + +func (s *session) getOrCreateReadStream(ssrc uint32, child streamSession, proto func() readStream) (readStream, bool) { + s.readStreamsLock.Lock() + defer s.readStreamsLock.Unlock() + + if s.readStreamsClosed { + return nil, false + } + + r, ok := s.readStreams[ssrc] + if ok { + return r, false + } + + // Create the readStream. + r = proto() + + if err := r.init(child, ssrc); err != nil { + return nil, false + } + + s.readStreams[ssrc] = r + return r, true +} + +func (s *session) removeReadStream(ssrc uint32) { + s.readStreamsLock.Lock() + defer s.readStreamsLock.Unlock() + + if s.readStreamsClosed { + return + } + + delete(s.readStreams, ssrc) +} + +func (s *session) close() error { + if s.nextConn == nil { + return nil + } else if err := s.nextConn.Close(); err != nil { + return err + } + + <-s.closed + return nil +} + +func (s *session) start(localMasterKey, localMasterSalt, remoteMasterKey, remoteMasterSalt []byte, profile ProtectionProfile, child streamSession) error { + var err error + s.localContext, err = CreateContext(localMasterKey, localMasterSalt, profile, s.localOptions...) + if err != nil { + return err + } + + s.remoteContext, err = CreateContext(remoteMasterKey, remoteMasterSalt, profile, s.remoteOptions...) + if err != nil { + return err + } + + go func() { + defer func() { + close(s.newStream) + + s.readStreamsLock.Lock() + s.readStreamsClosed = true + s.readStreamsLock.Unlock() + close(s.closed) + }() + + b := make([]byte, 8192) + for { + var i int + i, err = s.nextConn.Read(b) + if err != nil { + if err != io.EOF { + s.log.Error(err.Error()) + } + return + } + + if err = child.decrypt(b[:i]); err != nil { + s.log.Info(err.Error()) + } + } + }() + + close(s.started) + + return nil +} diff --git a/vendor/github.com/pion/srtp/v2/session_srtcp.go b/vendor/github.com/pion/srtp/v2/session_srtcp.go new file mode 100644 index 0000000..a5fb656 --- /dev/null +++ b/vendor/github.com/pion/srtp/v2/session_srtcp.go @@ -0,0 +1,180 @@ +package srtp + +import ( + "net" + "time" + + "github.com/pion/logging" + "github.com/pion/rtcp" +) + +const defaultSessionSRTCPReplayProtectionWindow = 64 + +// SessionSRTCP implements io.ReadWriteCloser and provides a bi-directional SRTCP session +// SRTCP itself does not have a design like this, but it is common in most applications +// for local/remote to each have their own keying material. This provides those patterns +// instead of making everyone re-implement +type SessionSRTCP struct { + session + writeStream *WriteStreamSRTCP +} + +// NewSessionSRTCP creates a SRTCP session using conn as the underlying transport. +func NewSessionSRTCP(conn net.Conn, config *Config) (*SessionSRTCP, error) { //nolint:dupl + if config == nil { + return nil, errNoConfig + } else if conn == nil { + return nil, errNoConn + } + + loggerFactory := config.LoggerFactory + if loggerFactory == nil { + loggerFactory = logging.NewDefaultLoggerFactory() + } + + localOpts := append( + []ContextOption{}, + config.LocalOptions..., + ) + remoteOpts := append( + []ContextOption{ + // Default options + SRTCPReplayProtection(defaultSessionSRTCPReplayProtectionWindow), + }, + config.RemoteOptions..., + ) + + s := &SessionSRTCP{ + session: session{ + nextConn: conn, + localOptions: localOpts, + remoteOptions: remoteOpts, + readStreams: map[uint32]readStream{}, + newStream: make(chan readStream), + started: make(chan interface{}), + closed: make(chan interface{}), + bufferFactory: config.BufferFactory, + log: loggerFactory.NewLogger("srtp"), + }, + } + s.writeStream = &WriteStreamSRTCP{s} + + err := s.session.start( + config.Keys.LocalMasterKey, config.Keys.LocalMasterSalt, + config.Keys.RemoteMasterKey, config.Keys.RemoteMasterSalt, + config.Profile, + s, + ) + if err != nil { + return nil, err + } + return s, nil +} + +// OpenWriteStream returns the global write stream for the Session +func (s *SessionSRTCP) OpenWriteStream() (*WriteStreamSRTCP, error) { + return s.writeStream, nil +} + +// OpenReadStream opens a read stream for the given SSRC, it can be used +// if you want a certain SSRC, but don't want to wait for AcceptStream +func (s *SessionSRTCP) OpenReadStream(ssrc uint32) (*ReadStreamSRTCP, error) { + r, _ := s.session.getOrCreateReadStream(ssrc, s, newReadStreamSRTCP) + + if readStream, ok := r.(*ReadStreamSRTCP); ok { + return readStream, nil + } + return nil, errFailedTypeAssertion +} + +// AcceptStream returns a stream to handle RTCP for a single SSRC +func (s *SessionSRTCP) AcceptStream() (*ReadStreamSRTCP, uint32, error) { + stream, ok := <-s.newStream + if !ok { + return nil, 0, errStreamAlreadyClosed + } + + readStream, ok := stream.(*ReadStreamSRTCP) + if !ok { + return nil, 0, errFailedTypeAssertion + } + + return readStream, stream.GetSSRC(), nil +} + +// Close ends the session +func (s *SessionSRTCP) Close() error { + return s.session.close() +} + +// Private + +func (s *SessionSRTCP) write(buf []byte) (int, error) { + if _, ok := <-s.session.started; ok { + return 0, errStartedChannelUsedIncorrectly + } + + s.session.localContextMutex.Lock() + encrypted, err := s.localContext.EncryptRTCP(nil, buf, nil) + s.session.localContextMutex.Unlock() + + if err != nil { + return 0, err + } + return s.session.nextConn.Write(encrypted) +} + +func (s *SessionSRTCP) setWriteDeadline(t time.Time) error { + return s.session.nextConn.SetWriteDeadline(t) +} + +// create a list of Destination SSRCs +// that's a superset of all Destinations in the slice. +func destinationSSRC(pkts []rtcp.Packet) []uint32 { + ssrcSet := make(map[uint32]struct{}) + for _, p := range pkts { + for _, ssrc := range p.DestinationSSRC() { + ssrcSet[ssrc] = struct{}{} + } + } + + out := make([]uint32, 0, len(ssrcSet)) + for ssrc := range ssrcSet { + out = append(out, ssrc) + } + + return out +} + +func (s *SessionSRTCP) decrypt(buf []byte) error { + decrypted, err := s.remoteContext.DecryptRTCP(buf, buf, nil) + if err != nil { + return err + } + + pkt, err := rtcp.Unmarshal(decrypted) + if err != nil { + return err + } + + for _, ssrc := range destinationSSRC(pkt) { + r, isNew := s.session.getOrCreateReadStream(ssrc, s, newReadStreamSRTCP) + if r == nil { + return nil // Session has been closed + } else if isNew { + s.session.newStream <- r // Notify AcceptStream + } + + readStream, ok := r.(*ReadStreamSRTCP) + if !ok { + return errFailedTypeAssertion + } + + _, err = readStream.write(decrypted) + if err != nil { + return err + } + } + + return nil +} diff --git a/vendor/github.com/pion/srtp/v2/session_srtp.go b/vendor/github.com/pion/srtp/v2/session_srtp.go new file mode 100644 index 0000000..dc815af --- /dev/null +++ b/vendor/github.com/pion/srtp/v2/session_srtp.go @@ -0,0 +1,171 @@ +package srtp + +import ( + "net" + "time" + + "github.com/pion/logging" + "github.com/pion/rtp" +) + +const defaultSessionSRTPReplayProtectionWindow = 64 + +// SessionSRTP implements io.ReadWriteCloser and provides a bi-directional SRTP session +// SRTP itself does not have a design like this, but it is common in most applications +// for local/remote to each have their own keying material. This provides those patterns +// instead of making everyone re-implement +type SessionSRTP struct { + session + writeStream *WriteStreamSRTP +} + +// NewSessionSRTP creates a SRTP session using conn as the underlying transport. +func NewSessionSRTP(conn net.Conn, config *Config) (*SessionSRTP, error) { //nolint:dupl + if config == nil { + return nil, errNoConfig + } else if conn == nil { + return nil, errNoConn + } + + loggerFactory := config.LoggerFactory + if loggerFactory == nil { + loggerFactory = logging.NewDefaultLoggerFactory() + } + + localOpts := append( + []ContextOption{}, + config.LocalOptions..., + ) + remoteOpts := append( + []ContextOption{ + // Default options + SRTPReplayProtection(defaultSessionSRTPReplayProtectionWindow), + }, + config.RemoteOptions..., + ) + + s := &SessionSRTP{ + session: session{ + nextConn: conn, + localOptions: localOpts, + remoteOptions: remoteOpts, + readStreams: map[uint32]readStream{}, + newStream: make(chan readStream), + started: make(chan interface{}), + closed: make(chan interface{}), + bufferFactory: config.BufferFactory, + log: loggerFactory.NewLogger("srtp"), + }, + } + s.writeStream = &WriteStreamSRTP{s} + + err := s.session.start( + config.Keys.LocalMasterKey, config.Keys.LocalMasterSalt, + config.Keys.RemoteMasterKey, config.Keys.RemoteMasterSalt, + config.Profile, + s, + ) + if err != nil { + return nil, err + } + return s, nil +} + +// OpenWriteStream returns the global write stream for the Session +func (s *SessionSRTP) OpenWriteStream() (*WriteStreamSRTP, error) { + return s.writeStream, nil +} + +// OpenReadStream opens a read stream for the given SSRC, it can be used +// if you want a certain SSRC, but don't want to wait for AcceptStream +func (s *SessionSRTP) OpenReadStream(ssrc uint32) (*ReadStreamSRTP, error) { + r, _ := s.session.getOrCreateReadStream(ssrc, s, newReadStreamSRTP) + + if readStream, ok := r.(*ReadStreamSRTP); ok { + return readStream, nil + } + + return nil, errFailedTypeAssertion +} + +// AcceptStream returns a stream to handle RTCP for a single SSRC +func (s *SessionSRTP) AcceptStream() (*ReadStreamSRTP, uint32, error) { + stream, ok := <-s.newStream + if !ok { + return nil, 0, errStreamAlreadyClosed + } + + readStream, ok := stream.(*ReadStreamSRTP) + if !ok { + return nil, 0, errFailedTypeAssertion + } + + return readStream, stream.GetSSRC(), nil +} + +// Close ends the session +func (s *SessionSRTP) Close() error { + return s.session.close() +} + +func (s *SessionSRTP) write(b []byte) (int, error) { + packet := &rtp.Packet{} + + err := packet.Unmarshal(b) + if err != nil { + return 0, nil + } + + return s.writeRTP(&packet.Header, packet.Payload) +} + +func (s *SessionSRTP) writeRTP(header *rtp.Header, payload []byte) (int, error) { + if _, ok := <-s.session.started; ok { + return 0, errStartedChannelUsedIncorrectly + } + + s.session.localContextMutex.Lock() + encrypted, err := s.localContext.encryptRTP(nil, header, payload) + s.session.localContextMutex.Unlock() + + if err != nil { + return 0, err + } + + return s.session.nextConn.Write(encrypted) +} + +func (s *SessionSRTP) setWriteDeadline(t time.Time) error { + return s.session.nextConn.SetWriteDeadline(t) +} + +func (s *SessionSRTP) decrypt(buf []byte) error { + h := &rtp.Header{} + if err := h.Unmarshal(buf); err != nil { + return err + } + + r, isNew := s.session.getOrCreateReadStream(h.SSRC, s, newReadStreamSRTP) + if r == nil { + return nil // Session has been closed + } else if isNew { + s.session.newStream <- r // Notify AcceptStream + } + + readStream, ok := r.(*ReadStreamSRTP) + if !ok { + return errFailedTypeAssertion + } + + decrypted, err := s.remoteContext.decryptRTP(buf, buf, h) + if err != nil { + return err + } + + _, err = readStream.write(decrypted) + if err != nil { + return err + } + + return nil +} diff --git a/vendor/github.com/pion/srtp/v2/srtcp.go b/vendor/github.com/pion/srtp/v2/srtcp.go new file mode 100644 index 0000000..dbf5125 --- /dev/null +++ b/vendor/github.com/pion/srtp/v2/srtcp.go @@ -0,0 +1,77 @@ +package srtp + +import ( + "encoding/binary" + "fmt" + + "github.com/pion/rtcp" +) + +const maxSRTCPIndex = 0x7FFFFFFF + +func (c *Context) decryptRTCP(dst, encrypted []byte) ([]byte, error) { + out := allocateIfMismatch(dst, encrypted) + tailOffset := len(encrypted) - (c.cipher.authTagLen() + srtcpIndexSize) + + if tailOffset < 0 { + return nil, fmt.Errorf("%w: %d", errTooShortRTCP, len(encrypted)) + } else if isEncrypted := encrypted[tailOffset] >> 7; isEncrypted == 0 { + return out, nil + } + + index := c.cipher.getRTCPIndex(encrypted) + ssrc := binary.BigEndian.Uint32(encrypted[4:]) + + s := c.getSRTCPSSRCState(ssrc) + markAsValid, ok := s.replayDetector.Check(uint64(index)) + if !ok { + return nil, &errorDuplicated{Proto: "srtcp", SSRC: ssrc, Index: index} + } + + out, err := c.cipher.decryptRTCP(out, encrypted, index, ssrc) + if err != nil { + return nil, err + } + + markAsValid() + return out, nil +} + +// DecryptRTCP decrypts a buffer that contains a RTCP packet +func (c *Context) DecryptRTCP(dst, encrypted []byte, header *rtcp.Header) ([]byte, error) { + if header == nil { + header = &rtcp.Header{} + } + + if err := header.Unmarshal(encrypted); err != nil { + return nil, err + } + + return c.decryptRTCP(dst, encrypted) +} + +func (c *Context) encryptRTCP(dst, decrypted []byte) ([]byte, error) { + ssrc := binary.BigEndian.Uint32(decrypted[4:]) + s := c.getSRTCPSSRCState(ssrc) + + // We roll over early because MSB is used for marking as encrypted + s.srtcpIndex++ + if s.srtcpIndex > maxSRTCPIndex { + s.srtcpIndex = 0 + } + + return c.cipher.encryptRTCP(dst, decrypted, s.srtcpIndex, ssrc) +} + +// EncryptRTCP Encrypts a RTCP packet +func (c *Context) EncryptRTCP(dst, decrypted []byte, header *rtcp.Header) ([]byte, error) { + if header == nil { + header = &rtcp.Header{} + } + + if err := header.Unmarshal(decrypted); err != nil { + return nil, err + } + + return c.encryptRTCP(dst, decrypted) +} diff --git a/vendor/github.com/pion/srtp/v2/srtp.go b/vendor/github.com/pion/srtp/v2/srtp.go new file mode 100644 index 0000000..c4ed3ac --- /dev/null +++ b/vendor/github.com/pion/srtp/v2/srtp.go @@ -0,0 +1,68 @@ +// Package srtp implements Secure Real-time Transport Protocol +package srtp + +import ( + "github.com/pion/rtp" +) + +func (c *Context) decryptRTP(dst, ciphertext []byte, header *rtp.Header) ([]byte, error) { + s := c.getSRTPSSRCState(header.SSRC) + + markAsValid, ok := s.replayDetector.Check(uint64(header.SequenceNumber)) + if !ok { + return nil, &errorDuplicated{ + Proto: "srtp", SSRC: header.SSRC, Index: uint32(header.SequenceNumber), + } + } + + dst = growBufferSize(dst, len(ciphertext)-c.cipher.authTagLen()) + roc, updateROC := s.nextRolloverCount(header.SequenceNumber) + + dst, err := c.cipher.decryptRTP(dst, ciphertext, header, roc) + if err != nil { + return nil, err + } + + markAsValid() + updateROC() + return dst, nil +} + +// DecryptRTP decrypts a RTP packet with an encrypted payload +func (c *Context) DecryptRTP(dst, encrypted []byte, header *rtp.Header) ([]byte, error) { + if header == nil { + header = &rtp.Header{} + } + + if err := header.Unmarshal(encrypted); err != nil { + return nil, err + } + + return c.decryptRTP(dst, encrypted, header) +} + +// EncryptRTP marshals and encrypts an RTP packet, writing to the dst buffer provided. +// If the dst buffer does not have the capacity to hold `len(plaintext) + 10` bytes, a new one will be allocated and returned. +// If a rtp.Header is provided, it will be Unmarshaled using the plaintext. +func (c *Context) EncryptRTP(dst []byte, plaintext []byte, header *rtp.Header) ([]byte, error) { + if header == nil { + header = &rtp.Header{} + } + + if err := header.Unmarshal(plaintext); err != nil { + return nil, err + } + + return c.encryptRTP(dst, header, plaintext[header.PayloadOffset:]) +} + +// encryptRTP marshals and encrypts an RTP packet, writing to the dst buffer provided. +// If the dst buffer does not have the capacity, a new one will be allocated and returned. +// Similar to above but faster because it can avoid unmarshaling the header and marshaling the payload. +func (c *Context) encryptRTP(dst []byte, header *rtp.Header, payload []byte) (ciphertext []byte, err error) { + s := c.getSRTPSSRCState(header.SSRC) + roc, updateROC := s.nextRolloverCount(header.SequenceNumber) + updateROC() + + return c.cipher.encryptRTP(dst, header, payload, roc) +} diff --git a/vendor/github.com/pion/srtp/v2/srtp_cipher.go b/vendor/github.com/pion/srtp/v2/srtp_cipher.go new file mode 100644 index 0000000..4c5cd88 --- /dev/null +++ b/vendor/github.com/pion/srtp/v2/srtp_cipher.go @@ -0,0 +1,46 @@ +package srtp + +import "github.com/pion/rtp" + +// cipher represents a implementation of one +// of the SRTP Specific ciphers +type srtpCipher interface { + // authTagLen returns auth key length of the cipher. + // See the note below. + authTagLen() int + // aeadAuthTagLen returns AEAD auth key length of the cipher. + // See the note below. + aeadAuthTagLen() int + getRTCPIndex([]byte) uint32 + + encryptRTP([]byte, *rtp.Header, []byte, uint32) ([]byte, error) + encryptRTCP([]byte, []byte, uint32, uint32) ([]byte, error) + + decryptRTP([]byte, []byte, *rtp.Header, uint32) ([]byte, error) + decryptRTCP([]byte, []byte, uint32, uint32) ([]byte, error) +} + +/* +NOTE: Auth tag and AEAD auth tag are placed at the different position in SRTCP + +In non-AEAD cipher, the authentication tag is placed *after* the ESRTCP word +(Encrypted-flag and SRTCP index). + +> AES_128_CM_HMAC_SHA1_80 +> | RTCP Header | Encrypted payload |E| SRTCP Index | Auth tag | +> ^ |----------| +> | ^ +> | authTagLen=10 +> aeadAuthTagLen=0 + +In AEAD cipher, the AEAD authentication tag is embedded in the ciphertext. +It is *before* the ESRTCP word (Encrypted-flag and SRTCP index). + +> AEAD_AES_128_GCM +> | RTCP Header | Encrypted payload | AEAD auth tag |E| SRTCP Index | +> |---------------| ^ +> ^ authTagLen=0 +> aeadAuthTagLen=16 + +See https://tools.ietf.org/html/rfc7714 for the full specifications. +*/ diff --git a/vendor/github.com/pion/srtp/v2/srtp_cipher_aead_aes_gcm.go b/vendor/github.com/pion/srtp/v2/srtp_cipher_aead_aes_gcm.go new file mode 100644 index 0000000..2720679 --- /dev/null +++ b/vendor/github.com/pion/srtp/v2/srtp_cipher_aead_aes_gcm.go @@ -0,0 +1,198 @@ +package srtp + +import ( + "crypto/aes" + "crypto/cipher" + "encoding/binary" + + "github.com/pion/rtp" +) + +const ( + rtcpEncryptionFlag = 0x80 +) + +type srtpCipherAeadAesGcm struct { + srtpCipher, srtcpCipher cipher.AEAD + + srtpSessionSalt, srtcpSessionSalt []byte +} + +func newSrtpCipherAeadAesGcm(masterKey, masterSalt []byte) (*srtpCipherAeadAesGcm, error) { + s := &srtpCipherAeadAesGcm{} + + srtpSessionKey, err := aesCmKeyDerivation(labelSRTPEncryption, masterKey, masterSalt, 0, len(masterKey)) + if err != nil { + return nil, err + } + + srtpBlock, err := aes.NewCipher(srtpSessionKey) + if err != nil { + return nil, err + } + + s.srtpCipher, err = cipher.NewGCM(srtpBlock) + if err != nil { + return nil, err + } + + srtcpSessionKey, err := aesCmKeyDerivation(labelSRTCPEncryption, masterKey, masterSalt, 0, len(masterKey)) + if err != nil { + return nil, err + } + + srtcpBlock, err := aes.NewCipher(srtcpSessionKey) + if err != nil { + return nil, err + } + + s.srtcpCipher, err = cipher.NewGCM(srtcpBlock) + if err != nil { + return nil, err + } + + if s.srtpSessionSalt, err = aesCmKeyDerivation(labelSRTPSalt, masterKey, masterSalt, 0, len(masterSalt)); err != nil { + return nil, err + } else if s.srtcpSessionSalt, err = aesCmKeyDerivation(labelSRTCPSalt, masterKey, masterSalt, 0, len(masterSalt)); err != nil { + return nil, err + } + + return s, nil +} + +func (s *srtpCipherAeadAesGcm) authTagLen() int { + return 0 +} + +func (s *srtpCipherAeadAesGcm) aeadAuthTagLen() int { + return 16 +} + +func (s *srtpCipherAeadAesGcm) encryptRTP(dst []byte, header *rtp.Header, payload []byte, roc uint32) (ciphertext []byte, err error) { + // Grow the given buffer to fit the output. + dst = growBufferSize(dst, header.MarshalSize()+len(payload)+s.aeadAuthTagLen()) + + hdr, err := header.Marshal() + if err != nil { + return nil, err + } + + iv := s.rtpInitializationVector(header, roc) + nHdr := len(hdr) + s.srtpCipher.Seal(dst[nHdr:nHdr], iv, payload, hdr) + copy(dst[:nHdr], hdr) + return dst, nil +} + +func (s *srtpCipherAeadAesGcm) decryptRTP(dst, ciphertext []byte, header *rtp.Header, roc uint32) ([]byte, error) { + // Grow the given buffer to fit the output. + nDst := len(ciphertext) - s.aeadAuthTagLen() + if nDst < 0 { + // Size of ciphertext is shorter than AEAD auth tag len. + return nil, errFailedToVerifyAuthTag + } + dst = growBufferSize(dst, nDst) + + iv := s.rtpInitializationVector(header, roc) + + if _, err := s.srtpCipher.Open( + dst[header.PayloadOffset:header.PayloadOffset], iv, ciphertext[header.PayloadOffset:], ciphertext[:header.PayloadOffset], + ); err != nil { + return nil, err + } + + copy(dst[:header.PayloadOffset], ciphertext[:header.PayloadOffset]) + return dst, nil +} + +func (s *srtpCipherAeadAesGcm) encryptRTCP(dst, decrypted []byte, srtcpIndex uint32, ssrc uint32) ([]byte, error) { + aadPos := len(decrypted) + s.aeadAuthTagLen() + // Grow the given buffer to fit the output. + dst = growBufferSize(dst, aadPos+srtcpIndexSize) + + iv := s.rtcpInitializationVector(srtcpIndex, ssrc) + aad := s.rtcpAdditionalAuthenticatedData(decrypted, srtcpIndex) + + s.srtcpCipher.Seal(dst[8:8], iv, decrypted[8:], aad) + + copy(dst[:8], decrypted[:8]) + copy(dst[aadPos:aadPos+4], aad[8:12]) + return dst, nil +} + +func (s *srtpCipherAeadAesGcm) decryptRTCP(dst, encrypted []byte, srtcpIndex, ssrc uint32) ([]byte, error) { + aadPos := len(encrypted) - srtcpIndexSize + // Grow the given buffer to fit the output. + nDst := aadPos - s.aeadAuthTagLen() + if nDst < 0 { + // Size of ciphertext is shorter than AEAD auth tag len. + return nil, errFailedToVerifyAuthTag + } + dst = growBufferSize(dst, nDst) + + iv := s.rtcpInitializationVector(srtcpIndex, ssrc) + aad := s.rtcpAdditionalAuthenticatedData(encrypted, srtcpIndex) + + if _, err := s.srtcpCipher.Open(dst[8:8], iv, encrypted[8:aadPos], aad); err != nil { + return nil, err + } + + copy(dst[:8], encrypted[:8]) + return dst, nil +} + +// The 12-octet IV used by AES-GCM SRTP is formed by first concatenating +// 2 octets of zeroes, the 4-octet SSRC, the 4-octet rollover counter +// (ROC), and the 2-octet sequence number (SEQ). The resulting 12-octet +// value is then XORed to the 12-octet salt to form the 12-octet IV. +// +// https://tools.ietf.org/html/rfc7714#section-8.1 +func (s *srtpCipherAeadAesGcm) rtpInitializationVector(header *rtp.Header, roc uint32) []byte { + iv := make([]byte, 12) + binary.BigEndian.PutUint32(iv[2:], header.SSRC) + binary.BigEndian.PutUint32(iv[6:], roc) + binary.BigEndian.PutUint16(iv[10:], header.SequenceNumber) + + for i := range iv { + iv[i] ^= s.srtpSessionSalt[i] + } + return iv +} + +// The 12-octet IV used by AES-GCM SRTCP is formed by first +// concatenating 2 octets of zeroes, the 4-octet SSRC identifier, +// 2 octets of zeroes, a single "0" bit, and the 31-bit SRTCP index. +// The resulting 12-octet value is then XORed to the 12-octet salt to +// form the 12-octet IV. +// +// https://tools.ietf.org/html/rfc7714#section-9.1 +func (s *srtpCipherAeadAesGcm) rtcpInitializationVector(srtcpIndex uint32, ssrc uint32) []byte { + iv := make([]byte, 12) + + binary.BigEndian.PutUint32(iv[2:], ssrc) + binary.BigEndian.PutUint32(iv[8:], srtcpIndex) + + for i := range iv { + iv[i] ^= s.srtcpSessionSalt[i] + } + return iv +} + +// In an SRTCP packet, a 1-bit Encryption flag is prepended to the +// 31-bit SRTCP index to form a 32-bit value we shall call the +// "ESRTCP word" +// +// https://tools.ietf.org/html/rfc7714#section-17 +func (s *srtpCipherAeadAesGcm) rtcpAdditionalAuthenticatedData(rtcpPacket []byte, srtcpIndex uint32) []byte { + aad := make([]byte, 12) + + copy(aad, rtcpPacket[:8]) + binary.BigEndian.PutUint32(aad[8:], srtcpIndex) + aad[8] |= rtcpEncryptionFlag + + return aad +} + +func (s *srtpCipherAeadAesGcm) getRTCPIndex(in []byte) uint32 { + return binary.BigEndian.Uint32(in[len(in)-4:]) &^ (rtcpEncryptionFlag << 24) +} diff --git a/vendor/github.com/pion/srtp/v2/srtp_cipher_aes_cm_hmac_sha1.go b/vendor/github.com/pion/srtp/v2/srtp_cipher_aes_cm_hmac_sha1.go new file mode 100644 index 0000000..9d783d1 --- /dev/null +++ b/vendor/github.com/pion/srtp/v2/srtp_cipher_aes_cm_hmac_sha1.go @@ -0,0 +1,228 @@ +package srtp + +import ( //nolint:gci + "crypto/aes" + "crypto/cipher" + "crypto/hmac" + "crypto/sha1" //nolint:gosec + "crypto/subtle" + "encoding/binary" + "hash" + + "github.com/pion/rtp" +) + +type srtpCipherAesCmHmacSha1 struct { + srtpSessionSalt []byte + srtpSessionAuth hash.Hash + srtpBlock cipher.Block + + srtcpSessionSalt []byte + srtcpSessionAuth hash.Hash + srtcpBlock cipher.Block +} + +func newSrtpCipherAesCmHmacSha1(masterKey, masterSalt []byte) (*srtpCipherAesCmHmacSha1, error) { + s := &srtpCipherAesCmHmacSha1{} + srtpSessionKey, err := aesCmKeyDerivation(labelSRTPEncryption, masterKey, masterSalt, 0, len(masterKey)) + if err != nil { + return nil, err + } else if s.srtpBlock, err = aes.NewCipher(srtpSessionKey); err != nil { + return nil, err + } + + srtcpSessionKey, err := aesCmKeyDerivation(labelSRTCPEncryption, masterKey, masterSalt, 0, len(masterKey)) + if err != nil { + return nil, err + } else if s.srtcpBlock, err = aes.NewCipher(srtcpSessionKey); err != nil { + return nil, err + } + + if s.srtpSessionSalt, err = aesCmKeyDerivation(labelSRTPSalt, masterKey, masterSalt, 0, len(masterSalt)); err != nil { + return nil, err + } else if s.srtcpSessionSalt, err = aesCmKeyDerivation(labelSRTCPSalt, masterKey, masterSalt, 0, len(masterSalt)); err != nil { + return nil, err + } + + authKeyLen, err := ProtectionProfileAes128CmHmacSha1_80.authKeyLen() + if err != nil { + return nil, err + } + + srtpSessionAuthTag, err := aesCmKeyDerivation(labelSRTPAuthenticationTag, masterKey, masterSalt, 0, authKeyLen) + if err != nil { + return nil, err + } + + srtcpSessionAuthTag, err := aesCmKeyDerivation(labelSRTCPAuthenticationTag, masterKey, masterSalt, 0, authKeyLen) + if err != nil { + return nil, err + } + + s.srtcpSessionAuth = hmac.New(sha1.New, srtcpSessionAuthTag) + s.srtpSessionAuth = hmac.New(sha1.New, srtpSessionAuthTag) + return s, nil +} + +func (s *srtpCipherAesCmHmacSha1) authTagLen() int { + return 10 +} + +func (s *srtpCipherAesCmHmacSha1) aeadAuthTagLen() int { + return 0 +} + +func (s *srtpCipherAesCmHmacSha1) encryptRTP(dst []byte, header *rtp.Header, payload []byte, roc uint32) (ciphertext []byte, err error) { + // Grow the given buffer to fit the output. + dst = growBufferSize(dst, header.MarshalSize()+len(payload)+s.authTagLen()) + + // Copy the header unencrypted. + n, err := header.MarshalTo(dst) + if err != nil { + return nil, err + } + + // Encrypt the payload + counter := generateCounter(header.SequenceNumber, roc, header.SSRC, s.srtpSessionSalt) + stream := cipher.NewCTR(s.srtpBlock, counter) + stream.XORKeyStream(dst[n:], payload) + n += len(payload) + + // Generate the auth tag. + authTag, err := s.generateSrtpAuthTag(dst[:n], roc) + if err != nil { + return nil, err + } + + // Write the auth tag to the dest. + copy(dst[n:], authTag) + + return dst, nil +} + +func (s *srtpCipherAesCmHmacSha1) decryptRTP(dst, ciphertext []byte, header *rtp.Header, roc uint32) ([]byte, error) { + // Split the auth tag and the cipher text into two parts. + actualTag := ciphertext[len(ciphertext)-s.authTagLen():] + ciphertext = ciphertext[:len(ciphertext)-s.authTagLen()] + + // Generate the auth tag we expect to see from the ciphertext. + expectedTag, err := s.generateSrtpAuthTag(ciphertext, roc) + if err != nil { + return nil, err + } + + // See if the auth tag actually matches. + // We use a constant time comparison to prevent timing attacks. + if subtle.ConstantTimeCompare(actualTag, expectedTag) != 1 { + return nil, errFailedToVerifyAuthTag + } + + // Write the plaintext header to the destination buffer. + copy(dst, ciphertext[:header.PayloadOffset]) + + // Decrypt the ciphertext for the payload. + counter := generateCounter(header.SequenceNumber, roc, header.SSRC, s.srtpSessionSalt) + stream := cipher.NewCTR(s.srtpBlock, counter) + stream.XORKeyStream(dst[header.PayloadOffset:], ciphertext[header.PayloadOffset:]) + return dst, nil +} + +func (s *srtpCipherAesCmHmacSha1) encryptRTCP(dst, decrypted []byte, srtcpIndex uint32, ssrc uint32) ([]byte, error) { + dst = allocateIfMismatch(dst, decrypted) + + // Encrypt everything after header + stream := cipher.NewCTR(s.srtcpBlock, generateCounter(uint16(srtcpIndex&0xffff), srtcpIndex>>16, ssrc, s.srtcpSessionSalt)) + stream.XORKeyStream(dst[8:], dst[8:]) + + // Add SRTCP Index and set Encryption bit + dst = append(dst, make([]byte, 4)...) + binary.BigEndian.PutUint32(dst[len(dst)-4:], srtcpIndex) + dst[len(dst)-4] |= 0x80 + + authTag, err := s.generateSrtcpAuthTag(dst) + if err != nil { + return nil, err + } + return append(dst, authTag...), nil +} + +func (s *srtpCipherAesCmHmacSha1) decryptRTCP(out, encrypted []byte, index, ssrc uint32) ([]byte, error) { + tailOffset := len(encrypted) - (s.authTagLen() + srtcpIndexSize) + out = out[0:tailOffset] + + expectedTag, err := s.generateSrtcpAuthTag(encrypted[:len(encrypted)-s.authTagLen()]) + if err != nil { + return nil, err + } + + actualTag := encrypted[len(encrypted)-s.authTagLen():] + if subtle.ConstantTimeCompare(actualTag, expectedTag) != 1 { + return nil, errFailedToVerifyAuthTag + } + + stream := cipher.NewCTR(s.srtcpBlock, generateCounter(uint16(index&0xffff), index>>16, ssrc, s.srtcpSessionSalt)) + stream.XORKeyStream(out[8:], out[8:]) + + return out, nil +} + +func (s *srtpCipherAesCmHmacSha1) generateSrtpAuthTag(buf []byte, roc uint32) ([]byte, error) { + // https://tools.ietf.org/html/rfc3711#section-4.2 + // In the case of SRTP, M SHALL consist of the Authenticated + // Portion of the packet (as specified in Figure 1) concatenated with + // the ROC, M = Authenticated Portion || ROC; + // + // The pre-defined authentication transform for SRTP is HMAC-SHA1 + // [RFC2104]. With HMAC-SHA1, the SRTP_PREFIX_LENGTH (Figure 3) SHALL + // be 0. For SRTP (respectively SRTCP), the HMAC SHALL be applied to + // the session authentication key and M as specified above, i.e., + // HMAC(k_a, M). The HMAC output SHALL then be truncated to the n_tag + // left-most bits. + // - Authenticated portion of the packet is everything BEFORE MKI + // - k_a is the session message authentication key + // - n_tag is the bit-length of the output authentication tag + s.srtpSessionAuth.Reset() + + if _, err := s.srtpSessionAuth.Write(buf); err != nil { + return nil, err + } + + // For SRTP only, we need to hash the rollover counter as well. + rocRaw := [4]byte{} + binary.BigEndian.PutUint32(rocRaw[:], roc) + + _, err := s.srtpSessionAuth.Write(rocRaw[:]) + if err != nil { + return nil, err + } + + // Truncate the hash to the first 10 bytes. + return s.srtpSessionAuth.Sum(nil)[0:s.authTagLen()], nil +} + +func (s *srtpCipherAesCmHmacSha1) generateSrtcpAuthTag(buf []byte) ([]byte, error) { + // https://tools.ietf.org/html/rfc3711#section-4.2 + // + // The pre-defined authentication transform for SRTP is HMAC-SHA1 + // [RFC2104]. With HMAC-SHA1, the SRTP_PREFIX_LENGTH (Figure 3) SHALL + // be 0. For SRTP (respectively SRTCP), the HMAC SHALL be applied to + // the session authentication key and M as specified above, i.e., + // HMAC(k_a, M). The HMAC output SHALL then be truncated to the n_tag + // left-most bits. + // - Authenticated portion of the packet is everything BEFORE MKI + // - k_a is the session message authentication key + // - n_tag is the bit-length of the output authentication tag + s.srtcpSessionAuth.Reset() + + if _, err := s.srtcpSessionAuth.Write(buf); err != nil { + return nil, err + } + + return s.srtcpSessionAuth.Sum(nil)[0:s.authTagLen()], nil +} + +func (s *srtpCipherAesCmHmacSha1) getRTCPIndex(in []byte) uint32 { + tailOffset := len(in) - (s.authTagLen() + srtcpIndexSize) + srtcpIndexBuffer := in[tailOffset : tailOffset+srtcpIndexSize] + return binary.BigEndian.Uint32(srtcpIndexBuffer) &^ (1 << 31) +} diff --git a/vendor/github.com/pion/srtp/v2/stream.go b/vendor/github.com/pion/srtp/v2/stream.go new file mode 100644 index 0000000..7b7a0cf --- /dev/null +++ b/vendor/github.com/pion/srtp/v2/stream.go @@ -0,0 +1,8 @@ +package srtp + +type readStream interface { + init(child streamSession, ssrc uint32) error + + Read(buf []byte) (int, error) + GetSSRC() uint32 +} diff --git a/vendor/github.com/pion/srtp/v2/stream_srtcp.go b/vendor/github.com/pion/srtp/v2/stream_srtcp.go new file mode 100644 index 0000000..e335937 --- /dev/null +++ b/vendor/github.com/pion/srtp/v2/stream_srtcp.go @@ -0,0 +1,157 @@ +package srtp + +import ( + "errors" + "io" + "sync" + "time" + + "github.com/pion/rtcp" + "github.com/pion/transport/packetio" +) + +// Limit the buffer size to 100KB +const srtcpBufferSize = 100 * 1000 + +// ReadStreamSRTCP handles decryption for a single RTCP SSRC +type ReadStreamSRTCP struct { + mu sync.Mutex + + isInited bool + isClosed chan bool + + session *SessionSRTCP + ssrc uint32 + + buffer io.ReadWriteCloser +} + +func (r *ReadStreamSRTCP) write(buf []byte) (n int, err error) { + n, err = r.buffer.Write(buf) + + if errors.Is(err, packetio.ErrFull) { + // Silently drop data when the buffer is full. + return len(buf), nil + } + + return n, err +} + +// Used by getOrCreateReadStream +func newReadStreamSRTCP() readStream { + return &ReadStreamSRTCP{} +} + +// ReadRTCP reads and decrypts full RTCP packet and its header from the nextConn +func (r *ReadStreamSRTCP) ReadRTCP(buf []byte) (int, *rtcp.Header, error) { + n, err := r.Read(buf) + if err != nil { + return 0, nil, err + } + + header := &rtcp.Header{} + err = header.Unmarshal(buf[:n]) + if err != nil { + return 0, nil, err + } + + return n, header, nil +} + +// Read reads and decrypts full RTCP packet from the nextConn +func (r *ReadStreamSRTCP) Read(buf []byte) (int, error) { + return r.buffer.Read(buf) +} + +// SetReadDeadline sets the deadline for the Read operation. +// Setting to zero means no deadline. +func (r *ReadStreamSRTCP) SetReadDeadline(t time.Time) error { + if b, ok := r.buffer.(interface { + SetReadDeadline(time.Time) error + }); ok { + return b.SetReadDeadline(t) + } + return nil +} + +// Close removes the ReadStream from the session and cleans up any associated state +func (r *ReadStreamSRTCP) Close() error { + r.mu.Lock() + defer r.mu.Unlock() + + if !r.isInited { + return errStreamNotInited + } + + select { + case <-r.isClosed: + return errStreamAlreadyClosed + default: + err := r.buffer.Close() + if err != nil { + return err + } + + r.session.removeReadStream(r.ssrc) + return nil + } +} + +func (r *ReadStreamSRTCP) init(child streamSession, ssrc uint32) error { + sessionSRTCP, ok := child.(*SessionSRTCP) + + r.mu.Lock() + defer r.mu.Unlock() + if !ok { + return errFailedTypeAssertion + } else if r.isInited { + return errStreamAlreadyInited + } + + r.session = sessionSRTCP + r.ssrc = ssrc + r.isInited = true + r.isClosed = make(chan bool) + + if r.session.bufferFactory != nil { + r.buffer = r.session.bufferFactory(packetio.RTCPBufferPacket, ssrc) + } else { + // Create a buffer and limit it to 100KB + buff := packetio.NewBuffer() + buff.SetLimitSize(srtcpBufferSize) + r.buffer = buff + } + + return nil +} + +// GetSSRC returns the SSRC we are demuxing for +func (r *ReadStreamSRTCP) GetSSRC() uint32 { + return r.ssrc +} + +// WriteStreamSRTCP is stream for a single Session that is used to encrypt RTCP +type WriteStreamSRTCP struct { + session *SessionSRTCP +} + +// WriteRTCP encrypts a RTCP header and its payload to the nextConn +func (w *WriteStreamSRTCP) WriteRTCP(header *rtcp.Header, payload []byte) (int, error) { + headerRaw, err := header.Marshal() + if err != nil { + return 0, err + } + + return w.session.write(append(headerRaw, payload...)) +} + +// Write encrypts and writes a full RTCP packets to the nextConn +func (w *WriteStreamSRTCP) Write(b []byte) (int, error) { + return w.session.write(b) +} + +// SetWriteDeadline sets the deadline for the Write operation. +// Setting to zero means no deadline. +func (w *WriteStreamSRTCP) SetWriteDeadline(t time.Time) error { + return w.session.setWriteDeadline(t) +} diff --git a/vendor/github.com/pion/srtp/v2/stream_srtp.go b/vendor/github.com/pion/srtp/v2/stream_srtp.go new file mode 100644 index 0000000..c391adb --- /dev/null +++ b/vendor/github.com/pion/srtp/v2/stream_srtp.go @@ -0,0 +1,154 @@ +package srtp + +import ( + "errors" + "io" + "sync" + "time" + + "github.com/pion/rtp" + "github.com/pion/transport/packetio" +) + +// Limit the buffer size to 1MB +const srtpBufferSize = 1000 * 1000 + +// ReadStreamSRTP handles decryption for a single RTP SSRC +type ReadStreamSRTP struct { + mu sync.Mutex + + isInited bool + isClosed chan bool + + session *SessionSRTP + ssrc uint32 + + buffer io.ReadWriteCloser +} + +// Used by getOrCreateReadStream +func newReadStreamSRTP() readStream { + return &ReadStreamSRTP{} +} + +func (r *ReadStreamSRTP) init(child streamSession, ssrc uint32) error { + sessionSRTP, ok := child.(*SessionSRTP) + + r.mu.Lock() + defer r.mu.Unlock() + + if !ok { + return errFailedTypeAssertion + } else if r.isInited { + return errStreamAlreadyInited + } + + r.session = sessionSRTP + r.ssrc = ssrc + r.isInited = true + r.isClosed = make(chan bool) + + // Create a buffer with a 1MB limit + if r.session.bufferFactory != nil { + r.buffer = r.session.bufferFactory(packetio.RTPBufferPacket, ssrc) + } else { + buff := packetio.NewBuffer() + buff.SetLimitSize(srtpBufferSize) + r.buffer = buff + } + + return nil +} + +func (r *ReadStreamSRTP) write(buf []byte) (n int, err error) { + n, err = r.buffer.Write(buf) + + if errors.Is(err, packetio.ErrFull) { + // Silently drop data when the buffer is full. + return len(buf), nil + } + + return n, err +} + +// Read reads and decrypts full RTP packet from the nextConn +func (r *ReadStreamSRTP) Read(buf []byte) (int, error) { + return r.buffer.Read(buf) +} + +// ReadRTP reads and decrypts full RTP packet and its header from the nextConn +func (r *ReadStreamSRTP) ReadRTP(buf []byte) (int, *rtp.Header, error) { + n, err := r.Read(buf) + if err != nil { + return 0, nil, err + } + + header := &rtp.Header{} + + err = header.Unmarshal(buf[:n]) + if err != nil { + return 0, nil, err + } + + return n, header, nil +} + +// SetReadDeadline sets the deadline for the Read operation. +// Setting to zero means no deadline. +func (r *ReadStreamSRTP) SetReadDeadline(t time.Time) error { + if b, ok := r.buffer.(interface { + SetReadDeadline(time.Time) error + }); ok { + return b.SetReadDeadline(t) + } + return nil +} + +// Close removes the ReadStream from the session and cleans up any associated state +func (r *ReadStreamSRTP) Close() error { + r.mu.Lock() + defer r.mu.Unlock() + + if !r.isInited { + return errStreamNotInited + } + + select { + case <-r.isClosed: + return errStreamAlreadyClosed + default: + err := r.buffer.Close() + if err != nil { + return err + } + + r.session.removeReadStream(r.ssrc) + return nil + } +} + +// GetSSRC returns the SSRC we are demuxing for +func (r *ReadStreamSRTP) GetSSRC() uint32 { + return r.ssrc +} + +// WriteStreamSRTP is stream for a single Session that is used to encrypt RTP +type WriteStreamSRTP struct { + session *SessionSRTP +} + +// WriteRTP encrypts a RTP packet and writes to the connection +func (w *WriteStreamSRTP) WriteRTP(header *rtp.Header, payload []byte) (int, error) { + return w.session.writeRTP(header, payload) +} + +// Write encrypts and writes a full RTP packets to the nextConn +func (w *WriteStreamSRTP) Write(b []byte) (int, error) { + return w.session.write(b) +} + +// SetWriteDeadline sets the deadline for the Write operation. +// Setting to zero means no deadline. +func (w *WriteStreamSRTP) SetWriteDeadline(t time.Time) error { + return w.session.setWriteDeadline(t) +} diff --git a/vendor/github.com/pion/srtp/v2/util.go b/vendor/github.com/pion/srtp/v2/util.go new file mode 100644 index 0000000..1ae34a6 --- /dev/null +++ b/vendor/github.com/pion/srtp/v2/util.go @@ -0,0 +1,33 @@ +package srtp + +import "bytes" + +// Grow the buffer size to the given number of bytes. +func growBufferSize(buf []byte, size int) []byte { + if size <= cap(buf) { + return buf[:size] + } + + buf2 := make([]byte, size) + copy(buf2, buf) + return buf2 +} + +// Check if buffers match, if not allocate a new buffer and return it +func allocateIfMismatch(dst, src []byte) []byte { + if dst == nil { + dst = make([]byte, len(src)) + copy(dst, src) + } else if !bytes.Equal(dst, src) { // bytes.Equal returns on ref equality, no optimization needed + extraNeeded := len(src) - len(dst) + if extraNeeded > 0 { + dst = append(dst, make([]byte, extraNeeded)...) + } else if extraNeeded < 0 { + dst = dst[:len(dst)+extraNeeded] + } + + copy(dst, src) + } + + return dst +} diff --git a/vendor/github.com/pion/stun/.codecov.yml b/vendor/github.com/pion/stun/.codecov.yml new file mode 100644 index 0000000..7fa67fe --- /dev/null +++ b/vendor/github.com/pion/stun/.codecov.yml @@ -0,0 +1,9 @@ +coverage: + status: + patch: off + project: + default: + # basic + target: 98 + threshold: null + base: auto diff --git a/vendor/github.com/pion/stun/.gitignore b/vendor/github.com/pion/stun/.gitignore new file mode 100644 index 0000000..b9420d9 --- /dev/null +++ b/vendor/github.com/pion/stun/.gitignore @@ -0,0 +1,17 @@ +*-fuzz.zip +.idea +benchmark.*.write +*.test +*.out +*.sw[poe] +bench.go-* +PACKAGES +cmd/stun-cli/stun-cli +cmd/stun-decode/stun-decode +cmd/stun-bench/stun-bench +cmd/stun-nat-behaviour/stun-nat-behaviour + +coverage.txt + +e2e/dump.pcap +e2e/log-*.txt diff --git a/vendor/github.com/pion/stun/.golangci.yml b/vendor/github.com/pion/stun/.golangci.yml new file mode 100644 index 0000000..40ca69c --- /dev/null +++ b/vendor/github.com/pion/stun/.golangci.yml @@ -0,0 +1,93 @@ +linters-settings: + govet: + check-shadowing: true + golint: + min-confidence: 0 + gocyclo: + min-complexity: 15 + maligned: + suggest-new: true + dupl: + threshold: 100 + goconst: + min-len: 2 + min-occurrences: 2 + misspell: + locale: US + lll: + line-length: 140 + goimports: + local-prefixes: github.com/pion + gocritic: + enabled-tags: + - performance + - style + - experimental + disabled-checks: + - commentedOutCode + - sloppyReassign + +issues: + exclude: + - "`assertHMACSize` - `blocksize` always receives `64`" + exclude-rules: + - text: "string `<nil>`" + linters: + - goconst + + # Exclude some linters from running on tests files. + - path: _test\.go + linters: + - gocyclo + - errcheck + - dupl + - gosec + - goconst + + # Ease some gocritic warnings on test files. + - path: _test\.go + text: "(unnamedResult|exitAfterDefer|unlambda)" + linters: + - gocritic + + # Exclude known linters from partially hard-vendored code, + # which is impossible to exclude via "nolint" comments. + - path: internal/hmac/ + text: "weak cryptographic primitive" + linters: + - gosec + - path: internal/hmac/ + text: "Write\\` is not checked" + linters: + - errcheck + + # Ease linting on benchmarking code. + - path: cmd/stun-bench/ + linters: + - gosec + - errcheck + - unparam + + - path: ^cmd/ + linters: + - gocyclo + - path: ^cmd/ + text: "(unnamedResult|exitAfterDefer)" + linters: + - gocritic + +linters: + enable-all: true + disable: + - funlen + - gochecknoglobals + - godox + - prealloc + - scopelint + +run: + skip-dirs: + - e2e + - fuzz + - testdata + - api diff --git a/vendor/github.com/pion/stun/.goreleaser.yml b/vendor/github.com/pion/stun/.goreleaser.yml new file mode 100644 index 0000000..d72bde6 --- /dev/null +++ b/vendor/github.com/pion/stun/.goreleaser.yml @@ -0,0 +1,39 @@ +before: + hooks: + - go mod tidy + +archives: +- replacements: + darwin: Darwin + linux: Linux + windows: Windows + 386: i386 + amd64: x86_64 + +checksum: + name_template: 'checksums.txt' + +snapshot: + name_template: "{{ .Tag }}-next" + +changelog: + sort: asc + filters: + exclude: + - '^docs:' + - '^test:' + +builds: + - binary: stun-not-behavior + id: stun-not-behavior + goos: + - darwin + - windows + - linux + - freebsd + goarch: + - amd64 + - 386 + env: + - CGO_ENABLED=0 + main: ./cmd/stun-nat-behavior diff --git a/vendor/github.com/pion/stun/.travis.yml b/vendor/github.com/pion/stun/.travis.yml new file mode 100644 index 0000000..e464e84 --- /dev/null +++ b/vendor/github.com/pion/stun/.travis.yml @@ -0,0 +1,135 @@ +# +# DO NOT EDIT THIS FILE DIRECTLY +# +# It is automatically copied from https://github.com/pion/.goassets repository. +# If this repository should have package specific CI config, +# remove the repository name from .goassets/.github/workflows/assets-sync.yml. +# + +dist: bionic +language: go + + +branches: + only: + - master + +env: + global: + - GO111MODULE=on + - GOLANGCI_LINT_VERSION=1.19.1 + +cache: + directories: + - ${HOME}/.cache/go-build + - ${GOPATH}/pkg/mod + npm: true + yarn: true + +_lint_job: &lint_job + env: CACHE_NAME=lint + before_install: + - if [ -f .github/.ci.conf ]; then . .github/.ci.conf; fi + install: skip + before_script: + - | + curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh \ + | bash -s - -b $GOPATH/bin v${GOLANGCI_LINT_VERSION} + script: + - bash .github/assert-contributors.sh + - bash .github/lint-disallowed-functions-in-library.sh + - bash .github/lint-commit-message.sh + - bash .github/lint-filename.sh + - golangci-lint run ./... +_test_job: &test_job + env: CACHE_NAME=test + before_install: + - if [ -f .github/.ci.conf ]; then . .github/.ci.conf; fi + - go mod download + install: + - go build ./... + script: + - testpkgs=${TEST_PACKAGES:-$(go list ./... | grep -v examples)} + - coverpkgs=$(echo "${testpkgs}" | paste -s -d ',') + - | + go test \ + -coverpkg=${coverpkgs} -coverprofile=cover.out -covermode=atomic \ + ${TEST_EXTRA_ARGS:-} \ + -v -race ${testpkgs} + - if [ -n "${TEST_HOOK}" ]; then ${TEST_HOOK}; fi + after_success: + - travis_retry bash <(curl -s https://codecov.io/bash) -c -F go +_test_i386_job: &test_i386_job + env: CACHE_NAME=test386 + services: docker + before_install: + - if [ -f .github/.ci.conf ]; then . .github/.ci.conf; fi + script: + - testpkgs=${TEST_PACKAGES:-$(go list ./... | grep -v examples)} + - | + docker run \ + -u $(id -u):$(id -g) \ + -e "GO111MODULE=on" \ + -e "CGO_ENABLED=0" \ + -v ${PWD}:/go/src/github.com/pion/$(basename ${PWD}) \ + -v ${HOME}/gopath/pkg/mod:/go/pkg/mod \ + -v ${HOME}/.cache/go-build:/.cache/go-build \ + -w /go/src/github.com/pion/$(basename ${PWD}) \ + -it i386/golang:${GO_VERSION}-alpine \ + /usr/local/go/bin/go test \ + ${TEST_EXTRA_ARGS:-} \ + -v ${testpkgs} +_test_wasm_job: &test_wasm_job + env: CACHE_NAME=wasm + language: node_js + node_js: 12 + before_install: + - if [ -f .github/.ci.conf ]; then . .github/.ci.conf; fi + - if ${SKIP_WASM_TEST:-false}; then exit 0; fi + install: + # Manually download and install Go instead of using gimme. + # It looks like gimme Go causes some errors on go-test for Wasm. + - curl -sSfL https://dl.google.com/go/go${GO_VERSION}.linux-amd64.tar.gz | tar -C ~ -xzf - + - export GOROOT=${HOME}/go + - export PATH=${GOROOT}/bin:${PATH} + - yarn install + - export GO_JS_WASM_EXEC=${GO_JS_WASM_EXEC:-${GOROOT}/misc/wasm/go_js_wasm_exec} + script: + - testpkgs=${TEST_PACKAGES:-$(go list ./... | grep -v examples)} + - coverpkgs=$(echo "${testpkgs}" | paste -s -d ',') + - | + GOOS=js GOARCH=wasm go test \ + -coverpkg=${coverpkgs} -coverprofile=cover.out -covermode=atomic \ + -exec="${GO_JS_WASM_EXEC}" \ + -v ${testpkgs} + after_success: + - travis_retry bash <(curl -s https://codecov.io/bash) -c -F wasm + +jobs: + include: + - <<: *lint_job + name: Lint 1.14 + go: 1.14 + - <<: *test_job + name: Test 1.13 + go: 1.13 + - <<: *test_job + name: Test 1.14 + go: 1.14 + - <<: *test_i386_job + name: Test i386 1.13 + env: GO_VERSION=1.13 + go: 1.14 # version for host environment used to go list + - <<: *test_i386_job + name: Test i386 1.14 + env: GO_VERSION=1.14 + go: 1.14 # version for host environment used to go list + - <<: *test_wasm_job + name: Test WASM 1.13 + env: GO_VERSION=1.13 + - <<: *test_wasm_job + name: Test WASM 1.14 + env: GO_VERSION=1.14 + +notifications: + email: false diff --git a/vendor/github.com/pion/stun/AUTHORS b/vendor/github.com/pion/stun/AUTHORS new file mode 100644 index 0000000..717af8f --- /dev/null +++ b/vendor/github.com/pion/stun/AUTHORS @@ -0,0 +1,10 @@ +Sean DuBois <https://github.com/Sean-Der> +Raphael Randschau <https://github.com/nicolai86> +Aleksandr Razumov <ar@cydev.ru> +Aliaksandr Valialkin <valyala@gmail.com> +Michiel De Backker <https://github.com/backkem> +Y.Horie <https://github.com/u5surf> +songjiayang <https://github.com/songjiayang> +The gortc project +The IETF Trust +The Go Authors diff --git a/vendor/github.com/pion/stun/Dockerfile b/vendor/github.com/pion/stun/Dockerfile new file mode 100644 index 0000000..429239a --- /dev/null +++ b/vendor/github.com/pion/stun/Dockerfile @@ -0,0 +1,5 @@ +FROM golang:1.14 + +COPY . /go/src/github.com/pion/stun + +RUN go test github.com/pion/stun diff --git a/vendor/github.com/pion/stun/LICENSE.md b/vendor/github.com/pion/stun/LICENSE.md new file mode 100644 index 0000000..5cc9cbd --- /dev/null +++ b/vendor/github.com/pion/stun/LICENSE.md @@ -0,0 +1,7 @@ +Copyright 2018 Pion LLC + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/github.com/pion/stun/Makefile b/vendor/github.com/pion/stun/Makefile new file mode 100644 index 0000000..43de8d0 --- /dev/null +++ b/vendor/github.com/pion/stun/Makefile @@ -0,0 +1,61 @@ +VERSION := $(shell git describe --tags | sed -e 's/^v//g' | awk -F "-" '{print $$1}') +ITERATION := $(shell git describe --tags --long | awk -F "-" '{print $$2}') +GO_VERSION=$(shell gobuild -v) +GO := $(or $(GOROOT),/usr/lib/go)/bin/go +PROCS := $(shell nproc) +cores: + @echo "cores: $(PROCS)" +bench: + go test -bench . +bench-record: + $(GO) test -bench . > "benchmarks/stun-go-$(GO_VERSION).txt" +fuzz-prepare-msg: + go-fuzz-build -func FuzzMessage -o stun-msg-fuzz.zip github.com/pion/stun +fuzz-prepare-typ: + go-fuzz-build -func FuzzType -o stun-typ-fuzz.zip github.com/pion/stun +fuzz-prepare-setters: + go-fuzz-build -func FuzzSetters -o stun-setters-fuzz.zip github.com/pion/stun +fuzz-msg: + go-fuzz -bin=./stun-msg-fuzz.zip -workdir=fuzz/stun-msg +fuzz-typ: + go-fuzz -bin=./stun-typ-fuzz.zip -workdir=fuzz/stun-typ +fuzz-setters: + go-fuzz -bin=./stun-setters-fuzz.zip -workdir=fuzz/stun-setters +fuzz-test: + go test -tags gofuzz -run TestFuzz -v . +fuzz-reset-setters: + rm -f -v -r stun-setters-fuzz.zip fuzz/stun-setters +lint: + @golangci-lint run ./... + @echo "ok" +escape: + @echo "Not escapes, except autogenerated:" + @go build -gcflags '-m -l' 2>&1 \ + | grep -v "<autogenerated>" \ + | grep escapes +format: + goimports -w . +bench-compare: + go test -bench . > bench.go-16 + go-tip test -bench . > bench.go-tip + @benchcmp bench.go-16 bench.go-tip +install-fuzz: + go get -u github.com/dvyukov/go-fuzz/go-fuzz-build + go get github.com/dvyukov/go-fuzz/go-fuzz +install: + go get gortc.io/api + go get -u github.com/golangci/golangci-lint/cmd/golangci-lint +docker-build: + docker build -t pion/stun . +test-integration: + @cd e2e && bash ./test.sh +prepush: assert test lint test-integration +check-api: + @cd api && bash ./check.sh +assert: + bash .github/assert-contributors.sh + bash .github/lint-disallowed-functions-in-library.sh + bash .github/lint-commit-message.sh +test: + @./go.test.sh +clean: diff --git a/vendor/github.com/pion/stun/README.md b/vendor/github.com/pion/stun/README.md new file mode 100644 index 0000000..6dd50d0 --- /dev/null +++ b/vendor/github.com/pion/stun/README.md @@ -0,0 +1,184 @@ +<h1 align="center"> + <br> + Pion STUN + <br> +</h1> +<h4 align="center">A Go implementation of STUN</h4> +<p align="center"> + <a href="https://pion.ly"><img src="https://img.shields.io/badge/pion-stun-gray.svg?longCache=true&colorB=brightgreen" alt="Pion stun"></a> + <!--<a href="https://sourcegraph.com/github.com/pion/webrtc?badge"><img src="https://sourcegraph.com/github.com/pion/webrtc/-/badge.svg" alt="Sourcegraph Widget"></a>--> + <a href="https://pion.ly/slack"><img src="https://img.shields.io/badge/join-us%20on%20slack-gray.svg?longCache=true&logo=slack&colorB=brightgreen" alt="Slack Widget"></a> + <br> + <a href="https://travis-ci.org/pion/stun"><img src="https://travis-ci.org/pion/stun.svg?branch=master" alt="Build Status"></a> + <a href="https://pkg.go.dev/github.com/pion/stun"><img src="https://godoc.org/github.com/pion/stun?status.svg" alt="GoDoc"></a> + <a href="https://codecov.io/gh/pion/stun"><img src="https://codecov.io/gh/pion/stun/branch/master/graph/badge.svg" alt="Coverage Status"></a> + <a href="https://goreportcard.com/report/github.com/pion/stun"><img src="https://goreportcard.com/badge/github.com/pion/stun" alt="Go Report Card"></a> + <!--<a href="https://www.codacy.com/app/Sean-Der/webrtc"><img src="https://api.codacy.com/project/badge/Grade/18f4aec384894e6aac0b94effe51961d" alt="Codacy Badge"></a>--> + <a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="License: MIT"></a> +</p> +<br> + +### Roadmap +The library is used as a part of our WebRTC implementation. Please refer to that [roadmap](https://github.com/pion/webrtc/issues/9) to track our major milestones. + +### Community +Pion has an active community on the [Golang Slack](https://invite.slack.golangbridge.org/). Sign up and join the **#pion** channel for discussions and support. You can also use [Pion mailing list](https://groups.google.com/forum/#!forum/pion). + +We are always looking to support **your projects**. Please reach out if you have something to build! + +If you need commercial support or don't want to use public methods you can contact us at [team@pion.ly](mailto:team@pion.ly) + +### Contributing +Check out the **[contributing wiki](https://github.com/pion/webrtc/wiki/Contributing)** to join the group of amazing people making this project possible: + +* [Sean DuBois](https://github.com/Sean-Der) - *Original Author* +* [Raphael Randschau](https://github.com/nicolai86) - *STUN client* +* [Michiel De Backker](https://github.com/backkem) - *Minor fixes* +* [Y.Horie](https://github.com/u5surf) - *Fix lint issues* +* [Aleksandr Razumov](https://github.com/ernado) - *The v0.3 version* +* [songjiayang](https://github.com/songjiayang) +* [Adam Kiss](https://github.com/masterada) +* [Moises Marangoni](https://github.com/Moisesbr) +* [Yutaka Takeda](https://github.com/enobufs) +* [Hugo Arregui](https://github.com/hugoArregui) +* [Maanas Royy](https://github.com/maanas) +* [Atsushi Watanabe](https://github.com/at-wat) +* [Cecylia Bocovich](https://github.com/cohosh) +* [Christian Muehlhaeuser](https://github.com/muesli) + +# STUN +Package stun implements Session Traversal Utilities for NAT (STUN) [[RFC5389](https://tools.ietf.org/html/rfc5389)] +protocol and [client](https://pkg.go.dev/github.com/pion/stun#Client) with no external dependencies and zero allocations in hot paths. +Client [supports](https://pkg.go.dev/github.com/pion/stun#WithRTO) automatic request retransmissions. + +# Example +You can get your current IP address from any STUN server by sending +binding request. See more idiomatic example at `cmd/stun-client`. +```go +package main + +import ( + "fmt" + + "github.com/pion/stun" +) + +func main() { + // Creating a "connection" to STUN server. + c, err := stun.Dial("udp", "stun.l.google.com:19302") + if err != nil { + panic(err) + } + // Building binding request with random transaction id. + message := stun.MustBuild(stun.TransactionID, stun.BindingRequest) + // Sending request to STUN server, waiting for response message. + if err := c.Do(message, func(res stun.Event) { + if res.Error != nil { + panic(res.Error) + } + // Decoding XOR-MAPPED-ADDRESS attribute from message. + var xorAddr stun.XORMappedAddress + if err := xorAddr.GetFrom(res.Message); err != nil { + panic(err) + } + fmt.Println("your IP is", xorAddr.IP) + }); err != nil { + panic(err) + } +} +``` + +## Supported RFCs +- [x] [RFC 5389](https://tools.ietf.org/html/rfc5389) — Session Traversal Utilities for NAT +- [x] [RFC 5769](https://tools.ietf.org/html/rfc5769) — Test Vectors for STUN +- [x] [RFC 6062](https://tools.ietf.org/html/rfc6062) — TURN extensions for TCP allocations +- [x] [RFC 7064](https://tools.ietf.org/html/rfc7064) — STUN URI +- [x] (TLS-over-)TCP client support +- [ ] [ALTERNATE-SERVER](https://tools.ietf.org/html/rfc5389#section-11) support [#48](https://github.com/pion/stun/issues/48) +- [ ] [RFC 5780](https://tools.ietf.org/html/rfc5780) — NAT Behavior Discovery Using STUN [#49](https://github.com/pion/stun/issues/49) + +# Stability +Package is currently stable, no backward incompatible changes are expected +with exception of critical bugs or security fixes. + +Additional attributes are unlikely to be implemented in scope of stun package, +the only exception is constants for attribute or message types. + +# RFC 3489 notes +RFC 5389 obsoletes RFC 3489, so implementation was ignored by purpose, however, +RFC 3489 can be easily implemented as separate package. + +# Requirements +Go 1.12 is currently supported and tested in CI. + +# Testing +Client behavior is tested and verified in many ways: + * End-To-End with long-term credentials + * **coturn**: The coturn [server](https://github.com/coturn/coturn/wiki/turnserver) (linux) + * Bunch of code static checkers (linters) + * Standard unit-tests with coverage reporting (linux {amd64, **arm**64}, windows and darwin) + * Explicit API backward compatibility [check](https://github.com/gortc/api), see `api` directory + +See [TeamCity project](https://tc.gortc.io/project.html?projectId=stun&guest=1) and `e2e` directory +for more information. Also the Wireshark `.pcap` files are available for e2e test in +artifacts for build. + +# Benchmarks + +Intel(R) Core(TM) i7-8700K: + +``` +version: 1.16.5 +goos: linux +goarch: amd64 +pkg: github.com/pion/stun +PASS +benchmark iter time/iter throughput bytes alloc allocs +--------- ---- --------- ---------- ----------- ------ +BenchmarkMappedAddress_AddTo-12 30000000 36.40 ns/op 0 B/op 0 allocs/op +BenchmarkAlternateServer_AddTo-12 50000000 36.70 ns/op 0 B/op 0 allocs/op +BenchmarkAgent_GC-12 500000 2552.00 ns/op 0 B/op 0 allocs/op +BenchmarkAgent_Process-12 50000000 38.00 ns/op 0 B/op 0 allocs/op +BenchmarkMessage_GetNotFound-12 200000000 6.90 ns/op 0 B/op 0 allocs/op +BenchmarkMessage_Get-12 200000000 7.61 ns/op 0 B/op 0 allocs/op +BenchmarkClient_Do-12 2000000 1072.00 ns/op 0 B/op 0 allocs/op +BenchmarkErrorCode_AddTo-12 20000000 67.00 ns/op 0 B/op 0 allocs/op +BenchmarkErrorCodeAttribute_AddTo-12 30000000 52.20 ns/op 0 B/op 0 allocs/op +BenchmarkErrorCodeAttribute_GetFrom-12 100000000 12.00 ns/op 0 B/op 0 allocs/op +BenchmarkFingerprint_AddTo-12 20000000 102.00 ns/op 430.08 MB/s 0 B/op 0 allocs/op +BenchmarkFingerprint_Check-12 30000000 54.80 ns/op 948.38 MB/s 0 B/op 0 allocs/op +BenchmarkBuildOverhead/Build-12 5000000 333.00 ns/op 0 B/op 0 allocs/op +BenchmarkBuildOverhead/BuildNonPointer-12 3000000 536.00 ns/op 100 B/op 4 allocs/op +BenchmarkBuildOverhead/Raw-12 10000000 181.00 ns/op 0 B/op 0 allocs/op +BenchmarkMessageIntegrity_AddTo-12 1000000 1053.00 ns/op 18.98 MB/s 0 B/op 0 allocs/op +BenchmarkMessageIntegrity_Check-12 1000000 1135.00 ns/op 28.17 MB/s 0 B/op 0 allocs/op +BenchmarkMessage_Write-12 100000000 27.70 ns/op 1011.09 MB/s 0 B/op 0 allocs/op +BenchmarkMessageType_Value-12 2000000000 0.49 ns/op 0 B/op 0 allocs/op +BenchmarkMessage_WriteTo-12 100000000 12.80 ns/op 0 B/op 0 allocs/op +BenchmarkMessage_ReadFrom-12 50000000 25.00 ns/op 801.19 MB/s 0 B/op 0 allocs/op +BenchmarkMessage_ReadBytes-12 100000000 18.00 ns/op 1113.03 MB/s 0 B/op 0 allocs/op +BenchmarkIsMessage-12 2000000000 1.08 ns/op 18535.57 MB/s 0 B/op 0 allocs/op +BenchmarkMessage_NewTransactionID-12 2000000 673.00 ns/op 0 B/op 0 allocs/op +BenchmarkMessageFull-12 5000000 316.00 ns/op 0 B/op 0 allocs/op +BenchmarkMessageFullHardcore-12 20000000 88.90 ns/op 0 B/op 0 allocs/op +BenchmarkMessage_WriteHeader-12 200000000 8.18 ns/op 0 B/op 0 allocs/op +BenchmarkMessage_CloneTo-12 30000000 37.90 ns/op 1795.32 MB/s 0 B/op 0 allocs/op +BenchmarkMessage_AddTo-12 300000000 4.77 ns/op 0 B/op 0 allocs/op +BenchmarkDecode-12 100000000 22.00 ns/op 0 B/op 0 allocs/op +BenchmarkUsername_AddTo-12 50000000 23.20 ns/op 0 B/op 0 allocs/op +BenchmarkUsername_GetFrom-12 100000000 17.90 ns/op 0 B/op 0 allocs/op +BenchmarkNonce_AddTo-12 50000000 34.40 ns/op 0 B/op 0 allocs/op +BenchmarkNonce_AddTo_BadLength-12 200000000 8.29 ns/op 0 B/op 0 allocs/op +BenchmarkNonce_GetFrom-12 100000000 17.50 ns/op 0 B/op 0 allocs/op +BenchmarkUnknownAttributes/AddTo-12 30000000 48.10 ns/op 0 B/op 0 allocs/op +BenchmarkUnknownAttributes/GetFrom-12 100000000 20.90 ns/op 0 B/op 0 allocs/op +BenchmarkXOR-12 50000000 25.80 ns/op 39652.86 MB/s 0 B/op 0 allocs/op +BenchmarkXORSafe-12 3000000 515.00 ns/op 1988.04 MB/s 0 B/op 0 allocs/op +BenchmarkXORFast-12 20000000 73.40 ns/op 13959.30 MB/s 0 B/op 0 allocs/op +BenchmarkXORMappedAddress_AddTo-12 20000000 56.70 ns/op 0 B/op 0 allocs/op +BenchmarkXORMappedAddress_GetFrom-12 50000000 37.40 ns/op 0 B/op 0 allocs/op +ok github.com/pion/stun 76.868s +``` + +### License +MIT License - see [LICENSE](LICENSE) for full text diff --git a/vendor/github.com/pion/stun/addr.go b/vendor/github.com/pion/stun/addr.go new file mode 100644 index 0000000..c4d9653 --- /dev/null +++ b/vendor/github.com/pion/stun/addr.go @@ -0,0 +1,134 @@ +package stun + +import ( + "fmt" + "io" + "net" + "strconv" +) + +// MappedAddress represents MAPPED-ADDRESS attribute. +// +// This attribute is used only by servers for achieving backwards +// compatibility with RFC 3489 clients. +// +// RFC 5389 Section 15.1 +type MappedAddress struct { + IP net.IP + Port int +} + +// AlternateServer represents ALTERNATE-SERVER attribute. +// +// RFC 5389 Section 15.11 +type AlternateServer struct { + IP net.IP + Port int +} + +// OtherAddress represents OTHER-ADDRESS attribute. +// +// RFC 5780 Section 7.4 +type OtherAddress struct { + IP net.IP + Port int +} + +// AddTo adds ALTERNATE-SERVER attribute to message. +func (s *AlternateServer) AddTo(m *Message) error { + a := (*MappedAddress)(s) + return a.addAs(m, AttrAlternateServer) +} + +// GetFrom decodes ALTERNATE-SERVER from message. +func (s *AlternateServer) GetFrom(m *Message) error { + a := (*MappedAddress)(s) + return a.getAs(m, AttrAlternateServer) +} + +func (a MappedAddress) String() string { + return net.JoinHostPort(a.IP.String(), strconv.Itoa(a.Port)) +} + +func (a *MappedAddress) getAs(m *Message, t AttrType) error { + v, err := m.Get(t) + if err != nil { + return err + } + if len(v) <= 4 { + return io.ErrUnexpectedEOF + } + family := bin.Uint16(v[0:2]) + if family != familyIPv6 && family != familyIPv4 { + return newDecodeErr("xor-mapped address", "family", + fmt.Sprintf("bad value %d", family), + ) + } + ipLen := net.IPv4len + if family == familyIPv6 { + ipLen = net.IPv6len + } + // Ensuring len(a.IP) == ipLen and reusing a.IP. + if len(a.IP) < ipLen { + a.IP = a.IP[:cap(a.IP)] + for len(a.IP) < ipLen { + a.IP = append(a.IP, 0) + } + } + a.IP = a.IP[:ipLen] + for i := range a.IP { + a.IP[i] = 0 + } + a.Port = int(bin.Uint16(v[2:4])) + copy(a.IP, v[4:]) + return nil +} + +func (a *MappedAddress) addAs(m *Message, t AttrType) error { + var ( + family = familyIPv4 + ip = a.IP + ) + if len(a.IP) == net.IPv6len { + if isIPv4(ip) { + ip = ip[12:16] // like in ip.To4() + } else { + family = familyIPv6 + } + } else if len(ip) != net.IPv4len { + return ErrBadIPLength + } + value := make([]byte, 128) + value[0] = 0 // first 8 bits are zeroes + bin.PutUint16(value[0:2], family) + bin.PutUint16(value[2:4], uint16(a.Port)) + copy(value[4:], ip) + m.Add(t, value[:4+len(ip)]) + return nil +} + +// AddTo adds MAPPED-ADDRESS to message. +func (a *MappedAddress) AddTo(m *Message) error { + return a.addAs(m, AttrMappedAddress) +} + +// GetFrom decodes MAPPED-ADDRESS from message. +func (a *MappedAddress) GetFrom(m *Message) error { + return a.getAs(m, AttrMappedAddress) +} + +// AddTo adds OTHER-ADDRESS attribute to message. +func (o *OtherAddress) AddTo(m *Message) error { + a := (*MappedAddress)(o) + return a.addAs(m, AttrOtherAddress) +} + +// GetFrom decodes OTHER-ADDRESS from message. +func (o *OtherAddress) GetFrom(m *Message) error { + a := (*MappedAddress)(o) + return a.getAs(m, AttrOtherAddress) +} + +func (o OtherAddress) String() string { + return net.JoinHostPort(o.IP.String(), strconv.Itoa(o.Port)) +} diff --git a/vendor/github.com/pion/stun/agent.go b/vendor/github.com/pion/stun/agent.go new file mode 100644 index 0000000..6a8a473 --- /dev/null +++ b/vendor/github.com/pion/stun/agent.go @@ -0,0 +1,228 @@ +package stun + +import ( + "errors" + "sync" + "time" +) + +// NoopHandler just discards any event. +var NoopHandler Handler = func(e Event) {} + +// NewAgent initializes and returns new Agent with provided handler. +// If h is nil, the NoopHandler will be used. +func NewAgent(h Handler) *Agent { + if h == nil { + h = NoopHandler + } + a := &Agent{ + transactions: make(map[transactionID]agentTransaction), + handler: h, + } + return a +} + +// Agent is low-level abstraction over transaction list that +// handles concurrency (all calls are goroutine-safe) and +// time outs (via Collect call). +type Agent struct { + // transactions is map of transactions that are currently + // in progress. Event handling is done in such way when + // transaction is unregistered before agentTransaction access, + // minimizing mux lock and protecting agentTransaction from + // data races via unexpected concurrent access. + transactions map[transactionID]agentTransaction + closed bool // all calls are invalid if true + mux sync.Mutex // protects transactions and closed + handler Handler // handles transactions +} + +// Handler handles state changes of transaction. +// +// Handler is called on transaction state change. +// Usage of e is valid only during call, user must +// copy needed fields explicitly. +type Handler func(e Event) + +// Event is passed to Handler describing the transaction event. +// Do not reuse outside Handler. +type Event struct { + TransactionID [TransactionIDSize]byte + Message *Message + Error error +} + +// agentTransaction represents transaction in progress. +// Concurrent access is invalid. +type agentTransaction struct { + id transactionID + deadline time.Time +} + +var ( + // ErrTransactionStopped indicates that transaction was manually stopped. + ErrTransactionStopped = errors.New("transaction is stopped") + // ErrTransactionNotExists indicates that agent failed to find transaction. + ErrTransactionNotExists = errors.New("transaction not exists") + // ErrTransactionExists indicates that transaction with same id is already + // registered. + ErrTransactionExists = errors.New("transaction exists with same id") +) + +// StopWithError removes transaction from list and calls handler with +// provided error. Can return ErrTransactionNotExists and ErrAgentClosed. +func (a *Agent) StopWithError(id [TransactionIDSize]byte, err error) error { + a.mux.Lock() + if a.closed { + a.mux.Unlock() + return ErrAgentClosed + } + t, exists := a.transactions[id] + delete(a.transactions, id) + h := a.handler + a.mux.Unlock() + if !exists { + return ErrTransactionNotExists + } + h(Event{ + TransactionID: t.id, + Error: err, + }) + return nil +} + +// Stop stops transaction by id with ErrTransactionStopped, blocking +// until handler returns. +func (a *Agent) Stop(id [TransactionIDSize]byte) error { + return a.StopWithError(id, ErrTransactionStopped) +} + +// ErrAgentClosed indicates that agent is in closed state and is unable +// to handle transactions. +var ErrAgentClosed = errors.New("agent is closed") + +// Start registers transaction with provided id and deadline. +// Could return ErrAgentClosed, ErrTransactionExists. +// +// Agent handler is guaranteed to be eventually called. +func (a *Agent) Start(id [TransactionIDSize]byte, deadline time.Time) error { + a.mux.Lock() + defer a.mux.Unlock() + if a.closed { + return ErrAgentClosed + } + _, exists := a.transactions[id] + if exists { + return ErrTransactionExists + } + a.transactions[id] = agentTransaction{ + id: id, + deadline: deadline, + } + return nil +} + +// agentCollectCap is initial capacity for Agent.Collect slices, +// sufficient to make function zero-alloc in most cases. +const agentCollectCap = 100 + +// ErrTransactionTimeOut indicates that transaction has reached deadline. +var ErrTransactionTimeOut = errors.New("transaction is timed out") + +// Collect terminates all transactions that have deadline before provided +// time, blocking until all handlers will process ErrTransactionTimeOut. +// Will return ErrAgentClosed if agent is already closed. +// +// It is safe to call Collect concurrently but makes no sense. +func (a *Agent) Collect(gcTime time.Time) error { + toRemove := make([]transactionID, 0, agentCollectCap) + a.mux.Lock() + if a.closed { + // Doing nothing if agent is closed. + // All transactions should be already closed + // during Close() call. + a.mux.Unlock() + return ErrAgentClosed + } + // Adding all transactions with deadline before gcTime + // to toCall and toRemove slices. + // No allocs if there are less than agentCollectCap + // timed out transactions. + for id, t := range a.transactions { + if t.deadline.Before(gcTime) { + toRemove = append(toRemove, id) + } + } + // Un-registering timed out transactions. + for _, id := range toRemove { + delete(a.transactions, id) + } + // Calling handler does not require locked mutex, + // reducing lock time. + h := a.handler + a.mux.Unlock() + // Sending ErrTransactionTimeOut to handler for all transactions, + // blocking until last one. + event := Event{ + Error: ErrTransactionTimeOut, + } + for _, id := range toRemove { + event.TransactionID = id + h(event) + } + return nil +} + +// Process incoming message, synchronously passing it to handler. +func (a *Agent) Process(m *Message) error { + e := Event{ + TransactionID: m.TransactionID, + Message: m, + } + a.mux.Lock() + if a.closed { + a.mux.Unlock() + return ErrAgentClosed + } + h := a.handler + delete(a.transactions, m.TransactionID) + a.mux.Unlock() + h(e) + return nil +} + +// SetHandler sets agent handler to h. +func (a *Agent) SetHandler(h Handler) error { + a.mux.Lock() + if a.closed { + a.mux.Unlock() + return ErrAgentClosed + } + a.handler = h + a.mux.Unlock() + return nil +} + +// Close terminates all transactions with ErrAgentClosed and renders Agent to +// closed state. +func (a *Agent) Close() error { + e := Event{ + Error: ErrAgentClosed, + } + a.mux.Lock() + if a.closed { + a.mux.Unlock() + return ErrAgentClosed + } + for _, t := range a.transactions { + e.TransactionID = t.id + a.handler(e) + } + a.transactions = nil + a.closed = true + a.handler = nil + a.mux.Unlock() + return nil +} + +type transactionID [TransactionIDSize]byte diff --git a/vendor/github.com/pion/stun/appveyor.yml b/vendor/github.com/pion/stun/appveyor.yml new file mode 100644 index 0000000..664099d --- /dev/null +++ b/vendor/github.com/pion/stun/appveyor.yml @@ -0,0 +1,22 @@ +version: "{build}" + +platform: x64 + +branches: + only: + - master + +skip_tags: true + +clone_folder: c:\gopath\src\github.com\pion\stun + +environment: + GOPATH: c:\gopath + GOVERSION: 1.12 + +install: + - go version + - go get -v -t . + +build_script: + - go test -v . diff --git a/vendor/github.com/pion/stun/attributes.go b/vendor/github.com/pion/stun/attributes.go new file mode 100644 index 0000000..7238234 --- /dev/null +++ b/vendor/github.com/pion/stun/attributes.go @@ -0,0 +1,226 @@ +package stun + +import ( + "errors" + "fmt" +) + +// Attributes is list of message attributes. +type Attributes []RawAttribute + +// Get returns first attribute from list by the type. +// If attribute is present the RawAttribute is returned and the +// boolean is true. Otherwise the returned RawAttribute will be +// empty and boolean will be false. +func (a Attributes) Get(t AttrType) (RawAttribute, bool) { + for _, candidate := range a { + if candidate.Type == t { + return candidate, true + } + } + return RawAttribute{}, false +} + +// AttrType is attribute type. +type AttrType uint16 + +// Required returns true if type is from comprehension-required range (0x0000-0x7FFF). +func (t AttrType) Required() bool { + return t <= 0x7FFF +} + +// Optional returns true if type is from comprehension-optional range (0x8000-0xFFFF). +func (t AttrType) Optional() bool { + return t >= 0x8000 +} + +// Attributes from comprehension-required range (0x0000-0x7FFF). +const ( + AttrMappedAddress AttrType = 0x0001 // MAPPED-ADDRESS + AttrUsername AttrType = 0x0006 // USERNAME + AttrMessageIntegrity AttrType = 0x0008 // MESSAGE-INTEGRITY + AttrErrorCode AttrType = 0x0009 // ERROR-CODE + AttrUnknownAttributes AttrType = 0x000A // UNKNOWN-ATTRIBUTES + AttrRealm AttrType = 0x0014 // REALM + AttrNonce AttrType = 0x0015 // NONCE + AttrXORMappedAddress AttrType = 0x0020 // XOR-MAPPED-ADDRESS +) + +// Attributes from comprehension-optional range (0x8000-0xFFFF). +const ( + AttrSoftware AttrType = 0x8022 // SOFTWARE + AttrAlternateServer AttrType = 0x8023 // ALTERNATE-SERVER + AttrFingerprint AttrType = 0x8028 // FINGERPRINT +) + +// Attributes from RFC 5245 ICE. +const ( + AttrPriority AttrType = 0x0024 // PRIORITY + AttrUseCandidate AttrType = 0x0025 // USE-CANDIDATE + AttrICEControlled AttrType = 0x8029 // ICE-CONTROLLED + AttrICEControlling AttrType = 0x802A // ICE-CONTROLLING +) + +// Attributes from RFC 5766 TURN. +const ( + AttrChannelNumber AttrType = 0x000C // CHANNEL-NUMBER + AttrLifetime AttrType = 0x000D // LIFETIME + AttrXORPeerAddress AttrType = 0x0012 // XOR-PEER-ADDRESS + AttrData AttrType = 0x0013 // DATA + AttrXORRelayedAddress AttrType = 0x0016 // XOR-RELAYED-ADDRESS + AttrEvenPort AttrType = 0x0018 // EVEN-PORT + AttrRequestedTransport AttrType = 0x0019 // REQUESTED-TRANSPORT + AttrDontFragment AttrType = 0x001A // DONT-FRAGMENT + AttrReservationToken AttrType = 0x0022 // RESERVATION-TOKEN +) + +// Attributes from RFC 5780 NAT Behavior Discovery +const ( + AttrOtherAddress AttrType = 0x802C // OTHER-ADDRESS + AttrChangeRequest AttrType = 0x0003 // CHANGE-REQUEST +) + +// Attributes from RFC 6062 TURN Extensions for TCP Allocations. +const ( + AttrConnectionID AttrType = 0x002a // CONNECTION-ID +) + +// Attributes from RFC 6156 TURN IPv6. +const ( + AttrRequestedAddressFamily AttrType = 0x0017 // REQUESTED-ADDRESS-FAMILY +) + +// Attributes from An Origin Attribute for the STUN Protocol. +const ( + AttrOrigin AttrType = 0x802F +) + +// Value returns uint16 representation of attribute type. +func (t AttrType) Value() uint16 { + return uint16(t) +} + +var attrNames = map[AttrType]string{ + AttrMappedAddress: "MAPPED-ADDRESS", + AttrUsername: "USERNAME", + AttrErrorCode: "ERROR-CODE", + AttrMessageIntegrity: "MESSAGE-INTEGRITY", + AttrUnknownAttributes: "UNKNOWN-ATTRIBUTES", + AttrRealm: "REALM", + AttrNonce: "NONCE", + AttrXORMappedAddress: "XOR-MAPPED-ADDRESS", + AttrSoftware: "SOFTWARE", + AttrAlternateServer: "ALTERNATE-SERVER", + AttrOtherAddress: "OTHER-ADDRESS", + AttrChangeRequest: "CHANGE-REQUEST", + AttrFingerprint: "FINGERPRINT", + AttrPriority: "PRIORITY", + AttrUseCandidate: "USE-CANDIDATE", + AttrICEControlled: "ICE-CONTROLLED", + AttrICEControlling: "ICE-CONTROLLING", + AttrChannelNumber: "CHANNEL-NUMBER", + AttrLifetime: "LIFETIME", + AttrXORPeerAddress: "XOR-PEER-ADDRESS", + AttrData: "DATA", + AttrXORRelayedAddress: "XOR-RELAYED-ADDRESS", + AttrEvenPort: "EVEN-PORT", + AttrRequestedTransport: "REQUESTED-TRANSPORT", + AttrDontFragment: "DONT-FRAGMENT", + AttrReservationToken: "RESERVATION-TOKEN", + AttrConnectionID: "CONNECTION-ID", + AttrRequestedAddressFamily: "REQUESTED-ADDRESS-FAMILY", + AttrOrigin: "ORIGIN", +} + +func (t AttrType) String() string { + s, ok := attrNames[t] + if !ok { + // Just return hex representation of unknown attribute type. + return fmt.Sprintf("0x%x", uint16(t)) + } + return s +} + +// RawAttribute is a Type-Length-Value (TLV) object that +// can be added to a STUN message. Attributes are divided into two +// types: comprehension-required and comprehension-optional. STUN +// agents can safely ignore comprehension-optional attributes they +// don't understand, but cannot successfully process a message if it +// contains comprehension-required attributes that are not +// understood. +type RawAttribute struct { + Type AttrType + Length uint16 // ignored while encoding + Value []byte +} + +// AddTo implements Setter, adding attribute as a.Type with a.Value and ignoring +// the Length field. +func (a RawAttribute) AddTo(m *Message) error { + m.Add(a.Type, a.Value) + return nil +} + +// Equal returns true if a == b. +func (a RawAttribute) Equal(b RawAttribute) bool { + if a.Type != b.Type { + return false + } + if a.Length != b.Length { + return false + } + if len(b.Value) != len(a.Value) { + return false + } + for i, v := range a.Value { + if b.Value[i] != v { + return false + } + } + return true +} + +func (a RawAttribute) String() string { + return fmt.Sprintf("%s: 0x%x", a.Type, a.Value) +} + +// ErrAttributeNotFound means that attribute with provided attribute +// type does not exist in message. +var ErrAttributeNotFound = errors.New("attribute not found") + +// Get returns byte slice that represents attribute value, +// if there is no attribute with such type, +// ErrAttributeNotFound is returned. +func (m *Message) Get(t AttrType) ([]byte, error) { + v, ok := m.Attributes.Get(t) + if !ok { + return nil, ErrAttributeNotFound + } + return v.Value, nil +} + +// STUN aligns attributes on 32-bit boundaries, attributes whose content +// is not a multiple of 4 bytes are padded with 1, 2, or 3 bytes of +// padding so that its value contains a multiple of 4 bytes. The +// padding bits are ignored, and may be any value. +// +// https://tools.ietf.org/html/rfc5389#section-15 +const padding = 4 + +func nearestPaddedValueLength(l int) int { + n := padding * (l / padding) + if n < l { + n += padding + } + return n +} + +// This method converts uint16 vlue to AttrType. If it finds an old attribute +// type value, it also translates it to the new value to enable backward +// compatibility. (See: https://github.com/pion/stun/issues/21) +func compatAttrType(val uint16) AttrType { + if val == 0x8020 { + return AttrXORMappedAddress // new: 0x0020 + } + return AttrType(val) +} diff --git a/vendor/github.com/pion/stun/attributes_debug.go b/vendor/github.com/pion/stun/attributes_debug.go new file mode 100644 index 0000000..7bf09af --- /dev/null +++ b/vendor/github.com/pion/stun/attributes_debug.go @@ -0,0 +1,33 @@ +// +build debug + +package stun + +import "fmt" + +// AttrOverflowErr occurs when len(v) > Max. +type AttrOverflowErr struct { + Type AttrType + Max int + Got int +} + +func (e AttrOverflowErr) Error() string { + return fmt.Sprintf("incorrect length of %s attribute: %d exceeds maximum %d", + e.Type, e.Got, e.Max, + ) +} + +// AttrLengthErr means that length for attribute is invalid. +type AttrLengthErr struct { + Attr AttrType + Got int + Expected int +} + +func (e AttrLengthErr) Error() string { + return fmt.Sprintf("incorrect length of %s attribute: got %d, expected %d", + e.Attr, + e.Got, + e.Expected, + ) +} diff --git a/vendor/github.com/pion/stun/checks.go b/vendor/github.com/pion/stun/checks.go new file mode 100644 index 0000000..a760997 --- /dev/null +++ b/vendor/github.com/pion/stun/checks.go @@ -0,0 +1,45 @@ +// +build !debug + +package stun + +import "github.com/pion/stun/internal/hmac" + +// CheckSize returns ErrAttrSizeInvalid if got is not equal to expected. +func CheckSize(_ AttrType, got, expected int) error { + if got == expected { + return nil + } + return ErrAttributeSizeInvalid +} + +func checkHMAC(got, expected []byte) error { + if hmac.Equal(got, expected) { + return nil + } + return ErrIntegrityMismatch +} + +func checkFingerprint(got, expected uint32) error { + if got == expected { + return nil + } + return ErrFingerprintMismatch +} + +// IsAttrSizeInvalid returns true if error means that attribute size is invalid. +func IsAttrSizeInvalid(err error) bool { + return err == ErrAttributeSizeInvalid +} + +// CheckOverflow returns ErrAttributeSizeOverflow if got is bigger that max. +func CheckOverflow(_ AttrType, got, max int) error { + if got <= max { + return nil + } + return ErrAttributeSizeOverflow +} + +// IsAttrSizeOverflow returns true if error means that attribute size is too big. +func IsAttrSizeOverflow(err error) bool { + return err == ErrAttributeSizeOverflow +} diff --git a/vendor/github.com/pion/stun/checks_debug.go b/vendor/github.com/pion/stun/checks_debug.go new file mode 100644 index 0000000..955f555 --- /dev/null +++ b/vendor/github.com/pion/stun/checks_debug.go @@ -0,0 +1,61 @@ +// +build debug + +package stun + +import "github.com/pion/stun/internal/hmac" + +// CheckSize returns *AttrLengthError if got is not equal to expected. +func CheckSize(a AttrType, got, expected int) error { + if got == expected { + return nil + } + return &AttrLengthErr{ + Got: got, + Expected: expected, + Attr: a, + } +} + +func checkHMAC(got, expected []byte) error { + if hmac.Equal(got, expected) { + return nil + } + return &IntegrityErr{ + Expected: expected, + Actual: got, + } +} + +func checkFingerprint(got, expected uint32) error { + if got == expected { + return nil + } + return &CRCMismatch{ + Actual: got, + Expected: expected, + } +} + +// IsAttrSizeInvalid returns true if error means that attribute size is invalid. +func IsAttrSizeInvalid(err error) bool { + _, ok := err.(*AttrLengthErr) + return ok +} + +// CheckOverflow returns *AttrOverflowErr if got is bigger that max. +func CheckOverflow(t AttrType, got, max int) error { + if got <= max { + return nil + } + return &AttrOverflowErr{ + Type: t, + Got: got, + Max: max, + } +} + +// IsAttrSizeOverflow returns true if error means that attribute size is too big. +func IsAttrSizeOverflow(err error) bool { + _, ok := err.(*AttrOverflowErr) + return ok +} diff --git a/vendor/github.com/pion/stun/client.go b/vendor/github.com/pion/stun/client.go new file mode 100644 index 0000000..62a0b6e --- /dev/null +++ b/vendor/github.com/pion/stun/client.go @@ -0,0 +1,631 @@ +package stun + +import ( + "errors" + "fmt" + "io" + "log" + "net" + "runtime" + "sync" + "sync/atomic" + "time" +) + +// Dial connects to the address on the named network and then +// initializes Client on that connection, returning error if any. +func Dial(network, address string) (*Client, error) { + conn, err := net.Dial(network, address) + if err != nil { + return nil, err + } + return NewClient(conn) +} + +// ErrNoConnection means that ClientOptions.Connection is nil. +var ErrNoConnection = errors.New("no connection provided") + +// ClientOption sets some client option. +type ClientOption func(c *Client) + +// WithHandler sets client handler which is called if Agent emits the Event +// with TransactionID that is not currently registered by Client. +// Useful for handling Data indications from TURN server. +func WithHandler(h Handler) ClientOption { + return func(c *Client) { + c.handler = h + } +} + +// WithRTO sets client RTO as defined in STUN RFC. +func WithRTO(rto time.Duration) ClientOption { + return func(c *Client) { + c.rto = int64(rto) + } +} + +// WithClock sets Clock of client, the source of current time. +// Also clock is passed to default collector if set. +func WithClock(clock Clock) ClientOption { + return func(c *Client) { + c.clock = clock + } +} + +// WithTimeoutRate sets RTO timer minimum resolution. +func WithTimeoutRate(d time.Duration) ClientOption { + return func(c *Client) { + c.rtoRate = d + } +} + +// WithAgent sets client STUN agent. +// +// Defaults to agent implementation in current package, +// see agent.go. +func WithAgent(a ClientAgent) ClientOption { + return func(c *Client) { + c.a = a + } +} + +// WithCollector rests client timeout collector, the implementation +// of ticker which calls function on each tick. +func WithCollector(coll Collector) ClientOption { + return func(c *Client) { + c.collector = coll + } +} + +// WithNoConnClose prevents client from closing underlying connection when +// the Close() method is called. +var WithNoConnClose ClientOption = func(c *Client) { + c.closeConn = false +} + +// WithNoRetransmit disables retransmissions and sets RTO to +// defaultMaxAttempts * defaultRTO which will be effectively time out +// if not set. +// +// Useful for TCP connections where transport handles RTO. +func WithNoRetransmit(c *Client) { + c.maxAttempts = 0 + if c.rto == 0 { + c.rto = defaultMaxAttempts * int64(defaultRTO) + } +} + +const ( + defaultTimeoutRate = time.Millisecond * 5 + defaultRTO = time.Millisecond * 300 + defaultMaxAttempts = 7 +) + +// NewClient initializes new Client from provided options, +// starting internal goroutines and using default options fields +// if necessary. Call Close method after using Client to close conn and +// release resources. +// +// The conn will be closed on Close call. Use WithNoConnClose option to +// prevent that. +// +// Note that user should handle the protocol multiplexing, client does not +// provide any API for it, so if you need to read application data, wrap the +// connection with your (de-)multiplexer and pass the wrapper as conn. +func NewClient(conn Connection, options ...ClientOption) (*Client, error) { + c := &Client{ + close: make(chan struct{}), + c: conn, + clock: systemClock, + rto: int64(defaultRTO), + rtoRate: defaultTimeoutRate, + t: make(map[transactionID]*clientTransaction, 100), + maxAttempts: defaultMaxAttempts, + closeConn: true, + } + for _, o := range options { + o(c) + } + if c.c == nil { + return nil, ErrNoConnection + } + if c.a == nil { + c.a = NewAgent(nil) + } + if err := c.a.SetHandler(c.handleAgentCallback); err != nil { + return nil, err + } + if c.collector == nil { + c.collector = &tickerCollector{ + close: make(chan struct{}), + clock: c.clock, + } + } + if err := c.collector.Start(c.rtoRate, func(t time.Time) { + closedOrPanic(c.a.Collect(t)) + }); err != nil { + return nil, err + } + c.wg.Add(1) + go c.readUntilClosed() + runtime.SetFinalizer(c, clientFinalizer) + return c, nil +} + +func clientFinalizer(c *Client) { + if c == nil { + return + } + err := c.Close() + if err == ErrClientClosed { + return + } + if err == nil { + log.Println("client: called finalizer on non-closed client") // nolint + return + } + log.Println("client: called finalizer on non-closed client:", err) // nolint +} + +// Connection wraps Reader, Writer and Closer interfaces. +type Connection interface { + io.Reader + io.Writer + io.Closer +} + +// ClientAgent is Agent implementation that is used by Client to +// process transactions. +type ClientAgent interface { + Process(*Message) error + Close() error + Start(id [TransactionIDSize]byte, deadline time.Time) error + Stop(id [TransactionIDSize]byte) error + Collect(time.Time) error + SetHandler(h Handler) error +} + +// Client simulates "connection" to STUN server. +type Client struct { + rto int64 // time.Duration + a ClientAgent + c Connection + close chan struct{} + rtoRate time.Duration + maxAttempts int32 + closed bool + closeConn bool // should call c.Close() while closing + wg sync.WaitGroup + clock Clock + handler Handler + collector Collector + t map[transactionID]*clientTransaction + + // mux guards closed and t + mux sync.RWMutex +} + +// clientTransaction represents transaction in progress. +// If transaction is succeed or failed, f will be called +// provided by event. +// Concurrent access is invalid. +type clientTransaction struct { + id transactionID + attempt int32 + calls int32 + h Handler + start time.Time + rto time.Duration + raw []byte +} + +func (t *clientTransaction) handle(e Event) { + if atomic.AddInt32(&t.calls, 1) == 1 { + t.h(e) + } +} + +var clientTransactionPool = &sync.Pool{ + New: func() interface{} { + return &clientTransaction{ + raw: make([]byte, 1500), + } + }, +} + +func acquireClientTransaction() *clientTransaction { + return clientTransactionPool.Get().(*clientTransaction) +} + +func putClientTransaction(t *clientTransaction) { + t.raw = t.raw[:0] + t.start = time.Time{} + t.attempt = 0 + t.id = transactionID{} + clientTransactionPool.Put(t) +} + +func (t *clientTransaction) nextTimeout(now time.Time) time.Time { + return now.Add(time.Duration(t.attempt+1) * t.rto) +} + +// start registers transaction. +// +// Could return ErrClientClosed, ErrTransactionExists. +func (c *Client) start(t *clientTransaction) error { + c.mux.Lock() + defer c.mux.Unlock() + if c.closed { + return ErrClientClosed + } + _, exists := c.t[t.id] + if exists { + return ErrTransactionExists + } + c.t[t.id] = t + return nil +} + +// Clock abstracts the source of current time. +type Clock interface { + Now() time.Time +} + +type systemClockService struct{} + +func (systemClockService) Now() time.Time { return time.Now() } + +var systemClock = systemClockService{} + +// SetRTO sets current RTO value. +func (c *Client) SetRTO(rto time.Duration) { + atomic.StoreInt64(&c.rto, int64(rto)) +} + +// StopErr occurs when Client fails to stop transaction while +// processing error. +type StopErr struct { + Err error // value returned by Stop() + Cause error // error that caused Stop() call +} + +func (e StopErr) Error() string { + return fmt.Sprintf("error while stopping due to %s: %s", sprintErr(e.Cause), sprintErr(e.Err)) +} + +// CloseErr indicates client close failure. +type CloseErr struct { + AgentErr error + ConnectionErr error +} + +func sprintErr(err error) string { + if err == nil { + return "<nil>" + } + return err.Error() +} + +func (c CloseErr) Error() string { + return fmt.Sprintf("failed to close: %s (connection), %s (agent)", sprintErr(c.ConnectionErr), sprintErr(c.AgentErr)) +} + +func (c *Client) readUntilClosed() { + defer c.wg.Done() + m := new(Message) + m.Raw = make([]byte, 1024) + for { + select { + case <-c.close: + return + default: + } + _, err := m.ReadFrom(c.c) + if err == nil { + if pErr := c.a.Process(m); pErr == ErrAgentClosed { + return + } + } + } +} + +func closedOrPanic(err error) { + if err == nil || err == ErrAgentClosed { + return + } + panic(err) // nolint +} + +type tickerCollector struct { + close chan struct{} + wg sync.WaitGroup + clock Clock +} + +// Collector calls function f with constant rate. +// +// The simple Collector is ticker which calls function on each tick. +type Collector interface { + Start(rate time.Duration, f func(now time.Time)) error + Close() error +} + +func (a *tickerCollector) Start(rate time.Duration, f func(now time.Time)) error { + t := time.NewTicker(rate) + a.wg.Add(1) + go func() { + defer a.wg.Done() + for { + select { + case <-a.close: + t.Stop() + return + case <-t.C: + f(a.clock.Now()) + } + } + }() + return nil +} + +func (a *tickerCollector) Close() error { + close(a.close) + a.wg.Wait() + return nil +} + +// ErrClientClosed indicates that client is closed. +var ErrClientClosed = errors.New("client is closed") + +// Close stops internal connection and agent, returning CloseErr on error. +func (c *Client) Close() error { + if err := c.checkInit(); err != nil { + return err + } + c.mux.Lock() + if c.closed { + c.mux.Unlock() + return ErrClientClosed + } + c.closed = true + c.mux.Unlock() + if closeErr := c.collector.Close(); closeErr != nil { + return closeErr + } + var connErr error + agentErr := c.a.Close() + if c.closeConn { + connErr = c.c.Close() + } + close(c.close) + c.wg.Wait() + if agentErr == nil && connErr == nil { + return nil + } + return CloseErr{ + AgentErr: agentErr, + ConnectionErr: connErr, + } +} + +// Indicate sends indication m to server. Shorthand to Start call +// with zero deadline and callback. +func (c *Client) Indicate(m *Message) error { + return c.Start(m, nil) +} + +// callbackWaitHandler blocks on wait() call until callback is called. +type callbackWaitHandler struct { + handler Handler + callback func(event Event) + cond *sync.Cond + processed bool +} + +func (s *callbackWaitHandler) HandleEvent(e Event) { + s.cond.L.Lock() + if s.callback == nil { + panic("s.callback is nil") // nolint + } + s.callback(e) + s.processed = true + s.cond.Broadcast() + s.cond.L.Unlock() +} + +func (s *callbackWaitHandler) wait() { + s.cond.L.Lock() + for !s.processed { + s.cond.Wait() + } + s.processed = false + s.callback = nil + s.cond.L.Unlock() +} + +func (s *callbackWaitHandler) setCallback(f func(event Event)) { + if f == nil { + panic("f is nil") // nolint + } + s.cond.L.Lock() + s.callback = f + if s.handler == nil { + s.handler = s.HandleEvent + } + s.cond.L.Unlock() +} + +var callbackWaitHandlerPool = sync.Pool{ + New: func() interface{} { + return &callbackWaitHandler{ + cond: sync.NewCond(new(sync.Mutex)), + } + }, +} + +// ErrClientNotInitialized means that client connection or agent is nil. +var ErrClientNotInitialized = errors.New("client not initialized") + +func (c *Client) checkInit() error { + if c == nil || c.c == nil || c.a == nil || c.close == nil { + return ErrClientNotInitialized + } + return nil +} + +// Do is Start wrapper that waits until callback is called. If no callback +// provided, Indicate is called instead. +// +// Do has cpu overhead due to blocking, see BenchmarkClient_Do. +// Use Start method for less overhead. +func (c *Client) Do(m *Message, f func(Event)) error { + if err := c.checkInit(); err != nil { + return err + } + if f == nil { + return c.Indicate(m) + } + h := callbackWaitHandlerPool.Get().(*callbackWaitHandler) + h.setCallback(f) + defer func() { + callbackWaitHandlerPool.Put(h) + }() + if err := c.Start(m, h.handler); err != nil { + return err + } + h.wait() + return nil +} + +func (c *Client) delete(id transactionID) { + c.mux.Lock() + if c.t != nil { + delete(c.t, id) + } + c.mux.Unlock() +} + +type buffer struct { + buf []byte +} + +var bufferPool = &sync.Pool{ + New: func() interface{} { + return &buffer{buf: make([]byte, 2048)} + }, +} + +func (c *Client) handleAgentCallback(e Event) { + c.mux.Lock() + if c.closed { + c.mux.Unlock() + return + } + t, found := c.t[e.TransactionID] + if found { + delete(c.t, t.id) + } + c.mux.Unlock() + if !found { + if c.handler != nil && e.Error != ErrTransactionStopped { + c.handler(e) + } + // Ignoring. + return + } + if atomic.LoadInt32(&c.maxAttempts) <= t.attempt || e.Error == nil { + // Transaction completed. + t.handle(e) + putClientTransaction(t) + return + } + // Doing re-transmission. + t.attempt++ + b := bufferPool.Get().(*buffer) + b.buf = b.buf[:copy(b.buf[:cap(b.buf)], t.raw)] + defer bufferPool.Put(b) + var ( + now = c.clock.Now() + timeOut = t.nextTimeout(now) + id = t.id + ) + // Starting client transaction. + if startErr := c.start(t); startErr != nil { + c.delete(id) + e.Error = startErr + t.handle(e) + putClientTransaction(t) + return + } + // Starting agent transaction. + if startErr := c.a.Start(id, timeOut); startErr != nil { + c.delete(id) + e.Error = startErr + t.handle(e) + putClientTransaction(t) + return + } + // Writing message to connection again. + _, writeErr := c.c.Write(b.buf) + if writeErr != nil { + c.delete(id) + e.Error = writeErr + // Stopping agent transaction instead of waiting until it's deadline. + // This will call handleAgentCallback with "ErrTransactionStopped" error + // which will be ignored. + if stopErr := c.a.Stop(id); stopErr != nil { + // Failed to stop agent transaction. Wrapping the error in StopError. + e.Error = StopErr{ + Err: stopErr, + Cause: writeErr, + } + } + t.handle(e) + putClientTransaction(t) + return + } +} + +// Start starts transaction (if h set) and writes message to server, handler +// is called asynchronously. +func (c *Client) Start(m *Message, h Handler) error { + if err := c.checkInit(); err != nil { + return err + } + c.mux.RLock() + closed := c.closed + c.mux.RUnlock() + if closed { + return ErrClientClosed + } + if h != nil { + // Starting transaction only if h is set. Useful for indications. + t := acquireClientTransaction() + t.id = m.TransactionID + t.start = c.clock.Now() + t.h = h + t.rto = time.Duration(atomic.LoadInt64(&c.rto)) + t.attempt = 0 + t.raw = append(t.raw[:0], m.Raw...) + t.calls = 0 + d := t.nextTimeout(t.start) + if err := c.start(t); err != nil { + return err + } + if err := c.a.Start(m.TransactionID, d); err != nil { + return err + } + } + _, err := m.WriteTo(c.c) + if err != nil && h != nil { + c.delete(m.TransactionID) + // Stopping transaction instead of waiting until deadline. + if stopErr := c.a.Stop(m.TransactionID); stopErr != nil { + return StopErr{ + Err: stopErr, + Cause: err, + } + } + } + return err +} diff --git a/vendor/github.com/pion/stun/errorcode.go b/vendor/github.com/pion/stun/errorcode.go new file mode 100644 index 0000000..8095048 --- /dev/null +++ b/vendor/github.com/pion/stun/errorcode.go @@ -0,0 +1,158 @@ +package stun + +import ( + "errors" + "fmt" + "io" +) + +// ErrorCodeAttribute represents ERROR-CODE attribute. +// +// RFC 5389 Section 15.6 +type ErrorCodeAttribute struct { + Code ErrorCode + Reason []byte +} + +func (c ErrorCodeAttribute) String() string { + return fmt.Sprintf("%d: %s", c.Code, c.Reason) +} + +// constants for ERROR-CODE encoding. +const ( + errorCodeReasonStart = 4 + errorCodeClassByte = 2 + errorCodeNumberByte = 3 + errorCodeReasonMaxB = 763 + errorCodeModulo = 100 +) + +// AddTo adds ERROR-CODE to m. +func (c ErrorCodeAttribute) AddTo(m *Message) error { + value := make([]byte, 0, errorCodeReasonMaxB) + if err := CheckOverflow(AttrErrorCode, + len(c.Reason)+errorCodeReasonStart, + errorCodeReasonMaxB+errorCodeReasonStart, + ); err != nil { + return err + } + value = value[:errorCodeReasonStart+len(c.Reason)] + number := byte(c.Code % errorCodeModulo) // error code modulo 100 + class := byte(c.Code / errorCodeModulo) // hundred digit + value[errorCodeClassByte] = class + value[errorCodeNumberByte] = number + copy(value[errorCodeReasonStart:], c.Reason) + m.Add(AttrErrorCode, value) + return nil +} + +// GetFrom decodes ERROR-CODE from m. Reason is valid until m.Raw is valid. +func (c *ErrorCodeAttribute) GetFrom(m *Message) error { + v, err := m.Get(AttrErrorCode) + if err != nil { + return err + } + if len(v) < errorCodeReasonStart { + return io.ErrUnexpectedEOF + } + var ( + class = uint16(v[errorCodeClassByte]) + number = uint16(v[errorCodeNumberByte]) + code = int(class*errorCodeModulo + number) + ) + c.Code = ErrorCode(code) + c.Reason = v[errorCodeReasonStart:] + return nil +} + +// ErrorCode is code for ERROR-CODE attribute. +type ErrorCode int + +// ErrNoDefaultReason means that default reason for provided error code +// is not defined in RFC. +var ErrNoDefaultReason = errors.New("no default reason for ErrorCode") + +// AddTo adds ERROR-CODE with default reason to m. If there +// is no default reason, returns ErrNoDefaultReason. +func (c ErrorCode) AddTo(m *Message) error { + reason := errorReasons[c] + if reason == nil { + return ErrNoDefaultReason + } + a := &ErrorCodeAttribute{ + Code: c, + Reason: reason, + } + return a.AddTo(m) +} + +// Possible error codes. +const ( + CodeTryAlternate ErrorCode = 300 + CodeBadRequest ErrorCode = 400 + CodeUnauthorized ErrorCode = 401 + CodeUnknownAttribute ErrorCode = 420 + CodeStaleNonce ErrorCode = 438 + CodeRoleConflict ErrorCode = 487 + CodeServerError ErrorCode = 500 +) + +// DEPRECATED constants. +const ( + // DEPRECATED, use CodeUnauthorized. + CodeUnauthorised = CodeUnauthorized +) + +// Error codes from RFC 5766. +// +// RFC 5766 Section 15 +const ( + CodeForbidden ErrorCode = 403 // Forbidden + CodeAllocMismatch ErrorCode = 437 // Allocation Mismatch + CodeWrongCredentials ErrorCode = 441 // Wrong Credentials + CodeUnsupportedTransProto ErrorCode = 442 // Unsupported Transport Protocol + CodeAllocQuotaReached ErrorCode = 486 // Allocation Quota Reached + CodeInsufficientCapacity ErrorCode = 508 // Insufficient Capacity +) + +// Error codes from RFC 6062. +// +// RFC 6062 Section 6.3 +const ( + CodeConnAlreadyExists ErrorCode = 446 + CodeConnTimeoutOrFailure ErrorCode = 447 +) + +// Error codes from RFC 6156. +// +// RFC 6156 Section 10.2 +const ( + CodeAddrFamilyNotSupported ErrorCode = 440 // Address Family not Supported + CodePeerAddrFamilyMismatch ErrorCode = 443 // Peer Address Family Mismatch +) + +var errorReasons = map[ErrorCode][]byte{ + CodeTryAlternate: []byte("Try Alternate"), + CodeBadRequest: []byte("Bad Request"), + CodeUnauthorized: []byte("Unauthorized"), + CodeUnknownAttribute: []byte("Unknown Attribute"), + CodeStaleNonce: []byte("Stale Nonce"), + CodeServerError: []byte("Server Error"), + CodeRoleConflict: []byte("Role Conflict"), + + // RFC 5766. + CodeForbidden: []byte("Forbidden"), + CodeAllocMismatch: []byte("Allocation Mismatch"), + CodeWrongCredentials: []byte("Wrong Credentials"), + CodeUnsupportedTransProto: []byte("Unsupported Transport Protocol"), + CodeAllocQuotaReached: []byte("Allocation Quota Reached"), + CodeInsufficientCapacity: []byte("Insufficient Capacity"), + + // RFC 6062. + CodeConnAlreadyExists: []byte("Connection Already Exists"), + CodeConnTimeoutOrFailure: []byte("Connection Timeout or Failure"), + + // RFC 6156. + CodeAddrFamilyNotSupported: []byte("Address Family not Supported"), + CodePeerAddrFamilyMismatch: []byte("Peer Address Family Mismatch"), +} diff --git a/vendor/github.com/pion/stun/errors.go b/vendor/github.com/pion/stun/errors.go new file mode 100644 index 0000000..029c9e4 --- /dev/null +++ b/vendor/github.com/pion/stun/errors.go @@ -0,0 +1,62 @@ +package stun + +import "errors" + +// DecodeErr records an error and place when it is occurred. +type DecodeErr struct { + Place DecodeErrPlace + Message string +} + +// IsInvalidCookie returns true if error means that magic cookie +// value is invalid. +func (e DecodeErr) IsInvalidCookie() bool { + return e.Place == DecodeErrPlace{"message", "cookie"} +} + +// IsPlaceParent reports if error place parent is p. +func (e DecodeErr) IsPlaceParent(p string) bool { + return e.Place.Parent == p +} + +// IsPlaceChildren reports if error place children is c. +func (e DecodeErr) IsPlaceChildren(c string) bool { + return e.Place.Children == c +} + +// IsPlace reports if error place is p. +func (e DecodeErr) IsPlace(p DecodeErrPlace) bool { + return e.Place == p +} + +// DecodeErrPlace records a place where error is occurred. +type DecodeErrPlace struct { + Parent string + Children string +} + +func (p DecodeErrPlace) String() string { + return p.Parent + "/" + p.Children +} + +func (e DecodeErr) Error() string { + return "BadFormat for " + e.Place.String() + ": " + e.Message +} + +func newDecodeErr(parent, children, message string) *DecodeErr { + return &DecodeErr{ + Place: DecodeErrPlace{Parent: parent, Children: children}, + Message: message, + } +} + +// TODO(ar): rewrite errors to be more precise. +func newAttrDecodeErr(children, message string) *DecodeErr { + return newDecodeErr("attribute", children, message) +} + +// ErrAttributeSizeInvalid means that decoded attribute size is invalid. +var ErrAttributeSizeInvalid = errors.New("attribute size is invalid") + +// ErrAttributeSizeOverflow means that decoded attribute size is too big. +var ErrAttributeSizeOverflow = errors.New("attribute size overflow") diff --git a/vendor/github.com/pion/stun/fingerprint.go b/vendor/github.com/pion/stun/fingerprint.go new file mode 100644 index 0000000..aef80a2 --- /dev/null +++ b/vendor/github.com/pion/stun/fingerprint.go @@ -0,0 +1,67 @@ +package stun + +import ( + "errors" + "hash/crc32" +) + +// FingerprintAttr represents FINGERPRINT attribute. +// +// RFC 5389 Section 15.5 +type FingerprintAttr struct{} + +// ErrFingerprintMismatch means that computed fingerprint differs from expected. +var ErrFingerprintMismatch = errors.New("fingerprint check failed") + +// Fingerprint is shorthand for FingerprintAttr. +// +// Example: +// +// m := New() +// Fingerprint.AddTo(m) +var Fingerprint FingerprintAttr + +const ( + fingerprintXORValue uint32 = 0x5354554e //nolint:staticcheck + fingerprintSize = 4 // 32 bit +) + +// FingerprintValue returns CRC-32 of b XOR-ed by 0x5354554e. +// +// The value of the attribute is computed as the CRC-32 of the STUN message +// up to (but excluding) the FINGERPRINT attribute itself, XOR'ed with +// the 32-bit value 0x5354554e (the XOR helps in cases where an +// application packet is also using CRC-32 in it). +func FingerprintValue(b []byte) uint32 { + return crc32.ChecksumIEEE(b) ^ fingerprintXORValue // XOR +} + +// AddTo adds fingerprint to message. +func (FingerprintAttr) AddTo(m *Message) error { + l := m.Length + // length in header should include size of fingerprint attribute + m.Length += fingerprintSize + attributeHeaderSize // increasing length + m.WriteLength() // writing Length to Raw + b := make([]byte, fingerprintSize) + val := FingerprintValue(m.Raw) + bin.PutUint32(b, val) + m.Length = l + m.Add(AttrFingerprint, b) + return nil +} + +// Check reads fingerprint value from m and checks it, returning error if any. +// Can return *AttrLengthErr, ErrAttributeNotFound, and *CRCMismatch. +func (FingerprintAttr) Check(m *Message) error { + b, err := m.Get(AttrFingerprint) + if err != nil { + return err + } + if err = CheckSize(AttrFingerprint, len(b), fingerprintSize); err != nil { + return err + } + val := bin.Uint32(b) + attrStart := len(m.Raw) - (fingerprintSize + attributeHeaderSize) + expected := FingerprintValue(m.Raw[:attrStart]) + return checkFingerprint(val, expected) +} diff --git a/vendor/github.com/pion/stun/fingerprint_debug.go b/vendor/github.com/pion/stun/fingerprint_debug.go new file mode 100644 index 0000000..6da074c --- /dev/null +++ b/vendor/github.com/pion/stun/fingerprint_debug.go @@ -0,0 +1,18 @@ +// +build debug + +package stun + +import "fmt" + +// CRCMismatch represents CRC check error. +type CRCMismatch struct { + Expected uint32 + Actual uint32 +} + +func (m CRCMismatch) Error() string { + return fmt.Sprintf("CRC mismatch: %x (expected) != %x (actual)", + m.Expected, + m.Actual, + ) +} diff --git a/vendor/github.com/pion/stun/fuzz.go b/vendor/github.com/pion/stun/fuzz.go new file mode 100644 index 0000000..debfa8d --- /dev/null +++ b/vendor/github.com/pion/stun/fuzz.go @@ -0,0 +1,140 @@ +// +build gofuzz + +package stun + +import ( + "encoding/binary" + "fmt" +) + +var ( + m = New() +) + +// FuzzMessage is go-fuzz endpoint for message. +func FuzzMessage(data []byte) int { + m.Reset() + // fuzzer dont know about cookies + binary.BigEndian.PutUint32(data[4:8], magicCookie) + // trying to read data as message + if _, err := m.Write(data); err != nil { + return 0 + } + m2 := New() + if _, err := m2.Write(m.Raw); err != nil { + panic(err) // nolint + } + if m2.TransactionID != m.TransactionID { + panic("transaction ID mismatch") // nolint + } + if m2.Type != m.Type { + panic("type missmatch") // nolint + } + if len(m2.Attributes) != len(m.Attributes) { + panic("attributes length missmatch") // nolint + } + return 1 +} + +// FuzzType is go-fuzz endpoint for message type. +func FuzzType(data []byte) int { + t := MessageType{} + vt, _ := binary.Uvarint(data) + v := uint16(vt) & 0x1fff // first 3 bits are empty + t.ReadValue(v) + v2 := t.Value() + if v != v2 { + panic("v != v2") // nolint + } + t2 := MessageType{} + t2.ReadValue(v2) + if t2 != t { + panic("t2 != t") // nolint + } + return 0 +} + +type attr interface { + Getter + Setter +} + +type attrs []struct { + g attr + t AttrType +} + +func (a attrs) pick(v byte) struct { + g attr + t AttrType +} { + idx := int(v) % len(a) + return a[idx] +} + +func FuzzSetters(data []byte) int { + var ( + m1 = &Message{ + Raw: make([]byte, 0, 2048), + } + m2 = &Message{ + Raw: make([]byte, 0, 2048), + } + m3 = &Message{ + Raw: make([]byte, 0, 2048), + } + ) + attributes := attrs{ + {new(Realm), AttrRealm}, + {new(XORMappedAddress), AttrXORMappedAddress}, + {new(Nonce), AttrNonce}, + {new(Software), AttrSoftware}, + {new(AlternateServer), AttrAlternateServer}, + {new(ErrorCodeAttribute), AttrErrorCode}, + {new(UnknownAttributes), AttrUnknownAttributes}, + {new(Username), AttrUsername}, + {new(MappedAddress), AttrMappedAddress}, + {new(Realm), AttrRealm}, + } + var firstByte = byte(0) + if len(data) > 0 { + firstByte = data[0] + } + a := attributes.pick(firstByte) + value := data + if len(data) > 1 { + value = value[1:] + } + m1.WriteHeader() + m1.Add(a.t, value) + err := a.g.GetFrom(m1) + if err == ErrAttributeNotFound { + fmt.Println("unexpected 404") // nolint + panic(err) // nolint + } + if err != nil { + return 1 + } + m2.WriteHeader() + if err = a.g.AddTo(m2); err != nil { + // We allow decoding some text attributes + // when their length is too big, but + // not encoding. + if !IsAttrSizeOverflow(err) { + panic(err) // nolint + } + return 1 + } + m3.WriteHeader() + v, err := m2.Get(a.t) + if err != nil { + panic(err) // nolint + } + m3.Add(a.t, v) + + if !m2.Equal(m3) { + fmt.Println(m2, "not equal", m3) // nolint + panic("not equal") // nolint + } + return 1 +} diff --git a/vendor/github.com/pion/stun/go.mod b/vendor/github.com/pion/stun/go.mod new file mode 100644 index 0000000..72b5671 --- /dev/null +++ b/vendor/github.com/pion/stun/go.mod @@ -0,0 +1,3 @@ +module github.com/pion/stun + +go 1.12 diff --git a/vendor/github.com/pion/stun/go.sum b/vendor/github.com/pion/stun/go.sum new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/vendor/github.com/pion/stun/go.sum diff --git a/vendor/github.com/pion/stun/go.test.sh b/vendor/github.com/pion/stun/go.test.sh new file mode 100644 index 0000000..25234e4 --- /dev/null +++ b/vendor/github.com/pion/stun/go.test.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash + +set -e +touch coverage.txt + +# test fuzz inputs +go test -tags gofuzz -run TestFuzz -v . + +# quick-test without -race +go test ./... + +# test with "debug" tag +go test -tags debug ./... + +# test concurrency +go test -race -cpu=1,2,4 -run TestClient_DoConcurrent + +for d in $(go list ./... | grep -v vendor); do + go test -race -coverprofile=profile.out -covermode=atomic "$d" + if [[ -f profile.out ]]; then + cat profile.out >> coverage.txt + rm profile.out + fi +done diff --git a/vendor/github.com/pion/stun/helpers.go b/vendor/github.com/pion/stun/helpers.go new file mode 100644 index 0000000..158a51d --- /dev/null +++ b/vendor/github.com/pion/stun/helpers.go @@ -0,0 +1,105 @@ +package stun + +// Interfaces that are implemented by message attributes, shorthands for them, +// or helpers for message fields as type or transaction id. +type ( + // Setter sets *Message attribute. + Setter interface { + AddTo(m *Message) error + } + // Getter parses attribute from *Message. + Getter interface { + GetFrom(m *Message) error + } + // Checker checks *Message attribute. + Checker interface { + Check(m *Message) error + } +) + +// Build resets message and applies setters to it in batch, returning on +// first error. To prevent allocations, pass pointers to values. +// +// Example: +// var ( +// t = BindingRequest +// username = NewUsername("username") +// nonce = NewNonce("nonce") +// realm = NewRealm("example.org") +// ) +// m := new(Message) +// m.Build(t, username, nonce, realm) // 4 allocations +// m.Build(&t, &username, &nonce, &realm) // 0 allocations +// +// See BenchmarkBuildOverhead. +func (m *Message) Build(setters ...Setter) error { + m.Reset() + m.WriteHeader() + for _, s := range setters { + if err := s.AddTo(m); err != nil { + return err + } + } + return nil +} + +// Check applies checkers to message in batch, returning on first error. +func (m *Message) Check(checkers ...Checker) error { + for _, c := range checkers { + if err := c.Check(m); err != nil { + return err + } + } + return nil +} + +// Parse applies getters to message in batch, returning on first error. +func (m *Message) Parse(getters ...Getter) error { + for _, c := range getters { + if err := c.GetFrom(m); err != nil { + return err + } + } + return nil +} + +// MustBuild wraps Build call and panics on error. +func MustBuild(setters ...Setter) *Message { + m, err := Build(setters...) + if err != nil { + panic(err) // nolint + } + return m +} + +// Build wraps Message.Build method. +func Build(setters ...Setter) (*Message, error) { + m := new(Message) + if err := m.Build(setters...); err != nil { + return nil, err + } + return m, nil +} + +// ForEach is helper that iterates over message attributes allowing to call +// Getter in f callback to get all attributes of type t and returning on first +// f error. +// +// The m.Get method inside f will be returning next attribute on each f call. +// Does not error if there are no results. +func (m *Message) ForEach(t AttrType, f func(m *Message) error) error { + attrs := m.Attributes + defer func() { + m.Attributes = attrs + }() + for i, a := range attrs { + if a.Type != t { + continue + } + m.Attributes = attrs[i:] + if err := f(m); err != nil { + return err + } + } + return nil +} diff --git a/vendor/github.com/pion/stun/integrity.go b/vendor/github.com/pion/stun/integrity.go new file mode 100644 index 0000000..39b0d50 --- /dev/null +++ b/vendor/github.com/pion/stun/integrity.go @@ -0,0 +1,124 @@ +package stun + +import ( + "crypto/md5" // #nosec + "crypto/sha1" // #nosec + "errors" + "fmt" + "strings" + + "github.com/pion/stun/internal/hmac" +) + +// separator for credentials. +const credentialsSep = ":" + +// NewLongTermIntegrity returns new MessageIntegrity with key for long-term +// credentials. Password, username, and realm must be SASL-prepared. +func NewLongTermIntegrity(username, realm, password string) MessageIntegrity { + k := strings.Join([]string{username, realm, password}, credentialsSep) + // #nosec + h := md5.New() + fmt.Fprint(h, k) + return MessageIntegrity(h.Sum(nil)) +} + +// NewShortTermIntegrity returns new MessageIntegrity with key for short-term +// credentials. Password must be SASL-prepared. +func NewShortTermIntegrity(password string) MessageIntegrity { + return MessageIntegrity(password) +} + +// MessageIntegrity represents MESSAGE-INTEGRITY attribute. +// +// AddTo and Check methods are using zero-allocation version of hmac, see +// newHMAC function and internal/hmac/pool.go. +// +// RFC 5389 Section 15.4 +type MessageIntegrity []byte + +func newHMAC(key, message, buf []byte) []byte { + mac := hmac.AcquireSHA1(key) + writeOrPanic(mac, message) + defer hmac.PutSHA1(mac) + return mac.Sum(buf) +} + +func (i MessageIntegrity) String() string { + return fmt.Sprintf("KEY: 0x%x", []byte(i)) +} + +const messageIntegritySize = 20 + +// ErrFingerprintBeforeIntegrity means that FINGERPRINT attribute is already in +// message, so MESSAGE-INTEGRITY attribute cannot be added. +var ErrFingerprintBeforeIntegrity = errors.New("FINGERPRINT before MESSAGE-INTEGRITY attribute") + +// AddTo adds MESSAGE-INTEGRITY attribute to message. +// +// CPU costly, see BenchmarkMessageIntegrity_AddTo. +func (i MessageIntegrity) AddTo(m *Message) error { + for _, a := range m.Attributes { + // Message should not contain FINGERPRINT attribute + // before MESSAGE-INTEGRITY. + if a.Type == AttrFingerprint { + return ErrFingerprintBeforeIntegrity + } + } + // The text used as input to HMAC is the STUN message, + // including the header, up to and including the attribute preceding the + // MESSAGE-INTEGRITY attribute. + length := m.Length + // Adjusting m.Length to contain MESSAGE-INTEGRITY TLV. + m.Length += messageIntegritySize + attributeHeaderSize + m.WriteLength() // writing length to m.Raw + v := newHMAC(i, m.Raw, m.Raw[len(m.Raw):]) // calculating HMAC for adjusted m.Raw + m.Length = length // changing m.Length back + + // Copy hmac value to temporary variable to protect it from resetting + // while processing m.Add call. + vBuf := make([]byte, sha1.Size) + copy(vBuf, v) + + m.Add(AttrMessageIntegrity, vBuf) + return nil +} + +// ErrIntegrityMismatch means that computed HMAC differs from expected. +var ErrIntegrityMismatch = errors.New("integrity check failed") + +// Check checks MESSAGE-INTEGRITY attribute. +// +// CPU costly, see BenchmarkMessageIntegrity_Check. +func (i MessageIntegrity) Check(m *Message) error { + v, err := m.Get(AttrMessageIntegrity) + if err != nil { + return err + } + + // Adjusting length in header to match m.Raw that was + // used when computing HMAC. + var ( + length = m.Length + afterIntegrity = false + sizeReduced int + ) + for _, a := range m.Attributes { + if afterIntegrity { + sizeReduced += nearestPaddedValueLength(int(a.Length)) + sizeReduced += attributeHeaderSize + } + if a.Type == AttrMessageIntegrity { + afterIntegrity = true + } + } + m.Length -= uint32(sizeReduced) + m.WriteLength() + // startOfHMAC should be first byte of integrity attribute. + startOfHMAC := messageHeaderSize + m.Length - (attributeHeaderSize + messageIntegritySize) + b := m.Raw[:startOfHMAC] // data before integrity attribute + expected := newHMAC(i, b, m.Raw[len(m.Raw):]) + m.Length = length + m.WriteLength() // writing length back + return checkHMAC(v, expected) +} diff --git a/vendor/github.com/pion/stun/integrity_debug.go b/vendor/github.com/pion/stun/integrity_debug.go new file mode 100644 index 0000000..6b8a303 --- /dev/null +++ b/vendor/github.com/pion/stun/integrity_debug.go @@ -0,0 +1,18 @@ +// +build debug + +package stun + +import "fmt" + +// IntegrityErr occurs when computed HMAC differs from expected. +type IntegrityErr struct { + Expected []byte + Actual []byte +} + +func (i *IntegrityErr) Error() string { + return fmt.Sprintf( + "Integrity check failed: 0x%x (expected) !- 0x%x (actual)", + i.Expected, i.Actual, + ) +} diff --git a/vendor/github.com/pion/stun/internal/hmac/hmac.go b/vendor/github.com/pion/stun/internal/hmac/hmac.go new file mode 100644 index 0000000..801ece6 --- /dev/null +++ b/vendor/github.com/pion/stun/internal/hmac/hmac.go @@ -0,0 +1,101 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +/* +Package hmac implements the Keyed-Hash Message Authentication Code (HMAC) as +defined in U.S. Federal Information Processing Standards Publication 198. +An HMAC is a cryptographic hash that uses a key to sign a message. +The receiver verifies the hash by recomputing it using the same key. + +Receivers should be careful to use Equal to compare MACs in order to avoid +timing side-channels: + + // ValidMAC reports whether messageMAC is a valid HMAC tag for message. + func ValidMAC(message, messageMAC, key []byte) bool { + mac := hmac.New(sha256.New, key) + mac.Write(message) + expectedMAC := mac.Sum(nil) + return hmac.Equal(messageMAC, expectedMAC) + } +*/ +package hmac + +import ( + "crypto/subtle" + "hash" +) + +// FIPS 198-1: +// https://csrc.nist.gov/publications/fips/fips198-1/FIPS-198-1_final.pdf + +// key is zero padded to the block size of the hash function +// ipad = 0x36 byte repeated for key length +// opad = 0x5c byte repeated for key length +// hmac = H([key ^ opad] H([key ^ ipad] text)) + +type hmac struct { + size int + blocksize int + opad, ipad []byte + outer, inner hash.Hash +} + +func (h *hmac) Sum(in []byte) []byte { + origLen := len(in) + in = h.inner.Sum(in) + h.outer.Reset() + h.outer.Write(h.opad) + h.outer.Write(in[origLen:]) + return h.outer.Sum(in[:origLen]) +} + +func (h *hmac) Write(p []byte) (n int, err error) { + return h.inner.Write(p) +} + +func (h *hmac) Size() int { return h.size } + +func (h *hmac) BlockSize() int { return h.blocksize } + +func (h *hmac) Reset() { + h.inner.Reset() + h.inner.Write(h.ipad) +} + +// New returns a new HMAC hash using the given hash.Hash type and key. +// Note that unlike other hash implementations in the standard library, +// the returned Hash does not implement encoding.BinaryMarshaler +// or encoding.BinaryUnmarshaler. +func New(h func() hash.Hash, key []byte) hash.Hash { + hm := new(hmac) + hm.outer = h() + hm.inner = h() + hm.size = hm.inner.Size() + hm.blocksize = hm.inner.BlockSize() + hm.ipad = make([]byte, hm.blocksize) + hm.opad = make([]byte, hm.blocksize) + if len(key) > hm.blocksize { + // If key is too big, hash it. + hm.outer.Write(key) + key = hm.outer.Sum(nil) + } + copy(hm.ipad, key) + copy(hm.opad, key) + for i := range hm.ipad { + hm.ipad[i] ^= 0x36 + } + for i := range hm.opad { + hm.opad[i] ^= 0x5c + } + hm.inner.Write(hm.ipad) + return hm +} + +// Equal compares two MACs for equality without leaking timing information. +func Equal(mac1, mac2 []byte) bool { + // We don't have to be constant time if the lengths of the MACs are + // different as that suggests that a completely different hash function + // was used. + return subtle.ConstantTimeCompare(mac1, mac2) == 1 +} diff --git a/vendor/github.com/pion/stun/internal/hmac/pool.go b/vendor/github.com/pion/stun/internal/hmac/pool.go new file mode 100644 index 0000000..4db61e5 --- /dev/null +++ b/vendor/github.com/pion/stun/internal/hmac/pool.go @@ -0,0 +1,92 @@ +package hmac + +import ( + "crypto/sha1" + "crypto/sha256" + "hash" + "sync" +) + +// setZeroes sets all bytes from b to zeroes. +// +// See https://github.com/golang/go/issues/5373 +func setZeroes(b []byte) { + for i := range b { + b[i] = 0 + } +} + +func (h *hmac) resetTo(key []byte) { + h.outer.Reset() + h.inner.Reset() + setZeroes(h.ipad) + setZeroes(h.opad) + if len(key) > h.blocksize { + // If key is too big, hash it. + h.outer.Write(key) + key = h.outer.Sum(nil) + } + copy(h.ipad, key) + copy(h.opad, key) + for i := range h.ipad { + h.ipad[i] ^= 0x36 + } + for i := range h.opad { + h.opad[i] ^= 0x5c + } + h.inner.Write(h.ipad) +} + +var hmacSHA1Pool = &sync.Pool{ + New: func() interface{} { + h := New(sha1.New, make([]byte, sha1.BlockSize)) + return h + }, +} + +// AcquireSHA1 returns new HMAC from pool. +func AcquireSHA1(key []byte) hash.Hash { + h := hmacSHA1Pool.Get().(*hmac) + assertHMACSize(h, sha1.Size, sha1.BlockSize) + h.resetTo(key) + return h +} + +// PutSHA1 puts h to pool. +func PutSHA1(h hash.Hash) { + hm := h.(*hmac) + assertHMACSize(hm, sha1.Size, sha1.BlockSize) + hmacSHA1Pool.Put(hm) +} + +var hmacSHA256Pool = &sync.Pool{ + New: func() interface{} { + h := New(sha256.New, make([]byte, sha256.BlockSize)) + return h + }, +} + +// AcquireSHA256 returns new HMAC from SHA256 pool. +func AcquireSHA256(key []byte) hash.Hash { + h := hmacSHA256Pool.Get().(*hmac) + assertHMACSize(h, sha256.Size, sha256.BlockSize) + h.resetTo(key) + return h +} + +// PutSHA256 puts h to SHA256 pool. +func PutSHA256(h hash.Hash) { + hm := h.(*hmac) + assertHMACSize(hm, sha256.Size, sha256.BlockSize) + hmacSHA256Pool.Put(hm) +} + +// assertHMACSize panics if h.size != size or h.blocksize != blocksize. +// +// Put and Acquire functions are internal functions to project, so +// checking it via such assert is optimal. +func assertHMACSize(h *hmac, size, blocksize int) { + if h.size != size || h.blocksize != blocksize { + panic("BUG: hmac size invalid") // nolint + } +} diff --git a/vendor/github.com/pion/stun/internal/hmac/vendor.sh b/vendor/github.com/pion/stun/internal/hmac/vendor.sh new file mode 100644 index 0000000..83a2b32 --- /dev/null +++ b/vendor/github.com/pion/stun/internal/hmac/vendor.sh @@ -0,0 +1,4 @@ +#!/bin/bash +cp -v $GOROOT/src/crypto/hmac/{hmac,hmac_test}.go . +git diff {hmac,hmac_test}.go + diff --git a/vendor/github.com/pion/stun/message.go b/vendor/github.com/pion/stun/message.go new file mode 100644 index 0000000..3819235 --- /dev/null +++ b/vendor/github.com/pion/stun/message.go @@ -0,0 +1,588 @@ +package stun + +import ( + "crypto/rand" + "encoding/base64" + "errors" + "fmt" + "io" +) + +const ( + // magicCookie is fixed value that aids in distinguishing STUN packets + // from packets of other protocols when STUN is multiplexed with those + // other protocols on the same Port. + // + // The magic cookie field MUST contain the fixed value 0x2112A442 in + // network byte order. + // + // Defined in "STUN Message Structure", section 6. + magicCookie = 0x2112A442 + attributeHeaderSize = 4 + messageHeaderSize = 20 + + // TransactionIDSize is length of transaction id array (in bytes). + TransactionIDSize = 12 // 96 bit +) + +// NewTransactionID returns new random transaction ID using crypto/rand +// as source. +func NewTransactionID() (b [TransactionIDSize]byte) { + readFullOrPanic(rand.Reader, b[:]) + return b +} + +// IsMessage returns true if b looks like STUN message. +// Useful for multiplexing. IsMessage does not guarantee +// that decoding will be successful. +func IsMessage(b []byte) bool { + return len(b) >= messageHeaderSize && bin.Uint32(b[4:8]) == magicCookie +} + +// New returns *Message with pre-allocated Raw. +func New() *Message { + const defaultRawCapacity = 120 + return &Message{ + Raw: make([]byte, messageHeaderSize, defaultRawCapacity), + } +} + +// ErrDecodeToNil occurs on Decode(data, nil) call. +var ErrDecodeToNil = errors.New("attempt to decode to nil message") + +// Decode decodes Message from data to m, returning error if any. +func Decode(data []byte, m *Message) error { + if m == nil { + return ErrDecodeToNil + } + m.Raw = append(m.Raw[:0], data...) + return m.Decode() +} + +// Message represents a single STUN packet. It uses aggressive internal +// buffering to enable zero-allocation encoding and decoding, +// so there are some usage constraints: +// +// Message, its fields, results of m.Get or any attribute a.GetFrom +// are valid only until Message.Raw is not modified. +type Message struct { + Type MessageType + Length uint32 // len(Raw) not including header + TransactionID [TransactionIDSize]byte + Attributes Attributes + Raw []byte +} + +// AddTo sets b.TransactionID to m.TransactionID. +// +// Implements Setter to aid in crafting responses. +func (m *Message) AddTo(b *Message) error { + b.TransactionID = m.TransactionID + b.WriteTransactionID() + return nil +} + +// NewTransactionID sets m.TransactionID to random value from crypto/rand +// and returns error if any. +func (m *Message) NewTransactionID() error { + _, err := io.ReadFull(rand.Reader, m.TransactionID[:]) + if err == nil { + m.WriteTransactionID() + } + return err +} + +func (m *Message) String() string { + tID := base64.StdEncoding.EncodeToString(m.TransactionID[:]) + return fmt.Sprintf("%s l=%d attrs=%d id=%s", m.Type, m.Length, len(m.Attributes), tID) +} + +// Reset resets Message, attributes and underlying buffer length. +func (m *Message) Reset() { + m.Raw = m.Raw[:0] + m.Length = 0 + m.Attributes = m.Attributes[:0] +} + +// grow ensures that internal buffer has n length. +func (m *Message) grow(n int) { + if len(m.Raw) >= n { + return + } + if cap(m.Raw) >= n { + m.Raw = m.Raw[:n] + return + } + m.Raw = append(m.Raw, make([]byte, n-len(m.Raw))...) +} + +// Add appends new attribute to message. Not goroutine-safe. +// +// Value of attribute is copied to internal buffer so +// it is safe to reuse v. +func (m *Message) Add(t AttrType, v []byte) { + // Allocating buffer for TLV (type-length-value). + // T = t, L = len(v), V = v. + // m.Raw will look like: + // [0:20] <- message header + // [20:20+m.Length] <- existing message attributes + // [20+m.Length:20+m.Length+len(v) + 4] <- allocated buffer for new TLV + // [first:last] <- same as previous + // [0 1|2 3|4 4 + len(v)] <- mapping for allocated buffer + // T L V + allocSize := attributeHeaderSize + len(v) // ~ len(TLV) = len(TL) + len(V) + first := messageHeaderSize + int(m.Length) // first byte number + last := first + allocSize // last byte number + m.grow(last) // growing cap(Raw) to fit TLV + m.Raw = m.Raw[:last] // now len(Raw) = last + m.Length += uint32(allocSize) // rendering length change + + // Sub-slicing internal buffer to simplify encoding. + buf := m.Raw[first:last] // slice for TLV + value := buf[attributeHeaderSize:] // slice for V + attr := RawAttribute{ + Type: t, // T + Length: uint16(len(v)), // L + Value: value, // V + } + + // Encoding attribute TLV to allocated buffer. + bin.PutUint16(buf[0:2], attr.Type.Value()) // T + bin.PutUint16(buf[2:4], attr.Length) // L + copy(value, v) // V + + // Checking that attribute value needs padding. + if attr.Length%padding != 0 { + // Performing padding. + bytesToAdd := nearestPaddedValueLength(len(v)) - len(v) + last += bytesToAdd + m.grow(last) + // setting all padding bytes to zero + // to prevent data leak from previous + // data in next bytesToAdd bytes + buf = m.Raw[last-bytesToAdd : last] + for i := range buf { + buf[i] = 0 + } + m.Raw = m.Raw[:last] // increasing buffer length + m.Length += uint32(bytesToAdd) // rendering length change + } + m.Attributes = append(m.Attributes, attr) + m.WriteLength() +} + +func attrSliceEqual(a, b Attributes) bool { + for _, attr := range a { + found := false + for _, attrB := range b { + if attrB.Type != attr.Type { + continue + } + if attrB.Equal(attr) { + found = true + break + } + } + if !found { + return false + } + } + return true +} + +func attrEqual(a, b Attributes) bool { + if a == nil && b == nil { + return true + } + if a == nil || b == nil { + return false + } + if len(a) != len(b) { + return false + } + if !attrSliceEqual(a, b) { + return false + } + if !attrSliceEqual(b, a) { + return false + } + return true +} + +// Equal returns true if Message b equals to m. +// Ignores m.Raw. +func (m *Message) Equal(b *Message) bool { + if m == nil && b == nil { + return true + } + if m == nil || b == nil { + return false + } + if m.Type != b.Type { + return false + } + if m.TransactionID != b.TransactionID { + return false + } + if m.Length != b.Length { + return false + } + if !attrEqual(m.Attributes, b.Attributes) { + return false + } + return true +} + +// WriteLength writes m.Length to m.Raw. +func (m *Message) WriteLength() { + m.grow(4) + bin.PutUint16(m.Raw[2:4], uint16(m.Length)) +} + +// WriteHeader writes header to underlying buffer. Not goroutine-safe. +func (m *Message) WriteHeader() { + m.grow(messageHeaderSize) + _ = m.Raw[:messageHeaderSize] // early bounds check to guarantee safety of writes below + + m.WriteType() + m.WriteLength() + bin.PutUint32(m.Raw[4:8], magicCookie) // magic cookie + copy(m.Raw[8:messageHeaderSize], m.TransactionID[:]) // transaction ID +} + +// WriteTransactionID writes m.TransactionID to m.Raw. +func (m *Message) WriteTransactionID() { + copy(m.Raw[8:messageHeaderSize], m.TransactionID[:]) // transaction ID +} + +// WriteAttributes encodes all m.Attributes to m. +func (m *Message) WriteAttributes() { + attributes := m.Attributes + m.Attributes = attributes[:0] + for _, a := range attributes { + m.Add(a.Type, a.Value) + } + m.Attributes = attributes +} + +// WriteType writes m.Type to m.Raw. +func (m *Message) WriteType() { + m.grow(2) + bin.PutUint16(m.Raw[0:2], m.Type.Value()) // message type +} + +// SetType sets m.Type and writes it to m.Raw. +func (m *Message) SetType(t MessageType) { + m.Type = t + m.WriteType() +} + +// Encode re-encodes message into m.Raw. +func (m *Message) Encode() { + m.Raw = m.Raw[:0] + m.WriteHeader() + m.Length = 0 + m.WriteAttributes() +} + +// WriteTo implements WriterTo via calling Write(m.Raw) on w and returning +// call result. +func (m *Message) WriteTo(w io.Writer) (int64, error) { + n, err := w.Write(m.Raw) + return int64(n), err +} + +// ReadFrom implements ReaderFrom. Reads message from r into m.Raw, +// Decodes it and return error if any. If m.Raw is too small, will return +// ErrUnexpectedEOF, ErrUnexpectedHeaderEOF or *DecodeErr. +// +// Can return *DecodeErr while decoding too. +func (m *Message) ReadFrom(r io.Reader) (int64, error) { + tBuf := m.Raw[:cap(m.Raw)] + var ( + n int + err error + ) + if n, err = r.Read(tBuf); err != nil { + return int64(n), err + } + m.Raw = tBuf[:n] + return int64(n), m.Decode() +} + +// ErrUnexpectedHeaderEOF means that there were not enough bytes in +// m.Raw to read header. +var ErrUnexpectedHeaderEOF = errors.New("unexpected EOF: not enough bytes to read header") + +// Decode decodes m.Raw into m. +func (m *Message) Decode() error { + // decoding message header + buf := m.Raw + if len(buf) < messageHeaderSize { + return ErrUnexpectedHeaderEOF + } + var ( + t = bin.Uint16(buf[0:2]) // first 2 bytes + size = int(bin.Uint16(buf[2:4])) // second 2 bytes + cookie = bin.Uint32(buf[4:8]) // last 4 bytes + fullSize = messageHeaderSize + size // len(m.Raw) + ) + if cookie != magicCookie { + msg := fmt.Sprintf("%x is invalid magic cookie (should be %x)", cookie, magicCookie) + return newDecodeErr("message", "cookie", msg) + } + if len(buf) < fullSize { + msg := fmt.Sprintf("buffer length %d is less than %d (expected message size)", len(buf), fullSize) + return newAttrDecodeErr("message", msg) + } + // saving header data + m.Type.ReadValue(t) + m.Length = uint32(size) + copy(m.TransactionID[:], buf[8:messageHeaderSize]) + + m.Attributes = m.Attributes[:0] + var ( + offset = 0 + b = buf[messageHeaderSize:fullSize] + ) + for offset < size { + // checking that we have enough bytes to read header + if len(b) < attributeHeaderSize { + msg := fmt.Sprintf("buffer length %d is less than %d (expected header size)", len(b), attributeHeaderSize) + return newAttrDecodeErr("header", msg) + } + var ( + a = RawAttribute{ + Type: compatAttrType(bin.Uint16(b[0:2])), // first 2 bytes + Length: bin.Uint16(b[2:4]), // second 2 bytes + } + aL = int(a.Length) // attribute length + aBuffL = nearestPaddedValueLength(aL) // expected buffer length (with padding) + ) + b = b[attributeHeaderSize:] // slicing again to simplify value read + offset += attributeHeaderSize + if len(b) < aBuffL { // checking size + msg := fmt.Sprintf("buffer length %d is less than %d (expected value size for %s)", len(b), aBuffL, a.Type) + return newAttrDecodeErr("value", msg) + } + a.Value = b[:aL] + offset += aBuffL + b = b[aBuffL:] + + m.Attributes = append(m.Attributes, a) + } + return nil +} + +// Write decodes message and return error if any. +// +// Any error is unrecoverable, but message could be partially decoded. +func (m *Message) Write(tBuf []byte) (int, error) { + m.Raw = append(m.Raw[:0], tBuf...) + return len(tBuf), m.Decode() +} + +// CloneTo clones m to b securing any further m mutations. +func (m *Message) CloneTo(b *Message) error { + // TODO(ar): implement low-level copy. + b.Raw = append(b.Raw[:0], m.Raw...) + return b.Decode() +} + +// MessageClass is 8-bit representation of 2-bit class of STUN Message Class. +type MessageClass byte + +// Possible values for message class in STUN Message Type. +const ( + ClassRequest MessageClass = 0x00 // 0b00 + ClassIndication MessageClass = 0x01 // 0b01 + ClassSuccessResponse MessageClass = 0x02 // 0b10 + ClassErrorResponse MessageClass = 0x03 // 0b11 +) + +// Common STUN message types. +var ( + // Binding request message type. + BindingRequest = NewType(MethodBinding, ClassRequest) + // Binding success response message type + BindingSuccess = NewType(MethodBinding, ClassSuccessResponse) + // Binding error response message type. + BindingError = NewType(MethodBinding, ClassErrorResponse) +) + +func (c MessageClass) String() string { + switch c { + case ClassRequest: + return "request" + case ClassIndication: + return "indication" + case ClassSuccessResponse: + return "success response" + case ClassErrorResponse: + return "error response" + default: + panic("unknown message class") // nolint: never happens unless wrongly casted + } +} + +// Method is uint16 representation of 12-bit STUN method. +type Method uint16 + +// Possible methods for STUN Message. +const ( + MethodBinding Method = 0x001 + MethodAllocate Method = 0x003 + MethodRefresh Method = 0x004 + MethodSend Method = 0x006 + MethodData Method = 0x007 + MethodCreatePermission Method = 0x008 + MethodChannelBind Method = 0x009 +) + +// Methods from RFC 6062. +const ( + MethodConnect Method = 0x000a + MethodConnectionBind Method = 0x000b + MethodConnectionAttempt Method = 0x000c +) + +var methodName = map[Method]string{ + MethodBinding: "Binding", + MethodAllocate: "Allocate", + MethodRefresh: "Refresh", + MethodSend: "Send", + MethodData: "Data", + MethodCreatePermission: "CreatePermission", + MethodChannelBind: "ChannelBind", + + // RFC 6062. + MethodConnect: "Connect", + MethodConnectionBind: "ConnectionBind", + MethodConnectionAttempt: "ConnectionAttempt", +} + +func (m Method) String() string { + s, ok := methodName[m] + if !ok { + // Falling back to hex representation. + s = fmt.Sprintf("0x%x", uint16(m)) + } + return s +} + +// MessageType is STUN Message Type Field. +type MessageType struct { + Method Method // e.g. binding + Class MessageClass // e.g. request +} + +// AddTo sets m type to t. +func (t MessageType) AddTo(m *Message) error { + m.SetType(t) + return nil +} + +// NewType returns new message type with provided method and class. +func NewType(method Method, class MessageClass) MessageType { + return MessageType{ + Method: method, + Class: class, + } +} + +const ( + methodABits = 0xf // 0b0000000000001111 + methodBBits = 0x70 // 0b0000000001110000 + methodDBits = 0xf80 // 0b0000111110000000 + + methodBShift = 1 + methodDShift = 2 + + firstBit = 0x1 + secondBit = 0x2 + + c0Bit = firstBit + c1Bit = secondBit + + classC0Shift = 4 + classC1Shift = 7 +) + +// Value returns bit representation of messageType. +func (t MessageType) Value() uint16 { + // 0 1 + // 2 3 4 5 6 7 8 9 0 1 2 3 4 5 + // +--+--+-+-+-+-+-+-+-+-+-+-+-+-+ + // |M |M |M|M|M|C|M|M|M|C|M|M|M|M| + // |11|10|9|8|7|1|6|5|4|0|3|2|1|0| + // +--+--+-+-+-+-+-+-+-+-+-+-+-+-+ + // Figure 3: Format of STUN Message Type Field + + // Warning: Abandon all hope ye who enter here. + // Splitting M into A(M0-M3), B(M4-M6), D(M7-M11). + m := uint16(t.Method) + a := m & methodABits // A = M * 0b0000000000001111 (right 4 bits) + b := m & methodBBits // B = M * 0b0000000001110000 (3 bits after A) + d := m & methodDBits // D = M * 0b0000111110000000 (5 bits after B) + + // Shifting to add "holes" for C0 (at 4 bit) and C1 (8 bit). + m = a + (b << methodBShift) + (d << methodDShift) + + // C0 is zero bit of C, C1 is first bit. + // C0 = C * 0b01, C1 = (C * 0b10) >> 1 + // Ct = C0 << 4 + C1 << 8. + // Optimizations: "((C * 0b10) >> 1) << 8" as "(C * 0b10) << 7" + // We need C0 shifted by 4, and C1 by 8 to fit "11" and "7" positions + // (see figure 3). + c := uint16(t.Class) + c0 := (c & c0Bit) << classC0Shift + c1 := (c & c1Bit) << classC1Shift + class := c0 + c1 + + return m + class +} + +// ReadValue decodes uint16 into MessageType. +func (t *MessageType) ReadValue(v uint16) { + // Decoding class. + // We are taking first bit from v >> 4 and second from v >> 7. + c0 := (v >> classC0Shift) & c0Bit + c1 := (v >> classC1Shift) & c1Bit + class := c0 + c1 + t.Class = MessageClass(class) + + // Decoding method. + a := v & methodABits // A(M0-M3) + b := (v >> methodBShift) & methodBBits // B(M4-M6) + d := (v >> methodDShift) & methodDBits // D(M7-M11) + m := a + b + d + t.Method = Method(m) +} + +func (t MessageType) String() string { + return fmt.Sprintf("%s %s", t.Method, t.Class) +} + +// Contains return true if message contain t attribute. +func (m *Message) Contains(t AttrType) bool { + for _, a := range m.Attributes { + if a.Type == t { + return true + } + } + return false +} + +type transactionIDValueSetter [TransactionIDSize]byte + +// NewTransactionIDSetter returns new Setter that sets message transaction id +// to provided value. +func NewTransactionIDSetter(value [TransactionIDSize]byte) Setter { + return transactionIDValueSetter(value) +} + +func (t transactionIDValueSetter) AddTo(m *Message) error { + m.TransactionID = t + m.WriteTransactionID() + return nil +} diff --git a/vendor/github.com/pion/stun/renovate.json b/vendor/github.com/pion/stun/renovate.json new file mode 100644 index 0000000..4400fd9 --- /dev/null +++ b/vendor/github.com/pion/stun/renovate.json @@ -0,0 +1,15 @@ +{ + "extends": [ + "config:base" + ], + "postUpdateOptions": [ + "gomodTidy" + ], + "commitBody": "Generated by renovateBot", + "packageRules": [ + { + "packagePatterns": ["^golang.org/x/"], + "schedule": ["on the first day of the month"] + } + ] +} diff --git a/vendor/github.com/pion/stun/stun.go b/vendor/github.com/pion/stun/stun.go new file mode 100644 index 0000000..9f804d2 --- /dev/null +++ b/vendor/github.com/pion/stun/stun.go @@ -0,0 +1,51 @@ +// Package stun implements Session Traversal Utilities for NAT (STUN) RFC 5389. +// +// The stun package is intended to use by package that implements extension +// to STUN (e.g. TURN) or client/server applications. +// +// Most methods are designed to be zero allocations. If it is not enough, +// low-level methods are available. On other hand, there are helpers that +// reduce code repeat. +// +// See examples for Message for basic usage, or https://github.com/pion/turn +// package for example of stun extension implementation. +package stun + +import ( + "encoding/binary" + "io" +) + +// bin is shorthand to binary.BigEndian. +var bin = binary.BigEndian + +func readFullOrPanic(r io.Reader, v []byte) int { + n, err := io.ReadFull(r, v) + if err != nil { + panic(err) // nolint + } + return n +} + +func writeOrPanic(w io.Writer, v []byte) int { + n, err := w.Write(v) + if err != nil { + panic(err) // nolint + } + return n +} + +// IANA assigned ports for "stun" protocol. +const ( + DefaultPort = 3478 + DefaultTLSPort = 5349 +) + +type transactionIDSetter struct{} + +func (transactionIDSetter) AddTo(m *Message) error { + return m.NewTransactionID() +} + +// TransactionID is Setter for m.TransactionID. +var TransactionID Setter = transactionIDSetter{} diff --git a/vendor/github.com/pion/stun/textattrs.go b/vendor/github.com/pion/stun/textattrs.go new file mode 100644 index 0000000..efdfbd9 --- /dev/null +++ b/vendor/github.com/pion/stun/textattrs.go @@ -0,0 +1,129 @@ +package stun + +// NewUsername returns Username with provided value. +func NewUsername(username string) Username { + return Username(username) +} + +// Username represents USERNAME attribute. +// +// RFC 5389 Section 15.3 +type Username []byte + +func (u Username) String() string { + return string(u) +} + +const maxUsernameB = 513 + +// AddTo adds USERNAME attribute to message. +func (u Username) AddTo(m *Message) error { + return TextAttribute(u).AddToAs(m, AttrUsername, maxUsernameB) +} + +// GetFrom gets USERNAME from message. +func (u *Username) GetFrom(m *Message) error { + return (*TextAttribute)(u).GetFromAs(m, AttrUsername) +} + +// NewRealm returns Realm with provided value. +// Must be SASL-prepared. +func NewRealm(realm string) Realm { + return Realm(realm) +} + +// Realm represents REALM attribute. +// +// RFC 5389 Section 15.7 +type Realm []byte + +func (n Realm) String() string { + return string(n) +} + +const maxRealmB = 763 + +// AddTo adds NONCE to message. +func (n Realm) AddTo(m *Message) error { + return TextAttribute(n).AddToAs(m, AttrRealm, maxRealmB) +} + +// GetFrom gets REALM from message. +func (n *Realm) GetFrom(m *Message) error { + return (*TextAttribute)(n).GetFromAs(m, AttrRealm) +} + +const softwareRawMaxB = 763 + +// Software is SOFTWARE attribute. +// +// RFC 5389 Section 15.10 +type Software []byte + +func (s Software) String() string { + return string(s) +} + +// NewSoftware returns *Software from string. +func NewSoftware(software string) Software { + return Software(software) +} + +// AddTo adds Software attribute to m. +func (s Software) AddTo(m *Message) error { + return TextAttribute(s).AddToAs(m, AttrSoftware, softwareRawMaxB) +} + +// GetFrom decodes Software from m. +func (s *Software) GetFrom(m *Message) error { + return (*TextAttribute)(s).GetFromAs(m, AttrSoftware) +} + +// Nonce represents NONCE attribute. +// +// RFC 5389 Section 15.8 +type Nonce []byte + +// NewNonce returns new Nonce from string. +func NewNonce(nonce string) Nonce { + return Nonce(nonce) +} + +func (n Nonce) String() string { + return string(n) +} + +const maxNonceB = 763 + +// AddTo adds NONCE to message. +func (n Nonce) AddTo(m *Message) error { + return TextAttribute(n).AddToAs(m, AttrNonce, maxNonceB) +} + +// GetFrom gets NONCE from message. +func (n *Nonce) GetFrom(m *Message) error { + return (*TextAttribute)(n).GetFromAs(m, AttrNonce) +} + +// TextAttribute is helper for adding and getting text attributes. +type TextAttribute []byte + +// AddToAs adds attribute with type t to m, checking maximum length. If maxLen +// is less than 0, no check is performed. +func (v TextAttribute) AddToAs(m *Message, t AttrType, maxLen int) error { + if err := CheckOverflow(t, len(v), maxLen); err != nil { + return err + } + m.Add(t, v) + return nil +} + +// GetFromAs gets t attribute from m and appends its value to reseted v. +func (v *TextAttribute) GetFromAs(m *Message, t AttrType) error { + a, err := m.Get(t) + if err != nil { + return err + } + *v = a + return nil +} diff --git a/vendor/github.com/pion/stun/uattrs.go b/vendor/github.com/pion/stun/uattrs.go new file mode 100644 index 0000000..238d32d --- /dev/null +++ b/vendor/github.com/pion/stun/uattrs.go @@ -0,0 +1,63 @@ +package stun + +import "errors" + +// UnknownAttributes represents UNKNOWN-ATTRIBUTES attribute. +// +// RFC 5389 Section 15.9 +type UnknownAttributes []AttrType + +func (a UnknownAttributes) String() string { + s := "" + if len(a) == 0 { + return "<nil>" + } + last := len(a) - 1 + for i, t := range a { + s += t.String() + if i != last { + s += ", " + } + } + return s +} + +// type size is 16 bit. +const attrTypeSize = 4 + +// AddTo adds UNKNOWN-ATTRIBUTES attribute to message. +func (a UnknownAttributes) AddTo(m *Message) error { + v := make([]byte, 0, attrTypeSize*20) // 20 should be enough + // If len(a.Types) > 20, there will be allocations. + for i, t := range a { + v = append(v, 0, 0, 0, 0) // 4 times by 0 (16 bits) + first := attrTypeSize * i + last := first + attrTypeSize + bin.PutUint16(v[first:last], t.Value()) + } + m.Add(AttrUnknownAttributes, v) + return nil +} + +// ErrBadUnknownAttrsSize means that UNKNOWN-ATTRIBUTES attribute value +// has invalid length. +var ErrBadUnknownAttrsSize = errors.New("bad UNKNOWN-ATTRIBUTES size") + +// GetFrom parses UNKNOWN-ATTRIBUTES from message. +func (a *UnknownAttributes) GetFrom(m *Message) error { + v, err := m.Get(AttrUnknownAttributes) + if err != nil { + return err + } + if len(v)%attrTypeSize != 0 { + return ErrBadUnknownAttrsSize + } + *a = (*a)[:0] + first := 0 + for first < len(v) { + last := first + attrTypeSize + *a = append(*a, AttrType(bin.Uint16(v[first:last]))) + first = last + } + return nil +} diff --git a/vendor/github.com/pion/stun/xor.go b/vendor/github.com/pion/stun/xor.go new file mode 100644 index 0000000..34365eb --- /dev/null +++ b/vendor/github.com/pion/stun/xor.go @@ -0,0 +1,62 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package stun + +import ( + "runtime" + "unsafe" +) + +// #nosec +const wordSize = int(unsafe.Sizeof(uintptr(0))) + +var supportsUnaligned = runtime.GOARCH == "386" || runtime.GOARCH == "amd64" + +// fastXORBytes xors in bulk. It only works on architectures that +// support unaligned read/writes. +// +// #nosec +func fastXORBytes(dst, a, b []byte) int { + n := len(a) + if len(b) < n { + n = len(b) + } + + w := n / wordSize + if w > 0 { + dw := *(*[]uintptr)(unsafe.Pointer(&dst)) + aw := *(*[]uintptr)(unsafe.Pointer(&a)) + bw := *(*[]uintptr)(unsafe.Pointer(&b)) + for i := 0; i < w; i++ { + dw[i] = aw[i] ^ bw[i] + } + } + + for i := n - n%wordSize; i < n; i++ { + dst[i] = a[i] ^ b[i] + } + + return n +} + +func safeXORBytes(dst, a, b []byte) int { + n := len(a) + if len(b) < n { + n = len(b) + } + for i := 0; i < n; i++ { + dst[i] = a[i] ^ b[i] + } + return n +} + +// xorBytes xors the bytes in a and b. The destination is assumed to have enough +// space. Returns the number of bytes xor'd. +func xorBytes(dst, a, b []byte) int { + if supportsUnaligned { + return fastXORBytes(dst, a, b) + } + return safeXORBytes(dst, a, b) +} diff --git a/vendor/github.com/pion/stun/xoraddr.go b/vendor/github.com/pion/stun/xoraddr.go new file mode 100644 index 0000000..23f7777 --- /dev/null +++ b/vendor/github.com/pion/stun/xoraddr.go @@ -0,0 +1,145 @@ +package stun + +import ( + "errors" + "fmt" + "io" + "net" + "strconv" +) + +const ( + familyIPv4 uint16 = 0x01 + familyIPv6 uint16 = 0x02 +) + +// XORMappedAddress implements XOR-MAPPED-ADDRESS attribute. +// +// RFC 5389 Section 15.2 +type XORMappedAddress struct { + IP net.IP + Port int +} + +func (a XORMappedAddress) String() string { + return net.JoinHostPort(a.IP.String(), strconv.Itoa(a.Port)) +} + +// isIPv4 returns true if ip with len of net.IPv6Len seems to be ipv4. +func isIPv4(ip net.IP) bool { + // Optimized for performance. Copied from net.IP.To4. + return isZeros(ip[0:10]) && ip[10] == 0xff && ip[11] == 0xff +} + +// Is p all zeros? +func isZeros(p net.IP) bool { + for i := 0; i < len(p); i++ { + if p[i] != 0 { + return false + } + } + return true +} + +// ErrBadIPLength means that len(IP) is not net.{IPv6len,IPv4len}. +var ErrBadIPLength = errors.New("invalid length of IP value") + +// AddToAs adds XOR-MAPPED-ADDRESS value to m as t attribute. +func (a XORMappedAddress) AddToAs(m *Message, t AttrType) error { + var ( + family = familyIPv4 + ip = a.IP + ) + if len(a.IP) == net.IPv6len { + if isIPv4(ip) { + ip = ip[12:16] // like in ip.To4() + } else { + family = familyIPv6 + } + } else if len(ip) != net.IPv4len { + return ErrBadIPLength + } + value := make([]byte, 32+128) + value[0] = 0 // first 8 bits are zeroes + xorValue := make([]byte, net.IPv6len) + copy(xorValue[4:], m.TransactionID[:]) + bin.PutUint32(xorValue[0:4], magicCookie) + bin.PutUint16(value[0:2], family) + bin.PutUint16(value[2:4], uint16(a.Port^magicCookie>>16)) + xorBytes(value[4:4+len(ip)], ip, xorValue) + m.Add(t, value[:4+len(ip)]) + return nil +} + +// AddTo adds XOR-MAPPED-ADDRESS to m. Can return ErrBadIPLength +// if len(a.IP) is invalid. +func (a XORMappedAddress) AddTo(m *Message) error { + return a.AddToAs(m, AttrXORMappedAddress) +} + +// GetFromAs decodes XOR-MAPPED-ADDRESS attribute value in message +// getting it as for t type. +func (a *XORMappedAddress) GetFromAs(m *Message, t AttrType) error { + v, err := m.Get(t) + if err != nil { + return err + } + family := bin.Uint16(v[0:2]) + if family != familyIPv6 && family != familyIPv4 { + return newDecodeErr("xor-mapped address", "family", + fmt.Sprintf("bad value %d", family), + ) + } + ipLen := net.IPv4len + if family == familyIPv6 { + ipLen = net.IPv6len + } + // Ensuring len(a.IP) == ipLen and reusing a.IP. + if len(a.IP) < ipLen { + a.IP = a.IP[:cap(a.IP)] + for len(a.IP) < ipLen { + a.IP = append(a.IP, 0) + } + } + a.IP = a.IP[:ipLen] + for i := range a.IP { + a.IP[i] = 0 + } + if len(v) <= 4 { + return io.ErrUnexpectedEOF + } + if err := CheckOverflow(t, len(v[4:]), len(a.IP)); err != nil { + return err + } + a.Port = int(bin.Uint16(v[2:4])) ^ (magicCookie >> 16) + xorValue := make([]byte, 4+TransactionIDSize) + bin.PutUint32(xorValue[0:4], magicCookie) + copy(xorValue[4:], m.TransactionID[:]) + xorBytes(a.IP, v[4:], xorValue) + return nil +} + +// GetFrom decodes XOR-MAPPED-ADDRESS attribute in message and returns +// error if any. While decoding, a.IP is reused if possible and can be +// rendered to invalid state (e.g. if a.IP was set to IPv6 and then +// IPv4 value were decoded into it), be careful. +// +// Example: +// +// expectedIP := net.ParseIP("213.141.156.236") +// expectedIP.String() // 213.141.156.236, 16 bytes, first 12 of them are zeroes +// expectedPort := 21254 +// addr := &XORMappedAddress{ +// IP: expectedIP, +// Port: expectedPort, +// } +// // addr were added to message that is decoded as newMessage +// // ... +// +// addr.GetFrom(newMessage) +// addr.IP.String() // 213.141.156.236, net.IPv4Len +// expectedIP.String() // d58d:9cec::ffff:d58d:9cec, 16 bytes, first 4 are IPv4 +// // now we have len(expectedIP) = 16 and len(addr.IP) = 4. +func (a *XORMappedAddress) GetFrom(m *Message) error { + return a.GetFromAs(m, AttrXORMappedAddress) +} diff --git a/vendor/github.com/pion/transport/LICENSE b/vendor/github.com/pion/transport/LICENSE new file mode 100644 index 0000000..ab60297 --- /dev/null +++ b/vendor/github.com/pion/transport/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/pion/transport/connctx/connctx.go b/vendor/github.com/pion/transport/connctx/connctx.go new file mode 100644 index 0000000..20510f2 --- /dev/null +++ b/vendor/github.com/pion/transport/connctx/connctx.go @@ -0,0 +1,172 @@ +// Package connctx wraps net.Conn using context.Context. +package connctx + +import ( + "context" + "errors" + "io" + "net" + "sync" + "sync/atomic" + "time" +) + +// ErrClosing is returned on Write to closed connection. +var ErrClosing = errors.New("use of closed network connection") + +// Reader is an interface for context controlled reader. +type Reader interface { + ReadContext(context.Context, []byte) (int, error) +} + +// Writer is an interface for context controlled writer. +type Writer interface { + WriteContext(context.Context, []byte) (int, error) +} + +// ReadWriter is a composite of ReadWriter. +type ReadWriter interface { + Reader + Writer +} + +// ConnCtx is a wrapper of net.Conn using context.Context. +type ConnCtx interface { + Reader + Writer + io.Closer + LocalAddr() net.Addr + RemoteAddr() net.Addr + Conn() net.Conn +} + +type connCtx struct { + nextConn net.Conn + closed chan struct{} + closeOnce sync.Once + readMu sync.Mutex + writeMu sync.Mutex +} + +var veryOld = time.Unix(0, 1) //nolint:gochecknoglobals + +// New creates a new ConnCtx wrapping given net.Conn. +func New(conn net.Conn) ConnCtx { + c := &connCtx{ + nextConn: conn, + closed: make(chan struct{}), + } + return c +} + +func (c *connCtx) ReadContext(ctx context.Context, b []byte) (int, error) { + c.readMu.Lock() + defer c.readMu.Unlock() + + select { + case <-c.closed: + return 0, io.EOF + default: + } + + done := make(chan struct{}) + var wg sync.WaitGroup + var errSetDeadline atomic.Value + wg.Add(1) + go func() { + defer wg.Done() + select { + case <-ctx.Done(): + // context canceled + if err := c.nextConn.SetReadDeadline(veryOld); err != nil { + errSetDeadline.Store(err) + return + } + <-done + if err := c.nextConn.SetReadDeadline(time.Time{}); err != nil { + errSetDeadline.Store(err) + } + case <-done: + } + }() + + n, err := c.nextConn.Read(b) + + close(done) + wg.Wait() + if e := ctx.Err(); e != nil && n == 0 { + err = e + } + if err2 := errSetDeadline.Load(); err == nil && err2 != nil { + err = err2.(error) + } + return n, err +} + +func (c *connCtx) WriteContext(ctx context.Context, b []byte) (int, error) { + c.writeMu.Lock() + defer c.writeMu.Unlock() + + select { + case <-c.closed: + return 0, ErrClosing + default: + } + + done := make(chan struct{}) + var wg sync.WaitGroup + var errSetDeadline atomic.Value + wg.Add(1) + go func() { + select { + case <-ctx.Done(): + // context canceled + if err := c.nextConn.SetWriteDeadline(veryOld); err != nil { + errSetDeadline.Store(err) + return + } + <-done + if err := c.nextConn.SetWriteDeadline(time.Time{}); err != nil { + errSetDeadline.Store(err) + } + case <-done: + } + wg.Done() + }() + + n, err := c.nextConn.Write(b) + + close(done) + wg.Wait() + if e := ctx.Err(); e != nil && n == 0 { + err = e + } + if err2 := errSetDeadline.Load(); err == nil && err2 != nil { + err = err2.(error) + } + return n, err +} + +func (c *connCtx) Close() error { + err := c.nextConn.Close() + c.closeOnce.Do(func() { + c.writeMu.Lock() + c.readMu.Lock() + close(c.closed) + c.readMu.Unlock() + c.writeMu.Unlock() + }) + return err +} + +func (c *connCtx) LocalAddr() net.Addr { + return c.nextConn.LocalAddr() +} + +func (c *connCtx) RemoteAddr() net.Addr { + return c.nextConn.RemoteAddr() +} + +func (c *connCtx) Conn() net.Conn { + return c.nextConn +} diff --git a/vendor/github.com/pion/transport/connctx/pipe.go b/vendor/github.com/pion/transport/connctx/pipe.go new file mode 100644 index 0000000..e2f0409 --- /dev/null +++ b/vendor/github.com/pion/transport/connctx/pipe.go @@ -0,0 +1,11 @@ +package connctx + +import ( + "net" +) + +// Pipe creates piped pair of ConnCtx. +func Pipe() (ConnCtx, ConnCtx) { + ca, cb := net.Pipe() + return New(ca), New(cb) +} diff --git a/vendor/github.com/pion/transport/deadline/deadline.go b/vendor/github.com/pion/transport/deadline/deadline.go new file mode 100644 index 0000000..6f97386 --- /dev/null +++ b/vendor/github.com/pion/transport/deadline/deadline.go @@ -0,0 +1,110 @@ +// Package deadline provides deadline timer used to implement +// net.Conn compatible connection +package deadline + +import ( + "context" + "sync" + "time" +) + +// Deadline signals updatable deadline timer. +// Also, it implements context.Context. +type Deadline struct { + exceeded chan struct{} + stop chan struct{} + stopped chan bool + deadline time.Time + mu sync.RWMutex +} + +// New creates new deadline timer. +func New() *Deadline { + d := &Deadline{ + exceeded: make(chan struct{}), + stop: make(chan struct{}), + stopped: make(chan bool, 1), + } + d.stopped <- true + return d +} + +// Set new deadline. Zero value means no deadline. +func (d *Deadline) Set(t time.Time) { + d.mu.Lock() + defer d.mu.Unlock() + + d.deadline = t + + close(d.stop) + + select { + case <-d.exceeded: + d.exceeded = make(chan struct{}) + default: + stopped := <-d.stopped + if !stopped { + d.exceeded = make(chan struct{}) + } + } + d.stop = make(chan struct{}) + d.stopped = make(chan bool, 1) + + if t.IsZero() { + d.stopped <- true + return + } + + if dur := time.Until(t); dur > 0 { + exceeded := d.exceeded + stopped := d.stopped + go func() { + select { + case <-time.After(dur): + close(exceeded) + stopped <- false + case <-d.stop: + stopped <- true + } + }() + return + } + + close(d.exceeded) + d.stopped <- false +} + +// Done receives deadline signal. +func (d *Deadline) Done() <-chan struct{} { + d.mu.RLock() + defer d.mu.RUnlock() + return d.exceeded +} + +// Err returns context.DeadlineExceeded if the deadline is exceeded. +// Otherwise, it returns nil. +func (d *Deadline) Err() error { + d.mu.RLock() + defer d.mu.RUnlock() + select { + case <-d.exceeded: + return context.DeadlineExceeded + default: + return nil + } +} + +// Deadline returns current deadline. +func (d *Deadline) Deadline() (time.Time, bool) { + d.mu.RLock() + defer d.mu.RUnlock() + if d.deadline.IsZero() { + return d.deadline, false + } + return d.deadline, true +} + +// Value returns nil. +func (d *Deadline) Value(interface{}) interface{} { + return nil +} diff --git a/vendor/github.com/pion/transport/packetio/buffer.go b/vendor/github.com/pion/transport/packetio/buffer.go new file mode 100644 index 0000000..97d86f8 --- /dev/null +++ b/vendor/github.com/pion/transport/packetio/buffer.go @@ -0,0 +1,347 @@ +// Package packetio provides packet buffer +package packetio + +import ( + "errors" + "io" + "sync" + "time" + + "github.com/pion/transport/deadline" +) + +var errPacketTooBig = errors.New("packet too big") + +// BufferPacketType allow the Buffer to know which packet protocol is writing. +type BufferPacketType int + +const ( + // RTPBufferPacket indicates the Buffer that is handling RTP packets + RTPBufferPacket BufferPacketType = 1 + // RTCPBufferPacket indicates the Buffer that is handling RTCP packets + RTCPBufferPacket BufferPacketType = 2 +) + +// Buffer allows writing packets to an intermediate buffer, which can then be read form. +// This is verify similar to bytes.Buffer but avoids combining multiple writes into a single read. +type Buffer struct { + mutex sync.Mutex + + // this is a circular buffer. If head <= tail, then the useful + // data is in the interval [head, tail[. If tail < head, then + // the useful data is the union of [head, len[ and [0, tail[. + // In order to avoid ambiguity when head = tail, we always leave + // an unused byte in the buffer. + data []byte + head, tail int + + notify chan struct{} + subs bool + closed bool + + count int + limitCount, limitSize int + + readDeadline *deadline.Deadline +} + +const ( + minSize = 2048 + cutoffSize = 128 * 1024 + maxSize = 4 * 1024 * 1024 +) + +// NewBuffer creates a new Buffer. +func NewBuffer() *Buffer { + return &Buffer{ + notify: make(chan struct{}), + readDeadline: deadline.New(), + } +} + +// available returns true if the buffer is large enough to fit a packet +// of the given size, taking overhead into account. +func (b *Buffer) available(size int) bool { + available := b.head - b.tail + if available <= 0 { + available += len(b.data) + } + // we interpret head=tail as empty, so always keep a byte free + if size+2+1 > available { + return false + } + + return true +} + +// grow increases the size of the buffer. If it returns nil, then the +// buffer has been grown. It returns ErrFull if hits a limit. +func (b *Buffer) grow() error { + var newsize int + if len(b.data) < cutoffSize { + newsize = 2 * len(b.data) + } else { + newsize = 5 * len(b.data) / 4 + } + if newsize < minSize { + newsize = minSize + } + if (b.limitSize <= 0 || sizeHardlimit) && newsize > maxSize { + newsize = maxSize + } + + // one byte slack + if b.limitSize > 0 && newsize > b.limitSize+1 { + newsize = b.limitSize + 1 + } + + if newsize <= len(b.data) { + return ErrFull + } + + newdata := make([]byte, newsize) + + var n int + if b.head <= b.tail { + // data was contiguous + n = copy(newdata, b.data[b.head:b.tail]) + } else { + // data was discontiguous + n = copy(newdata, b.data[b.head:]) + n += copy(newdata[n:], b.data[:b.tail]) + } + b.head = 0 + b.tail = n + b.data = newdata + + return nil +} + +// Write appends a copy of the packet data to the buffer. +// Returns ErrFull if the packet doesn't fit. +// +// Note that the packet size is limited to 65536 bytes since v0.11.0 due to the internal data structure. +func (b *Buffer) Write(packet []byte) (int, error) { + if len(packet) >= 0x10000 { + return 0, errPacketTooBig + } + + b.mutex.Lock() + + if b.closed { + b.mutex.Unlock() + return 0, io.ErrClosedPipe + } + + if (b.limitCount > 0 && b.count >= b.limitCount) || + (b.limitSize > 0 && b.size()+2+len(packet) > b.limitSize) { + b.mutex.Unlock() + return 0, ErrFull + } + + // grow the buffer until the packet fits + for !b.available(len(packet)) { + err := b.grow() + if err != nil { + b.mutex.Unlock() + return 0, err + } + } + + var notify chan struct{} + + if b.subs { + // readers are waiting. Prepare to notify, but only + // actually do it after we release the lock. + notify = b.notify + b.notify = make(chan struct{}) + b.subs = false + } + + // store the length of the packet + b.data[b.tail] = uint8(len(packet) >> 8) + b.tail++ + if b.tail >= len(b.data) { + b.tail = 0 + } + b.data[b.tail] = uint8(len(packet)) + b.tail++ + if b.tail >= len(b.data) { + b.tail = 0 + } + + // store the packet + n := copy(b.data[b.tail:], packet) + b.tail += n + if b.tail >= len(b.data) { + // we reached the end, wrap around + m := copy(b.data, packet[n:]) + b.tail = m + } + b.count++ + b.mutex.Unlock() + + if notify != nil { + close(notify) + } + + return len(packet), nil +} + +// Read populates the given byte slice, returning the number of bytes read. +// Blocks until data is available or the buffer is closed. +// Returns io.ErrShortBuffer is the packet is too small to copy the Write. +// Returns io.EOF if the buffer is closed. +func (b *Buffer) Read(packet []byte) (n int, err error) { + // Return immediately if the deadline is already exceeded. + select { + case <-b.readDeadline.Done(): + return 0, &netError{ErrTimeout, true, true} + default: + } + + for { + b.mutex.Lock() + + if b.head != b.tail { + // decode the packet size + n1 := b.data[b.head] + b.head++ + if b.head >= len(b.data) { + b.head = 0 + } + n2 := b.data[b.head] + b.head++ + if b.head >= len(b.data) { + b.head = 0 + } + count := int((uint16(n1) << 8) | uint16(n2)) + + // determine the number of bytes we'll actually copy + copied := count + if copied > len(packet) { + copied = len(packet) + } + + // copy the data + if b.head+copied < len(b.data) { + copy(packet, b.data[b.head:b.head+copied]) + } else { + k := copy(packet, b.data[b.head:]) + copy(packet[k:], b.data[:copied-k]) + } + + // advance head, discarding any data that wasn't copied + b.head += count + if b.head >= len(b.data) { + b.head -= len(b.data) + } + + if b.head == b.tail { + // the buffer is empty, reset to beginning + // in order to improve cache locality. + b.head = 0 + b.tail = 0 + } + + b.count-- + + b.mutex.Unlock() + + if copied < count { + return copied, io.ErrShortBuffer + } + return copied, nil + } + + if b.closed { + b.mutex.Unlock() + return 0, io.EOF + } + + notify := b.notify + b.subs = true + b.mutex.Unlock() + + select { + case <-b.readDeadline.Done(): + return 0, &netError{ErrTimeout, true, true} + case <-notify: + } + } +} + +// Close the buffer, unblocking any pending reads. +// Data in the buffer can still be read, Read will return io.EOF only when empty. +func (b *Buffer) Close() (err error) { + b.mutex.Lock() + + if b.closed { + b.mutex.Unlock() + return nil + } + + notify := b.notify + b.closed = true + + b.mutex.Unlock() + + close(notify) + + return nil +} + +// Count returns the number of packets in the buffer. +func (b *Buffer) Count() int { + b.mutex.Lock() + defer b.mutex.Unlock() + return b.count +} + +// SetLimitCount controls the maximum number of packets that can be buffered. +// Causes Write to return ErrFull when this limit is reached. +// A zero value will disable this limit. +func (b *Buffer) SetLimitCount(limit int) { + b.mutex.Lock() + defer b.mutex.Unlock() + + b.limitCount = limit +} + +// Size returns the total byte size of packets in the buffer, including +// a small amount of administrative overhead. +func (b *Buffer) Size() int { + b.mutex.Lock() + defer b.mutex.Unlock() + + return b.size() +} + +func (b *Buffer) size() int { + size := b.tail - b.head + if size < 0 { + size += len(b.data) + } + return size +} + +// SetLimitSize controls the maximum number of bytes that can be buffered. +// Causes Write to return ErrFull when this limit is reached. +// A zero value means 4MB since v0.11.0. +// +// User can set packetioSizeHardlimit build tag to enable 4MB hardlimit. +// When packetioSizeHardlimit build tag is set, SetLimitSize exceeding +// the hardlimit will be silently discarded. +func (b *Buffer) SetLimitSize(limit int) { + b.mutex.Lock() + defer b.mutex.Unlock() + + b.limitSize = limit +} + +// SetReadDeadline sets the deadline for the Read operation. +// Setting to zero means no deadline. +func (b *Buffer) SetReadDeadline(t time.Time) error { + b.readDeadline.Set(t) + return nil +} diff --git a/vendor/github.com/pion/transport/packetio/errors.go b/vendor/github.com/pion/transport/packetio/errors.go new file mode 100644 index 0000000..06f1b9d --- /dev/null +++ b/vendor/github.com/pion/transport/packetio/errors.go @@ -0,0 +1,27 @@ +package packetio + +import ( + "errors" +) + +// netError implements net.Error +type netError struct { + error + timeout, temporary bool +} + +func (e *netError) Timeout() bool { + return e.timeout +} + +func (e *netError) Temporary() bool { + return e.temporary +} + +var ( + // ErrFull is returned when the buffer has hit the configured limits. + ErrFull = errors.New("packetio.Buffer is full, discarding write") + + // ErrTimeout is returned when a deadline has expired + ErrTimeout = errors.New("i/o timeout") +) diff --git a/vendor/github.com/pion/transport/packetio/hardlimit.go b/vendor/github.com/pion/transport/packetio/hardlimit.go new file mode 100644 index 0000000..5ddacc7 --- /dev/null +++ b/vendor/github.com/pion/transport/packetio/hardlimit.go @@ -0,0 +1,5 @@ +// +build packetioSizeHardlimit + +package packetio + +const sizeHardlimit = true diff --git a/vendor/github.com/pion/transport/packetio/no_hardlimit.go b/vendor/github.com/pion/transport/packetio/no_hardlimit.go new file mode 100644 index 0000000..55ea308 --- /dev/null +++ b/vendor/github.com/pion/transport/packetio/no_hardlimit.go @@ -0,0 +1,5 @@ +// +build !packetioSizeHardlimit + +package packetio + +const sizeHardlimit = false diff --git a/vendor/github.com/pion/transport/replaydetector/fixedbig.go b/vendor/github.com/pion/transport/replaydetector/fixedbig.go new file mode 100644 index 0000000..a571a1a --- /dev/null +++ b/vendor/github.com/pion/transport/replaydetector/fixedbig.go @@ -0,0 +1,78 @@ +package replaydetector + +import ( + "fmt" +) + +// fixedBigInt is the fix-sized multi-word integer. +type fixedBigInt struct { + bits []uint64 + n uint + msbMask uint64 +} + +// newFixedBigInt creates a new fix-sized multi-word int. +func newFixedBigInt(n uint) *fixedBigInt { + chunkSize := (n + 63) / 64 + if chunkSize == 0 { + chunkSize = 1 + } + return &fixedBigInt{ + bits: make([]uint64, chunkSize), + n: n, + msbMask: (1 << (64 - n%64)) - 1, + } +} + +// Lsh is the left shift operation. +func (s *fixedBigInt) Lsh(n uint) { + if n == 0 { + return + } + nChunk := int(n / 64) + nN := n % 64 + + for i := len(s.bits) - 1; i >= 0; i-- { + var carry uint64 + if i-nChunk >= 0 { + carry = s.bits[i-nChunk] << nN + if i-nChunk-1 >= 0 { + carry |= s.bits[i-nChunk-1] >> (64 - nN) + } + } + s.bits[i] = (s.bits[i] << n) | carry + } + s.bits[len(s.bits)-1] &= s.msbMask +} + +// Bit returns i-th bit of the fixedBigInt. +func (s *fixedBigInt) Bit(i uint) uint { + if i >= s.n { + return 0 + } + chunk := i / 64 + pos := i % 64 + if s.bits[chunk]&(1<<pos) != 0 { + return 1 + } + return 0 +} + +// SetBit sets i-th bit to 1. +func (s *fixedBigInt) SetBit(i uint) { + if i >= s.n { + return + } + chunk := i / 64 + pos := i % 64 + s.bits[chunk] |= 1 << pos +} + +// String returns string representation of fixedBigInt. +func (s *fixedBigInt) String() string { + var out string + for i := len(s.bits) - 1; i >= 0; i-- { + out += fmt.Sprintf("%016X", s.bits[i]) + } + return out +} diff --git a/vendor/github.com/pion/transport/replaydetector/replaydetector.go b/vendor/github.com/pion/transport/replaydetector/replaydetector.go new file mode 100644 index 0000000..d942002 --- /dev/null +++ b/vendor/github.com/pion/transport/replaydetector/replaydetector.go @@ -0,0 +1,116 @@ +// Package replaydetector provides packet replay detection algorithm. +package replaydetector + +// ReplayDetector is the interface of sequence replay detector. +type ReplayDetector interface { + // Check returns true if given sequence number is not replayed. + // Call accept() to mark the packet is received properly. + Check(seq uint64) (accept func(), ok bool) +} + +type slidingWindowDetector struct { + latestSeq uint64 + maxSeq uint64 + windowSize uint + mask *fixedBigInt +} + +// New creates ReplayDetector. +// Created ReplayDetector doesn't allow wrapping. +// It can handle monotonically increasing sequence number up to +// full 64bit number. It is suitable for DTLS replay protection. +func New(windowSize uint, maxSeq uint64) ReplayDetector { + return &slidingWindowDetector{ + maxSeq: maxSeq, + windowSize: windowSize, + mask: newFixedBigInt(windowSize), + } +} + +func (d *slidingWindowDetector) Check(seq uint64) (accept func(), ok bool) { + if seq > d.maxSeq { + // Exceeded upper limit. + return func() {}, false + } + + if seq <= d.latestSeq { + if d.latestSeq >= uint64(d.windowSize)+seq { + return func() {}, false + } + if d.mask.Bit(uint(d.latestSeq-seq)) != 0 { + // The sequence number is duplicated. + return func() {}, false + } + } + + return func() { + if seq > d.latestSeq { + // Update the head of the window. + d.mask.Lsh(uint(seq - d.latestSeq)) + d.latestSeq = seq + } + diff := (d.latestSeq - seq) % d.maxSeq + d.mask.SetBit(uint(diff)) + }, true +} + +// WithWrap creates ReplayDetector allowing sequence wrapping. +// This is suitable for short bitwidth counter like SRTP and SRTCP. +func WithWrap(windowSize uint, maxSeq uint64) ReplayDetector { + return &wrappedSlidingWindowDetector{ + maxSeq: maxSeq, + windowSize: windowSize, + mask: newFixedBigInt(windowSize), + } +} + +type wrappedSlidingWindowDetector struct { + latestSeq uint64 + maxSeq uint64 + windowSize uint + mask *fixedBigInt + init bool +} + +func (d *wrappedSlidingWindowDetector) Check(seq uint64) (accept func(), ok bool) { + if seq > d.maxSeq { + // Exceeded upper limit. + return func() {}, false + } + if !d.init { + if seq != 0 { + d.latestSeq = seq - 1 + } else { + d.latestSeq = d.maxSeq + } + d.init = true + } + + diff := int64(d.latestSeq) - int64(seq) + // Wrap the number. + if diff > int64(d.maxSeq)/2 { + diff -= int64(d.maxSeq + 1) + } else if diff <= -int64(d.maxSeq)/2 { + diff += int64(d.maxSeq + 1) + } + + if diff >= int64(d.windowSize) { + // Too old. + return func() {}, false + } + if diff >= 0 { + if d.mask.Bit(uint(diff)) != 0 { + // The sequence number is duplicated. + return func() {}, false + } + } + + return func() { + if diff < 0 { + // Update the head of the window. + d.mask.Lsh(uint(-diff)) + d.latestSeq = seq + } + d.mask.SetBit(uint(d.latestSeq - seq)) + }, true +} diff --git a/vendor/github.com/pion/transport/vnet/.gitignore b/vendor/github.com/pion/transport/vnet/.gitignore new file mode 100644 index 0000000..d39fb86 --- /dev/null +++ b/vendor/github.com/pion/transport/vnet/.gitignore @@ -0,0 +1 @@ +*.sw[poe] diff --git a/vendor/github.com/pion/transport/vnet/README.md b/vendor/github.com/pion/transport/vnet/README.md new file mode 100644 index 0000000..b502f9f --- /dev/null +++ b/vendor/github.com/pion/transport/vnet/README.md @@ -0,0 +1,239 @@ +# vnet +A virtual network layer for pion. + +## Overview + +### Goals +* To make NAT traversal tests easy. +* To emulate packet impairment at application level for testing. +* To monitor packets at specified arbitrary interfaces. + +### Features +* Configurable virtual LAN and WAN +* Virtually hosted ICE servers + +### Virtual network components + +#### Top View +``` + ...................................... + : Virtual Network (vnet) : + : : + +-------+ * 1 +----+ +--------+ : + | :App |------------>|:Net|--o<-----|:Router | : + +-------+ +----+ | | : + +-----------+ * 1 +----+ | | : + |:STUNServer|-------->|:Net|--o<-----| | : + +-----------+ +----+ | | : + +-----------+ * 1 +----+ | | : + |:TURNServer|-------->|:Net|--o<-----| | : + +-----------+ +----+ [1] | | : + : 1 | | 1 <<has>> : + : +---<>| |<>----+ [2] : + : | +--------+ | : + To form | *| v 0..1 : + a subnet tree | o [3] +-----+ : + : | ^ |:NAT | : + : | | +-----+ : + : +-------+ : + ...................................... + Note: + o: NIC (Netork Interface Controller) + [1]: Net implments NIC interface. + [2]: Root router has no NAT. All child routers have a NAT always. + [3]: Router implements NIC interface for accesses from the + parent router. +``` + +#### Net +Net provides 3 interfaces: +* Configuration API (direct) +* Network API via Net (equivalent to net.Xxx()) +* Router access via NIC interface +``` + (Pion module/app, ICE servers, etc.) + +-----------+ + | :App | + +-----------+ + * | + | <<uses>> + 1 v + +---------+ 1 * +-----------+ 1 * +-----------+ 1 * +------+ + ..| :Router |----+------>o--| :Net |<>------|:Interface |<>------|:Addr | + +---------+ | NIC +-----------+ +-----------+ +------+ + | <<interface>> (vnet.Interface) (net.Addr) + | + | * +-----------+ 1 * +-----------+ 1 * +------+ + +------>o--| :Router |<>------|:Interface |<>------|:Addr | + NIC +-----------+ +-----------+ +------+ + <<interface>> (vnet.Interface) (net.Addr) +``` + +> The instance of `Net` will be the one passed around the project. +> Net class has public methods for configuration and for application use. + + +## Implementation + +### Design Policy +* Each pion package should have config object which has `Net` (of type vnet.Net) property. (just like how + we distribute `LoggerFactory` throughout the pion project. +* DNS => a simple dictionary (global)? +* Each Net has routing capability (a goroutine) +* Use interface provided net package as much as possible +* Routers are connected in a tree structure (no loop is allowed) + - To simplify routing + - Easy to control / monitor (stats, etc) +* Root router has no NAT (== Internet / WAN) +* Non-root router has a NAT always +* When a Net is instantiated, it will automatically add `lo0` and `eth0` interface, and `lo0` will +have one IP address, 127.0.0.1. (this is not used in pion/ice, however) +* When a Net is added to a router, the router automatically assign an IP address for `eth0` +interface. + - For simplicity +* User data won't fragment, but optionally drop chunk larger than MTU +* IPv6 is not supported + +### Basic steps for setting up virtual network +1. Create a root router (WAN) +1. Create child routers and add to its parent (forms a tree, don't create a loop!) +1. Add instances of Net to each routers +1. Call Stop(), or Stop(), on the top router, which propages all other routers + +#### Example: WAN with one endpoint (vnet) +```go +import ( + "net" + + "github.com/pion/transport/vnet" + "github.com/pion/logging" +) + +// Create WAN (a root router). +wan, err := vnet.NewRouter(&RouterConfig{ + CIDR: "0.0.0.0/0", + LoggerFactory: logging.NewDefaultLoggerFactory(), +}) + +// Create a network. +// You can specify a static IP for the instance of Net to use. If not specified, +// router will assign an IP address that is contained in the router's CIDR. +nw := vnet.NewNet(&vnet.NetConfig{ + StaticIP: "27.1.2.3", +}) + +// Add the network to the router. +// The router will assign an IP address to `nw`. +if err = wan.AddNet(nw); err != nil { + // handle error +} + +// Start router. +// This will start internal goroutine to route packets. +// If you set child routers (using AddRouter), the call on the root router +// will start the rest of routers for you. +if err = wan.Start(); err != nil { + // handle error +} + +// +// Your application runs here using `nw`. +// + +// Stop the router. +// This will stop all internal goroutines in the router tree. +// (No need to call Stop() on child routers) +if err = wan.Stop(); err != nil { + // handle error +} +``` + +#### Example of how to pass around the instance of vnet.Net +The instance of vnet.Net wraps a subset of net package to enable operations +on the virtual network. Your project must be able to pass the instance to +all your routines that do network operation with net package. A typical way +is to use a config param to create your instances with the virtual network +instance (`nw` in the above example) like this: + +```go +type AgentConfig struct { + : + Net: *vnet.Net, +} + +type Agent struct { + : + net: *vnet.Net, +} + +func NetAgent(config *AgentConfig) *Agent { + if config.Net == nil { + config.Net = vnet.NewNet(nil) // defaults to native operation + } + + return &Agent { + : + net: config.Net, + } +} +``` + +```go +// a.net is the instance of vnet.Net class +func (a *Agent) listenUDP(...) error { + conn, err := a.net.ListenPacket(udpString, ...) + if err != nil { + return nil, err + } + : +} +``` + + +### Compatibility and Support Status + +|`net`<br>(built-in)|`vnet`|Note| +|---|---|---| +|net.Interfaces()|a.net.Interfaces()|| +|net.InterfaceByName()|a.net.InterfaceByName()|| +|net.ResolveUDPAddr()|a.net.ResolveUDPAddr()|| +|net.ListenPacket()|a.net.ListenPacket()|| +|net.ListenUDP()|a.net.ListenUDP()|(ListenPacket() is recommended)| +|net.Listen()|a.net.Listen()|(TODO)| +|net.ListenTCP()|(not supported)|(Listen() would be recommended)| +|net.Dial()|a.net.Dial()|| +|net.DialUDP()|a.net.DialUDP()|| +|net.DialTCP()|(not supported)|| +|net.Interface|vnet.Interface|| +|net.PacketConn|(use it as-is)|| +|net.UDPConn|vnet.UDPConn|Use vnet.UDPPacketConn in your code| +|net.TCPConn|vnet.TCPConn|(TODO)|Use net.Conn in your code| +|net.Dialer|vnet.Dialer|Use a.net.CreateDialer() to create it.<br>The use of vnet.Dialer is currently experimental.| + +> `a.net` is an instance of Net class, and types are defined under the package name `vnet` + +> Most of other `interface` types in net package can be used as is. + +> Please post a github issue when other types/methods need to be added to vnet/vnet.Net. + +## TODO / Next Step +* Implement TCP (TCPConn, Listen) +* Support of IPv6 +* Write a bunch of examples for building virtual networks. +* Add network impairment features (on Router) + - Introduce lantecy / jitter + - Packet filtering handler (allow selectively drop packets, etc.) +* Add statistics data retrieval + - Total number of packets forward by each router + - Total number of packet loss + - Total number of connection failure (TCP) + +## References +* [Comparing Simulated Packet Loss and RealWorld Network Congestion](https://www.riverbed.com/document/fpo/WhitePaper-Riverbed-SimulatedPacketLoss.pdf) + +### Code experiments +* [CIDR and IPMask](https://play.golang.org/p/B7OBhkZqjmj) +* [Test with net.IP](https://play.golang.org/p/AgXd23wKY4W) +* [ListenPacket](https://play.golang.org/p/d4vasbnRimQ) +* [isDottedIP()](https://play.golang.org/p/t4aZ47TgJfO) +* [SplitHostPort](https://play.golang.org/p/JtvurlcMbhn) diff --git a/vendor/github.com/pion/transport/vnet/chunk.go b/vendor/github.com/pion/transport/vnet/chunk.go new file mode 100644 index 0000000..7a87a2f --- /dev/null +++ b/vendor/github.com/pion/transport/vnet/chunk.go @@ -0,0 +1,283 @@ +package vnet + +import ( + "fmt" + "net" + "strconv" + "strings" + "sync/atomic" + "time" +) + +type tcpFlag uint8 + +const ( + tcpFIN tcpFlag = 0x01 + tcpSYN tcpFlag = 0x02 + tcpRST tcpFlag = 0x04 + tcpPSH tcpFlag = 0x08 + tcpACK tcpFlag = 0x10 +) + +func (f tcpFlag) String() string { + var sa []string + if f&tcpFIN != 0 { + sa = append(sa, "FIN") + } + if f&tcpSYN != 0 { + sa = append(sa, "SYN") + } + if f&tcpRST != 0 { + sa = append(sa, "RST") + } + if f&tcpPSH != 0 { + sa = append(sa, "PSH") + } + if f&tcpACK != 0 { + sa = append(sa, "ACK") + } + + return strings.Join(sa, "-") +} + +// Generate a base36-encoded unique tag +// See: https://play.golang.org/p/0ZaAID1q-HN +var assignChunkTag = func() func() string { //nolint:gochecknoglobals + var tagCtr uint64 + + return func() string { + n := atomic.AddUint64(&tagCtr, 1) + return strconv.FormatUint(n, 36) + } +}() + +// Chunk represents a packet passed around in the vnet +type Chunk interface { + setTimestamp() time.Time // used by router + getTimestamp() time.Time // used by router + getSourceIP() net.IP // used by router + getDestinationIP() net.IP // used by router + setSourceAddr(address string) error // used by nat + setDestinationAddr(address string) error // used by nat + + SourceAddr() net.Addr + DestinationAddr() net.Addr + UserData() []byte + Tag() string + Clone() Chunk + Network() string // returns "udp" or "tcp" + String() string +} + +type chunkIP struct { + timestamp time.Time + sourceIP net.IP + destinationIP net.IP + tag string +} + +func (c *chunkIP) setTimestamp() time.Time { + c.timestamp = time.Now() + return c.timestamp +} + +func (c *chunkIP) getTimestamp() time.Time { + return c.timestamp +} + +func (c *chunkIP) getDestinationIP() net.IP { + return c.destinationIP +} + +func (c *chunkIP) getSourceIP() net.IP { + return c.sourceIP +} + +func (c *chunkIP) Tag() string { + return c.tag +} + +type chunkUDP struct { + chunkIP + sourcePort int + destinationPort int + userData []byte +} + +func newChunkUDP(srcAddr, dstAddr *net.UDPAddr) *chunkUDP { + return &chunkUDP{ + chunkIP: chunkIP{ + sourceIP: srcAddr.IP, + destinationIP: dstAddr.IP, + tag: assignChunkTag(), + }, + sourcePort: srcAddr.Port, + destinationPort: dstAddr.Port, + } +} + +func (c *chunkUDP) SourceAddr() net.Addr { + return &net.UDPAddr{ + IP: c.sourceIP, + Port: c.sourcePort, + } +} + +func (c *chunkUDP) DestinationAddr() net.Addr { + return &net.UDPAddr{ + IP: c.destinationIP, + Port: c.destinationPort, + } +} + +func (c *chunkUDP) UserData() []byte { + return c.userData +} + +func (c *chunkUDP) Clone() Chunk { + var userData []byte + if c.userData != nil { + userData = make([]byte, len(c.userData)) + copy(userData, c.userData) + } + + return &chunkUDP{ + chunkIP: chunkIP{ + timestamp: c.timestamp, + sourceIP: c.sourceIP, + destinationIP: c.destinationIP, + tag: c.tag, + }, + sourcePort: c.sourcePort, + destinationPort: c.destinationPort, + userData: userData, + } +} + +func (c *chunkUDP) Network() string { + return udpString +} + +func (c *chunkUDP) String() string { + src := c.SourceAddr() + dst := c.DestinationAddr() + return fmt.Sprintf("%s chunk %s %s => %s", + src.Network(), + c.tag, + src.String(), + dst.String(), + ) +} + +func (c *chunkUDP) setSourceAddr(address string) error { + addr, err := net.ResolveUDPAddr(udpString, address) + if err != nil { + return err + } + c.sourceIP = addr.IP + c.sourcePort = addr.Port + return nil +} + +func (c *chunkUDP) setDestinationAddr(address string) error { + addr, err := net.ResolveUDPAddr(udpString, address) + if err != nil { + return err + } + c.destinationIP = addr.IP + c.destinationPort = addr.Port + return nil +} + +type chunkTCP struct { + chunkIP + sourcePort int + destinationPort int + flags tcpFlag // control bits + userData []byte // only with PSH flag + // seq uint32 // always starts with 0 + // ack uint32 // always starts with 0 +} + +func newChunkTCP(srcAddr, dstAddr *net.TCPAddr, flags tcpFlag) *chunkTCP { + return &chunkTCP{ + chunkIP: chunkIP{ + sourceIP: srcAddr.IP, + destinationIP: dstAddr.IP, + tag: assignChunkTag(), + }, + sourcePort: srcAddr.Port, + destinationPort: dstAddr.Port, + flags: flags, + } +} + +func (c *chunkTCP) SourceAddr() net.Addr { + return &net.TCPAddr{ + IP: c.sourceIP, + Port: c.sourcePort, + } +} + +func (c *chunkTCP) DestinationAddr() net.Addr { + return &net.TCPAddr{ + IP: c.destinationIP, + Port: c.destinationPort, + } +} + +func (c *chunkTCP) UserData() []byte { + return c.userData +} + +func (c *chunkTCP) Clone() Chunk { + userData := make([]byte, len(c.userData)) + copy(userData, c.userData) + + return &chunkTCP{ + chunkIP: chunkIP{ + timestamp: c.timestamp, + sourceIP: c.sourceIP, + destinationIP: c.destinationIP, + }, + sourcePort: c.sourcePort, + destinationPort: c.destinationPort, + userData: userData, + } +} + +func (c *chunkTCP) Network() string { + return "tcp" +} + +func (c *chunkTCP) String() string { + src := c.SourceAddr() + dst := c.DestinationAddr() + return fmt.Sprintf("%s %s chunk %s %s => %s", + src.Network(), + c.flags.String(), + c.tag, + src.String(), + dst.String(), + ) +} + +func (c *chunkTCP) setSourceAddr(address string) error { + addr, err := net.ResolveTCPAddr("tcp", address) + if err != nil { + return err + } + c.sourceIP = addr.IP + c.sourcePort = addr.Port + return nil +} + +func (c *chunkTCP) setDestinationAddr(address string) error { + addr, err := net.ResolveTCPAddr("tcp", address) + if err != nil { + return err + } + c.destinationIP = addr.IP + c.destinationPort = addr.Port + return nil +} diff --git a/vendor/github.com/pion/transport/vnet/chunk_queue.go b/vendor/github.com/pion/transport/vnet/chunk_queue.go new file mode 100644 index 0000000..7b24462 --- /dev/null +++ b/vendor/github.com/pion/transport/vnet/chunk_queue.go @@ -0,0 +1,52 @@ +package vnet + +import ( + "sync" +) + +type chunkQueue struct { + chunks []Chunk + maxSize int // 0 or negative value: unlimited + mutex sync.RWMutex +} + +func newChunkQueue(maxSize int) *chunkQueue { + return &chunkQueue{maxSize: maxSize} +} + +func (q *chunkQueue) push(c Chunk) bool { + q.mutex.Lock() + defer q.mutex.Unlock() + + if q.maxSize > 0 && len(q.chunks) >= q.maxSize { + return false // dropped + } + + q.chunks = append(q.chunks, c) + return true +} + +func (q *chunkQueue) pop() (Chunk, bool) { + q.mutex.Lock() + defer q.mutex.Unlock() + + if len(q.chunks) == 0 { + return nil, false + } + + c := q.chunks[0] + q.chunks = q.chunks[1:] + + return c, true +} + +func (q *chunkQueue) peek() Chunk { + q.mutex.RLock() + defer q.mutex.RUnlock() + + if len(q.chunks) == 0 { + return nil + } + + return q.chunks[0] +} diff --git a/vendor/github.com/pion/transport/vnet/conn.go b/vendor/github.com/pion/transport/vnet/conn.go new file mode 100644 index 0000000..f4b8b92 --- /dev/null +++ b/vendor/github.com/pion/transport/vnet/conn.go @@ -0,0 +1,246 @@ +package vnet + +import ( + "errors" + "io" + "math" + "net" + "sync" + "time" +) + +const ( + maxReadQueueSize = 1024 +) + +var ( + errObsCannotBeNil = errors.New("obs cannot be nil") + errUseClosedNetworkConn = errors.New("use of closed network connection") + errAddrNotUDPAddr = errors.New("addr is not a net.UDPAddr") + errLocAddr = errors.New("something went wrong with locAddr") + errAlreadyClosed = errors.New("already closed") + errNoRemAddr = errors.New("no remAddr defined") +) + +// UDPPacketConn is packet-oriented connection for UDP. +type UDPPacketConn interface { + net.PacketConn + Read(b []byte) (int, error) + RemoteAddr() net.Addr + Write(b []byte) (int, error) +} + +// vNet implements this +type connObserver interface { + write(c Chunk) error + onClosed(addr net.Addr) + determineSourceIP(locIP, dstIP net.IP) net.IP +} + +// UDPConn is the implementation of the Conn and PacketConn interfaces for UDP network connections. +// comatible with net.PacketConn and net.Conn +type UDPConn struct { + locAddr *net.UDPAddr // read-only + remAddr *net.UDPAddr // read-only + obs connObserver // read-only + readCh chan Chunk // thread-safe + closed bool // requires mutex + mu sync.Mutex // to mutex closed flag + readTimer *time.Timer // thread-safe +} + +func newUDPConn(locAddr, remAddr *net.UDPAddr, obs connObserver) (*UDPConn, error) { + if obs == nil { + return nil, errObsCannotBeNil + } + + return &UDPConn{ + locAddr: locAddr, + remAddr: remAddr, + obs: obs, + readCh: make(chan Chunk, maxReadQueueSize), + readTimer: time.NewTimer(time.Duration(math.MaxInt64)), + }, nil +} + +// ReadFrom reads a packet from the connection, +// copying the payload into p. It returns the number of +// bytes copied into p and the return address that +// was on the packet. +// It returns the number of bytes read (0 <= n <= len(p)) +// and any error encountered. Callers should always process +// the n > 0 bytes returned before considering the error err. +// ReadFrom can be made to time out and return +// an Error with Timeout() == true after a fixed time limit; +// see SetDeadline and SetReadDeadline. +func (c *UDPConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) { +loop: + for { + select { + case chunk, ok := <-c.readCh: + if !ok { + break loop + } + var err error + n := copy(p, chunk.UserData()) + addr := chunk.SourceAddr() + if n < len(chunk.UserData()) { + err = io.ErrShortBuffer + } + + if c.remAddr != nil { + if addr.String() != c.remAddr.String() { + break // discard (shouldn't happen) + } + } + return n, addr, err + + case <-c.readTimer.C: + return 0, nil, &net.OpError{ + Op: "read", + Net: c.locAddr.Network(), + Addr: c.locAddr, + Err: newTimeoutError("i/o timeout"), + } + } + } + + return 0, nil, &net.OpError{ + Op: "read", + Net: c.locAddr.Network(), + Addr: c.locAddr, + Err: errUseClosedNetworkConn, + } +} + +// WriteTo writes a packet with payload p to addr. +// WriteTo can be made to time out and return +// an Error with Timeout() == true after a fixed time limit; +// see SetDeadline and SetWriteDeadline. +// On packet-oriented connections, write timeouts are rare. +func (c *UDPConn) WriteTo(p []byte, addr net.Addr) (n int, err error) { + dstAddr, ok := addr.(*net.UDPAddr) + if !ok { + return 0, errAddrNotUDPAddr + } + + srcIP := c.obs.determineSourceIP(c.locAddr.IP, dstAddr.IP) + if srcIP == nil { + return 0, errLocAddr + } + srcAddr := &net.UDPAddr{ + IP: srcIP, + Port: c.locAddr.Port, + } + + chunk := newChunkUDP(srcAddr, dstAddr) + chunk.userData = make([]byte, len(p)) + copy(chunk.userData, p) + if err := c.obs.write(chunk); err != nil { + return 0, err + } + return len(p), nil +} + +// Close closes the connection. +// Any blocked ReadFrom or WriteTo operations will be unblocked and return errors. +func (c *UDPConn) Close() error { + c.mu.Lock() + defer c.mu.Unlock() + + if c.closed { + return errAlreadyClosed + } + c.closed = true + close(c.readCh) + + c.obs.onClosed(c.locAddr) + return nil +} + +// LocalAddr returns the local network address. +func (c *UDPConn) LocalAddr() net.Addr { + return c.locAddr +} + +// SetDeadline sets the read and write deadlines associated +// with the connection. It is equivalent to calling both +// SetReadDeadline and SetWriteDeadline. +// +// A deadline is an absolute time after which I/O operations +// fail with a timeout (see type Error) instead of +// blocking. The deadline applies to all future and pending +// I/O, not just the immediately following call to ReadFrom or +// WriteTo. After a deadline has been exceeded, the connection +// can be refreshed by setting a deadline in the future. +// +// An idle timeout can be implemented by repeatedly extending +// the deadline after successful ReadFrom or WriteTo calls. +// +// A zero value for t means I/O operations will not time out. +func (c *UDPConn) SetDeadline(t time.Time) error { + return c.SetReadDeadline(t) +} + +// SetReadDeadline sets the deadline for future ReadFrom calls +// and any currently-blocked ReadFrom call. +// A zero value for t means ReadFrom will not time out. +func (c *UDPConn) SetReadDeadline(t time.Time) error { + var d time.Duration + var noDeadline time.Time + if t == noDeadline { + d = time.Duration(math.MaxInt64) + } else { + d = time.Until(t) + } + c.readTimer.Reset(d) + return nil +} + +// SetWriteDeadline sets the deadline for future WriteTo calls +// and any currently-blocked WriteTo call. +// Even if write times out, it may return n > 0, indicating that +// some of the data was successfully written. +// A zero value for t means WriteTo will not time out. +func (c *UDPConn) SetWriteDeadline(t time.Time) error { + // Write never blocks. + return nil +} + +// Read reads data from the connection. +// Read can be made to time out and return an Error with Timeout() == true +// after a fixed time limit; see SetDeadline and SetReadDeadline. +func (c *UDPConn) Read(b []byte) (int, error) { + n, _, err := c.ReadFrom(b) + return n, err +} + +// RemoteAddr returns the remote network address. +func (c *UDPConn) RemoteAddr() net.Addr { + return c.remAddr +} + +// Write writes data to the connection. +// Write can be made to time out and return an Error with Timeout() == true +// after a fixed time limit; see SetDeadline and SetWriteDeadline. +func (c *UDPConn) Write(b []byte) (int, error) { + if c.remAddr == nil { + return 0, errNoRemAddr + } + + return c.WriteTo(b, c.remAddr) +} + +func (c *UDPConn) onInboundChunk(chunk Chunk) { + c.mu.Lock() + defer c.mu.Unlock() + + if c.closed { + return + } + + select { + case c.readCh <- chunk: + default: + } +} diff --git a/vendor/github.com/pion/transport/vnet/conn_map.go b/vendor/github.com/pion/transport/vnet/conn_map.go new file mode 100644 index 0000000..d52818d --- /dev/null +++ b/vendor/github.com/pion/transport/vnet/conn_map.go @@ -0,0 +1,136 @@ +package vnet + +import ( + "errors" + "net" + "sync" +) + +var ( + errAddressAlreadyInUse = errors.New("address already in use") + errNoSuchUDPConn = errors.New("no such UDPConn") + errCannotRemoveUnspecifiedIP = errors.New("cannot remove unspecified IP by the specified IP") +) + +type udpConnMap struct { + portMap map[int][]*UDPConn + mutex sync.RWMutex +} + +func newUDPConnMap() *udpConnMap { + return &udpConnMap{ + portMap: map[int][]*UDPConn{}, + } +} + +func (m *udpConnMap) insert(conn *UDPConn) error { + m.mutex.Lock() + defer m.mutex.Unlock() + + udpAddr := conn.LocalAddr().(*net.UDPAddr) + + // check if the port has a listener + conns, ok := m.portMap[udpAddr.Port] + if ok { + if udpAddr.IP.IsUnspecified() { + return errAddressAlreadyInUse + } + + for _, conn := range conns { + laddr := conn.LocalAddr().(*net.UDPAddr) + if laddr.IP.IsUnspecified() || laddr.IP.Equal(udpAddr.IP) { + return errAddressAlreadyInUse + } + } + + conns = append(conns, conn) + } else { + conns = []*UDPConn{conn} + } + + m.portMap[udpAddr.Port] = conns + return nil +} + +func (m *udpConnMap) find(addr net.Addr) (*UDPConn, bool) { + m.mutex.Lock() // could be RLock, but we have delete() op + defer m.mutex.Unlock() + + udpAddr := addr.(*net.UDPAddr) + + if conns, ok := m.portMap[udpAddr.Port]; ok { + if udpAddr.IP.IsUnspecified() { + // pick the first one appears in the iteration + if len(conns) == 0 { + // This can't happen! + delete(m.portMap, udpAddr.Port) + return nil, false + } + return conns[0], true + } + + for _, conn := range conns { + laddr := conn.LocalAddr().(*net.UDPAddr) + if laddr.IP.IsUnspecified() || laddr.IP.Equal(udpAddr.IP) { + return conn, ok + } + } + } + + return nil, false +} + +func (m *udpConnMap) delete(addr net.Addr) error { + m.mutex.Lock() + defer m.mutex.Unlock() + + udpAddr := addr.(*net.UDPAddr) + + conns, ok := m.portMap[udpAddr.Port] + if !ok { + return errNoSuchUDPConn + } + + if udpAddr.IP.IsUnspecified() { + // remove all from this port + delete(m.portMap, udpAddr.Port) + return nil + } + + newConns := []*UDPConn{} + + for _, conn := range conns { + laddr := conn.LocalAddr().(*net.UDPAddr) + if laddr.IP.IsUnspecified() { + // This can't happen! + return errCannotRemoveUnspecifiedIP + } + + if laddr.IP.Equal(udpAddr.IP) { + continue + } + + newConns = append(newConns, conn) + } + + if len(newConns) == 0 { + delete(m.portMap, udpAddr.Port) + } else { + m.portMap[udpAddr.Port] = newConns + } + + return nil +} + +// size returns the number of UDPConns (UDP listeners) +func (m *udpConnMap) size() int { + m.mutex.RLock() + defer m.mutex.RUnlock() + + n := 0 + for _, conns := range m.portMap { + n += len(conns) + } + + return n +} diff --git a/vendor/github.com/pion/transport/vnet/errors.go b/vendor/github.com/pion/transport/vnet/errors.go new file mode 100644 index 0000000..d0e9394 --- /dev/null +++ b/vendor/github.com/pion/transport/vnet/errors.go @@ -0,0 +1,19 @@ +package vnet + +type timeoutError struct { + msg string +} + +func newTimeoutError(msg string) error { + return &timeoutError{ + msg: msg, + } +} + +func (e *timeoutError) Error() string { + return e.msg +} + +func (e *timeoutError) Timeout() bool { + return true +} diff --git a/vendor/github.com/pion/transport/vnet/interface.go b/vendor/github.com/pion/transport/vnet/interface.go new file mode 100644 index 0000000..ec80c0b --- /dev/null +++ b/vendor/github.com/pion/transport/vnet/interface.go @@ -0,0 +1,40 @@ +package vnet + +import ( + "errors" + "net" +) + +var errNoAddressAssigned = errors.New("no address assigned") + +// See: https://play.golang.org/p/nBO9KGYEziv + +// InterfaceBase ... +type InterfaceBase net.Interface + +// Interface ... +type Interface struct { + InterfaceBase + addrs []net.Addr +} + +// NewInterface ... +func NewInterface(ifc net.Interface) *Interface { + return &Interface{ + InterfaceBase: InterfaceBase(ifc), + addrs: nil, + } +} + +// AddAddr ... +func (ifc *Interface) AddAddr(addr net.Addr) { + ifc.addrs = append(ifc.addrs, addr) +} + +// Addrs ... +func (ifc *Interface) Addrs() ([]net.Addr, error) { + if len(ifc.addrs) == 0 { + return nil, errNoAddressAssigned + } + return ifc.addrs, nil +} diff --git a/vendor/github.com/pion/transport/vnet/nat.go b/vendor/github.com/pion/transport/vnet/nat.go new file mode 100644 index 0000000..4ece5fa --- /dev/null +++ b/vendor/github.com/pion/transport/vnet/nat.go @@ -0,0 +1,338 @@ +package vnet + +import ( + "errors" + "fmt" + "net" + "sync" + "time" + + "github.com/pion/logging" +) + +var ( + errNATRequriesMapping = errors.New("1:1 NAT requires more than one mapping") + errMismatchLengthIP = errors.New("length mismtach between mappedIPs and localIPs") + errNonUDPTranslationNotSupported = errors.New("non-udp translation is not supported yet") + errNoAssociatedLocalAddress = errors.New("no associated local address") + errNoNATBindingFound = errors.New("no NAT binding found") + errHasNoPermission = errors.New("has no permission") +) + +// EndpointDependencyType defines a type of behavioral dependendency on the +// remote endpoint's IP address or port number. This is used for the two +// kinds of behaviors: +// - Port mapping behavior +// - Filtering behavior +// See: https://tools.ietf.org/html/rfc4787 +type EndpointDependencyType uint8 + +const ( + // EndpointIndependent means the behavior is independent of the endpoint's address or port + EndpointIndependent EndpointDependencyType = iota + // EndpointAddrDependent means the behavior is dependent on the endpoint's address + EndpointAddrDependent + // EndpointAddrPortDependent means the behavior is dependent on the endpoint's address and port + EndpointAddrPortDependent +) + +// NATMode defines basic behavior of the NAT +type NATMode uint8 + +const ( + // NATModeNormal means the NAT behaves as a standard NAPT (RFC 2663). + NATModeNormal NATMode = iota + // NATModeNAT1To1 exhibits 1:1 DNAT where the external IP address is statically mapped to + // a specific local IP address with port number is preserved always between them. + // When this mode is selected, MappingBehavior, FilteringBehavior, PortPreservation and + // MappingLifeTime of NATType are ignored. + NATModeNAT1To1 +) + +const ( + defaultNATMappingLifeTime = 30 * time.Second +) + +// NATType has a set of parameters that define the behavior of NAT. +type NATType struct { + Mode NATMode + MappingBehavior EndpointDependencyType + FilteringBehavior EndpointDependencyType + Hairpining bool // Not implemented yet + PortPreservation bool // Not implemented yet + MappingLifeTime time.Duration +} + +type natConfig struct { + name string + natType NATType + mappedIPs []net.IP // mapped IPv4 + localIPs []net.IP // local IPv4, required only when the mode is NATModeNAT1To1 + loggerFactory logging.LoggerFactory +} + +type mapping struct { + proto string // "udp" or "tcp" + local string // "<local-ip>:<local-port>" + mapped string // "<mapped-ip>:<mapped-port>" + bound string // key: "[<remote-ip>[:<remote-port>]]" + filters map[string]struct{} // key: "[<remote-ip>[:<remote-port>]]" + expires time.Time // time to expire +} + +type networkAddressTranslator struct { + name string + natType NATType + mappedIPs []net.IP // mapped IPv4 + localIPs []net.IP // local IPv4, required only when the mode is NATModeNAT1To1 + outboundMap map[string]*mapping // key: "<proto>:<local-ip>:<local-port>[:remote-ip[:remote-port]] + inboundMap map[string]*mapping // key: "<proto>:<mapped-ip>:<mapped-port>" + udpPortCounter int + mutex sync.RWMutex + log logging.LeveledLogger +} + +func newNAT(config *natConfig) (*networkAddressTranslator, error) { + natType := config.natType + + if natType.Mode == NATModeNAT1To1 { + // 1:1 NAT behavior + natType.MappingBehavior = EndpointIndependent + natType.FilteringBehavior = EndpointIndependent + natType.PortPreservation = true + natType.MappingLifeTime = 0 + + if len(config.mappedIPs) == 0 { + return nil, errNATRequriesMapping + } + if len(config.mappedIPs) != len(config.localIPs) { + return nil, errMismatchLengthIP + } + } else { + // Normal (NAPT) behavior + natType.Mode = NATModeNormal + if natType.MappingLifeTime == 0 { + natType.MappingLifeTime = defaultNATMappingLifeTime + } + } + + return &networkAddressTranslator{ + name: config.name, + natType: natType, + mappedIPs: config.mappedIPs, + localIPs: config.localIPs, + outboundMap: map[string]*mapping{}, + inboundMap: map[string]*mapping{}, + log: config.loggerFactory.NewLogger("vnet"), + }, nil +} + +func (n *networkAddressTranslator) getPairedMappedIP(locIP net.IP) net.IP { + for i, ip := range n.localIPs { + if ip.Equal(locIP) { + return n.mappedIPs[i] + } + } + return nil +} + +func (n *networkAddressTranslator) getPairedLocalIP(mappedIP net.IP) net.IP { + for i, ip := range n.mappedIPs { + if ip.Equal(mappedIP) { + return n.localIPs[i] + } + } + return nil +} + +func (n *networkAddressTranslator) translateOutbound(from Chunk) (Chunk, error) { + n.mutex.Lock() + defer n.mutex.Unlock() + + to := from.Clone() + + if from.Network() == udpString { + if n.natType.Mode == NATModeNAT1To1 { + // 1:1 NAT behavior + srcAddr := from.SourceAddr().(*net.UDPAddr) + srcIP := n.getPairedMappedIP(srcAddr.IP) + if srcIP == nil { + n.log.Debugf("[%s] drop outbound chunk %s with not route", n.name, from.String()) + return nil, nil // silently discard + } + srcPort := srcAddr.Port + if err := to.setSourceAddr(fmt.Sprintf("%s:%d", srcIP.String(), srcPort)); err != nil { + return nil, err + } + } else { + // Normal (NAPT) behavior + var bound, filterKey string + switch n.natType.MappingBehavior { + case EndpointIndependent: + bound = "" + case EndpointAddrDependent: + bound = from.getDestinationIP().String() + case EndpointAddrPortDependent: + bound = from.DestinationAddr().String() + } + + switch n.natType.FilteringBehavior { + case EndpointIndependent: + filterKey = "" + case EndpointAddrDependent: + filterKey = from.getDestinationIP().String() + case EndpointAddrPortDependent: + filterKey = from.DestinationAddr().String() + } + + oKey := fmt.Sprintf("udp:%s:%s", from.SourceAddr().String(), bound) + + m := n.findOutboundMapping(oKey) + if m == nil { + // Create a new mapping + mappedPort := 0xC000 + n.udpPortCounter + n.udpPortCounter++ + + m = &mapping{ + proto: from.SourceAddr().Network(), + local: from.SourceAddr().String(), + bound: bound, + mapped: fmt.Sprintf("%s:%d", n.mappedIPs[0].String(), mappedPort), + filters: map[string]struct{}{}, + expires: time.Now().Add(n.natType.MappingLifeTime), + } + + n.outboundMap[oKey] = m + + iKey := fmt.Sprintf("udp:%s", m.mapped) + + n.log.Debugf("[%s] created a new NAT binding oKey=%s iKey=%s\n", + n.name, + oKey, + iKey) + + m.filters[filterKey] = struct{}{} + n.log.Debugf("[%s] permit access from %s to %s\n", n.name, filterKey, m.mapped) + n.inboundMap[iKey] = m + } else if _, ok := m.filters[filterKey]; !ok { + n.log.Debugf("[%s] permit access from %s to %s\n", n.name, filterKey, m.mapped) + m.filters[filterKey] = struct{}{} + } + + if err := to.setSourceAddr(m.mapped); err != nil { + return nil, err + } + } + + n.log.Debugf("[%s] translate outbound chunk from %s to %s", n.name, from.String(), to.String()) + + return to, nil + } + + return nil, errNonUDPTranslationNotSupported +} + +func (n *networkAddressTranslator) translateInbound(from Chunk) (Chunk, error) { + n.mutex.Lock() + defer n.mutex.Unlock() + + to := from.Clone() + + if from.Network() == udpString { + if n.natType.Mode == NATModeNAT1To1 { + // 1:1 NAT behavior + dstAddr := from.DestinationAddr().(*net.UDPAddr) + dstIP := n.getPairedLocalIP(dstAddr.IP) + if dstIP == nil { + return nil, fmt.Errorf("drop %s as %w", from.String(), errNoAssociatedLocalAddress) + } + dstPort := from.DestinationAddr().(*net.UDPAddr).Port + if err := to.setDestinationAddr(fmt.Sprintf("%s:%d", dstIP, dstPort)); err != nil { + return nil, err + } + } else { + // Normal (NAPT) behavior + iKey := fmt.Sprintf("udp:%s", from.DestinationAddr().String()) + m := n.findInboundMapping(iKey) + if m == nil { + return nil, fmt.Errorf("drop %s as %w", from.String(), errNoNATBindingFound) + } + + var filterKey string + switch n.natType.FilteringBehavior { + case EndpointIndependent: + filterKey = "" + case EndpointAddrDependent: + filterKey = from.getSourceIP().String() + case EndpointAddrPortDependent: + filterKey = from.SourceAddr().String() + } + + if _, ok := m.filters[filterKey]; !ok { + return nil, fmt.Errorf("drop %s as the remote %s %w", from.String(), filterKey, errHasNoPermission) + } + + // See RFC 4847 Section 4.3. Mapping Refresh + // a) Inbound refresh may be useful for applications with no outgoing + // UDP traffic. However, allowing inbound refresh may allow an + // external attacker or misbehaving application to keep a mapping + // alive indefinitely. This may be a security risk. Also, if the + // process is repeated with different ports, over time, it could + // use up all the ports on the NAT. + + if err := to.setDestinationAddr(m.local); err != nil { + return nil, err + } + } + + n.log.Debugf("[%s] translate inbound chunk from %s to %s", n.name, from.String(), to.String()) + + return to, nil + } + + return nil, errNonUDPTranslationNotSupported +} + +// caller must hold the mutex +func (n *networkAddressTranslator) findOutboundMapping(oKey string) *mapping { + now := time.Now() + + m, ok := n.outboundMap[oKey] + if ok { + // check if this mapping is expired + if now.After(m.expires) { + n.removeMapping(m) + m = nil // expired + } else { + m.expires = time.Now().Add(n.natType.MappingLifeTime) + } + } + + return m +} + +// caller must hold the mutex +func (n *networkAddressTranslator) findInboundMapping(iKey string) *mapping { + now := time.Now() + m, ok := n.inboundMap[iKey] + if !ok { + return nil + } + + // check if this mapping is expired + if now.After(m.expires) { + n.removeMapping(m) + return nil + } + + return m +} + +// caller must hold the mutex +func (n *networkAddressTranslator) removeMapping(m *mapping) { + oKey := fmt.Sprintf("%s:%s:%s", m.proto, m.local, m.bound) + iKey := fmt.Sprintf("%s:%s", m.proto, m.mapped) + + delete(n.outboundMap, oKey) + delete(n.inboundMap, iKey) +} diff --git a/vendor/github.com/pion/transport/vnet/net.go b/vendor/github.com/pion/transport/vnet/net.go new file mode 100644 index 0000000..4dc6a2a --- /dev/null +++ b/vendor/github.com/pion/transport/vnet/net.go @@ -0,0 +1,677 @@ +package vnet + +import ( + "encoding/binary" + "errors" + "fmt" + "math/rand" + "net" + "strconv" + "strings" + "sync" +) + +const ( + lo0String = "lo0String" + udpString = "udp" +) + +var ( + macAddrCounter uint64 = 0xBEEFED910200 //nolint:gochecknoglobals + errNoInterface = errors.New("no interface is available") + errNotFound = errors.New("not found") + errUnexpectedNetwork = errors.New("unexpected network") + errCantAssignRequestedAddr = errors.New("can't assign requested address") + errUnknownNetwork = errors.New("unknown network") + errNoRouterLinked = errors.New("no router linked") + errInvalidPortNumber = errors.New("invalid port number") + errUnexpectedTypeSwitchFailure = errors.New("unexpected type-switch failure") + errBindFailerFor = errors.New("bind failed for") + errEndPortLessThanStart = errors.New("end port is less than the start") + errPortSpaceExhausted = errors.New("port space exhausted") + errVNetDisabled = errors.New("vnet is not enabled") +) + +func newMACAddress() net.HardwareAddr { + b := make([]byte, 8) + binary.BigEndian.PutUint64(b, macAddrCounter) + macAddrCounter++ + return b[2:] +} + +type vNet struct { + interfaces []*Interface // read-only + staticIPs []net.IP // read-only + router *Router // read-only + udpConns *udpConnMap // read-only + mutex sync.RWMutex +} + +func (v *vNet) _getInterfaces() ([]*Interface, error) { + if len(v.interfaces) == 0 { + return nil, errNoInterface + } + + return v.interfaces, nil +} + +func (v *vNet) getInterfaces() ([]*Interface, error) { + v.mutex.RLock() + defer v.mutex.RUnlock() + + return v._getInterfaces() +} + +// caller must hold the mutex (read) +func (v *vNet) _getInterface(ifName string) (*Interface, error) { + ifs, err := v._getInterfaces() + if err != nil { + return nil, err + } + for _, ifc := range ifs { + if ifc.Name == ifName { + return ifc, nil + } + } + + return nil, fmt.Errorf("interface %s %w", ifName, errNotFound) +} + +func (v *vNet) getInterface(ifName string) (*Interface, error) { + v.mutex.RLock() + defer v.mutex.RUnlock() + + return v._getInterface(ifName) +} + +// caller must hold the mutex +func (v *vNet) getAllIPAddrs(ipv6 bool) []net.IP { + ips := []net.IP{} + + for _, ifc := range v.interfaces { + addrs, err := ifc.Addrs() + if err != nil { + continue + } + + for _, addr := range addrs { + var ip net.IP + if ipNet, ok := addr.(*net.IPNet); ok { + ip = ipNet.IP + } else if ipAddr, ok := addr.(*net.IPAddr); ok { + ip = ipAddr.IP + } else { + continue + } + + if !ipv6 { + if ip.To4() != nil { + ips = append(ips, ip) + } + } + } + } + + return ips +} + +func (v *vNet) setRouter(r *Router) error { + v.mutex.Lock() + defer v.mutex.Unlock() + + v.router = r + return nil +} + +func (v *vNet) onInboundChunk(c Chunk) { + v.mutex.Lock() + defer v.mutex.Unlock() + + if c.Network() == udpString { + if conn, ok := v.udpConns.find(c.DestinationAddr()); ok { + conn.onInboundChunk(c) + } + } +} + +// caller must hold the mutex +func (v *vNet) _dialUDP(network string, locAddr, remAddr *net.UDPAddr) (UDPPacketConn, error) { + // validate network + if network != udpString && network != "udp4" { + return nil, fmt.Errorf("%w: %s", errUnexpectedNetwork, network) + } + + if locAddr == nil { + locAddr = &net.UDPAddr{ + IP: net.IPv4zero, + } + } else if locAddr.IP == nil { + locAddr.IP = net.IPv4zero + } + + // validate address. do we have that address? + if !v.hasIPAddr(locAddr.IP) { + return nil, &net.OpError{ + Op: "listen", + Net: network, + Addr: locAddr, + Err: fmt.Errorf("bind: %w", errCantAssignRequestedAddr), + } + } + + if locAddr.Port == 0 { + // choose randomly from the range between 5000 and 5999 + port, err := v.assignPort(locAddr.IP, 5000, 5999) + if err != nil { + return nil, &net.OpError{ + Op: "listen", + Net: network, + Addr: locAddr, + Err: err, + } + } + locAddr.Port = port + } else if _, ok := v.udpConns.find(locAddr); ok { + return nil, &net.OpError{ + Op: "listen", + Net: network, + Addr: locAddr, + Err: fmt.Errorf("bind: %w", errAddressAlreadyInUse), + } + } + + conn, err := newUDPConn(locAddr, remAddr, v) + if err != nil { + return nil, err + } + + err = v.udpConns.insert(conn) + if err != nil { + return nil, err + } + + return conn, nil +} + +func (v *vNet) listenPacket(network string, address string) (UDPPacketConn, error) { + v.mutex.Lock() + defer v.mutex.Unlock() + + locAddr, err := v.resolveUDPAddr(network, address) + if err != nil { + return nil, err + } + + return v._dialUDP(network, locAddr, nil) +} + +func (v *vNet) listenUDP(network string, locAddr *net.UDPAddr) (UDPPacketConn, error) { + v.mutex.Lock() + defer v.mutex.Unlock() + + return v._dialUDP(network, locAddr, nil) +} + +func (v *vNet) dialUDP(network string, locAddr, remAddr *net.UDPAddr) (UDPPacketConn, error) { + v.mutex.Lock() + defer v.mutex.Unlock() + + return v._dialUDP(network, locAddr, remAddr) +} + +func (v *vNet) dial(network string, address string) (UDPPacketConn, error) { + v.mutex.Lock() + defer v.mutex.Unlock() + + remAddr, err := v.resolveUDPAddr(network, address) + if err != nil { + return nil, err + } + + // Determine source address + srcIP := v.determineSourceIP(nil, remAddr.IP) + + locAddr := &net.UDPAddr{IP: srcIP, Port: 0} + + return v._dialUDP(network, locAddr, remAddr) +} + +func (v *vNet) resolveUDPAddr(network, address string) (*net.UDPAddr, error) { + if network != udpString && network != "udp4" { + return nil, fmt.Errorf("%w %s", errUnknownNetwork, network) + } + + host, sPort, err := net.SplitHostPort(address) + if err != nil { + return nil, err + } + + // Check if host is a domain name + ip := net.ParseIP(host) + if ip == nil { + host = strings.ToLower(host) + if host == "localhost" { + ip = net.IPv4(127, 0, 0, 1) + } else { + // host is a domain name. resolve IP address by the name + if v.router == nil { + return nil, errNoRouterLinked + } + + ip, err = v.router.resolver.lookUp(host) + if err != nil { + return nil, err + } + } + } + + port, err := strconv.Atoi(sPort) + if err != nil { + return nil, errInvalidPortNumber + } + + udpAddr := &net.UDPAddr{ + IP: ip, + Port: port, + } + + return udpAddr, nil +} + +func (v *vNet) write(c Chunk) error { + if c.Network() == udpString { + if udp, ok := c.(*chunkUDP); ok { + if c.getDestinationIP().IsLoopback() { + if conn, ok := v.udpConns.find(udp.DestinationAddr()); ok { + conn.onInboundChunk(udp) + } + return nil + } + } else { + return errUnexpectedTypeSwitchFailure + } + } + + if v.router == nil { + return errNoRouterLinked + } + + v.router.push(c) + return nil +} + +func (v *vNet) onClosed(addr net.Addr) { + if addr.Network() == udpString { + //nolint:errcheck + v.udpConns.delete(addr) // #nosec + } +} + +// This method determines the srcIP based on the dstIP when locIP +// is any IP address ("0.0.0.0" or "::"). If locIP is a non-any addr, +// this method simply returns locIP. +// caller must hold the mutex +func (v *vNet) determineSourceIP(locIP, dstIP net.IP) net.IP { + if locIP != nil && !locIP.IsUnspecified() { + return locIP + } + + var srcIP net.IP + + if dstIP.IsLoopback() { + srcIP = net.ParseIP("127.0.0.1") + } else { + ifc, err2 := v._getInterface("eth0") + if err2 != nil { + return nil + } + + addrs, err2 := ifc.Addrs() + if err2 != nil { + return nil + } + + if len(addrs) == 0 { + return nil + } + + var findIPv4 bool + if locIP != nil { + findIPv4 = (locIP.To4() != nil) + } else { + findIPv4 = (dstIP.To4() != nil) + } + + for _, addr := range addrs { + ip := addr.(*net.IPNet).IP + if findIPv4 { + if ip.To4() != nil { + srcIP = ip + break + } + } else { + if ip.To4() == nil { + srcIP = ip + break + } + } + } + } + + return srcIP +} + +// caller must hold the mutex +func (v *vNet) hasIPAddr(ip net.IP) bool { //nolint:gocognit + for _, ifc := range v.interfaces { + if addrs, err := ifc.Addrs(); err == nil { + for _, addr := range addrs { + var locIP net.IP + if ipNet, ok := addr.(*net.IPNet); ok { + locIP = ipNet.IP + } else if ipAddr, ok := addr.(*net.IPAddr); ok { + locIP = ipAddr.IP + } else { + continue + } + + switch ip.String() { + case "0.0.0.0": + if locIP.To4() != nil { + return true + } + case "::": + if locIP.To4() == nil { + return true + } + default: + if locIP.Equal(ip) { + return true + } + } + } + } + } + + return false +} + +// caller must hold the mutex +func (v *vNet) allocateLocalAddr(ip net.IP, port int) error { + // gather local IP addresses to bind + var ips []net.IP + if ip.IsUnspecified() { + ips = v.getAllIPAddrs(ip.To4() == nil) + } else if v.hasIPAddr(ip) { + ips = []net.IP{ip} + } + + if len(ips) == 0 { + return fmt.Errorf("%w %s", errBindFailerFor, ip.String()) + } + + // check if all these transport addresses are not in use + for _, ip2 := range ips { + addr := &net.UDPAddr{ + IP: ip2, + Port: port, + } + if _, ok := v.udpConns.find(addr); ok { + return &net.OpError{ + Op: "bind", + Net: udpString, + Addr: addr, + Err: fmt.Errorf("bind: %w", errAddressAlreadyInUse), + } + } + } + + return nil +} + +// caller must hold the mutex +func (v *vNet) assignPort(ip net.IP, start, end int) (int, error) { + // choose randomly from the range between start and end (inclusive) + if end < start { + return -1, errEndPortLessThanStart + } + + space := end + 1 - start + offset := rand.Intn(space) //nolint:gosec + for i := 0; i < space; i++ { + port := ((offset + i) % space) + start + + err := v.allocateLocalAddr(ip, port) + if err == nil { + return port, nil + } + } + + return -1, errPortSpaceExhausted +} + +// NetConfig is a bag of configuration parameters passed to NewNet(). +type NetConfig struct { + // StaticIPs is an array of static IP addresses to be assigned for this Net. + // If no static IP address is given, the router will automatically assign + // an IP address. + StaticIPs []string + + // StaticIP is deprecated. Use StaticIPs. + StaticIP string +} + +// Net represents a local network stack euivalent to a set of layers from NIC +// up to the transport (UDP / TCP) layer. +type Net struct { + v *vNet + ifs []*Interface +} + +// NewNet creates an instance of Net. +// If config is nil, the virtual network is disabled. (uses corresponding +// net.Xxxx() operations. +// By design, it always have lo0 and eth0 interfaces. +// The lo0 has the address 127.0.0.1 assigned by default. +// IP address for eth0 will be assigned when this Net is added to a router. +func NewNet(config *NetConfig) *Net { + if config == nil { + ifs := []*Interface{} + if orgIfs, err := net.Interfaces(); err == nil { + for _, orgIfc := range orgIfs { + ifc := NewInterface(orgIfc) + if addrs, err := orgIfc.Addrs(); err == nil { + for _, addr := range addrs { + ifc.AddAddr(addr) + } + } + + ifs = append(ifs, ifc) + } + } + + return &Net{ifs: ifs} + } + + lo0 := NewInterface(net.Interface{ + Index: 1, + MTU: 16384, + Name: lo0String, + HardwareAddr: nil, + Flags: net.FlagUp | net.FlagLoopback | net.FlagMulticast, + }) + lo0.AddAddr(&net.IPNet{ + IP: net.ParseIP("127.0.0.1"), + Mask: net.CIDRMask(8, 32), + }) + + eth0 := NewInterface(net.Interface{ + Index: 2, + MTU: 1500, + Name: "eth0", + HardwareAddr: newMACAddress(), + Flags: net.FlagUp | net.FlagMulticast, + }) + + var staticIPs []net.IP + for _, ipStr := range config.StaticIPs { + if ip := net.ParseIP(ipStr); ip != nil { + staticIPs = append(staticIPs, ip) + } + } + if len(config.StaticIP) > 0 { + if ip := net.ParseIP(config.StaticIP); ip != nil { + staticIPs = append(staticIPs, ip) + } + } + + v := &vNet{ + interfaces: []*Interface{lo0, eth0}, + staticIPs: staticIPs, + udpConns: newUDPConnMap(), + } + + return &Net{ + v: v, + } +} + +// Interfaces returns a list of the system's network interfaces. +func (n *Net) Interfaces() ([]*Interface, error) { + if n.v == nil { + return n.ifs, nil + } + + return n.v.getInterfaces() +} + +// InterfaceByName returns the interface specified by name. +func (n *Net) InterfaceByName(name string) (*Interface, error) { + if n.v == nil { + for _, ifc := range n.ifs { + if ifc.Name == name { + return ifc, nil + } + } + + return nil, fmt.Errorf("interface %s %w", name, errNotFound) + } + + return n.v.getInterface(name) +} + +// ListenPacket announces on the local network address. +func (n *Net) ListenPacket(network string, address string) (net.PacketConn, error) { + if n.v == nil { + return net.ListenPacket(network, address) + } + + return n.v.listenPacket(network, address) +} + +// ListenUDP acts like ListenPacket for UDP networks. +func (n *Net) ListenUDP(network string, locAddr *net.UDPAddr) (UDPPacketConn, error) { + if n.v == nil { + return net.ListenUDP(network, locAddr) + } + + return n.v.listenUDP(network, locAddr) +} + +// Dial connects to the address on the named network. +func (n *Net) Dial(network, address string) (net.Conn, error) { + if n.v == nil { + return net.Dial(network, address) + } + + return n.v.dial(network, address) +} + +// CreateDialer creates an instance of vnet.Dialer +func (n *Net) CreateDialer(dialer *net.Dialer) Dialer { + if n.v == nil { + return &vDialer{ + dialer: dialer, + } + } + + return &vDialer{ + dialer: dialer, + v: n.v, + } +} + +// DialUDP acts like Dial for UDP networks. +func (n *Net) DialUDP(network string, laddr, raddr *net.UDPAddr) (UDPPacketConn, error) { + if n.v == nil { + return net.DialUDP(network, laddr, raddr) + } + + return n.v.dialUDP(network, laddr, raddr) +} + +// ResolveUDPAddr returns an address of UDP end point. +func (n *Net) ResolveUDPAddr(network, address string) (*net.UDPAddr, error) { + if n.v == nil { + return net.ResolveUDPAddr(network, address) + } + + return n.v.resolveUDPAddr(network, address) +} + +func (n *Net) getInterface(ifName string) (*Interface, error) { + if n.v == nil { + return nil, errVNetDisabled + } + + return n.v.getInterface(ifName) +} + +func (n *Net) setRouter(r *Router) error { + if n.v == nil { + return errVNetDisabled + } + + return n.v.setRouter(r) +} + +func (n *Net) onInboundChunk(c Chunk) { + if n.v == nil { + return + } + + n.v.onInboundChunk(c) +} + +func (n *Net) getStaticIPs() []net.IP { + if n.v == nil { + return nil + } + + return n.v.staticIPs +} + +// IsVirtual tests if the virtual network is enabled. +func (n *Net) IsVirtual() bool { + return n.v != nil +} + +// Dialer is identical to net.Dialer excepts that its methods +// (Dial, DialContext) are overridden to use virtual network. +// Use vnet.CreateDialer() to create an instance of this Dialer. +type Dialer interface { + Dial(network, address string) (net.Conn, error) +} + +type vDialer struct { + dialer *net.Dialer + v *vNet +} + +func (d *vDialer) Dial(network, address string) (net.Conn, error) { + if d.v == nil { + return d.dialer.Dial(network, address) + } + + return d.v.dial(network, address) +} diff --git a/vendor/github.com/pion/transport/vnet/resolver.go b/vendor/github.com/pion/transport/vnet/resolver.go new file mode 100644 index 0000000..e5166e3 --- /dev/null +++ b/vendor/github.com/pion/transport/vnet/resolver.go @@ -0,0 +1,89 @@ +package vnet + +import ( + "errors" + "fmt" + "net" + "sync" + + "github.com/pion/logging" +) + +var ( + errHostnameEmpty = errors.New("host name must not be empty") + errFailedtoParseIPAddr = errors.New("failed to parse IP address") +) + +type resolverConfig struct { + LoggerFactory logging.LoggerFactory +} + +type resolver struct { + parent *resolver // read-only + hosts map[string]net.IP // requires mutex + mutex sync.RWMutex // thread-safe + log logging.LeveledLogger // read-only +} + +func newResolver(config *resolverConfig) *resolver { + r := &resolver{ + hosts: map[string]net.IP{}, + log: config.LoggerFactory.NewLogger("vnet"), + } + + if err := r.addHost("localhost", "127.0.0.1"); err != nil { + r.log.Warn("failed to add localhost to resolver") + } + return r +} + +func (r *resolver) setParent(parent *resolver) { + r.mutex.Lock() + defer r.mutex.Unlock() + + r.parent = parent +} + +func (r *resolver) addHost(name string, ipAddr string) error { + r.mutex.Lock() + defer r.mutex.Unlock() + + if len(name) == 0 { + return errHostnameEmpty + } + ip := net.ParseIP(ipAddr) + if ip == nil { + return fmt.Errorf("%w \"%s\"", errFailedtoParseIPAddr, ipAddr) + } + r.hosts[name] = ip + return nil +} + +func (r *resolver) lookUp(hostName string) (net.IP, error) { + ip := func() net.IP { + r.mutex.RLock() + defer r.mutex.RUnlock() + + if ip2, ok := r.hosts[hostName]; ok { + return ip2 + } + return nil + }() + if ip != nil { + return ip, nil + } + + // mutex must be unlocked before calling into parent resolver + + if r.parent != nil { + return r.parent.lookUp(hostName) + } + + return nil, &net.DNSError{ + Err: "host not found", + Name: hostName, + Server: "vnet resolver", + IsTimeout: false, + IsTemporary: false, + } +} diff --git a/vendor/github.com/pion/transport/vnet/router.go b/vendor/github.com/pion/transport/vnet/router.go new file mode 100644 index 0000000..616d2c9 --- /dev/null +++ b/vendor/github.com/pion/transport/vnet/router.go @@ -0,0 +1,605 @@ +package vnet + +import ( + "errors" + "fmt" + "math/rand" + "net" + "strings" + "sync" + "sync/atomic" + "time" + + "github.com/pion/logging" +) + +const ( + defaultRouterQueueSize = 0 // unlimited +) + +var ( + errInvalidLocalIPinStaticIPs = errors.New("invalid local IP in StaticIPs") + errLocalIPBeyondStaticIPsSubset = errors.New("mapped in StaticIPs is beyond subnet") + errLocalIPNoStaticsIPsAssociated = errors.New("all StaticIPs must have associated local IPs") + errRouterAlreadyStarted = errors.New("router already started") + errRouterAlreadyStopped = errors.New("router already stopped") + errStaticIPisBeyondSubnet = errors.New("static IP is beyond subnet") + errAddressSpaceExhausted = errors.New("address space exhausted") + errNoIPAddrEth0 = errors.New("no IP address is assigned for eth0") +) + +// Generate a unique router name +var assignRouterName = func() func() string { //nolint:gochecknoglobals + var routerIDCtr uint64 + + return func() string { + n := atomic.AddUint64(&routerIDCtr, 1) + return fmt.Sprintf("router%d", n) + } +}() + +// RouterConfig ... +type RouterConfig struct { + // Name of router. If not specified, a unique name will be assigned. + Name string + // CIDR notation, like "192.0.2.0/24" + CIDR string + // StaticIPs is an array of static IP addresses to be assigned for this router. + // If no static IP address is given, the router will automatically assign + // an IP address. + // This will be ignored if this router is the root. + StaticIPs []string + // StaticIP is deprecated. Use StaticIPs. + StaticIP string + // Internal queue size + QueueSize int + // Effective only when this router has a parent router + NATType *NATType + // Minimum Delay + MinDelay time.Duration + // Max Jitter + MaxJitter time.Duration + // Logger factory + LoggerFactory logging.LoggerFactory +} + +// NIC is a nework inerface controller that interfaces Router +type NIC interface { + getInterface(ifName string) (*Interface, error) + onInboundChunk(c Chunk) + getStaticIPs() []net.IP + setRouter(r *Router) error +} + +// ChunkFilter is a handler users can add to filter chunks. +// If the filter returns false, the packet will be dropped. +type ChunkFilter func(c Chunk) bool + +// Router ... +type Router struct { + name string // read-only + interfaces []*Interface // read-only + ipv4Net *net.IPNet // read-only + staticIPs []net.IP // read-only + staticLocalIPs map[string]net.IP // read-only, + lastID byte // requires mutex [x], used to assign the last digit of IPv4 address + queue *chunkQueue // read-only + parent *Router // read-only + children []*Router // read-only + natType *NATType // read-only + nat *networkAddressTranslator // read-only + nics map[string]NIC // read-only + stopFunc func() // requires mutex [x] + resolver *resolver // read-only + chunkFilters []ChunkFilter // requires mutex [x] + minDelay time.Duration // requires mutex [x] + maxJitter time.Duration // requires mutex [x] + mutex sync.RWMutex // thread-safe + pushCh chan struct{} // writer requires mutex + loggerFactory logging.LoggerFactory // read-only + log logging.LeveledLogger // read-only +} + +// NewRouter ... +func NewRouter(config *RouterConfig) (*Router, error) { + loggerFactory := config.LoggerFactory + log := loggerFactory.NewLogger("vnet") + + _, ipv4Net, err := net.ParseCIDR(config.CIDR) + if err != nil { + return nil, err + } + + queueSize := defaultRouterQueueSize + if config.QueueSize > 0 { + queueSize = config.QueueSize + } + + // set up network interface, lo0 + lo0 := NewInterface(net.Interface{ + Index: 1, + MTU: 16384, + Name: lo0String, + HardwareAddr: nil, + Flags: net.FlagUp | net.FlagLoopback | net.FlagMulticast, + }) + lo0.AddAddr(&net.IPAddr{IP: net.ParseIP("127.0.0.1"), Zone: ""}) + + // set up network interface, eth0 + eth0 := NewInterface(net.Interface{ + Index: 2, + MTU: 1500, + Name: "eth0", + HardwareAddr: newMACAddress(), + Flags: net.FlagUp | net.FlagMulticast, + }) + + // local host name resolver + resolver := newResolver(&resolverConfig{ + LoggerFactory: config.LoggerFactory, + }) + + name := config.Name + if len(name) == 0 { + name = assignRouterName() + } + + var staticIPs []net.IP + staticLocalIPs := map[string]net.IP{} + for _, ipStr := range config.StaticIPs { + ipPair := strings.Split(ipStr, "/") + if ip := net.ParseIP(ipPair[0]); ip != nil { + if len(ipPair) > 1 { + locIP := net.ParseIP(ipPair[1]) + if locIP == nil { + return nil, errInvalidLocalIPinStaticIPs + } + if !ipv4Net.Contains(locIP) { + return nil, fmt.Errorf("local IP %s %w", locIP.String(), errLocalIPBeyondStaticIPsSubset) + } + staticLocalIPs[ip.String()] = locIP + } + staticIPs = append(staticIPs, ip) + } + } + if len(config.StaticIP) > 0 { + log.Warn("StaticIP is deprecated. Use StaticIPs instead") + if ip := net.ParseIP(config.StaticIP); ip != nil { + staticIPs = append(staticIPs, ip) + } + } + + if nStaticLocal := len(staticLocalIPs); nStaticLocal > 0 { + if nStaticLocal != len(staticIPs) { + return nil, errLocalIPNoStaticsIPsAssociated + } + } + + return &Router{ + name: name, + interfaces: []*Interface{lo0, eth0}, + ipv4Net: ipv4Net, + staticIPs: staticIPs, + staticLocalIPs: staticLocalIPs, + queue: newChunkQueue(queueSize), + natType: config.NATType, + nics: map[string]NIC{}, + resolver: resolver, + minDelay: config.MinDelay, + maxJitter: config.MaxJitter, + pushCh: make(chan struct{}, 1), + loggerFactory: loggerFactory, + log: log, + }, nil +} + +// caller must hold the mutex +func (r *Router) getInterfaces() ([]*Interface, error) { + if len(r.interfaces) == 0 { + return nil, fmt.Errorf("%w is available", errNoInterface) + } + + return r.interfaces, nil +} + +func (r *Router) getInterface(ifName string) (*Interface, error) { + r.mutex.RLock() + defer r.mutex.RUnlock() + + ifs, err := r.getInterfaces() + if err != nil { + return nil, err + } + for _, ifc := range ifs { + if ifc.Name == ifName { + return ifc, nil + } + } + + return nil, fmt.Errorf("interface %s %w", ifName, errNotFound) +} + +// Start ... +func (r *Router) Start() error { + r.mutex.Lock() + defer r.mutex.Unlock() + + if r.stopFunc != nil { + return errRouterAlreadyStarted + } + + cancelCh := make(chan struct{}) + + go func() { + loop: + for { + d, err := r.processChunks() + if err != nil { + r.log.Errorf("[%s] %s", r.name, err.Error()) + break + } + + if d <= 0 { + select { + case <-r.pushCh: + case <-cancelCh: + break loop + } + } else { + t := time.NewTimer(d) + select { + case <-t.C: + case <-cancelCh: + break loop + } + } + } + }() + + r.stopFunc = func() { + close(cancelCh) + } + + for _, child := range r.children { + if err := child.Start(); err != nil { + return err + } + } + + return nil +} + +// Stop ... +func (r *Router) Stop() error { + r.mutex.Lock() + defer r.mutex.Unlock() + + if r.stopFunc == nil { + return errRouterAlreadyStopped + } + + for _, router := range r.children { + r.mutex.Unlock() + err := router.Stop() + r.mutex.Lock() + + if err != nil { + return err + } + } + + r.stopFunc() + r.stopFunc = nil + return nil +} + +// caller must hold the mutex +func (r *Router) addNIC(nic NIC) error { + ifc, err := nic.getInterface("eth0") + if err != nil { + return err + } + + var ips []net.IP + + if ips = nic.getStaticIPs(); len(ips) == 0 { + // assign an IP address + ip, err2 := r.assignIPAddress() + if err2 != nil { + return err2 + } + ips = append(ips, ip) + } + + for _, ip := range ips { + if !r.ipv4Net.Contains(ip) { + return fmt.Errorf("%w: %s", errStaticIPisBeyondSubnet, r.ipv4Net.String()) + } + + ifc.AddAddr(&net.IPNet{ + IP: ip, + Mask: r.ipv4Net.Mask, + }) + + r.nics[ip.String()] = nic + } + + if err = nic.setRouter(r); err != nil { + return err + } + + return nil +} + +// AddRouter adds a chile Router. +func (r *Router) AddRouter(router *Router) error { + r.mutex.Lock() + defer r.mutex.Unlock() + + // Router is a NIC. Add it as a NIC so that packets are routed to this child + // router. + err := r.addNIC(router) + if err != nil { + return err + } + + if err = router.setRouter(r); err != nil { + return err + } + + r.children = append(r.children, router) + return nil +} + +// AddNet ... +func (r *Router) AddNet(nic NIC) error { + r.mutex.Lock() + defer r.mutex.Unlock() + + return r.addNIC(nic) +} + +// AddHost adds a mapping of hostname and an IP address to the local resolver. +func (r *Router) AddHost(hostName string, ipAddr string) error { + return r.resolver.addHost(hostName, ipAddr) +} + +// AddChunkFilter adds a filter for chunks traversing this router. +// You may add more than one filter. The filters are called in the order of this method call. +// If a chunk is dropped by a filter, subsequent filter will not receive the chunk. +func (r *Router) AddChunkFilter(filter ChunkFilter) { + r.mutex.Lock() + defer r.mutex.Unlock() + + r.chunkFilters = append(r.chunkFilters, filter) +} + +// caller should hold the mutex +func (r *Router) assignIPAddress() (net.IP, error) { + // See: https://stackoverflow.com/questions/14915188/ip-address-ending-with-zero + + if r.lastID == 0xfe { + return nil, errAddressSpaceExhausted + } + + ip := make(net.IP, 4) + copy(ip, r.ipv4Net.IP[:3]) + r.lastID++ + ip[3] = r.lastID + return ip, nil +} + +func (r *Router) push(c Chunk) { + r.mutex.Lock() + defer r.mutex.Unlock() + + r.log.Debugf("[%s] route %s", r.name, c.String()) + if r.stopFunc != nil { + c.setTimestamp() + if r.queue.push(c) { + select { + case r.pushCh <- struct{}{}: + default: + } + } else { + r.log.Warnf("[%s] queue was full. dropped a chunk", r.name) + } + } +} + +func (r *Router) processChunks() (time.Duration, error) { + r.mutex.Lock() + defer r.mutex.Unlock() + + // Introduce jitter by delaying the processing of chunks. + if r.maxJitter > 0 { + jitter := time.Duration(rand.Int63n(int64(r.maxJitter))) //nolint:gosec + time.Sleep(jitter) + } + + // cutOff + // v min delay + // |<--->| + // +------------:-- + // |OOOOOOXXXXX : --> time + // +------------:-- + // |<--->| now + // due + + enteredAt := time.Now() + cutOff := enteredAt.Add(-r.minDelay) + + var d time.Duration // the next sleep duration + + for { + d = 0 + + c := r.queue.peek() + if c == nil { + break // no more chunk in the queue + } + + // check timestamp to find if the chunk is due + if c.getTimestamp().After(cutOff) { + // There is one or more chunk in the queue but none of them are due. + // Calculate the next sleep duration here. + nextExpire := c.getTimestamp().Add(r.minDelay) + d = nextExpire.Sub(enteredAt) + break + } + + var ok bool + if c, ok = r.queue.pop(); !ok { + break // no more chunk in the queue + } + + blocked := false + for i := 0; i < len(r.chunkFilters); i++ { + filter := r.chunkFilters[i] + if !filter(c) { + blocked = true + break + } + } + if blocked { + continue // discard + } + + dstIP := c.getDestinationIP() + + // check if the desination is in our subnet + if r.ipv4Net.Contains(dstIP) { + // search for the destination NIC + var nic NIC + if nic, ok = r.nics[dstIP.String()]; !ok { + // NIC not found. drop it. + r.log.Debugf("[%s] %s unreachable", r.name, c.String()) + continue + } + + // found the NIC, forward the chunk to the NIC. + // call to NIC must unlock mutex + r.mutex.Unlock() + nic.onInboundChunk(c) + r.mutex.Lock() + continue + } + + // the destination is outside of this subnet + // is this WAN? + if r.parent == nil { + // this WAN. No route for this chunk + r.log.Debugf("[%s] no route found for %s", r.name, c.String()) + continue + } + + // Pass it to the parent via NAT + toParent, err := r.nat.translateOutbound(c) + if err != nil { + return 0, err + } + + if toParent == nil { + continue + } + + //nolint:godox + /* FIXME: this implementation would introduce a duplicate packet! + if r.nat.natType.Hairpining { + hairpinned, err := r.nat.translateInbound(toParent) + if err != nil { + r.log.Warnf("[%s] %s", r.name, err.Error()) + } else { + go func() { + r.push(hairpinned) + }() + } + } + */ + + // call to parent router mutex unlock mutex + r.mutex.Unlock() + r.parent.push(toParent) + r.mutex.Lock() + } + + return d, nil +} + +// caller must hold the mutex +func (r *Router) setRouter(parent *Router) error { + r.parent = parent + r.resolver.setParent(parent.resolver) + + // when this method is called, one or more IP address has already been assigned by + // the parent router. + ifc, err := r.getInterface("eth0") + if err != nil { + return err + } + + if len(ifc.addrs) == 0 { + return errNoIPAddrEth0 + } + + mappedIPs := []net.IP{} + localIPs := []net.IP{} + + for _, ifcAddr := range ifc.addrs { + var ip net.IP + switch addr := ifcAddr.(type) { + case *net.IPNet: + ip = addr.IP + case *net.IPAddr: // Do we really need this case? + ip = addr.IP + default: + } + + if ip == nil { + continue + } + + mappedIPs = append(mappedIPs, ip) + + if locIP := r.staticLocalIPs[ip.String()]; locIP != nil { + localIPs = append(localIPs, locIP) + } + } + + // Set up NAT here + if r.natType == nil { + r.natType = &NATType{ + MappingBehavior: EndpointIndependent, + FilteringBehavior: EndpointAddrPortDependent, + Hairpining: false, + PortPreservation: false, + MappingLifeTime: 30 * time.Second, + } + } + r.nat, err = newNAT(&natConfig{ + name: r.name, + natType: *r.natType, + mappedIPs: mappedIPs, + localIPs: localIPs, + loggerFactory: r.loggerFactory, + }) + if err != nil { + return err + } + + return nil +} + +func (r *Router) onInboundChunk(c Chunk) { + fromParent, err := r.nat.translateInbound(c) + if err != nil { + r.log.Warnf("[%s] %s", r.name, err.Error()) + return + } + + r.push(fromParent) +} + +func (r *Router) getStaticIPs() []net.IP { + return r.staticIPs +} diff --git a/vendor/github.com/pion/transport/vnet/udpproxy.go b/vendor/github.com/pion/transport/vnet/udpproxy.go new file mode 100644 index 0000000..c774955 --- /dev/null +++ b/vendor/github.com/pion/transport/vnet/udpproxy.go @@ -0,0 +1,176 @@ +package vnet + +import ( + "net" + "sync" + "time" +) + +// UDPProxy is a proxy between real server(net.UDPConn) and vnet.UDPConn. +// +// High level design: +// .............................................. +// : Virtual Network (vnet) : +// : : +// +-------+ * 1 +----+ +--------+ : +// | :App |------------>|:Net|--o<-----|:Router | ............................. +// +-------+ +----+ | | : UDPProxy : +// : | | +----+ +---------+ +---------+ +--------+ +// : | |--->o--|:Net|-->o-| vnet. |-->o-| net. |--->-| :Real | +// : | | +----+ | UDPConn | | UDPConn | | Server | +// : | | : +---------+ +---------+ +--------+ +// : | | ............................: +// : +--------+ : +// ............................................... +type UDPProxy struct { + // The router bind to. + router *Router + + // Each vnet source, bind to a real socket to server. + // key is real server addr, which is net.Addr + // value is *aUDPProxyWorker + workers sync.Map + + // For each endpoint, we never know when to start and stop proxy, + // so we stop the endpoint when timeout. + timeout time.Duration + + // For utest, to mock the target real server. + // Optional, use the address of received client packet. + mockRealServerAddr *net.UDPAddr +} + +// NewProxy create a proxy, the router for this proxy belongs/bind to. If need to proxy for +// please create a new proxy for each router. For all addresses we proxy, we will create a +// vnet.Net in this router and proxy all packets. +func NewProxy(router *Router) (*UDPProxy, error) { + v := &UDPProxy{router: router, timeout: 2 * time.Minute} + return v, nil +} + +// Close the proxy, stop all workers. +func (v *UDPProxy) Close() error { + // nolint:godox // TODO: FIXME: Do cleanup. + return nil +} + +// Proxy starts a worker for server, ignore if already started. +func (v *UDPProxy) Proxy(client *Net, server *net.UDPAddr) error { + // Note that even if the worker exists, it's also ok to create a same worker, + // because the router will use the last one, and the real server will see a address + // change event after we switch to the next worker. + if _, ok := v.workers.Load(server.String()); ok { + // nolint:godox // TODO: Need to restart the stopped worker? + return nil + } + + // Not exists, create a new one. + worker := &aUDPProxyWorker{ + router: v.router, mockRealServerAddr: v.mockRealServerAddr, + } + v.workers.Store(server.String(), worker) + + return worker.Proxy(client, server) +} + +// A proxy worker for a specified proxy server. +type aUDPProxyWorker struct { + router *Router + mockRealServerAddr *net.UDPAddr + + // Each vnet source, bind to a real socket to server. + // key is vnet client addr, which is net.Addr + // value is *net.UDPConn + endpoints sync.Map +} + +func (v *aUDPProxyWorker) Proxy(client *Net, serverAddr *net.UDPAddr) error { // nolint:gocognit + // Create vnet for real server by serverAddr. + nw := NewNet(&NetConfig{ + StaticIP: serverAddr.IP.String(), + }) + if err := v.router.AddNet(nw); err != nil { + return err + } + + // We must create a "same" vnet.UDPConn as the net.UDPConn, + // which has the same ip:port, to copy packets between them. + vnetSocket, err := nw.ListenUDP("udp4", serverAddr) + if err != nil { + return err + } + + // Got new vnet client, start a new endpoint. + findEndpointBy := func(addr net.Addr) (*net.UDPConn, error) { + // Exists binding. + if value, ok := v.endpoints.Load(addr.String()); ok { + // Exists endpoint, reuse it. + return value.(*net.UDPConn), nil + } + + // The real server we proxy to, for utest to mock it. + realAddr := serverAddr + if v.mockRealServerAddr != nil { + realAddr = v.mockRealServerAddr + } + + // Got new vnet client, create new endpoint. + realSocket, err := net.DialUDP("udp4", nil, realAddr) + if err != nil { + return nil, err + } + + // Bind address. + v.endpoints.Store(addr.String(), realSocket) + + // Got packet from real serverAddr, we should proxy it to vnet. + // nolint:godox // TODO: FIXME: Do cleanup. + go func(vnetClientAddr net.Addr) { + buf := make([]byte, 1500) + for { + n, _, err := realSocket.ReadFrom(buf) + if err != nil { + return + } + + if n <= 0 { + continue // Drop packet + } + + if _, err := vnetSocket.WriteTo(buf[:n], vnetClientAddr); err != nil { + return + } + } + }(addr) + + return realSocket, nil + } + + // Start a proxy goroutine. + // nolint:godox // TODO: FIXME: Do cleanup. + go func() { + buf := make([]byte, 1500) + + for { + n, addr, err := vnetSocket.ReadFrom(buf) + if err != nil { + return + } + + if n <= 0 || addr == nil { + continue // Drop packet + } + + realSocket, err := findEndpointBy(addr) + if err != nil { + continue // Drop packet. + } + + if _, err := realSocket.Write(buf[:n]); err != nil { + return + } + } + }() + + return nil +} diff --git a/vendor/github.com/pion/transport/vnet/vnet.go b/vendor/github.com/pion/transport/vnet/vnet.go new file mode 100644 index 0000000..bfe0f0f --- /dev/null +++ b/vendor/github.com/pion/transport/vnet/vnet.go @@ -0,0 +1,2 @@ +// Package vnet provides a virtual network layer for pion +package vnet diff --git a/vendor/github.com/pion/turn/v2/.gitignore b/vendor/github.com/pion/turn/v2/.gitignore new file mode 100644 index 0000000..98c12d9 --- /dev/null +++ b/vendor/github.com/pion/turn/v2/.gitignore @@ -0,0 +1,9 @@ +*.sw[poe] +examples/turn-client/tcp/tcp +examples/turn-client/udp/udp +examples/turn-server/add-software-attribute/add-software-attribute +examples/turn-server/log/log +examples/turn-server/simple/simple +examples/turn-server/tcp/tcp +examples/lt-cred-generator/lt-cred-generator +examples/turn-server/lt-cred/lt-cred diff --git a/vendor/github.com/pion/turn/v2/.golangci.yml b/vendor/github.com/pion/turn/v2/.golangci.yml new file mode 100644 index 0000000..8e4185a --- /dev/null +++ b/vendor/github.com/pion/turn/v2/.golangci.yml @@ -0,0 +1,88 @@ +linters-settings: + govet: + check-shadowing: true + misspell: + locale: US + exhaustive: + default-signifies-exhaustive: true + gomodguard: + blocked: + modules: + - github.com/pkg/errors: + recommendations: + - errors + +linters: + enable: + - asciicheck # Simple linter to check that your code does not contain non-ASCII identifiers + - bodyclose # checks whether HTTP response body is closed successfully + - deadcode # Finds unused code + - depguard # Go linter that checks if package imports are in a list of acceptable packages + - dogsled # Checks assignments with too many blank identifiers (e.g. x, _, _, _, := f()) + - dupl # Tool for code clone detection + - errcheck # Errcheck is a program for checking for unchecked errors in go programs. These unchecked errors can be critical bugs in some cases + - exhaustive # check exhaustiveness of enum switch statements + - exportloopref # checks for pointers to enclosing loop variables + - gci # Gci control golang package import order and make it always deterministic. + - gochecknoglobals # Checks that no globals are present in Go code + - gochecknoinits # Checks that no init functions are present in Go code + - gocognit # Computes and checks the cognitive complexity of functions + - goconst # Finds repeated strings that could be replaced by a constant + - gocritic # The most opinionated Go source code linter + - godox # Tool for detection of FIXME, TODO and other comment keywords + - goerr113 # Golang linter to check the errors handling expressions + - gofmt # Gofmt checks whether code was gofmt-ed. By default this tool runs with -s option to check for code simplification + - gofumpt # Gofumpt checks whether code was gofumpt-ed. + - goheader # Checks is file header matches to pattern + - goimports # Goimports does everything that gofmt does. Additionally it checks unused imports + - golint # Golint differs from gofmt. Gofmt reformats Go source code, whereas golint prints out style mistakes + - gomodguard # Allow and block list linter for direct Go module dependencies. This is different from depguard where there are different block types for example version constraints and module recommendations. + - goprintffuncname # Checks that printf-like functions are named with `f` at the end + - gosec # Inspects source code for security problems + - gosimple # Linter for Go source code that specializes in simplifying a code + - govet # Vet examines Go source code and reports suspicious constructs, such as Printf calls whose arguments do not align with the format string + - ineffassign # Detects when assignments to existing variables are not used + - misspell # Finds commonly misspelled English words in comments + - nakedret # Finds naked returns in functions greater than a specified function length + - noctx # noctx finds sending http request without context.Context + - scopelint # Scopelint checks for unpinned variables in go programs + - staticcheck # Staticcheck is a go vet on steroids, applying a ton of static analysis checks + - structcheck # Finds unused struct fields + - stylecheck # Stylecheck is a replacement for golint + - typecheck # Like the front-end of a Go compiler, parses and type-checks Go code + - unconvert # Remove unnecessary type conversions + - unparam # Reports unused function parameters + - unused # Checks Go code for unused constants, variables, functions and types + - varcheck # Finds unused global variables and constants + - whitespace # Tool for detection of leading and trailing whitespace + disable: + - funlen # Tool for detection of long functions + - gocyclo # Computes and checks the cyclomatic complexity of functions + - godot # Check if comments end in a period + - gomnd # An analyzer to detect magic numbers. + - lll # Reports long lines + - maligned # Tool to detect Go structs that would take less memory if their fields were sorted + - nestif # Reports deeply nested if statements + - nlreturn # nlreturn checks for a new line before return and branch statements to increase code clarity + - nolintlint # Reports ill-formed or insufficient nolint directives + - prealloc # Finds slice declarations that could potentially be preallocated + - rowserrcheck # checks whether Err of rows is checked successfully + - sqlclosecheck # Checks that sql.Rows and sql.Stmt are closed. + - testpackage # linter that makes you use a separate _test package + - wsl # Whitespace Linter - Forces you to use empty lines! + +issues: + exclude-rules: + # Allow complex tests, better to be self contained + - path: _test\.go + linters: + - gocognit + + # Allow complex main function in examples + - path: examples + text: "of func `main` is high" + linters: + - gocognit + +run: + skip-dirs-use-default: false diff --git a/vendor/github.com/pion/turn/v2/.goreleaser.yml b/vendor/github.com/pion/turn/v2/.goreleaser.yml new file mode 100644 index 0000000..c7f2efd --- /dev/null +++ b/vendor/github.com/pion/turn/v2/.goreleaser.yml @@ -0,0 +1,109 @@ +before: + hooks: + - go mod tidy + +archives: +- replacements: + darwin: Darwin + linux: Linux + windows: Windows + 386: i386 + amd64: x86_64 + +checksum: + name_template: 'checksums.txt' + +snapshot: + name_template: "{{ .Tag }}-next" + +changelog: + sort: asc + filters: + exclude: + - '^docs:' + - '^test:' + +builds: + - binary: turn-client-tcp + id: turn-client-tcp + goos: + - darwin + - windows + - linux + - freebsd + goarch: + - amd64 + - 386 + env: + - CGO_ENABLED=0 + main: ./examples/turn-client/tcp + + - binary: turn-client-udp + id: turn-client-udp + goos: + - darwin + - windows + - linux + - freebsd + goarch: + - amd64 + - 386 + env: + - CGO_ENABLED=0 + main: ./examples/turn-client/udp + + - binary: turn-server-add-software-attribute + id: turn-server-add-software-attribute + goos: + - darwin + - windows + - linux + - freebsd + goarch: + - amd64 + - 386 + env: + - CGO_ENABLED=0 + main: ./examples/turn-server/add-software-attribute + + - binary: turn-server-log + id: turn-server-log + goos: + - darwin + - windows + - linux + - freebsd + goarch: + - amd64 + - 386 + env: + - CGO_ENABLED=0 + main: ./examples/turn-server/log + + - binary: turn-server-simple + id: turn-server-simple + goos: + - darwin + - windows + - linux + - freebsd + goarch: + - amd64 + - 386 + env: + - CGO_ENABLED=0 + main: ./examples/turn-server/simple/ + + - binary: turn-server-tcp + id: turn-server-tcp + goos: + - darwin + - windows + - linux + - freebsd + goarch: + - amd64 + - 386 + env: + - CGO_ENABLED=0 + main: ./examples/turn-server/tcp/ diff --git a/vendor/github.com/pion/turn/v2/DESIGN.md b/vendor/github.com/pion/turn/v2/DESIGN.md new file mode 100644 index 0000000..2c52466 --- /dev/null +++ b/vendor/github.com/pion/turn/v2/DESIGN.md @@ -0,0 +1,31 @@ +# Why Pion TURN +TURN servers aren't exactly a hot technology, they are usually an after thought when building something. Most of the time +beginners build an interesting WebRTC application, but at the very end realize they need a TURN server. It is really frustrating when you +want to share your cool new project, only to realize you have to run another service. + +Then you find yourself building from source, fighting with config files and making changes you don't fully understand. Pion TURN was born +hoping to solve these frustrations. These are the guiding principals/features that define pion-turn. + +## Easy setup +simple-turn is a statically built TURN server, configured by environment variables. The entire install setup is 5 commands, on any platform! +The goal is that anyone should be able to run a TURN server on any platform. + +## Integration first +pion-turn makes no assumptions about how you authenticate users, how you log, or even your topology! Instead of running a dedicated TURN server you +can inherit from github.com/pion/turn and set whatever logger you want. + +## Embeddable +You can add this to an existing service. This means all your config files stay homogeneous instead of having the mismatch that makes it harder to manage your services. +For small setups it is usually an overkill to deploy dedicated TURN servers, this makes it easier to solve the problems you care about. + +## Safe +Golang provides a great foundation to build safe network services. Especially when running a networked service that is highly concurrent bugs can be devastating. + +## Readable +All network interaction is commented with a link to the spec. This makes learning and debugging easier, the TURN server was written to also serve as a guide for others. + +## Tested +Every commit is tested via travis-ci Go provides fantastic facilities for testing, and more will be added as time goes on. + +## Shared libraries +Every pion product is built using shared libraries, allowing others to build things using existing tested STUN and TURN tools. diff --git a/vendor/github.com/pion/turn/v2/LICENSE.md b/vendor/github.com/pion/turn/v2/LICENSE.md new file mode 100644 index 0000000..5cc9cbd --- /dev/null +++ b/vendor/github.com/pion/turn/v2/LICENSE.md @@ -0,0 +1,7 @@ +Copyright 2018 Pion LLC + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/github.com/pion/turn/v2/README.md b/vendor/github.com/pion/turn/v2/README.md new file mode 100644 index 0000000..372b286 --- /dev/null +++ b/vendor/github.com/pion/turn/v2/README.md @@ -0,0 +1,96 @@ +<h1 align="center"> + <a href="https://pion.ly"><img src="./.github/gopher-pion.png" alt="Pion TURN" height="250px"></a> + <br> + Pion TURN + <br> +</h1> +<h4 align="center">A toolkit for building TURN clients and servers in Go</h4> +<p align="center"> + <a href="https://pion.ly"><img src="https://img.shields.io/badge/pion-turn-gray.svg?longCache=true&colorB=brightgreen" alt="Pion TURN"></a> + <a href="http://gophers.slack.com/messages/pion"><img src="https://img.shields.io/badge/join-us%20on%20slack-gray.svg?longCache=true&logo=slack&colorB=brightgreen" alt="Slack Widget"></a> + <a href="https://github.com/pion/awesome-pion" alt="Awesome Pion"><img src="https://cdn.rawgit.com/sindresorhus/awesome/d7305f38d29fed78fa85652e3a63e154dd8e8829/media/badge.svg"></a> + <br> + <a href="https://travis-ci.org/pion/turn"><img src="https://travis-ci.org/pion/turn.svg?branch=master" alt="Build Status"></a> + <a href="https://pkg.go.dev/github.com/pion/turn/v2"><img src="https://godoc.org/github.com/pion/turn?status.svg" alt="GoDoc"></a> + <a href="https://codecov.io/gh/pion/turn"><img src="https://codecov.io/gh/pion/turn/branch/master/graph/badge.svg" alt="Coverage Status"></a> + <a href="https://goreportcard.com/report/github.com/pion/turn"><img src="https://goreportcard.com/badge/github.com/pion/turn" alt="Go Report Card"></a> + <a href="https://www.codacy.com/app/pion/turn"><img src="https://api.codacy.com/project/badge/Grade/d53ec6c70576476cb16c140c2964afde" alt="Codacy Badge"></a> + <a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="License: MIT"></a> +</p> +<br> + +Pion TURN is a Go toolkit for building TURN servers and clients. We wrote it to solve problems we had when building RTC projects. + +* **Deployable** - Use modern tooling of the Go ecosystem. Stop generating config files. +* **Embeddable** - Include `pion/turn` in your existing applications. No need to manage another service. +* **Extendable** - TURN as an API so you can easily integrate with your existing monitoring and metrics. +* **Maintainable** - `pion/turn` is simple and well documented. Designed for learning and easy debugging. +* **Portable** - Quickly deploy to multiple architectures/platforms just by setting an environment variable. +* **Safe** - Stability and safety is important for network services. Go provides everything we need. +* **Scalable** - Create allocations and mutate state at runtime. Designed to make scaling easy. + +# Using +`pion/turn` is an API for building STUN/TURN clients and servers, not a binary you deploy then configure. It may require copying our examples and +making minor modifications to fit your need, no knowledge of Go is required however. You may be able to download the pre-made binaries of our examples +if you wish to get started quickly. + +The advantage of this is that you don't need to deal with complicated config files, or custom APIs to modify the state of Pion TURN. +After you instantiate an instance of a Pion TURN server or client you interact with it like any library. The quickest way to get started is to look at the +[examples](examples) or [GoDoc](https://godoc.org/github.com/pion/turn) + +# Examples +We try to cover most common use cases in [examples](examples). If more examples could be helpful please file an issue, we are always looking +to expand and improve `pion/turn` to make it easier for developers. + +To build any example you just need to run `go build` in the directory of the example you care about. +It is also very easy to [cross compile](https://dave.cheney.net/2015/08/22/cross-compilation-with-go-1-5) Go programs. + +You can also see `pion/turn` usage in [pion/ice](https://github.com/pion/ice) + +# [FAQ](https://github.com/pion/webrtc/wiki/FAQ) + +### RFCs +#### Implemented +* [RFC 5389: Session Traversal Utilities for NAT (STUN)](https://tools.ietf.org/html/rfc5389) +* [RFC 5766: Traversal Using Relays around NAT (TURN)](https://tools.ietf.org/html/rfc5766) + +#### Planned +* [RFC 6062: Traversal Using Relays around NAT (TURN) Extensions for TCP Allocations](https://tools.ietf.org/html/rfc6062) +* [RFC 6156: Traversal Using Relays around NAT (TURN) Extension for IPv6](https://tools.ietf.org/html/rfc6156) + +### Community +Pion has an active community on the [Golang Slack](https://pion.ly/slack). Sign up and join the **#pion** channel for discussions and support. + +We are always looking to support **your projects**. Please reach out if you have something to build! + +### Contributing +Check out the [CONTRIBUTING.md](CONTRIBUTING.md) to join the group of amazing people making this project possible: + +* [Michiel De Backker](https://github.com/backkem) - *Documentation* +* [Ingmar Wittkau](https://github.com/iwittkau) - *STUN client* +* [John Bradley](https://github.com/kc5nra) - *Original Author* +* [jose nazario](https://github.com/paralax) - *Documentation* +* [Mészáros Mihály](https://github.com/misi) - *Documentation* +* [Mike Santry](https://github.com/santrym) - *Mascot* +* [Sean DuBois](https://github.com/Sean-Der) - *Original Author* +* [winds2016](https://github.com/winds2016) - *Windows platform testing* +* [songjiayang](https://github.com/songjiayang) - *SongJiaYang* +* [Yutaka Takeda](https://github.com/enobufs) - *vnet* +* [namreg](https://github.com/namreg) - *Igor German* +* [Aleksandr Razumov](https://github.com/ernado) - *protocol* +* [Robert Eperjesi](https://github.com/epes) +* [Lukas Rezek](https://github.com/lrezek) +* [Hugo Arregui](https://github.com/hugoArregui) +* [Aaron France](https://github.com/AeroNotix) +* [Atsushi Watanabe](https://github.com/at-wat) +* [Tom Clift](https://github.com/tclift) +* [lllf](https://github.com/LittleLightLittleFire) +* nindolabs (Marouane) +* [Onwuka Gideon](https://github.com/dongido001) +* [Herman Banken](https://github.com/hermanbanken) +* [Jannis Mattheis](https://github.com/jmattheis) + +### License +MIT License - see [LICENSE.md](LICENSE.md) for full text + + diff --git a/vendor/github.com/pion/turn/v2/client.go b/vendor/github.com/pion/turn/v2/client.go new file mode 100644 index 0000000..b04e7d1 --- /dev/null +++ b/vendor/github.com/pion/turn/v2/client.go @@ -0,0 +1,569 @@ +package turn + +import ( + b64 "encoding/base64" + "fmt" + "math" + "net" + "sync" + "time" + + "github.com/pion/logging" + "github.com/pion/stun" + "github.com/pion/transport/vnet" + "github.com/pion/turn/v2/internal/client" + "github.com/pion/turn/v2/internal/proto" +) + +const ( + defaultRTO = 200 * time.Millisecond + maxRtxCount = 7 // total 7 requests (Rc) + maxDataBufferSize = math.MaxUint16 // message size limit for Chromium +) + +// interval [msec] +// 0: 0 ms +500 +// 1: 500 ms +1000 +// 2: 1500 ms +2000 +// 3: 3500 ms +4000 +// 4: 7500 ms +8000 +// 5: 15500 ms +16000 +// 6: 31500 ms +32000 +// -: 63500 ms failed + +// ClientConfig is a bag of config parameters for Client. +type ClientConfig struct { + STUNServerAddr string // STUN server address (e.g. "stun.abc.com:3478") + TURNServerAddr string // TURN server addrees (e.g. "turn.abc.com:3478") + Username string + Password string + Realm string + Software string + RTO time.Duration + Conn net.PacketConn // Listening socket (net.PacketConn) + LoggerFactory logging.LoggerFactory + Net *vnet.Net +} + +// Client is a STUN server client +type Client struct { + conn net.PacketConn // read-only + stunServ net.Addr // read-only + turnServ net.Addr // read-only + stunServStr string // read-only, used for dmuxing + turnServStr string // read-only, used for dmuxing + username stun.Username // read-only + password string // read-only + realm stun.Realm // read-only + integrity stun.MessageIntegrity // read-only + software stun.Software // read-only + trMap *client.TransactionMap // thread-safe + rto time.Duration // read-only + relayedConn *client.UDPConn // protected by mutex *** + allocTryLock client.TryLock // thread-safe + listenTryLock client.TryLock // thread-safe + net *vnet.Net // read-only + mutex sync.RWMutex // thread-safe + mutexTrMap sync.Mutex // thread-safe + log logging.LeveledLogger // read-only +} + +// NewClient returns a new Client instance. listeningAddress is the address and port to listen on, default "0.0.0.0:0" +func NewClient(config *ClientConfig) (*Client, error) { + loggerFactory := config.LoggerFactory + if loggerFactory == nil { + loggerFactory = logging.NewDefaultLoggerFactory() + } + + log := loggerFactory.NewLogger("turnc") + + if config.Conn == nil { + return nil, errNilConn + } + + if config.Net == nil { + config.Net = vnet.NewNet(nil) // defaults to native operation + } else if config.Net.IsVirtual() { + log.Warn("vnet is enabled") + } + + var stunServ, turnServ net.Addr + var stunServStr, turnServStr string + var err error + if len(config.STUNServerAddr) > 0 { + log.Debugf("resolving %s", config.STUNServerAddr) + stunServ, err = config.Net.ResolveUDPAddr("udp4", config.STUNServerAddr) + if err != nil { + return nil, err + } + stunServStr = stunServ.String() + log.Debugf("stunServ: %s", stunServStr) + } + if len(config.TURNServerAddr) > 0 { + log.Debugf("resolving %s", config.TURNServerAddr) + turnServ, err = config.Net.ResolveUDPAddr("udp4", config.TURNServerAddr) + if err != nil { + return nil, err + } + turnServStr = turnServ.String() + log.Debugf("turnServ: %s", turnServStr) + } + + rto := defaultRTO + if config.RTO > 0 { + rto = config.RTO + } + + c := &Client{ + conn: config.Conn, + stunServ: stunServ, + turnServ: turnServ, + stunServStr: stunServStr, + turnServStr: turnServStr, + username: stun.NewUsername(config.Username), + password: config.Password, + realm: stun.NewRealm(config.Realm), + software: stun.NewSoftware(config.Software), + net: config.Net, + trMap: client.NewTransactionMap(), + rto: rto, + log: log, + } + + return c, nil +} + +// TURNServerAddr return the TURN server address +func (c *Client) TURNServerAddr() net.Addr { + return c.turnServ +} + +// STUNServerAddr return the STUN server address +func (c *Client) STUNServerAddr() net.Addr { + return c.stunServ +} + +// Username returns username +func (c *Client) Username() stun.Username { + return c.username +} + +// Realm return realm +func (c *Client) Realm() stun.Realm { + return c.realm +} + +// WriteTo sends data to the specified destination using the base socket. +func (c *Client) WriteTo(data []byte, to net.Addr) (int, error) { + return c.conn.WriteTo(data, to) +} + +// Listen will have this client start listening on the conn provided via the config. +// This is optional. If not used, you will need to call HandleInbound method +// to supply incoming data, instead. +func (c *Client) Listen() error { + if err := c.listenTryLock.Lock(); err != nil { + return fmt.Errorf("%w: %s", errAlreadyListening, err.Error()) + } + + go func() { + buf := make([]byte, maxDataBufferSize) + for { + n, from, err := c.conn.ReadFrom(buf) + if err != nil { + c.log.Debugf("exiting read loop: %s", err.Error()) + break + } + + _, err = c.HandleInbound(buf[:n], from) + if err != nil { + c.log.Debugf("exiting read loop: %s", err.Error()) + break + } + } + + c.listenTryLock.Unlock() + }() + + return nil +} + +// Close closes this client +func (c *Client) Close() { + c.mutexTrMap.Lock() + defer c.mutexTrMap.Unlock() + + c.trMap.CloseAndDeleteAll() +} + +// TransactionID & Base64: https://play.golang.org/p/EEgmJDI971P + +// SendBindingRequestTo sends a new STUN request to the given transport address +func (c *Client) SendBindingRequestTo(to net.Addr) (net.Addr, error) { + attrs := []stun.Setter{stun.TransactionID, stun.BindingRequest} + if len(c.software) > 0 { + attrs = append(attrs, c.software) + } + + msg, err := stun.Build(attrs...) + if err != nil { + return nil, err + } + trRes, err := c.PerformTransaction(msg, to, false) + if err != nil { + return nil, err + } + + var reflAddr stun.XORMappedAddress + if err := reflAddr.GetFrom(trRes.Msg); err != nil { + return nil, err + } + + return &net.UDPAddr{ + IP: reflAddr.IP, + Port: reflAddr.Port, + }, nil +} + +// SendBindingRequest sends a new STUN request to the STUN server +func (c *Client) SendBindingRequest() (net.Addr, error) { + if c.stunServ == nil { + return nil, errSTUNServerAddressNotSet + } + return c.SendBindingRequestTo(c.stunServ) +} + +// Allocate sends a TURN allocation request to the given transport address +func (c *Client) Allocate() (net.PacketConn, error) { + if err := c.allocTryLock.Lock(); err != nil { + return nil, fmt.Errorf("%w: %s", errOneAllocateOnly, err.Error()) + } + defer c.allocTryLock.Unlock() + + relayedConn := c.relayedUDPConn() + if relayedConn != nil { + return nil, fmt.Errorf("%w: %s", errAlreadyAllocated, relayedConn.LocalAddr().String()) + } + + msg, err := stun.Build( + stun.TransactionID, + stun.NewType(stun.MethodAllocate, stun.ClassRequest), + proto.RequestedTransport{Protocol: proto.ProtoUDP}, + stun.Fingerprint, + ) + if err != nil { + return nil, err + } + + trRes, err := c.PerformTransaction(msg, c.turnServ, false) + if err != nil { + return nil, err + } + + res := trRes.Msg + + // Anonymous allocate failed, trying to authenticate. + var nonce stun.Nonce + if err = nonce.GetFrom(res); err != nil { + return nil, err + } + if err = c.realm.GetFrom(res); err != nil { + return nil, err + } + c.realm = append([]byte(nil), c.realm...) + c.integrity = stun.NewLongTermIntegrity( + c.username.String(), c.realm.String(), c.password, + ) + // Trying to authorize. + msg, err = stun.Build( + stun.TransactionID, + stun.NewType(stun.MethodAllocate, stun.ClassRequest), + proto.RequestedTransport{Protocol: proto.ProtoUDP}, + &c.username, + &c.realm, + &nonce, + &c.integrity, + stun.Fingerprint, + ) + if err != nil { + return nil, err + } + + trRes, err = c.PerformTransaction(msg, c.turnServ, false) + if err != nil { + return nil, err + } + res = trRes.Msg + + if res.Type.Class == stun.ClassErrorResponse { + var code stun.ErrorCodeAttribute + if err = code.GetFrom(res); err == nil { + return nil, fmt.Errorf("%s (error %s)", res.Type, code) //nolint:goerr113 + } + return nil, fmt.Errorf("%s", res.Type) //nolint:goerr113 + } + + // Getting relayed addresses from response. + var relayed proto.RelayedAddress + if err := relayed.GetFrom(res); err != nil { + return nil, err + } + relayedAddr := &net.UDPAddr{ + IP: relayed.IP, + Port: relayed.Port, + } + + // Getting lifetime from response + var lifetime proto.Lifetime + if err := lifetime.GetFrom(res); err != nil { + return nil, err + } + + relayedConn = client.NewUDPConn(&client.UDPConnConfig{ + Observer: c, + RelayedAddr: relayedAddr, + Integrity: c.integrity, + Nonce: nonce, + Lifetime: lifetime.Duration, + Log: c.log, + }) + + c.setRelayedUDPConn(relayedConn) + + return relayedConn, nil +} + +// PerformTransaction performs STUN transaction +func (c *Client) PerformTransaction(msg *stun.Message, to net.Addr, ignoreResult bool) (client.TransactionResult, + error) { + trKey := b64.StdEncoding.EncodeToString(msg.TransactionID[:]) + + raw := make([]byte, len(msg.Raw)) + copy(raw, msg.Raw) + + tr := client.NewTransaction(&client.TransactionConfig{ + Key: trKey, + Raw: raw, + To: to, + Interval: c.rto, + IgnoreResult: ignoreResult, + }) + + c.trMap.Insert(trKey, tr) + + c.log.Tracef("start %s transaction %s to %s", msg.Type, trKey, tr.To.String()) + _, err := c.conn.WriteTo(tr.Raw, to) + if err != nil { + return client.TransactionResult{}, err + } + + tr.StartRtxTimer(c.onRtxTimeout) + + // If dontWait is true, get the transaction going and return immediately + if ignoreResult { + return client.TransactionResult{}, nil + } + + res := tr.WaitForResult() + if res.Err != nil { + return res, res.Err + } + return res, nil +} + +// OnDeallocated is called when deallocation of relay address has been complete. +// (Called by UDPConn) +func (c *Client) OnDeallocated(relayedAddr net.Addr) { + c.setRelayedUDPConn(nil) +} + +// HandleInbound handles data received. +// This method handles incoming packet demultiplex it by the source address +// and the types of the message. +// This return a booleen (handled or not) and if there was an error. +// Caller should check if the packet was handled by this client or not. +// If not handled, it is assumed that the packet is application data. +// If an error is returned, the caller should discard the packet regardless. +func (c *Client) HandleInbound(data []byte, from net.Addr) (bool, error) { + // +-------------------+-------------------------------+ + // | Return Values | | + // +-------------------+ Meaning / Action | + // | handled | error | | + // |=========+=========+===============================+ + // | false | nil | Handle the packet as app data | + // |---------+---------+-------------------------------+ + // | true | nil | Nothing to do | + // |---------+---------+-------------------------------+ + // | false | error | (shouldn't happen) | + // |---------+---------+-------------------------------+ + // | true | error | Error occurred while handling | + // +---------+---------+-------------------------------+ + // Possible causes of the error: + // - Malformed packet (parse error) + // - STUN message was a request + // - Non-STUN message from the STUN server + + switch { + case stun.IsMessage(data): + return true, c.handleSTUNMessage(data, from) + case proto.IsChannelData(data): + return true, c.handleChannelData(data) + case len(c.stunServStr) != 0 && from.String() == c.stunServStr: + // received from STUN server but it is not a STUN message + return true, errNonSTUNMessage + default: + // assume, this is an application data + c.log.Tracef("non-STUN/TURN packect, unhandled") + } + + return false, nil +} + +func (c *Client) handleSTUNMessage(data []byte, from net.Addr) error { + raw := make([]byte, len(data)) + copy(raw, data) + + msg := &stun.Message{Raw: raw} + if err := msg.Decode(); err != nil { + return fmt.Errorf("%w: %s", errFailedToDecodeSTUN, err.Error()) + } + + if msg.Type.Class == stun.ClassRequest { + return fmt.Errorf("%w : %s", errUnexpectedSTUNRequestMessage, msg.String()) + } + + if msg.Type.Class == stun.ClassIndication { + if msg.Type.Method == stun.MethodData { + var peerAddr proto.PeerAddress + if err := peerAddr.GetFrom(msg); err != nil { + return err + } + from = &net.UDPAddr{ + IP: peerAddr.IP, + Port: peerAddr.Port, + } + + var data proto.Data + if err := data.GetFrom(msg); err != nil { + return err + } + + c.log.Debugf("data indication received from %s", from.String()) + + relayedConn := c.relayedUDPConn() + if relayedConn == nil { + c.log.Debug("no relayed conn allocated") + return nil // silently discard + } + + relayedConn.HandleInbound(data, from) + } + return nil + } + + // This is a STUN response message (transactional) + // The type is either: + // - stun.ClassSuccessResponse + // - stun.ClassErrorResponse + + trKey := b64.StdEncoding.EncodeToString(msg.TransactionID[:]) + + c.mutexTrMap.Lock() + tr, ok := c.trMap.Find(trKey) + if !ok { + c.mutexTrMap.Unlock() + // silently discard + c.log.Debugf("no transaction for %s", msg.String()) + return nil + } + + // End the transaction + tr.StopRtxTimer() + c.trMap.Delete(trKey) + c.mutexTrMap.Unlock() + + if !tr.WriteResult(client.TransactionResult{ + Msg: msg, + From: from, + Retries: tr.Retries(), + }) { + c.log.Debugf("no listener for %s", msg.String()) + } + + return nil +} + +func (c *Client) handleChannelData(data []byte) error { + chData := &proto.ChannelData{ + Raw: make([]byte, len(data)), + } + copy(chData.Raw, data) + if err := chData.Decode(); err != nil { + return err + } + + relayedConn := c.relayedUDPConn() + if relayedConn == nil { + c.log.Debug("no relayed conn allocated") + return nil // silently discard + } + + addr, ok := relayedConn.FindAddrByChannelNumber(uint16(chData.Number)) + if !ok { + return fmt.Errorf("%w: %d", errChannelBindNotFound, int(chData.Number)) + } + + c.log.Tracef("channel data received from %s (ch=%d)", addr.String(), int(chData.Number)) + + relayedConn.HandleInbound(chData.Data, addr) + return nil +} + +func (c *Client) onRtxTimeout(trKey string, nRtx int) { + c.mutexTrMap.Lock() + defer c.mutexTrMap.Unlock() + + tr, ok := c.trMap.Find(trKey) + if !ok { + return // already gone + } + + if nRtx == maxRtxCount { + // all retransmisstions failed + c.trMap.Delete(trKey) + if !tr.WriteResult(client.TransactionResult{ + Err: fmt.Errorf("%w %s", errAllRetransmissionsFailed, trKey), + }) { + c.log.Debug("no listener for transaction") + } + return + } + + c.log.Tracef("retransmitting transaction %s to %s (nRtx=%d)", + trKey, tr.To.String(), nRtx) + _, err := c.conn.WriteTo(tr.Raw, tr.To) + if err != nil { + c.trMap.Delete(trKey) + if !tr.WriteResult(client.TransactionResult{ + Err: fmt.Errorf("%w %s", errFailedToRetransmitTransaction, trKey), + }) { + c.log.Debug("no listener for transaction") + } + return + } + tr.StartRtxTimer(c.onRtxTimeout) +} + +func (c *Client) setRelayedUDPConn(conn *client.UDPConn) { + c.mutex.Lock() + defer c.mutex.Unlock() + + c.relayedConn = conn +} + +func (c *Client) relayedUDPConn() *client.UDPConn { + c.mutex.RLock() + defer c.mutex.RUnlock() + + return c.relayedConn +} diff --git a/vendor/github.com/pion/turn/v2/codecov.yml b/vendor/github.com/pion/turn/v2/codecov.yml new file mode 100644 index 0000000..085200a --- /dev/null +++ b/vendor/github.com/pion/turn/v2/codecov.yml @@ -0,0 +1,20 @@ +# +# DO NOT EDIT THIS FILE +# +# It is automatically copied from https://github.com/pion/.goassets repository. +# + +coverage: + status: + project: + default: + # Allow decreasing 2% of total coverage to avoid noise. + threshold: 2% + patch: + default: + target: 70% + only_pulls: true + +ignore: + - "examples/*" + - "examples/**/*" diff --git a/vendor/github.com/pion/turn/v2/errors.go b/vendor/github.com/pion/turn/v2/errors.go new file mode 100644 index 0000000..12d5e0e --- /dev/null +++ b/vendor/github.com/pion/turn/v2/errors.go @@ -0,0 +1,28 @@ +package turn + +import "errors" + +var ( + errRelayAddressInvalid = errors.New("turn: RelayAddress must be valid IP to use RelayAddressGeneratorStatic") + errNoAvailableConns = errors.New("turn: PacketConnConfigs and ConnConfigs are empty, unable to proceed") + errConnUnset = errors.New("turn: PacketConnConfig must have a non-nil Conn") + errListenerUnset = errors.New("turn: ListenerConfig must have a non-nil Listener") + errListeningAddressInvalid = errors.New("turn: RelayAddressGenerator has invalid ListeningAddress") + errRelayAddressGeneratorUnset = errors.New("turn: RelayAddressGenerator in RelayConfig is unset") + errMaxRetriesExceeded = errors.New("turn: max retries exceeded") + errMaxPortNotZero = errors.New("turn: MaxPort must be not 0") + errMinPortNotZero = errors.New("turn: MaxPort must be not 0") + errNilConn = errors.New("turn: conn cannot not be nil") + errTODO = errors.New("turn: TODO") + errAlreadyListening = errors.New("turn: already listening") + errFailedToClose = errors.New("turn: Server failed to close") + errFailedToRetransmitTransaction = errors.New("turn: failed to retransmit transaction") + errAllRetransmissionsFailed = errors.New("all retransmissions failed for") + errChannelBindNotFound = errors.New("no binding found for channel") + errSTUNServerAddressNotSet = errors.New("STUN server address is not set for the client") + errOneAllocateOnly = errors.New("only one Allocate() caller is allowed") + errAlreadyAllocated = errors.New("already allocated") + errNonSTUNMessage = errors.New("non-STUN message from STUN server") + errFailedToDecodeSTUN = errors.New("failed to decode STUN message") + errUnexpectedSTUNRequestMessage = errors.New("unexpected STUN request message") +) diff --git a/vendor/github.com/pion/turn/v2/go.mod b/vendor/github.com/pion/turn/v2/go.mod new file mode 100644 index 0000000..de599c3 --- /dev/null +++ b/vendor/github.com/pion/turn/v2/go.mod @@ -0,0 +1,11 @@ +module github.com/pion/turn/v2 + +go 1.13 + +require ( + github.com/pion/logging v0.2.2 + github.com/pion/randutil v0.1.0 + github.com/pion/stun v0.3.5 + github.com/pion/transport v0.10.1 + github.com/stretchr/testify v1.6.1 +) diff --git a/vendor/github.com/pion/turn/v2/go.sum b/vendor/github.com/pion/turn/v2/go.sum new file mode 100644 index 0000000..7756c37 --- /dev/null +++ b/vendor/github.com/pion/turn/v2/go.sum @@ -0,0 +1,28 @@ +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY= +github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms= +github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA= +github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8= +github.com/pion/stun v0.3.5 h1:uLUCBCkQby4S1cf6CGuR9QrVOKcvUwFeemaC865QHDg= +github.com/pion/stun v0.3.5/go.mod h1:gDMim+47EeEtfWogA37n6qXZS88L5V6LqFcf+DZA2UA= +github.com/pion/transport v0.10.1 h1:2W+yJT+0mOQ160ThZYUx5Zp2skzshiNgxrNE9GUfhJM= +github.com/pion/transport v0.10.1/go.mod h1:PBis1stIILMiis0PewDw91WJeLJkyIMcEk+DwKOzf4A= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20200625001655-4c5254603344 h1:vGXIOMxbNfDTk/aXCmfdLgkrSV+Z2tcbze+pEc3v5W4= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/vendor/github.com/pion/turn/v2/internal/allocation/allocation.go b/vendor/github.com/pion/turn/v2/internal/allocation/allocation.go new file mode 100644 index 0000000..d1cb8c6 --- /dev/null +++ b/vendor/github.com/pion/turn/v2/internal/allocation/allocation.go @@ -0,0 +1,259 @@ +// Package allocation contains all CRUD operations for allocations +package allocation + +import ( + "net" + "sync" + "time" + + "github.com/pion/logging" + "github.com/pion/stun" + "github.com/pion/turn/v2/internal/ipnet" + "github.com/pion/turn/v2/internal/proto" +) + +// Allocation is tied to a FiveTuple and relays traffic +// use CreateAllocation and GetAllocation to operate +type Allocation struct { + RelayAddr net.Addr + Protocol Protocol + TurnSocket net.PacketConn + RelaySocket net.PacketConn + fiveTuple *FiveTuple + permissionsLock sync.RWMutex + permissions map[string]*Permission + channelBindingsLock sync.RWMutex + channelBindings []*ChannelBind + lifetimeTimer *time.Timer + closed chan interface{} + log logging.LeveledLogger +} + +func addr2IPFingerprint(addr net.Addr) string { + switch a := addr.(type) { + case *net.UDPAddr: + return a.IP.String() + case *net.TCPAddr: // Do we really need this case? + return a.IP.String() + } + return "" // shoud never happen +} + +// NewAllocation creates a new instance of NewAllocation. +func NewAllocation(turnSocket net.PacketConn, fiveTuple *FiveTuple, log logging.LeveledLogger) *Allocation { + return &Allocation{ + TurnSocket: turnSocket, + fiveTuple: fiveTuple, + permissions: make(map[string]*Permission, 64), + closed: make(chan interface{}), + log: log, + } +} + +// GetPermission gets the Permission from the allocation +func (a *Allocation) GetPermission(addr net.Addr) *Permission { + a.permissionsLock.RLock() + defer a.permissionsLock.RUnlock() + + return a.permissions[addr2IPFingerprint(addr)] +} + +// AddPermission adds a new permission to the allocation +func (a *Allocation) AddPermission(p *Permission) { + fingerprint := addr2IPFingerprint(p.Addr) + + a.permissionsLock.RLock() + existedPermission, ok := a.permissions[fingerprint] + a.permissionsLock.RUnlock() + + if ok { + existedPermission.refresh(permissionTimeout) + return + } + + p.allocation = a + a.permissionsLock.Lock() + a.permissions[fingerprint] = p + a.permissionsLock.Unlock() + + p.start(permissionTimeout) +} + +// RemovePermission removes the net.Addr's fingerprint from the allocation's permissions +func (a *Allocation) RemovePermission(addr net.Addr) { + a.permissionsLock.Lock() + defer a.permissionsLock.Unlock() + delete(a.permissions, addr2IPFingerprint(addr)) +} + +// AddChannelBind adds a new ChannelBind to the allocation, it also updates the +// permissions needed for this ChannelBind +func (a *Allocation) AddChannelBind(c *ChannelBind, lifetime time.Duration) error { + // Check that this channel id isn't bound to another transport address, and + // that this transport address isn't bound to another channel number. + channelByNumber := a.GetChannelByNumber(c.Number) + + if channelByNumber != a.GetChannelByAddr(c.Peer) { + return errSameChannelDifferentPeer + } + + // Add or refresh this channel. + if channelByNumber == nil { + a.channelBindingsLock.Lock() + defer a.channelBindingsLock.Unlock() + + c.allocation = a + a.channelBindings = append(a.channelBindings, c) + c.start(lifetime) + + // Channel binds also refresh permissions. + a.AddPermission(NewPermission(c.Peer, a.log)) + } else { + channelByNumber.refresh(lifetime) + + // Channel binds also refresh permissions. + a.AddPermission(NewPermission(channelByNumber.Peer, a.log)) + } + + return nil +} + +// RemoveChannelBind removes the ChannelBind from this allocation by id +func (a *Allocation) RemoveChannelBind(number proto.ChannelNumber) bool { + a.channelBindingsLock.Lock() + defer a.channelBindingsLock.Unlock() + + for i := len(a.channelBindings) - 1; i >= 0; i-- { + if a.channelBindings[i].Number == number { + a.channelBindings = append(a.channelBindings[:i], a.channelBindings[i+1:]...) + return true + } + } + + return false +} + +// GetChannelByNumber gets the ChannelBind from this allocation by id +func (a *Allocation) GetChannelByNumber(number proto.ChannelNumber) *ChannelBind { + a.channelBindingsLock.RLock() + defer a.channelBindingsLock.RUnlock() + for _, cb := range a.channelBindings { + if cb.Number == number { + return cb + } + } + return nil +} + +// GetChannelByAddr gets the ChannelBind from this allocation by net.Addr +func (a *Allocation) GetChannelByAddr(addr net.Addr) *ChannelBind { + a.channelBindingsLock.RLock() + defer a.channelBindingsLock.RUnlock() + for _, cb := range a.channelBindings { + if ipnet.AddrEqual(cb.Peer, addr) { + return cb + } + } + return nil +} + +// Refresh updates the allocations lifetime +func (a *Allocation) Refresh(lifetime time.Duration) { + if !a.lifetimeTimer.Reset(lifetime) { + a.log.Errorf("Failed to reset allocation timer for %v", a.fiveTuple) + } +} + +// Close closes the allocation +func (a *Allocation) Close() error { + select { + case <-a.closed: + return nil + default: + } + close(a.closed) + + a.lifetimeTimer.Stop() + + a.permissionsLock.RLock() + for _, p := range a.permissions { + p.lifetimeTimer.Stop() + } + a.permissionsLock.RUnlock() + + a.channelBindingsLock.RLock() + for _, c := range a.channelBindings { + c.lifetimeTimer.Stop() + } + a.channelBindingsLock.RUnlock() + + return a.RelaySocket.Close() +} + +// https://tools.ietf.org/html/rfc5766#section-10.3 +// When the server receives a UDP datagram at a currently allocated +// relayed transport address, the server looks up the allocation +// associated with the relayed transport address. The server then +// checks to see whether the set of permissions for the allocation allow +// the relaying of the UDP datagram as described in Section 8. +// +// If relaying is permitted, then the server checks if there is a +// channel bound to the peer that sent the UDP datagram (see +// Section 11). If a channel is bound, then processing proceeds as +// described in Section 11.7. +// +// If relaying is permitted but no channel is bound to the peer, then +// the server forms and sends a Data indication. The Data indication +// MUST contain both an XOR-PEER-ADDRESS and a DATA attribute. The DATA +// attribute is set to the value of the 'data octets' field from the +// datagram, and the XOR-PEER-ADDRESS attribute is set to the source +// transport address of the received UDP datagram. The Data indication +// is then sent on the 5-tuple associated with the allocation. + +const rtpMTU = 1500 + +func (a *Allocation) packetHandler(m *Manager) { + buffer := make([]byte, rtpMTU) + + for { + n, srcAddr, err := a.RelaySocket.ReadFrom(buffer) + if err != nil { + m.DeleteAllocation(a.fiveTuple) + return + } + + a.log.Debugf("relay socket %s received %d bytes from %s", + a.RelaySocket.LocalAddr().String(), + n, + srcAddr.String()) + + if channel := a.GetChannelByAddr(srcAddr); channel != nil { + channelData := &proto.ChannelData{ + Data: buffer[:n], + Number: channel.Number, + } + channelData.Encode() + + if _, err = a.TurnSocket.WriteTo(channelData.Raw, a.fiveTuple.SrcAddr); err != nil { + a.log.Errorf("Failed to send ChannelData from allocation %v %v", srcAddr, err) + } + } else if p := a.GetPermission(srcAddr); p != nil { + udpAddr := srcAddr.(*net.UDPAddr) + peerAddressAttr := proto.PeerAddress{IP: udpAddr.IP, Port: udpAddr.Port} + dataAttr := proto.Data(buffer[:n]) + + msg, err := stun.Build(stun.TransactionID, stun.NewType(stun.MethodData, stun.ClassIndication), peerAddressAttr, dataAttr) + if err != nil { + a.log.Errorf("Failed to send DataIndication from allocation %v %v", srcAddr, err) + } + a.log.Debugf("relaying message from %s to client at %s", + srcAddr.String(), + a.fiveTuple.SrcAddr.String()) + if _, err = a.TurnSocket.WriteTo(msg.Raw, a.fiveTuple.SrcAddr); err != nil { + a.log.Errorf("Failed to send DataIndication from allocation %v %v", srcAddr, err) + } + } else { + a.log.Infof("No Permission or Channel exists for %v on allocation %v", srcAddr, a.RelayAddr.String()) + } + } +} diff --git a/vendor/github.com/pion/turn/v2/internal/allocation/allocation_manager.go b/vendor/github.com/pion/turn/v2/internal/allocation/allocation_manager.go new file mode 100644 index 0000000..9a1271a --- /dev/null +++ b/vendor/github.com/pion/turn/v2/internal/allocation/allocation_manager.go @@ -0,0 +1,186 @@ +package allocation + +import ( + "fmt" + "net" + "sync" + "time" + + "github.com/pion/logging" +) + +// ManagerConfig a bag of config params for Manager. +type ManagerConfig struct { + LeveledLogger logging.LeveledLogger + AllocatePacketConn func(network string, requestedPort int) (net.PacketConn, net.Addr, error) + AllocateConn func(network string, requestedPort int) (net.Conn, net.Addr, error) +} + +type reservation struct { + token string + port int +} + +// Manager is used to hold active allocations +type Manager struct { + lock sync.RWMutex + log logging.LeveledLogger + + allocations map[string]*Allocation + reservations []*reservation + + allocatePacketConn func(network string, requestedPort int) (net.PacketConn, net.Addr, error) + allocateConn func(network string, requestedPort int) (net.Conn, net.Addr, error) +} + +// NewManager creates a new instance of Manager. +func NewManager(config ManagerConfig) (*Manager, error) { + switch { + case config.AllocatePacketConn == nil: + return nil, errAllocatePacketConnMustBeSet + case config.AllocateConn == nil: + return nil, errAllocateConnMustBeSet + case config.LeveledLogger == nil: + return nil, errLeveledLoggerMustBeSet + } + + return &Manager{ + log: config.LeveledLogger, + allocations: make(map[string]*Allocation, 64), + allocatePacketConn: config.AllocatePacketConn, + allocateConn: config.AllocateConn, + }, nil +} + +// GetAllocation fetches the allocation matching the passed FiveTuple +func (m *Manager) GetAllocation(fiveTuple *FiveTuple) *Allocation { + m.lock.RLock() + defer m.lock.RUnlock() + return m.allocations[fiveTuple.Fingerprint()] +} + +// Close closes the manager and closes all allocations it manages +func (m *Manager) Close() error { + m.lock.Lock() + defer m.lock.Unlock() + + for _, a := range m.allocations { + if err := a.Close(); err != nil { + return err + } + } + return nil +} + +// CreateAllocation creates a new allocation and starts relaying +func (m *Manager) CreateAllocation(fiveTuple *FiveTuple, turnSocket net.PacketConn, requestedPort int, lifetime time.Duration) (*Allocation, error) { + switch { + case fiveTuple == nil: + return nil, errNilFiveTuple + case fiveTuple.SrcAddr == nil: + return nil, errNilFiveTupleSrcAddr + case fiveTuple.DstAddr == nil: + return nil, errNilFiveTupleDstAddr + case turnSocket == nil: + return nil, errNilTurnSocket + case lifetime == 0: + return nil, errLifetimeZero + } + + if a := m.GetAllocation(fiveTuple); a != nil { + return nil, fmt.Errorf("%w: %v", errDupeFiveTuple, fiveTuple) + } + a := NewAllocation(turnSocket, fiveTuple, m.log) + + conn, relayAddr, err := m.allocatePacketConn("udp4", requestedPort) + if err != nil { + return nil, err + } + + a.RelaySocket = conn + a.RelayAddr = relayAddr + + m.log.Debugf("listening on relay addr: %s", a.RelayAddr.String()) + + a.lifetimeTimer = time.AfterFunc(lifetime, func() { + m.DeleteAllocation(a.fiveTuple) + }) + + m.lock.Lock() + m.allocations[fiveTuple.Fingerprint()] = a + m.lock.Unlock() + + go a.packetHandler(m) + return a, nil +} + +// DeleteAllocation removes an allocation +func (m *Manager) DeleteAllocation(fiveTuple *FiveTuple) { + fingerprint := fiveTuple.Fingerprint() + + m.lock.Lock() + allocation := m.allocations[fingerprint] + delete(m.allocations, fingerprint) + m.lock.Unlock() + + if allocation == nil { + return + } + + if err := allocation.Close(); err != nil { + m.log.Errorf("Failed to close allocation: %v", err) + } +} + +// CreateReservation stores the reservation for the token+port +func (m *Manager) CreateReservation(reservationToken string, port int) { + time.AfterFunc(30*time.Second, func() { + m.lock.Lock() + defer m.lock.Unlock() + for i := len(m.reservations) - 1; i >= 0; i-- { + if m.reservations[i].token == reservationToken { + m.reservations = append(m.reservations[:i], m.reservations[i+1:]...) + return + } + } + }) + + m.lock.Lock() + m.reservations = append(m.reservations, &reservation{ + token: reservationToken, + port: port, + }) + m.lock.Unlock() +} + +// GetReservation returns the port for a given reservation if it exists +func (m *Manager) GetReservation(reservationToken string) (int, bool) { + m.lock.RLock() + defer m.lock.RUnlock() + + for _, r := range m.reservations { + if r.token == reservationToken { + return r.port, true + } + } + return 0, false +} + +// GetRandomEvenPort returns a random un-allocated udp4 port +func (m *Manager) GetRandomEvenPort() (int, error) { + conn, addr, err := m.allocatePacketConn("udp4", 0) + if err != nil { + return 0, err + } + + udpAddr, ok := addr.(*net.UDPAddr) + if !ok { + return 0, errFailedToCastUDPAddr + } else if err := conn.Close(); err != nil { + return 0, err + } else if udpAddr.Port%2 == 1 { + return m.GetRandomEvenPort() + } + + return udpAddr.Port, nil +} diff --git a/vendor/github.com/pion/turn/v2/internal/allocation/channel_bind.go b/vendor/github.com/pion/turn/v2/internal/allocation/channel_bind.go new file mode 100644 index 0000000..6216369 --- /dev/null +++ b/vendor/github.com/pion/turn/v2/internal/allocation/channel_bind.go @@ -0,0 +1,43 @@ +package allocation + +import ( + "net" + "time" + + "github.com/pion/logging" + "github.com/pion/turn/v2/internal/proto" +) + +// ChannelBind represents a TURN Channel +// https://tools.ietf.org/html/rfc5766#section-2.5 +type ChannelBind struct { + Peer net.Addr + Number proto.ChannelNumber + + allocation *Allocation + lifetimeTimer *time.Timer + log logging.LeveledLogger +} + +// NewChannelBind creates a new ChannelBind +func NewChannelBind(number proto.ChannelNumber, peer net.Addr, log logging.LeveledLogger) *ChannelBind { + return &ChannelBind{ + Number: number, + Peer: peer, + log: log, + } +} + +func (c *ChannelBind) start(lifetime time.Duration) { + c.lifetimeTimer = time.AfterFunc(lifetime, func() { + if !c.allocation.RemoveChannelBind(c.Number) { + c.log.Errorf("Failed to remove ChannelBind for %v %x %v", c.Number, c.Peer, c.allocation.fiveTuple) + } + }) +} + +func (c *ChannelBind) refresh(lifetime time.Duration) { + if !c.lifetimeTimer.Reset(lifetime) { + c.log.Errorf("Failed to reset ChannelBind timer for %v %x %v", c.Number, c.Peer, c.allocation.fiveTuple) + } +} diff --git a/vendor/github.com/pion/turn/v2/internal/allocation/errors.go b/vendor/github.com/pion/turn/v2/internal/allocation/errors.go new file mode 100644 index 0000000..fdd8335 --- /dev/null +++ b/vendor/github.com/pion/turn/v2/internal/allocation/errors.go @@ -0,0 +1,17 @@ +package allocation + +import "errors" + +var ( + errAllocatePacketConnMustBeSet = errors.New("AllocatePacketConn must be set") + errAllocateConnMustBeSet = errors.New("AllocateConn must be set") + errLeveledLoggerMustBeSet = errors.New("LeveledLogger must be set") + errSameChannelDifferentPeer = errors.New("you cannot use the same channel number with different peer") + errNilFiveTuple = errors.New("allocations must not be created with nil FivTuple") + errNilFiveTupleSrcAddr = errors.New("allocations must not be created with nil FiveTuple.SrcAddr") + errNilFiveTupleDstAddr = errors.New("allocations must not be created with nil FiveTuple.DstAddr") + errNilTurnSocket = errors.New("allocations must not be created with nil turnSocket") + errLifetimeZero = errors.New("allocations must not be created with a lifetime of 0") + errDupeFiveTuple = errors.New("allocation attempt created with duplicate FiveTuple") + errFailedToCastUDPAddr = errors.New("failed to cast net.Addr to *net.UDPAddr") +) diff --git a/vendor/github.com/pion/turn/v2/internal/allocation/five_tuple.go b/vendor/github.com/pion/turn/v2/internal/allocation/five_tuple.go new file mode 100644 index 0000000..1f2b3b5 --- /dev/null +++ b/vendor/github.com/pion/turn/v2/internal/allocation/five_tuple.go @@ -0,0 +1,36 @@ +package allocation + +import ( + "fmt" + "net" +) + +// Protocol is an enum for relay protocol +type Protocol uint8 + +// Network protocols for relay +const ( + UDP Protocol = iota + TCP +) + +// FiveTuple is the combination (client IP address and port, server IP +// address and port, and transport protocol (currently one of UDP, +// TCP, or TLS)) used to communicate between the client and the +// server. The 5-tuple uniquely identifies this communication +// stream. The 5-tuple also uniquely identifies the Allocation on +// the server. +type FiveTuple struct { + Protocol + SrcAddr, DstAddr net.Addr +} + +// Equal asserts if two FiveTuples are equal +func (f *FiveTuple) Equal(b *FiveTuple) bool { + return f.Fingerprint() == b.Fingerprint() +} + +// Fingerprint is the identity of a FiveTuple +func (f *FiveTuple) Fingerprint() string { + return fmt.Sprintf("%d_%s_%s", f.Protocol, f.SrcAddr.String(), f.DstAddr.String()) +} diff --git a/vendor/github.com/pion/turn/v2/internal/allocation/permission.go b/vendor/github.com/pion/turn/v2/internal/allocation/permission.go new file mode 100644 index 0000000..03538eb --- /dev/null +++ b/vendor/github.com/pion/turn/v2/internal/allocation/permission.go @@ -0,0 +1,40 @@ +package allocation + +import ( + "net" + "time" + + "github.com/pion/logging" +) + +const permissionTimeout = time.Duration(5) * time.Minute + +// Permission represents a TURN permission. TURN permissions mimic the address-restricted +// filtering mechanism of NATs that comply with [RFC4787]. +// https://tools.ietf.org/html/rfc5766#section-2.3 +type Permission struct { + Addr net.Addr + allocation *Allocation + lifetimeTimer *time.Timer + log logging.LeveledLogger +} + +// NewPermission create a new Permission +func NewPermission(addr net.Addr, log logging.LeveledLogger) *Permission { + return &Permission{ + Addr: addr, + log: log, + } +} + +func (p *Permission) start(lifetime time.Duration) { + p.lifetimeTimer = time.AfterFunc(lifetime, func() { + p.allocation.RemovePermission(p.Addr) + }) +} + +func (p *Permission) refresh(lifetime time.Duration) { + if !p.lifetimeTimer.Reset(lifetime) { + p.log.Errorf("Failed to reset permission timer for %v %v", p.Addr, p.allocation.fiveTuple) + } +} diff --git a/vendor/github.com/pion/turn/v2/internal/client/binding.go b/vendor/github.com/pion/turn/v2/internal/client/binding.go new file mode 100644 index 0000000..ee52053 --- /dev/null +++ b/vendor/github.com/pion/turn/v2/internal/client/binding.go @@ -0,0 +1,151 @@ +package client + +import ( + "net" + "sync" + "sync/atomic" + "time" +) + +// Chanel number: +// 0x4000 through 0x7FFF: These values are the allowed channel +// numbers (16,383 possible values). +const ( + minChannelNumber uint16 = 0x4000 + maxChannelNumber uint16 = 0x7fff +) + +type bindingState int32 + +const ( + bindingStateIdle bindingState = iota + bindingStateRequest + bindingStateReady + bindingStateRefresh + bindingStateFailed +) + +type binding struct { + number uint16 // read-only + st bindingState // thread-safe (atomic op) + addr net.Addr // read-only + mgr *bindingManager // read-only + muBind sync.Mutex // thread-safe, for ChannelBind ops + _refreshedAt time.Time // protected by mutex + mutex sync.RWMutex // thread-safe +} + +func (b *binding) setState(state bindingState) { + atomic.StoreInt32((*int32)(&b.st), int32(state)) +} + +func (b *binding) state() bindingState { + return bindingState(atomic.LoadInt32((*int32)(&b.st))) +} + +func (b *binding) setRefreshedAt(at time.Time) { + b.mutex.Lock() + defer b.mutex.Unlock() + + b._refreshedAt = at +} + +func (b *binding) refreshedAt() time.Time { + b.mutex.RLock() + defer b.mutex.RUnlock() + + return b._refreshedAt +} + +// Thread-safe binding map +type bindingManager struct { + chanMap map[uint16]*binding + addrMap map[string]*binding + next uint16 + mutex sync.RWMutex +} + +func newBindingManager() *bindingManager { + return &bindingManager{ + chanMap: map[uint16]*binding{}, + addrMap: map[string]*binding{}, + next: minChannelNumber, + } +} + +func (mgr *bindingManager) assignChannelNumber() uint16 { + n := mgr.next + if mgr.next == maxChannelNumber { + mgr.next = minChannelNumber + } else { + mgr.next++ + } + return n +} + +func (mgr *bindingManager) create(addr net.Addr) *binding { + mgr.mutex.Lock() + defer mgr.mutex.Unlock() + + b := &binding{ + number: mgr.assignChannelNumber(), + addr: addr, + mgr: mgr, + _refreshedAt: time.Now(), + } + + mgr.chanMap[b.number] = b + mgr.addrMap[b.addr.String()] = b + return b +} + +func (mgr *bindingManager) findByAddr(addr net.Addr) (*binding, bool) { + mgr.mutex.RLock() + defer mgr.mutex.RUnlock() + + b, ok := mgr.addrMap[addr.String()] + return b, ok +} + +func (mgr *bindingManager) findByNumber(number uint16) (*binding, bool) { + mgr.mutex.RLock() + defer mgr.mutex.RUnlock() + + b, ok := mgr.chanMap[number] + return b, ok +} + +func (mgr *bindingManager) deleteByAddr(addr net.Addr) bool { + mgr.mutex.Lock() + defer mgr.mutex.Unlock() + + b, ok := mgr.addrMap[addr.String()] + if !ok { + return false + } + + delete(mgr.addrMap, addr.String()) + delete(mgr.chanMap, b.number) + return true +} + +func (mgr *bindingManager) deleteByNumber(number uint16) bool { + mgr.mutex.Lock() + defer mgr.mutex.Unlock() + + b, ok := mgr.chanMap[number] + if !ok { + return false + } + + delete(mgr.addrMap, b.addr.String()) + delete(mgr.chanMap, number) + return true +} + +func (mgr *bindingManager) size() int { + mgr.mutex.RLock() + defer mgr.mutex.RUnlock() + + return len(mgr.chanMap) +} diff --git a/vendor/github.com/pion/turn/v2/internal/client/conn.go b/vendor/github.com/pion/turn/v2/internal/client/conn.go new file mode 100644 index 0000000..8a2b1ae --- /dev/null +++ b/vendor/github.com/pion/turn/v2/internal/client/conn.go @@ -0,0 +1,613 @@ +// Package client implements the API for a TURN client +package client + +import ( + "errors" + "fmt" + "io" + "math" + "net" + "sync" + "time" + + "github.com/pion/logging" + "github.com/pion/stun" + "github.com/pion/turn/v2/internal/proto" +) + +const ( + maxReadQueueSize = 1024 + permRefreshInterval = 120 * time.Second + maxRetryAttempts = 3 +) + +const ( + timerIDRefreshAlloc int = iota + timerIDRefreshPerms +) + +func noDeadline() time.Time { + return time.Time{} +} + +type inboundData struct { + data []byte + from net.Addr +} + +// UDPConnObserver is an interface to UDPConn observer +type UDPConnObserver interface { + TURNServerAddr() net.Addr + Username() stun.Username + Realm() stun.Realm + WriteTo(data []byte, to net.Addr) (int, error) + PerformTransaction(msg *stun.Message, to net.Addr, dontWait bool) (TransactionResult, error) + OnDeallocated(relayedAddr net.Addr) +} + +// UDPConnConfig is a set of configuration params use by NewUDPConn +type UDPConnConfig struct { + Observer UDPConnObserver + RelayedAddr net.Addr + Integrity stun.MessageIntegrity + Nonce stun.Nonce + Lifetime time.Duration + Log logging.LeveledLogger +} + +// UDPConn is the implementation of the Conn and PacketConn interfaces for UDP network connections. +// comatible with net.PacketConn and net.Conn +type UDPConn struct { + obs UDPConnObserver // read-only + relayedAddr net.Addr // read-only + permMap *permissionMap // thread-safe + bindingMgr *bindingManager // thread-safe + integrity stun.MessageIntegrity // read-only + _nonce stun.Nonce // needs mutex x + _lifetime time.Duration // needs mutex x + readCh chan *inboundData // thread-safe + closeCh chan struct{} // thread-safe + readTimer *time.Timer // thread-safe + refreshAllocTimer *PeriodicTimer // thread-safe + refreshPermsTimer *PeriodicTimer // thread-safe + mutex sync.RWMutex // thread-safe + log logging.LeveledLogger // read-only +} + +// NewUDPConn creates a new instance of UDPConn +func NewUDPConn(config *UDPConnConfig) *UDPConn { + c := &UDPConn{ + obs: config.Observer, + relayedAddr: config.RelayedAddr, + permMap: newPermissionMap(), + bindingMgr: newBindingManager(), + integrity: config.Integrity, + _nonce: config.Nonce, + _lifetime: config.Lifetime, + readCh: make(chan *inboundData, maxReadQueueSize), + closeCh: make(chan struct{}), + readTimer: time.NewTimer(time.Duration(math.MaxInt64)), + log: config.Log, + } + + c.log.Debugf("initial lifetime: %d seconds", int(c.lifetime().Seconds())) + + c.refreshAllocTimer = NewPeriodicTimer( + timerIDRefreshAlloc, + c.onRefreshTimers, + c.lifetime()/2, + ) + + c.refreshPermsTimer = NewPeriodicTimer( + timerIDRefreshPerms, + c.onRefreshTimers, + permRefreshInterval, + ) + + if c.refreshAllocTimer.Start() { + c.log.Debugf("refreshAllocTimer started") + } + if c.refreshPermsTimer.Start() { + c.log.Debugf("refreshPermsTimer started") + } + + return c +} + +// ReadFrom reads a packet from the connection, +// copying the payload into p. It returns the number of +// bytes copied into p and the return address that +// was on the packet. +// It returns the number of bytes read (0 <= n <= len(p)) +// and any error encountered. Callers should always process +// the n > 0 bytes returned before considering the error err. +// ReadFrom can be made to time out and return +// an Error with Timeout() == true after a fixed time limit; +// see SetDeadline and SetReadDeadline. +func (c *UDPConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) { + for { + select { + case ibData := <-c.readCh: + n := copy(p, ibData.data) + if n < len(ibData.data) { + return 0, nil, io.ErrShortBuffer + } + return n, ibData.from, nil + + case <-c.readTimer.C: + return 0, nil, &net.OpError{ + Op: "read", + Net: c.LocalAddr().Network(), + Addr: c.LocalAddr(), + Err: newTimeoutError("i/o timeout"), + } + + case <-c.closeCh: + return 0, nil, &net.OpError{ + Op: "read", + Net: c.LocalAddr().Network(), + Addr: c.LocalAddr(), + Err: errClosed, + } + } + } +} + +// WriteTo writes a packet with payload p to addr. +// WriteTo can be made to time out and return +// an Error with Timeout() == true after a fixed time limit; +// see SetDeadline and SetWriteDeadline. +// On packet-oriented connections, write timeouts are rare. +func (c *UDPConn) WriteTo(p []byte, addr net.Addr) (int, error) { //nolint: gocognit + var err error + _, ok := addr.(*net.UDPAddr) + if !ok { + return 0, errUDPAddrCast + } + + // check if we have a permission for the destination IP addr + perm, ok := c.permMap.find(addr) + if !ok { + perm = &permission{} + c.permMap.insert(addr, perm) + } + + // This func-block would block, per destination IP (, or perm), until + // the perm state becomes "requested". Purpose of this is to guarantee + // the order of packets (within the same perm). + // Note that CreatePermission transaction may not be complete before + // all the data transmission. This is done assuming that the request + // will be mostly likely successful and we can tolerate some loss of + // UDP packet (or reorder), inorder to minimize the latency in most cases. + createPermission := func() error { + perm.mutex.Lock() + defer perm.mutex.Unlock() + + if perm.state() == permStateIdle { + // punch a hole! (this would block a bit..) + if err = c.createPermissions(addr); err != nil { + c.permMap.delete(addr) + return err + } + perm.setState(permStatePermitted) + } + return nil + } + + for i := 0; i < maxRetryAttempts; i++ { + if err = createPermission(); !errors.Is(err, errTryAgain) { + break + } + } + if err != nil { + return 0, err + } + + // bind channel + b, ok := c.bindingMgr.findByAddr(addr) + if !ok { + b = c.bindingMgr.create(addr) + } + + bindSt := b.state() + + if bindSt == bindingStateIdle || bindSt == bindingStateRequest || bindSt == bindingStateFailed { + func() { + // block only callers with the same binding until + // the binding transaction has been complete + b.muBind.Lock() + defer b.muBind.Unlock() + + // binding state may have been changed while waiting. check again. + if b.state() == bindingStateIdle { + b.setState(bindingStateRequest) + go func() { + err2 := c.bind(b) + if err2 != nil { + c.log.Warnf("bind() failed: %s", err2.Error()) + b.setState(bindingStateFailed) + // keep going... + } else { + b.setState(bindingStateReady) + } + }() + } + }() + + // send data using SendIndication + peerAddr := addr2PeerAddress(addr) + var msg *stun.Message + msg, err = stun.Build( + stun.TransactionID, + stun.NewType(stun.MethodSend, stun.ClassIndication), + proto.Data(p), + peerAddr, + stun.Fingerprint, + ) + if err != nil { + return 0, err + } + + // indication has no transaction (fire-and-forget) + + return c.obs.WriteTo(msg.Raw, c.obs.TURNServerAddr()) + } + + // binding is either ready + + // check if the binding needs a refresh + func() { + b.muBind.Lock() + defer b.muBind.Unlock() + + if b.state() == bindingStateReady && time.Since(b.refreshedAt()) > 5*time.Minute { + b.setState(bindingStateRefresh) + go func() { + err = c.bind(b) + if err != nil { + c.log.Warnf("bind() for refresh failed: %s", err.Error()) + b.setState(bindingStateFailed) + // keep going... + } else { + b.setRefreshedAt(time.Now()) + b.setState(bindingStateReady) + } + }() + } + }() + + // send via ChannelData + return c.sendChannelData(p, b.number) +} + +// Close closes the connection. +// Any blocked ReadFrom or WriteTo operations will be unblocked and return errors. +func (c *UDPConn) Close() error { + c.refreshAllocTimer.Stop() + c.refreshPermsTimer.Stop() + + select { + case <-c.closeCh: + return errAlreadyClosed + default: + close(c.closeCh) + } + + c.obs.OnDeallocated(c.relayedAddr) + return c.refreshAllocation(0, true /* dontWait=true */) +} + +// LocalAddr returns the local network address. +func (c *UDPConn) LocalAddr() net.Addr { + return c.relayedAddr +} + +// SetDeadline sets the read and write deadlines associated +// with the connection. It is equivalent to calling both +// SetReadDeadline and SetWriteDeadline. +// +// A deadline is an absolute time after which I/O operations +// fail with a timeout (see type Error) instead of +// blocking. The deadline applies to all future and pending +// I/O, not just the immediately following call to ReadFrom or +// WriteTo. After a deadline has been exceeded, the connection +// can be refreshed by setting a deadline in the future. +// +// An idle timeout can be implemented by repeatedly extending +// the deadline after successful ReadFrom or WriteTo calls. +// +// A zero value for t means I/O operations will not time out. +func (c *UDPConn) SetDeadline(t time.Time) error { + return c.SetReadDeadline(t) +} + +// SetReadDeadline sets the deadline for future ReadFrom calls +// and any currently-blocked ReadFrom call. +// A zero value for t means ReadFrom will not time out. +func (c *UDPConn) SetReadDeadline(t time.Time) error { + var d time.Duration + if t == noDeadline() { + d = time.Duration(math.MaxInt64) + } else { + d = time.Until(t) + } + c.readTimer.Reset(d) + return nil +} + +// SetWriteDeadline sets the deadline for future WriteTo calls +// and any currently-blocked WriteTo call. +// Even if write times out, it may return n > 0, indicating that +// some of the data was successfully written. +// A zero value for t means WriteTo will not time out. +func (c *UDPConn) SetWriteDeadline(t time.Time) error { + // Write never blocks. + return nil +} + +func addr2PeerAddress(addr net.Addr) proto.PeerAddress { + var peerAddr proto.PeerAddress + switch a := addr.(type) { + case *net.UDPAddr: + peerAddr.IP = a.IP + peerAddr.Port = a.Port + case *net.TCPAddr: + peerAddr.IP = a.IP + peerAddr.Port = a.Port + } + + return peerAddr +} + +func (c *UDPConn) createPermissions(addrs ...net.Addr) error { + setters := []stun.Setter{ + stun.TransactionID, + stun.NewType(stun.MethodCreatePermission, stun.ClassRequest), + } + + for _, addr := range addrs { + setters = append(setters, addr2PeerAddress(addr)) + } + + setters = append(setters, + c.obs.Username(), + c.obs.Realm(), + c.nonce(), + c.integrity, + stun.Fingerprint) + + msg, err := stun.Build(setters...) + if err != nil { + return err + } + + trRes, err := c.obs.PerformTransaction(msg, c.obs.TURNServerAddr(), false) + if err != nil { + return err + } + + res := trRes.Msg + + if res.Type.Class == stun.ClassErrorResponse { + var code stun.ErrorCodeAttribute + if err = code.GetFrom(res); err == nil { + if code.Code == stun.CodeStaleNonce { + c.setNonceFromMsg(res) + return errTryAgain + } + return fmt.Errorf("%s (error %s)", res.Type, code) //nolint:goerr113 + } + + return fmt.Errorf("%s", res.Type) //nolint:goerr113 + } + + return nil +} + +// HandleInbound passes inbound data in UDPConn +func (c *UDPConn) HandleInbound(data []byte, from net.Addr) { + // copy data + copied := make([]byte, len(data)) + copy(copied, data) + + select { + case c.readCh <- &inboundData{data: copied, from: from}: + default: + c.log.Warnf("receive buffer full") + } +} + +// FindAddrByChannelNumber returns a peer address associated with the +// channel number on this UDPConn +func (c *UDPConn) FindAddrByChannelNumber(chNum uint16) (net.Addr, bool) { + b, ok := c.bindingMgr.findByNumber(chNum) + if !ok { + return nil, false + } + return b.addr, true +} + +func (c *UDPConn) setNonceFromMsg(msg *stun.Message) { + // Update nonce + var nonce stun.Nonce + if err := nonce.GetFrom(msg); err == nil { + c.setNonce(nonce) + c.log.Debug("refresh allocation: 438, got new nonce.") + } else { + c.log.Warn("refresh allocation: 438 but no nonce.") + } +} + +func (c *UDPConn) refreshAllocation(lifetime time.Duration, dontWait bool) error { + msg, err := stun.Build( + stun.TransactionID, + stun.NewType(stun.MethodRefresh, stun.ClassRequest), + proto.Lifetime{Duration: lifetime}, + c.obs.Username(), + c.obs.Realm(), + c.nonce(), + c.integrity, + stun.Fingerprint, + ) + if err != nil { + return fmt.Errorf("%w: %s", errFailedToBuildRefreshRequest, err.Error()) + } + + c.log.Debugf("send refresh request (dontWait=%v)", dontWait) + trRes, err := c.obs.PerformTransaction(msg, c.obs.TURNServerAddr(), dontWait) + if err != nil { + return fmt.Errorf("%w: %s", errFailedToRefreshAllocation, err.Error()) + } + + if dontWait { + c.log.Debug("refresh request sent") + return nil + } + + c.log.Debug("refresh request sent, and waiting response") + + res := trRes.Msg + if res.Type.Class == stun.ClassErrorResponse { + var code stun.ErrorCodeAttribute + if err = code.GetFrom(res); err == nil { + if code.Code == stun.CodeStaleNonce { + c.setNonceFromMsg(res) + return errTryAgain + } + return err + } + return fmt.Errorf("%s", res.Type) //nolint:goerr113 + } + + // Getting lifetime from response + var updatedLifetime proto.Lifetime + if err := updatedLifetime.GetFrom(res); err != nil { + return fmt.Errorf("%w: %s", errFailedToGetLifetime, err.Error()) + } + + c.setLifetime(updatedLifetime.Duration) + c.log.Debugf("updated lifetime: %d seconds", int(c.lifetime().Seconds())) + return nil +} + +func (c *UDPConn) refreshPermissions() error { + addrs := c.permMap.addrs() + if len(addrs) == 0 { + c.log.Debug("no permission to refresh") + return nil + } + if err := c.createPermissions(addrs...); err != nil { + if errors.Is(err, errTryAgain) { + return errTryAgain + } + c.log.Errorf("fail to refresh permissions: %s", err.Error()) + return err + } + c.log.Debug("refresh permissions successful") + return nil +} + +func (c *UDPConn) bind(b *binding) error { + setters := []stun.Setter{ + stun.TransactionID, + stun.NewType(stun.MethodChannelBind, stun.ClassRequest), + addr2PeerAddress(b.addr), + proto.ChannelNumber(b.number), + c.obs.Username(), + c.obs.Realm(), + c.nonce(), + c.integrity, + stun.Fingerprint, + } + + msg, err := stun.Build(setters...) + if err != nil { + return err + } + + trRes, err := c.obs.PerformTransaction(msg, c.obs.TURNServerAddr(), false) + if err != nil { + c.bindingMgr.deleteByAddr(b.addr) + return err + } + + res := trRes.Msg + + if res.Type != stun.NewType(stun.MethodChannelBind, stun.ClassSuccessResponse) { + return fmt.Errorf("unexpected response type %s", res.Type) //nolint:goerr113 + } + + c.log.Debugf("channel binding successful: %s %d", b.addr.String(), b.number) + + // Success. + return nil +} + +func (c *UDPConn) sendChannelData(data []byte, chNum uint16) (int, error) { + chData := &proto.ChannelData{ + Data: data, + Number: proto.ChannelNumber(chNum), + } + chData.Encode() + return c.obs.WriteTo(chData.Raw, c.obs.TURNServerAddr()) +} + +func (c *UDPConn) onRefreshTimers(id int) { + c.log.Debugf("refresh timer %d expired", id) + switch id { + case timerIDRefreshAlloc: + var err error + lifetime := c.lifetime() + // limit the max retries on errTryAgain to 3 + // when stale nonce returns, sencond retry should succeed + for i := 0; i < maxRetryAttempts; i++ { + err = c.refreshAllocation(lifetime, false) + if !errors.Is(err, errTryAgain) { + break + } + } + if err != nil { + c.log.Warnf("refresh allocation failed") + } + case timerIDRefreshPerms: + var err error + for i := 0; i < maxRetryAttempts; i++ { + err = c.refreshPermissions() + if !errors.Is(err, errTryAgain) { + break + } + } + if err != nil { + c.log.Warnf("refresh permissions failed") + } + } +} + +func (c *UDPConn) nonce() stun.Nonce { + c.mutex.RLock() + defer c.mutex.RUnlock() + + return c._nonce +} + +func (c *UDPConn) setNonce(nonce stun.Nonce) { + c.mutex.Lock() + defer c.mutex.Unlock() + + c.log.Debugf("set new nonce with %d bytes", len(nonce)) + c._nonce = nonce +} + +func (c *UDPConn) lifetime() time.Duration { + c.mutex.RLock() + defer c.mutex.RUnlock() + + return c._lifetime +} + +func (c *UDPConn) setLifetime(lifetime time.Duration) { + c.mutex.Lock() + defer c.mutex.Unlock() + + c._lifetime = lifetime +} diff --git a/vendor/github.com/pion/turn/v2/internal/client/errors.go b/vendor/github.com/pion/turn/v2/internal/client/errors.go new file mode 100644 index 0000000..2d4bd30 --- /dev/null +++ b/vendor/github.com/pion/turn/v2/internal/client/errors.go @@ -0,0 +1,37 @@ +package client + +import ( + "errors" +) + +var ( + errFakeErr = errors.New("fake error") + errTryAgain = errors.New("try again") + errClosed = errors.New("use of closed network connection") + errUDPAddrCast = errors.New("addr is not a net.UDPAddr") + errAlreadyClosed = errors.New("already closed") + errDoubleLock = errors.New("try-lock is already locked") + errTransactionClosed = errors.New("transaction closed") + errWaitForResultOnNonResultTransaction = errors.New("WaitForResult called on non-result transaction") + errFailedToBuildRefreshRequest = errors.New("failed to build refresh request") + errFailedToRefreshAllocation = errors.New("failed to refresh allocation") + errFailedToGetLifetime = errors.New("failed to get lifetime from refresh response") +) + +type timeoutError struct { + msg string +} + +func newTimeoutError(msg string) error { + return &timeoutError{ + msg: msg, + } +} + +func (e *timeoutError) Error() string { + return e.msg +} + +func (e *timeoutError) Timeout() bool { + return true +} diff --git a/vendor/github.com/pion/turn/v2/internal/client/periodic_timer.go b/vendor/github.com/pion/turn/v2/internal/client/periodic_timer.go new file mode 100644 index 0000000..fcd5678 --- /dev/null +++ b/vendor/github.com/pion/turn/v2/internal/client/periodic_timer.go @@ -0,0 +1,82 @@ +package client + +import ( + "sync" + "time" +) + +// PeriodicTimerTimeoutHandler is a handler called on timeout +type PeriodicTimerTimeoutHandler func(timerID int) + +// PeriodicTimer is a periodic timer +type PeriodicTimer struct { + id int + interval time.Duration + timeoutHandler PeriodicTimerTimeoutHandler + stopFunc func() + mutex sync.RWMutex +} + +// NewPeriodicTimer create a new timer +func NewPeriodicTimer(id int, timeoutHandler PeriodicTimerTimeoutHandler, interval time.Duration) *PeriodicTimer { + return &PeriodicTimer{ + id: id, + interval: interval, + timeoutHandler: timeoutHandler, + } +} + +// Start starts the timer. +func (t *PeriodicTimer) Start() bool { + t.mutex.Lock() + defer t.mutex.Unlock() + + // this is a noop if the timer is always running + if t.stopFunc != nil { + return false + } + + cancelCh := make(chan struct{}) + + go func() { + canceling := false + + for !canceling { + timer := time.NewTimer(t.interval) + + select { + case <-timer.C: + t.timeoutHandler(t.id) + case <-cancelCh: + canceling = true + timer.Stop() + } + } + }() + + t.stopFunc = func() { + close(cancelCh) + } + + return true +} + +// Stop stops the timer. +func (t *PeriodicTimer) Stop() { + t.mutex.Lock() + defer t.mutex.Unlock() + + if t.stopFunc != nil { + t.stopFunc() + t.stopFunc = nil + } +} + +// IsRunning tests if the timer is running. +// Debug purpose only +func (t *PeriodicTimer) IsRunning() bool { + t.mutex.RLock() + defer t.mutex.RUnlock() + + return (t.stopFunc != nil) +} diff --git a/vendor/github.com/pion/turn/v2/internal/client/permission.go b/vendor/github.com/pion/turn/v2/internal/client/permission.go new file mode 100644 index 0000000..5546a22 --- /dev/null +++ b/vendor/github.com/pion/turn/v2/internal/client/permission.go @@ -0,0 +1,90 @@ +package client + +import ( + "net" + "sync" + "sync/atomic" +) + +type permState int32 + +const ( + permStateIdle permState = iota + permStatePermitted +) + +type permission struct { + st permState // thread-safe (atomic op) + mutex sync.RWMutex // thread-safe +} + +func (p *permission) setState(state permState) { + atomic.StoreInt32((*int32)(&p.st), int32(state)) +} + +func (p *permission) state() permState { + return permState(atomic.LoadInt32((*int32)(&p.st))) +} + +// Thread-safe permission map +type permissionMap struct { + permMap map[string]*permission + mutex sync.RWMutex +} + +func (m *permissionMap) insert(addr net.Addr, p *permission) bool { + m.mutex.Lock() + defer m.mutex.Unlock() + + udpAddr, ok := addr.(*net.UDPAddr) + if !ok { + return false + } + + m.permMap[udpAddr.IP.String()] = p + return true +} + +func (m *permissionMap) find(addr net.Addr) (*permission, bool) { + m.mutex.RLock() + defer m.mutex.RUnlock() + + udpAddr, ok := addr.(*net.UDPAddr) + if !ok { + return nil, false + } + + p, ok := m.permMap[udpAddr.IP.String()] + return p, ok +} + +func (m *permissionMap) delete(addr net.Addr) { + m.mutex.Lock() + defer m.mutex.Unlock() + + udpAddr, ok := addr.(*net.UDPAddr) + if !ok { + return + } + + delete(m.permMap, udpAddr.IP.String()) +} + +func (m *permissionMap) addrs() []net.Addr { + m.mutex.RLock() + defer m.mutex.RUnlock() + + addrs := []net.Addr{} + for k := range m.permMap { + addrs = append(addrs, &net.UDPAddr{ + IP: net.ParseIP(k), + }) + } + return addrs +} + +func newPermissionMap() *permissionMap { + return &permissionMap{ + permMap: map[string]*permission{}, + } +} diff --git a/vendor/github.com/pion/turn/v2/internal/client/transaction.go b/vendor/github.com/pion/turn/v2/internal/client/transaction.go new file mode 100644 index 0000000..610a4d4 --- /dev/null +++ b/vendor/github.com/pion/turn/v2/internal/client/transaction.go @@ -0,0 +1,185 @@ +package client + +import ( + "net" + "sync" + "time" + + "github.com/pion/stun" +) + +const ( + maxRtxInterval time.Duration = 1600 * time.Millisecond +) + +// TransactionResult is a bag of result values of a transaction +type TransactionResult struct { + Msg *stun.Message + From net.Addr + Retries int + Err error +} + +// TransactionConfig is a set of config params used by NewTransaction +type TransactionConfig struct { + Key string + Raw []byte + To net.Addr + Interval time.Duration + IgnoreResult bool // true to throw away the result of this transaction (it will not be readable using WaitForResult) +} + +// Transaction represents a transaction +type Transaction struct { + Key string // read-only + Raw []byte // read-only + To net.Addr // read-only + nRtx int // modified only by the timer thread + interval time.Duration // modified only by the timer thread + timer *time.Timer // thread-safe, set only by the creator, and stopper + resultCh chan TransactionResult // thread-safe + mutex sync.RWMutex +} + +// NewTransaction creates a new instance of Transaction +func NewTransaction(config *TransactionConfig) *Transaction { + var resultCh chan TransactionResult + if !config.IgnoreResult { + resultCh = make(chan TransactionResult) + } + + return &Transaction{ + Key: config.Key, // read-only + Raw: config.Raw, // read-only + To: config.To, // read-only + interval: config.Interval, // modified only by the timer thread + resultCh: resultCh, // thread-safe + } +} + +// StartRtxTimer starts the transaction timer +func (t *Transaction) StartRtxTimer(onTimeout func(trKey string, nRtx int)) { + t.mutex.Lock() + defer t.mutex.Unlock() + + t.timer = time.AfterFunc(t.interval, func() { + t.mutex.Lock() + t.nRtx++ + nRtx := t.nRtx + t.interval *= 2 + if t.interval > maxRtxInterval { + t.interval = maxRtxInterval + } + t.mutex.Unlock() + onTimeout(t.Key, nRtx) + }) +} + +// StopRtxTimer stop the transaction timer +func (t *Transaction) StopRtxTimer() { + t.mutex.Lock() + defer t.mutex.Unlock() + + if t.timer != nil { + t.timer.Stop() + } +} + +// WriteResult writes the result to the result channel +func (t *Transaction) WriteResult(res TransactionResult) bool { + if t.resultCh == nil { + return false + } + + t.resultCh <- res + + return true +} + +// WaitForResult waits for the transaction result +func (t *Transaction) WaitForResult() TransactionResult { + if t.resultCh == nil { + return TransactionResult{ + Err: errWaitForResultOnNonResultTransaction, + } + } + + result, ok := <-t.resultCh + if !ok { + result.Err = errTransactionClosed + } + return result +} + +// Close closes the transaction +func (t *Transaction) Close() { + if t.resultCh != nil { + close(t.resultCh) + } +} + +// Retries returns the number of retransmission it has made +func (t *Transaction) Retries() int { + t.mutex.RLock() + defer t.mutex.RUnlock() + + return t.nRtx +} + +// TransactionMap is a thread-safe transaction map +type TransactionMap struct { + trMap map[string]*Transaction + mutex sync.RWMutex +} + +// NewTransactionMap create a new instance of the transaction map +func NewTransactionMap() *TransactionMap { + return &TransactionMap{ + trMap: map[string]*Transaction{}, + } +} + +// Insert inserts a trasaction to the map +func (m *TransactionMap) Insert(key string, tr *Transaction) bool { + m.mutex.Lock() + defer m.mutex.Unlock() + + m.trMap[key] = tr + return true +} + +// Find looks up a transaction by its key +func (m *TransactionMap) Find(key string) (*Transaction, bool) { + m.mutex.RLock() + defer m.mutex.RUnlock() + + tr, ok := m.trMap[key] + return tr, ok +} + +// Delete deletes a transaction by its key +func (m *TransactionMap) Delete(key string) { + m.mutex.Lock() + defer m.mutex.Unlock() + + delete(m.trMap, key) +} + +// CloseAndDeleteAll closes and deletes all transactions +func (m *TransactionMap) CloseAndDeleteAll() { + m.mutex.Lock() + defer m.mutex.Unlock() + + for trKey, tr := range m.trMap { + tr.Close() + delete(m.trMap, trKey) + } +} + +// Size returns the length of the transaction map +func (m *TransactionMap) Size() int { + m.mutex.RLock() + defer m.mutex.RUnlock() + + return len(m.trMap) +} diff --git a/vendor/github.com/pion/turn/v2/internal/client/trylock.go b/vendor/github.com/pion/turn/v2/internal/client/trylock.go new file mode 100644 index 0000000..48e25a0 --- /dev/null +++ b/vendor/github.com/pion/turn/v2/internal/client/trylock.go @@ -0,0 +1,24 @@ +package client + +import ( + "sync/atomic" +) + +// TryLock implement the classic "try-lock" operation. +type TryLock struct { + n int32 +} + +// Lock tries to lock the try-lock. If successful, it returns true. +// Otherwise, it returns false immedidately. +func (c *TryLock) Lock() error { + if !atomic.CompareAndSwapInt32(&c.n, 0, 1) { + return errDoubleLock + } + return nil +} + +// Unlock unlocks the try-lock. +func (c *TryLock) Unlock() { + atomic.StoreInt32(&c.n, 0) +} diff --git a/vendor/github.com/pion/turn/v2/internal/ipnet/util.go b/vendor/github.com/pion/turn/v2/internal/ipnet/util.go new file mode 100644 index 0000000..9df7f56 --- /dev/null +++ b/vendor/github.com/pion/turn/v2/internal/ipnet/util.go @@ -0,0 +1,40 @@ +// Package ipnet contains helper functions around net and IP +package ipnet + +import ( + "errors" + "net" +) + +var errFailedToCastAddr = errors.New("failed to cast net.Addr to *net.UDPAddr or *net.TCPAddr") + +// AddrIPPort extracts the IP and Port from a net.Addr +func AddrIPPort(a net.Addr) (net.IP, int, error) { + aUDP, ok := a.(*net.UDPAddr) + if ok { + return aUDP.IP, aUDP.Port, nil + } + + aTCP, ok := a.(*net.TCPAddr) + if ok { + return aTCP.IP, aTCP.Port, nil + } + + return nil, 0, errFailedToCastAddr +} + +// AddrEqual asserts that two net.Addrs are equal +// Currently only supprots UDP but will be extended in the future to support others +func AddrEqual(a, b net.Addr) bool { + aUDP, ok := a.(*net.UDPAddr) + if !ok { + return false + } + + bUDP, ok := b.(*net.UDPAddr) + if !ok { + return false + } + + return aUDP.IP.Equal(bUDP.IP) && aUDP.Port == bUDP.Port +} diff --git a/vendor/github.com/pion/turn/v2/internal/proto/addr.go b/vendor/github.com/pion/turn/v2/internal/proto/addr.go new file mode 100644 index 0000000..b1d654d --- /dev/null +++ b/vendor/github.com/pion/turn/v2/internal/proto/addr.go @@ -0,0 +1,65 @@ +package proto + +import ( + "fmt" + "net" +) + +// Addr is ip:port. +type Addr struct { + IP net.IP + Port int +} + +// Network implements net.Addr. +func (Addr) Network() string { return "turn" } + +// FromUDPAddr sets addr to UDPAddr. +func (a *Addr) FromUDPAddr(n *net.UDPAddr) { + a.IP = n.IP + a.Port = n.Port +} + +// Equal returns true if b == a. +func (a Addr) Equal(b Addr) bool { + if a.Port != b.Port { + return false + } + return a.IP.Equal(b.IP) +} + +// EqualIP returns true if a and b have equal IP addresses. +func (a Addr) EqualIP(b Addr) bool { + return a.IP.Equal(b.IP) +} + +func (a Addr) String() string { + return fmt.Sprintf("%s:%d", a.IP, a.Port) +} + +// FiveTuple represents 5-TUPLE value. +type FiveTuple struct { + Client Addr + Server Addr + Proto Protocol +} + +func (t FiveTuple) String() string { + return fmt.Sprintf("%s->%s (%s)", + t.Client, t.Server, t.Proto, + ) +} + +// Equal returns true if b == t. +func (t FiveTuple) Equal(b FiveTuple) bool { + if t.Proto != b.Proto { + return false + } + if !t.Client.Equal(b.Client) { + return false + } + if !t.Server.Equal(b.Server) { + return false + } + return true +} diff --git a/vendor/github.com/pion/turn/v2/internal/proto/chandata.go b/vendor/github.com/pion/turn/v2/internal/proto/chandata.go new file mode 100644 index 0000000..fb1295b --- /dev/null +++ b/vendor/github.com/pion/turn/v2/internal/proto/chandata.go @@ -0,0 +1,140 @@ +package proto + +import ( + "bytes" + "encoding/binary" + "errors" + "io" +) + +// ChannelData represents The ChannelData Message. +// +// See RFC 5766 Section 11.4 +type ChannelData struct { + Data []byte // can be subslice of Raw + Length int // ignored while encoding, len(Data) is used + Number ChannelNumber + Raw []byte +} + +// Equal returns true if b == c. +func (c *ChannelData) Equal(b *ChannelData) bool { + if c == nil && b == nil { + return true + } + if c == nil || b == nil { + return false + } + if c.Number != b.Number { + return false + } + if len(c.Data) != len(b.Data) { + return false + } + return bytes.Equal(c.Data, b.Data) +} + +// grow ensures that internal buffer will fit v more bytes and +// increases it capacity if necessary. +// +// Similar to stun.Message.grow method. +func (c *ChannelData) grow(v int) { + n := len(c.Raw) + v + for cap(c.Raw) < n { + c.Raw = append(c.Raw, 0) + } + c.Raw = c.Raw[:n] +} + +// Reset resets Length, Data and Raw length. +func (c *ChannelData) Reset() { + c.Raw = c.Raw[:0] + c.Length = 0 + c.Data = c.Data[:0] +} + +// Encode encodes ChannelData Message to Raw. +func (c *ChannelData) Encode() { + c.Raw = c.Raw[:0] + c.WriteHeader() + c.Raw = append(c.Raw, c.Data...) + padded := nearestPaddedValueLength(len(c.Raw)) + if bytesToAdd := padded - len(c.Raw); bytesToAdd > 0 { + for i := 0; i < bytesToAdd; i++ { + c.Raw = append(c.Raw, 0) + } + } +} + +const padding = 4 + +func nearestPaddedValueLength(l int) int { + n := padding * (l / padding) + if n < l { + n += padding + } + return n +} + +// WriteHeader writes channel number and length. +func (c *ChannelData) WriteHeader() { + if len(c.Raw) < channelDataHeaderSize { + // Making WriteHeader call valid even when c.Raw + // is nil or len(c.Raw) is less than needed for header. + c.grow(channelDataHeaderSize) + } + // Early bounds check to guarantee safety of writes below. + _ = c.Raw[:channelDataHeaderSize] + binary.BigEndian.PutUint16(c.Raw[:channelDataNumberSize], uint16(c.Number)) + binary.BigEndian.PutUint16(c.Raw[channelDataNumberSize:channelDataHeaderSize], + uint16(len(c.Data)), + ) +} + +// ErrBadChannelDataLength means that channel data length is not equal +// to actual data length. +var ErrBadChannelDataLength = errors.New("channelData length != len(Data)") + +// Decode decodes The ChannelData Message from Raw. +func (c *ChannelData) Decode() error { + buf := c.Raw + if len(buf) < channelDataHeaderSize { + return io.ErrUnexpectedEOF + } + num := binary.BigEndian.Uint16(buf[:channelDataNumberSize]) + c.Number = ChannelNumber(num) + l := binary.BigEndian.Uint16(buf[channelDataNumberSize:channelDataHeaderSize]) + c.Data = buf[channelDataHeaderSize:] + c.Length = int(l) + if !c.Number.Valid() { + return ErrInvalidChannelNumber + } + if int(l) < len(c.Data) { + c.Data = c.Data[:int(l)] + } + if int(l) > len(buf[channelDataHeaderSize:]) { + return ErrBadChannelDataLength + } + return nil +} + +const ( + channelDataLengthSize = 2 + channelDataNumberSize = channelDataLengthSize + channelDataHeaderSize = channelDataLengthSize + channelDataNumberSize +) + +// IsChannelData returns true if buf looks like the ChannelData Message. +func IsChannelData(buf []byte) bool { + if len(buf) < channelDataHeaderSize { + return false + } + + if int(binary.BigEndian.Uint16(buf[channelDataNumberSize:channelDataHeaderSize])) > len(buf[channelDataHeaderSize:]) { + return false + } + + // Quick check for channel number. + num := binary.BigEndian.Uint16(buf[0:channelNumberSize]) + return isChannelNumberValid(num) +} diff --git a/vendor/github.com/pion/turn/v2/internal/proto/chann.go b/vendor/github.com/pion/turn/v2/internal/proto/chann.go new file mode 100644 index 0000000..d64ef73 --- /dev/null +++ b/vendor/github.com/pion/turn/v2/internal/proto/chann.go @@ -0,0 +1,67 @@ +package proto + +import ( + "encoding/binary" + "errors" + "strconv" + + "github.com/pion/stun" +) + +// ChannelNumber represents CHANNEL-NUMBER attribute. +// +// The CHANNEL-NUMBER attribute contains the number of the channel. +// +// RFC 5766 Section 14.1 +type ChannelNumber uint16 // encoded as uint16 + +func (n ChannelNumber) String() string { return strconv.Itoa(int(n)) } + +// 16 bits of uint + 16 bits of RFFU = 0. +const channelNumberSize = 4 + +// AddTo adds CHANNEL-NUMBER to message. +func (n ChannelNumber) AddTo(m *stun.Message) error { + v := make([]byte, channelNumberSize) + binary.BigEndian.PutUint16(v[:2], uint16(n)) + // v[2:4] are zeroes (RFFU = 0) + m.Add(stun.AttrChannelNumber, v) + return nil +} + +// GetFrom decodes CHANNEL-NUMBER from message. +func (n *ChannelNumber) GetFrom(m *stun.Message) error { + v, err := m.Get(stun.AttrChannelNumber) + if err != nil { + return err + } + if err = stun.CheckSize(stun.AttrChannelNumber, len(v), channelNumberSize); err != nil { + return err + } + _ = v[channelNumberSize-1] // asserting length + *n = ChannelNumber(binary.BigEndian.Uint16(v[:2])) + // v[2:4] is RFFU and equals to 0. + return nil +} + +// See https://tools.ietf.org/html/rfc5766#section-11: +// +// 0x4000 through 0x7FFF: These values are the allowed channel +// numbers (16,383 possible values). +const ( + MinChannelNumber = 0x4000 + MaxChannelNumber = 0x7FFF +) + +// ErrInvalidChannelNumber means that channel number is not valid as by RFC 5766 Section 11. +var ErrInvalidChannelNumber = errors.New("channel number not in [0x4000, 0x7FFF]") + +// isChannelNumberValid returns true if c in [0x4000, 0x7FFF]. +func isChannelNumberValid(c uint16) bool { + return c >= MinChannelNumber && c <= MaxChannelNumber +} + +// Valid returns true if channel number has correct value that complies RFC 5766 Section 11 range. +func (n ChannelNumber) Valid() bool { + return isChannelNumberValid(uint16(n)) +} diff --git a/vendor/github.com/pion/turn/v2/internal/proto/data.go b/vendor/github.com/pion/turn/v2/internal/proto/data.go new file mode 100644 index 0000000..64243e0 --- /dev/null +++ b/vendor/github.com/pion/turn/v2/internal/proto/data.go @@ -0,0 +1,30 @@ +package proto + +import "github.com/pion/stun" + +// Data represents DATA attribute. +// +// The DATA attribute is present in all Send and Data indications. The +// value portion of this attribute is variable length and consists of +// the application data (that is, the data that would immediately follow +// the UDP header if the data was been sent directly between the client +// and the peer). +// +// RFC 5766 Section 14.4 +type Data []byte + +// AddTo adds DATA to message. +func (d Data) AddTo(m *stun.Message) error { + m.Add(stun.AttrData, d) + return nil +} + +// GetFrom decodes DATA from message. +func (d *Data) GetFrom(m *stun.Message) error { + v, err := m.Get(stun.AttrData) + if err != nil { + return err + } + *d = v + return nil +} diff --git a/vendor/github.com/pion/turn/v2/internal/proto/dontfrag.go b/vendor/github.com/pion/turn/v2/internal/proto/dontfrag.go new file mode 100644 index 0000000..eb4d8ca --- /dev/null +++ b/vendor/github.com/pion/turn/v2/internal/proto/dontfrag.go @@ -0,0 +1,18 @@ +package proto + +import "github.com/pion/stun" + +// DontFragmentAttr represents DONT-FRAGMENT attribute. +type DontFragmentAttr struct{} + +// AddTo adds DONT-FRAGMENT attribute to message. +func (DontFragmentAttr) AddTo(m *stun.Message) error { + m.Add(stun.AttrDontFragment, nil) + return nil +} + +// IsSet returns true if DONT-FRAGMENT attribute is set. +func (DontFragmentAttr) IsSet(m *stun.Message) bool { + _, err := m.Get(stun.AttrDontFragment) + return err == nil +} diff --git a/vendor/github.com/pion/turn/v2/internal/proto/evenport.go b/vendor/github.com/pion/turn/v2/internal/proto/evenport.go new file mode 100644 index 0000000..a5a3882 --- /dev/null +++ b/vendor/github.com/pion/turn/v2/internal/proto/evenport.go @@ -0,0 +1,55 @@ +package proto + +import "github.com/pion/stun" + +// EvenPort represents EVEN-PORT attribute. +// +// This attribute allows the client to request that the port in the +// relayed transport address be even, and (optionally) that the server +// reserve the next-higher port number. +// +// RFC 5766 Section 14.6 +type EvenPort struct { + // ReservePort means that the server is requested to reserve + // the next-higher port number (on the same IP address) + // for a subsequent allocation. + ReservePort bool +} + +func (p EvenPort) String() string { + if p.ReservePort { + return "reserve: true" + } + return "reserve: false" +} + +const ( + evenPortSize = 1 + firstBitSet = (1 << 8) - 1 // 0b100000000 +) + +// AddTo adds EVEN-PORT to message. +func (p EvenPort) AddTo(m *stun.Message) error { + v := make([]byte, evenPortSize) + if p.ReservePort { + // Set first bit to 1. + v[0] = firstBitSet + } + m.Add(stun.AttrEvenPort, v) + return nil +} + +// GetFrom decodes EVEN-PORT from message. +func (p *EvenPort) GetFrom(m *stun.Message) error { + v, err := m.Get(stun.AttrEvenPort) + if err != nil { + return err + } + if err = stun.CheckSize(stun.AttrEvenPort, len(v), evenPortSize); err != nil { + return err + } + if v[0]&firstBitSet > 0 { + p.ReservePort = true + } + return nil +} diff --git a/vendor/github.com/pion/turn/v2/internal/proto/fuzz.go b/vendor/github.com/pion/turn/v2/internal/proto/fuzz.go new file mode 100644 index 0000000..1a171fb --- /dev/null +++ b/vendor/github.com/pion/turn/v2/internal/proto/fuzz.go @@ -0,0 +1,111 @@ +// +build gofuzz + +package proto + +import ( + "fmt" + + "github.com/pion/stun" +) + +type attr interface { + stun.Getter + stun.Setter +} + +type attrs []struct { + g attr + t stun.AttrType +} + +func (a attrs) pick(v byte) struct { + g attr + t stun.AttrType +} { + idx := int(v) % len(a) + return a[idx] +} + +func FuzzSetters(data []byte) int { + var ( + m1 = &stun.Message{ + Raw: make([]byte, 0, 2048), + } + m2 = &stun.Message{ + Raw: make([]byte, 0, 2048), + } + m3 = &stun.Message{ + Raw: make([]byte, 0, 2048), + } + ) + attributes := attrs{ + {new(RequestedTransport), stun.AttrRequestedTransport}, + {new(RelayedAddress), stun.AttrXORRelayedAddress}, + {new(ChannelNumber), stun.AttrChannelNumber}, + {new(Data), stun.AttrData}, + {new(EvenPort), stun.AttrEvenPort}, + {new(Lifetime), stun.AttrLifetime}, + {new(ReservationToken), stun.AttrReservationToken}, + } + var firstByte = byte(0) + if len(data) > 0 { + firstByte = data[0] + } + a := attributes.pick(firstByte) + value := data + if len(data) > 1 { + value = value[1:] + } + m1.WriteHeader() + m1.Add(a.t, value) + err := a.g.GetFrom(m1) + if err == stun.ErrAttributeNotFound { + fmt.Println("unexpected 404") // nolint + panic(err) // nolint + } + if err != nil { + return 1 + } + m2.WriteHeader() + if err := a.g.AddTo(m2); err != nil { + fmt.Println("failed to add attribute to m2") // nolint + panic(err) // nolint + } + m3.WriteHeader() + v, err := m2.Get(a.t) + if err != nil { + panic(err) // nolint + } + m3.Add(a.t, v) + + if !m2.Equal(m3) { + fmt.Println(m2, "not equal", m3) // nolint + panic("not equal") // nolint + } + return 1 +} + +var d = &ChannelData{} + +func FuzzChannelData(data []byte) int { + d.Reset() + if b := bin.Uint16(data[0:4]); b > 20000 { + bin.PutUint16(data[0:4], MinChannelNumber-1) + } else if b > 40000 { + bin.PutUint16(data[0:4], MinChannelNumber+(MaxChannelNumber-MinChannelNumber)%b) + } + d.Raw = append(d.Raw, data...) + if d.Decode() != nil { + return 0 + } + d2 := &ChannelData{} + d.Encode() + if !d.Number.Valid() { + return 1 + } + d2.Raw = d.Raw + if err := d2.Decode(); err != nil { + panic(err) //nolint + } + return 1 +} diff --git a/vendor/github.com/pion/turn/v2/internal/proto/lifetime.go b/vendor/github.com/pion/turn/v2/internal/proto/lifetime.go new file mode 100644 index 0000000..b781696 --- /dev/null +++ b/vendor/github.com/pion/turn/v2/internal/proto/lifetime.go @@ -0,0 +1,52 @@ +package proto + +import ( + "encoding/binary" + "time" + + "github.com/pion/stun" +) + +// DefaultLifetime in RFC 5766 is 10 minutes. +// +// RFC 5766 Section 2.2 +const DefaultLifetime = time.Minute * 10 + +// Lifetime represents LIFETIME attribute. +// +// The LIFETIME attribute represents the duration for which the server +// will maintain an allocation in the absence of a refresh. The value +// portion of this attribute is 4-bytes long and consists of a 32-bit +// unsigned integral value representing the number of seconds remaining +// until expiration. +// +// RFC 5766 Section 14.2 +type Lifetime struct { + time.Duration +} + +// uint32 seconds +const lifetimeSize = 4 // 4 bytes, 32 bits + +// AddTo adds LIFETIME to message. +func (l Lifetime) AddTo(m *stun.Message) error { + v := make([]byte, lifetimeSize) + binary.BigEndian.PutUint32(v, uint32(l.Seconds())) + m.Add(stun.AttrLifetime, v) + return nil +} + +// GetFrom decodes LIFETIME from message. +func (l *Lifetime) GetFrom(m *stun.Message) error { + v, err := m.Get(stun.AttrLifetime) + if err != nil { + return err + } + if err = stun.CheckSize(stun.AttrLifetime, len(v), lifetimeSize); err != nil { + return err + } + _ = v[lifetimeSize-1] // asserting length + seconds := binary.BigEndian.Uint32(v) + l.Duration = time.Second * time.Duration(seconds) + return nil +} diff --git a/vendor/github.com/pion/turn/v2/internal/proto/peeraddr.go b/vendor/github.com/pion/turn/v2/internal/proto/peeraddr.go new file mode 100644 index 0000000..b357b82 --- /dev/null +++ b/vendor/github.com/pion/turn/v2/internal/proto/peeraddr.go @@ -0,0 +1,42 @@ +package proto + +import ( + "net" + + "github.com/pion/stun" +) + +// PeerAddress implements XOR-PEER-ADDRESS attribute. +// +// The XOR-PEER-ADDRESS specifies the address and port of the peer as +// seen from the TURN server. (For example, the peer's server-reflexive +// transport address if the peer is behind a NAT.) +// +// RFC 5766 Section 14.3 +type PeerAddress struct { + IP net.IP + Port int +} + +func (a PeerAddress) String() string { + return stun.XORMappedAddress(a).String() +} + +// AddTo adds XOR-PEER-ADDRESS to message. +func (a PeerAddress) AddTo(m *stun.Message) error { + return stun.XORMappedAddress(a).AddToAs(m, stun.AttrXORPeerAddress) +} + +// GetFrom decodes XOR-PEER-ADDRESS from message. +func (a *PeerAddress) GetFrom(m *stun.Message) error { + return (*stun.XORMappedAddress)(a).GetFromAs(m, stun.AttrXORPeerAddress) +} + +// XORPeerAddress implements XOR-PEER-ADDRESS attribute. +// +// The XOR-PEER-ADDRESS specifies the address and port of the peer as +// seen from the TURN server. (For example, the peer's server-reflexive +// transport address if the peer is behind a NAT.) +// +// RFC 5766 Section 14.3 +type XORPeerAddress = PeerAddress diff --git a/vendor/github.com/pion/turn/v2/internal/proto/proto.go b/vendor/github.com/pion/turn/v2/internal/proto/proto.go new file mode 100644 index 0000000..4b08c76 --- /dev/null +++ b/vendor/github.com/pion/turn/v2/internal/proto/proto.go @@ -0,0 +1,30 @@ +// Package proto implements RFC 5766 Traversal Using Relays around NAT. +// +// Merged from gortc/turn v0.80. +package proto + +import ( + "github.com/pion/stun" +) + +// Default ports for TURN from RFC 5766 Section 4. +const ( + // DefaultPort for TURN is same as STUN. + DefaultPort = stun.DefaultPort + // DefaultTLSPort is for TURN over TLS and is same as STUN. + DefaultTLSPort = stun.DefaultTLSPort +) + +// CreatePermissionRequest is shorthand for create permission request type. +func CreatePermissionRequest() stun.MessageType { + return stun.NewType(stun.MethodCreatePermission, stun.ClassRequest) +} + +// AllocateRequest is shorthand for allocation request message type. +func AllocateRequest() stun.MessageType { return stun.NewType(stun.MethodAllocate, stun.ClassRequest) } + +// SendIndication is shorthand for send indication message type. +func SendIndication() stun.MessageType { return stun.NewType(stun.MethodSend, stun.ClassIndication) } + +// RefreshRequest is shorthand for refresh request message type. +func RefreshRequest() stun.MessageType { return stun.NewType(stun.MethodRefresh, stun.ClassRequest) } diff --git a/vendor/github.com/pion/turn/v2/internal/proto/relayedaddr.go b/vendor/github.com/pion/turn/v2/internal/proto/relayedaddr.go new file mode 100644 index 0000000..2169cb7 --- /dev/null +++ b/vendor/github.com/pion/turn/v2/internal/proto/relayedaddr.go @@ -0,0 +1,40 @@ +package proto + +import ( + "net" + + "github.com/pion/stun" +) + +// RelayedAddress implements XOR-RELAYED-ADDRESS attribute. +// +// It specifies the address and port that the server allocated to the +// client. It is encoded in the same way as XOR-MAPPED-ADDRESS. +// +// RFC 5766 Section 14.5 +type RelayedAddress struct { + IP net.IP + Port int +} + +func (a RelayedAddress) String() string { + return stun.XORMappedAddress(a).String() +} + +// AddTo adds XOR-PEER-ADDRESS to message. +func (a RelayedAddress) AddTo(m *stun.Message) error { + return stun.XORMappedAddress(a).AddToAs(m, stun.AttrXORRelayedAddress) +} + +// GetFrom decodes XOR-PEER-ADDRESS from message. +func (a *RelayedAddress) GetFrom(m *stun.Message) error { + return (*stun.XORMappedAddress)(a).GetFromAs(m, stun.AttrXORRelayedAddress) +} + +// XORRelayedAddress implements XOR-RELAYED-ADDRESS attribute. +// +// It specifies the address and port that the server allocated to the +// client. It is encoded in the same way as XOR-MAPPED-ADDRESS. +// +// RFC 5766 Section 14.5 +type XORRelayedAddress = RelayedAddress diff --git a/vendor/github.com/pion/turn/v2/internal/proto/reqfamily.go b/vendor/github.com/pion/turn/v2/internal/proto/reqfamily.go new file mode 100644 index 0000000..e83d6bb --- /dev/null +++ b/vendor/github.com/pion/turn/v2/internal/proto/reqfamily.go @@ -0,0 +1,61 @@ +package proto + +import ( + "errors" + + "github.com/pion/stun" +) + +// RequestedAddressFamily represents the REQUESTED-ADDRESS-FAMILY Attribute as +// defined in RFC 6156 Section 4.1.1. +type RequestedAddressFamily byte + +const requestedFamilySize = 4 + +var errInvalidRequestedFamilyValue = errors.New("invalid value for requested family attribute") + +// GetFrom decodes REQUESTED-ADDRESS-FAMILY from message. +func (f *RequestedAddressFamily) GetFrom(m *stun.Message) error { + v, err := m.Get(stun.AttrRequestedAddressFamily) + if err != nil { + return err + } + if err = stun.CheckSize(stun.AttrRequestedAddressFamily, len(v), requestedFamilySize); err != nil { + return err + } + switch v[0] { + case byte(RequestedFamilyIPv4), byte(RequestedFamilyIPv6): + *f = RequestedAddressFamily(v[0]) + default: + return errInvalidRequestedFamilyValue + } + return nil +} + +func (f RequestedAddressFamily) String() string { + switch f { + case RequestedFamilyIPv4: + return "IPv4" + case RequestedFamilyIPv6: + return "IPv6" + default: + return "unknown" + } +} + +// AddTo adds REQUESTED-ADDRESS-FAMILY to message. +func (f RequestedAddressFamily) AddTo(m *stun.Message) error { + v := make([]byte, requestedFamilySize) + v[0] = byte(f) + // b[1:4] is RFFU = 0. + // The RFFU field MUST be set to zero on transmission and MUST be + // ignored on reception. It is reserved for future uses. + m.Add(stun.AttrRequestedAddressFamily, v) + return nil +} + +// Values for RequestedAddressFamily as defined in RFC 6156 Section 4.1.1. +const ( + RequestedFamilyIPv4 RequestedAddressFamily = 0x01 + RequestedFamilyIPv6 RequestedAddressFamily = 0x02 +) diff --git a/vendor/github.com/pion/turn/v2/internal/proto/reqtrans.go b/vendor/github.com/pion/turn/v2/internal/proto/reqtrans.go new file mode 100644 index 0000000..a4e4863 --- /dev/null +++ b/vendor/github.com/pion/turn/v2/internal/proto/reqtrans.go @@ -0,0 +1,65 @@ +package proto + +import ( + "strconv" + + "github.com/pion/stun" +) + +// Protocol is IANA assigned protocol number. +type Protocol byte + +const ( + // ProtoUDP is IANA assigned protocol number for UDP. + ProtoUDP Protocol = 17 +) + +func (p Protocol) String() string { + switch p { + case ProtoUDP: + return "UDP" + default: + return strconv.Itoa(int(p)) + } +} + +// RequestedTransport represents REQUESTED-TRANSPORT attribute. +// +// This attribute is used by the client to request a specific transport +// protocol for the allocated transport address. RFC 5766 only allows the use of +// codepoint 17 (User Datagram Protocol). +// +// RFC 5766 Section 14.7 +type RequestedTransport struct { + Protocol Protocol +} + +func (t RequestedTransport) String() string { + return "protocol: " + t.Protocol.String() +} + +const requestedTransportSize = 4 + +// AddTo adds REQUESTED-TRANSPORT to message. +func (t RequestedTransport) AddTo(m *stun.Message) error { + v := make([]byte, requestedTransportSize) + v[0] = byte(t.Protocol) + // b[1:4] is RFFU = 0. + // The RFFU field MUST be set to zero on transmission and MUST be + // ignored on reception. It is reserved for future uses. + m.Add(stun.AttrRequestedTransport, v) + return nil +} + +// GetFrom decodes REQUESTED-TRANSPORT from message. +func (t *RequestedTransport) GetFrom(m *stun.Message) error { + v, err := m.Get(stun.AttrRequestedTransport) + if err != nil { + return err + } + if err = stun.CheckSize(stun.AttrRequestedTransport, len(v), requestedTransportSize); err != nil { + return err + } + t.Protocol = Protocol(v[0]) + return nil +} diff --git a/vendor/github.com/pion/turn/v2/internal/proto/rsrvtoken.go b/vendor/github.com/pion/turn/v2/internal/proto/rsrvtoken.go new file mode 100644 index 0000000..6e2ed4d --- /dev/null +++ b/vendor/github.com/pion/turn/v2/internal/proto/rsrvtoken.go @@ -0,0 +1,39 @@ +package proto + +import "github.com/pion/stun" + +// ReservationToken represents RESERVATION-TOKEN attribute. +// +// The RESERVATION-TOKEN attribute contains a token that uniquely +// identifies a relayed transport address being held in reserve by the +// server. The server includes this attribute in a success response to +// tell the client about the token, and the client includes this +// attribute in a subsequent Allocate request to request the server use +// that relayed transport address for the allocation. +// +// RFC 5766 Section 14.9 +type ReservationToken []byte + +const reservationTokenSize = 8 // 8 bytes + +// AddTo adds RESERVATION-TOKEN to message. +func (t ReservationToken) AddTo(m *stun.Message) error { + if err := stun.CheckSize(stun.AttrReservationToken, len(t), reservationTokenSize); err != nil { + return err + } + m.Add(stun.AttrReservationToken, t) + return nil +} + +// GetFrom decodes RESERVATION-TOKEN from message. +func (t *ReservationToken) GetFrom(m *stun.Message) error { + v, err := m.Get(stun.AttrReservationToken) + if err != nil { + return err + } + if err = stun.CheckSize(stun.AttrReservationToken, len(v), reservationTokenSize); err != nil { + return err + } + *t = v + return nil +} diff --git a/vendor/github.com/pion/turn/v2/internal/server/errors.go b/vendor/github.com/pion/turn/v2/internal/server/errors.go new file mode 100644 index 0000000..13f8ee1 --- /dev/null +++ b/vendor/github.com/pion/turn/v2/internal/server/errors.go @@ -0,0 +1,26 @@ +package server + +import "errors" + +var ( + errFailedToGenerateNonce = errors.New("failed to generate nonce") + errFailedToSendError = errors.New("failed to send error message") + errDuplicatedNonce = errors.New("duplicated Nonce generated, discarding request") + errNoSuchUser = errors.New("no such user exists") + errUnexpectedClass = errors.New("unexpected class") + errUnexpectedMethod = errors.New("unexpected method") + errFailedToHandle = errors.New("failed to handle") + errUnhandledSTUNPacket = errors.New("unhandled STUN packet") + errUnableToHandleChannelData = errors.New("unable to handle ChannelData") + errFailedToCreateSTUNPacket = errors.New("failed to create stun message from packet") + errFailedToCreateChannelData = errors.New("failed to create channel data from packet") + errRelayAlreadyAllocatedForFiveTuple = errors.New("relay already allocated for 5-TUPLE") + errRequestedTransportMustBeUDP = errors.New("RequestedTransport must be UDP") + errNoDontFragmentSupport = errors.New("no support for DONT-FRAGMENT") + errRequestWithReservationTokenAndEvenPort = errors.New("Request must not contain RESERVATION-TOKEN and EVEN-PORT") + errNoAllocationFound = errors.New("no allocation found") + errNoPermission = errors.New("unable to handle send-indication, no permission added") + errShortWrite = errors.New("packet write smaller than packet") + errNoSuchChannelBind = errors.New("no such channel bind") + errFailedWriteSocket = errors.New("failed writing to socket") +) diff --git a/vendor/github.com/pion/turn/v2/internal/server/server.go b/vendor/github.com/pion/turn/v2/internal/server/server.go new file mode 100644 index 0000000..27f3758 --- /dev/null +++ b/vendor/github.com/pion/turn/v2/internal/server/server.go @@ -0,0 +1,109 @@ +// Package server implements the private API to implement a TURN server +package server + +import ( + "fmt" + "net" + "sync" + "time" + + "github.com/pion/logging" + "github.com/pion/stun" + "github.com/pion/turn/v2/internal/allocation" + "github.com/pion/turn/v2/internal/proto" +) + +// Request contains all the state needed to process a single incoming datagram +type Request struct { + // Current Request State + Conn net.PacketConn + SrcAddr net.Addr + Buff []byte + + // Server State + AllocationManager *allocation.Manager + Nonces *sync.Map + + // User Configuration + AuthHandler func(username string, realm string, srcAddr net.Addr) (key []byte, ok bool) + Log logging.LeveledLogger + Realm string + ChannelBindTimeout time.Duration +} + +// HandleRequest processes the give Request +func HandleRequest(r Request) error { + r.Log.Debugf("received %d bytes of udp from %s on %s", len(r.Buff), r.SrcAddr.String(), r.Conn.LocalAddr().String()) + + if proto.IsChannelData(r.Buff) { + return handleDataPacket(r) + } + + return handleTURNPacket(r) +} + +func handleDataPacket(r Request) error { + r.Log.Debugf("received DataPacket from %s", r.SrcAddr.String()) + c := proto.ChannelData{Raw: r.Buff} + if err := c.Decode(); err != nil { + return fmt.Errorf("%w: %v", errFailedToCreateChannelData, err) + } + + err := handleChannelData(r, &c) + if err != nil { + err = fmt.Errorf("%w from %v: %v", errUnableToHandleChannelData, r.SrcAddr, err) + } + + return err +} + +func handleTURNPacket(r Request) error { + r.Log.Debug("handleTURNPacket") + m := &stun.Message{Raw: append([]byte{}, r.Buff...)} + if err := m.Decode(); err != nil { + return fmt.Errorf("%w: %v", errFailedToCreateSTUNPacket, err) + } + + h, err := getMessageHandler(m.Type.Class, m.Type.Method) + if err != nil { + return fmt.Errorf("%w %v-%v from %v: %v", errUnhandledSTUNPacket, m.Type.Method, m.Type.Class, r.SrcAddr, err) + } + + err = h(r, m) + if err != nil { + return fmt.Errorf("%w %v-%v from %v: %v", errFailedToHandle, m.Type.Method, m.Type.Class, r.SrcAddr, err) + } + + return nil +} + +func getMessageHandler(class stun.MessageClass, method stun.Method) (func(r Request, m *stun.Message) error, error) { + switch class { + case stun.ClassIndication: + switch method { + case stun.MethodSend: + return handleSendIndication, nil + default: + return nil, fmt.Errorf("%w: %s", errUnexpectedMethod, method) + } + + case stun.ClassRequest: + switch method { + case stun.MethodAllocate: + return handleAllocateRequest, nil + case stun.MethodRefresh: + return handleRefreshRequest, nil + case stun.MethodCreatePermission: + return handleCreatePermissionRequest, nil + case stun.MethodChannelBind: + return handleChannelBindRequest, nil + case stun.MethodBinding: + return handleBindingRequest, nil + default: + return nil, fmt.Errorf("%w: %s", errUnexpectedMethod, method) + } + + default: + return nil, fmt.Errorf("%w: %s", errUnexpectedClass, class) + } +} diff --git a/vendor/github.com/pion/turn/v2/internal/server/stun.go b/vendor/github.com/pion/turn/v2/internal/server/stun.go new file mode 100644 index 0000000..673e99f --- /dev/null +++ b/vendor/github.com/pion/turn/v2/internal/server/stun.go @@ -0,0 +1,22 @@ +package server + +import ( + "github.com/pion/stun" + "github.com/pion/turn/v2/internal/ipnet" +) + +func handleBindingRequest(r Request, m *stun.Message) error { + r.Log.Debugf("received BindingRequest from %s", r.SrcAddr.String()) + + ip, port, err := ipnet.AddrIPPort(r.SrcAddr) + if err != nil { + return err + } + + attrs := buildMsg(m.TransactionID, stun.BindingSuccess, &stun.XORMappedAddress{ + IP: ip, + Port: port, + }, stun.Fingerprint) + + return buildAndSend(r.Conn, r.SrcAddr, attrs...) +} diff --git a/vendor/github.com/pion/turn/v2/internal/server/turn.go b/vendor/github.com/pion/turn/v2/internal/server/turn.go new file mode 100644 index 0000000..cbac09c --- /dev/null +++ b/vendor/github.com/pion/turn/v2/internal/server/turn.go @@ -0,0 +1,352 @@ +package server + +import ( + "fmt" + "net" + + "github.com/pion/stun" + "github.com/pion/turn/v2/internal/allocation" + "github.com/pion/turn/v2/internal/ipnet" + "github.com/pion/turn/v2/internal/proto" +) + +// // https://tools.ietf.org/html/rfc5766#section-6.2 +func handleAllocateRequest(r Request, m *stun.Message) error { + r.Log.Debugf("received AllocateRequest from %s", r.SrcAddr.String()) + + // 1. The server MUST require that the request be authenticated. This + // authentication MUST be done using the long-term credential + // mechanism of [https://tools.ietf.org/html/rfc5389#section-10.2.2] + // unless the client and server agree to use another mechanism through + // some procedure outside the scope of this document. + messageIntegrity, hasAuth, err := authenticateRequest(r, m, stun.MethodAllocate) + if !hasAuth { + return err + } + + fiveTuple := &allocation.FiveTuple{ + SrcAddr: r.SrcAddr, + DstAddr: r.Conn.LocalAddr(), + Protocol: allocation.UDP, + } + requestedPort := 0 + reservationToken := "" + + badRequestMsg := buildMsg(m.TransactionID, stun.NewType(stun.MethodAllocate, stun.ClassErrorResponse), &stun.ErrorCodeAttribute{Code: stun.CodeBadRequest}) + insufficentCapacityMsg := buildMsg(m.TransactionID, stun.NewType(stun.MethodAllocate, stun.ClassErrorResponse), &stun.ErrorCodeAttribute{Code: stun.CodeInsufficientCapacity}) + + // 2. The server checks if the 5-tuple is currently in use by an + // existing allocation. If yes, the server rejects the request with + // a 437 (Allocation Mismatch) error. + if alloc := r.AllocationManager.GetAllocation(fiveTuple); alloc != nil { + msg := buildMsg(m.TransactionID, stun.NewType(stun.MethodAllocate, stun.ClassErrorResponse), &stun.ErrorCodeAttribute{Code: stun.CodeAllocMismatch}) + return buildAndSendErr(r.Conn, r.SrcAddr, errRelayAlreadyAllocatedForFiveTuple, msg...) + } + + // 3. The server checks if the request contains a REQUESTED-TRANSPORT + // attribute. If the REQUESTED-TRANSPORT attribute is not included + // or is malformed, the server rejects the request with a 400 (Bad + // Request) error. Otherwise, if the attribute is included but + // specifies a protocol other that UDP, the server rejects the + // request with a 442 (Unsupported Transport Protocol) error. + var requestedTransport proto.RequestedTransport + if err = requestedTransport.GetFrom(m); err != nil { + return buildAndSendErr(r.Conn, r.SrcAddr, err, badRequestMsg...) + } else if requestedTransport.Protocol != proto.ProtoUDP { + msg := buildMsg(m.TransactionID, stun.NewType(stun.MethodAllocate, stun.ClassErrorResponse), &stun.ErrorCodeAttribute{Code: stun.CodeUnsupportedTransProto}) + return buildAndSendErr(r.Conn, r.SrcAddr, errRequestedTransportMustBeUDP, msg...) + } + + // 4. The request may contain a DONT-FRAGMENT attribute. If it does, + // but the server does not support sending UDP datagrams with the DF + // bit set to 1 (see Section 12), then the server treats the DONT- + // FRAGMENT attribute in the Allocate request as an unknown + // comprehension-required attribute. + if m.Contains(stun.AttrDontFragment) { + msg := buildMsg(m.TransactionID, stun.NewType(stun.MethodAllocate, stun.ClassErrorResponse), &stun.ErrorCodeAttribute{Code: stun.CodeUnknownAttribute}, &stun.UnknownAttributes{stun.AttrDontFragment}) + return buildAndSendErr(r.Conn, r.SrcAddr, errNoDontFragmentSupport, msg...) + } + + // 5. The server checks if the request contains a RESERVATION-TOKEN + // attribute. If yes, and the request also contains an EVEN-PORT + // attribute, then the server rejects the request with a 400 (Bad + // Request) error. Otherwise, it checks to see if the token is + // valid (i.e., the token is in range and has not expired and the + // corresponding relayed transport address is still available). If + // the token is not valid for some reason, the server rejects the + // request with a 508 (Insufficient Capacity) error. + var reservationTokenAttr proto.ReservationToken + if err = reservationTokenAttr.GetFrom(m); err == nil { + var evenPort proto.EvenPort + if err = evenPort.GetFrom(m); err == nil { + return buildAndSendErr(r.Conn, r.SrcAddr, errRequestWithReservationTokenAndEvenPort, badRequestMsg...) + } + } + + // 6. The server checks if the request contains an EVEN-PORT attribute. + // If yes, then the server checks that it can satisfy the request + // (i.e., can allocate a relayed transport address as described + // below). If the server cannot satisfy the request, then the + // server rejects the request with a 508 (Insufficient Capacity) + // error. + var evenPort proto.EvenPort + if err = evenPort.GetFrom(m); err == nil { + randomPort := 0 + randomPort, err = r.AllocationManager.GetRandomEvenPort() + if err != nil { + return buildAndSendErr(r.Conn, r.SrcAddr, err, insufficentCapacityMsg...) + } + requestedPort = randomPort + reservationToken = randSeq(8) + } + + // 7. At any point, the server MAY choose to reject the request with a + // 486 (Allocation Quota Reached) error if it feels the client is + // trying to exceed some locally defined allocation quota. The + // server is free to define this allocation quota any way it wishes, + // but SHOULD define it based on the username used to authenticate + // the request, and not on the client's transport address. + + // 8. Also at any point, the server MAY choose to reject the request + // with a 300 (Try Alternate) error if it wishes to redirect the + // client to a different server. The use of this error code and + // attribute follow the specification in [RFC5389]. + lifetimeDuration := allocationLifeTime(m) + a, err := r.AllocationManager.CreateAllocation( + fiveTuple, + r.Conn, + requestedPort, + lifetimeDuration) + if err != nil { + return buildAndSendErr(r.Conn, r.SrcAddr, err, insufficentCapacityMsg...) + } + + // Once the allocation is created, the server replies with a success + // response. The success response contains: + // * An XOR-RELAYED-ADDRESS attribute containing the relayed transport + // address. + // * A LIFETIME attribute containing the current value of the time-to- + // expiry timer. + // * A RESERVATION-TOKEN attribute (if a second relayed transport + // address was reserved). + // * An XOR-MAPPED-ADDRESS attribute containing the client's IP address + // and port (from the 5-tuple). + + srcIP, srcPort, err := ipnet.AddrIPPort(r.SrcAddr) + if err != nil { + return buildAndSendErr(r.Conn, r.SrcAddr, err, badRequestMsg...) + } + + relayIP, relayPort, err := ipnet.AddrIPPort(a.RelayAddr) + if err != nil { + return buildAndSendErr(r.Conn, r.SrcAddr, err, badRequestMsg...) + } + + responseAttrs := []stun.Setter{ + &proto.RelayedAddress{ + IP: relayIP, + Port: relayPort, + }, + &proto.Lifetime{ + Duration: lifetimeDuration, + }, + &stun.XORMappedAddress{ + IP: srcIP, + Port: srcPort, + }, + } + + if reservationToken != "" { + r.AllocationManager.CreateReservation(reservationToken, relayPort) + responseAttrs = append(responseAttrs, proto.ReservationToken([]byte(reservationToken))) + } + + msg := buildMsg(m.TransactionID, stun.NewType(stun.MethodAllocate, stun.ClassSuccessResponse), append(responseAttrs, messageIntegrity)...) + return buildAndSend(r.Conn, r.SrcAddr, msg...) +} + +func handleRefreshRequest(r Request, m *stun.Message) error { + r.Log.Debugf("received RefreshRequest from %s", r.SrcAddr.String()) + + messageIntegrity, hasAuth, err := authenticateRequest(r, m, stun.MethodRefresh) + if !hasAuth { + return err + } + + lifetimeDuration := allocationLifeTime(m) + fiveTuple := &allocation.FiveTuple{ + SrcAddr: r.SrcAddr, + DstAddr: r.Conn.LocalAddr(), + Protocol: allocation.UDP, + } + + if lifetimeDuration != 0 { + a := r.AllocationManager.GetAllocation(fiveTuple) + + if a == nil { + return fmt.Errorf("%w %v:%v", errNoAllocationFound, r.SrcAddr, r.Conn.LocalAddr()) + } + a.Refresh(lifetimeDuration) + } else { + r.AllocationManager.DeleteAllocation(fiveTuple) + } + + return buildAndSend(r.Conn, r.SrcAddr, buildMsg(m.TransactionID, stun.NewType(stun.MethodRefresh, stun.ClassSuccessResponse), []stun.Setter{ + &proto.Lifetime{ + Duration: lifetimeDuration, + }, + messageIntegrity, + }...)...) +} + +func handleCreatePermissionRequest(r Request, m *stun.Message) error { + r.Log.Debugf("received CreatePermission from %s", r.SrcAddr.String()) + + a := r.AllocationManager.GetAllocation(&allocation.FiveTuple{ + SrcAddr: r.SrcAddr, + DstAddr: r.Conn.LocalAddr(), + Protocol: allocation.UDP, + }) + if a == nil { + return fmt.Errorf("%w %v:%v", errNoAllocationFound, r.SrcAddr, r.Conn.LocalAddr()) + } + + messageIntegrity, hasAuth, err := authenticateRequest(r, m, stun.MethodCreatePermission) + if !hasAuth { + return err + } + + addCount := 0 + + if err := m.ForEach(stun.AttrXORPeerAddress, func(m *stun.Message) error { + var peerAddress proto.PeerAddress + if err := peerAddress.GetFrom(m); err != nil { + return err + } + + r.Log.Debugf("adding permission for %s", fmt.Sprintf("%s:%d", + peerAddress.IP.String(), peerAddress.Port)) + a.AddPermission(allocation.NewPermission( + &net.UDPAddr{ + IP: peerAddress.IP, + Port: peerAddress.Port, + }, + r.Log, + )) + addCount++ + return nil + }); err != nil { + addCount = 0 + } + + respClass := stun.ClassSuccessResponse + if addCount == 0 { + respClass = stun.ClassErrorResponse + } + + return buildAndSend(r.Conn, r.SrcAddr, buildMsg(m.TransactionID, stun.NewType(stun.MethodCreatePermission, respClass), []stun.Setter{messageIntegrity}...)...) +} + +func handleSendIndication(r Request, m *stun.Message) error { + r.Log.Debugf("received SendIndication from %s", r.SrcAddr.String()) + a := r.AllocationManager.GetAllocation(&allocation.FiveTuple{ + SrcAddr: r.SrcAddr, + DstAddr: r.Conn.LocalAddr(), + Protocol: allocation.UDP, + }) + if a == nil { + return fmt.Errorf("%w %v:%v", errNoAllocationFound, r.SrcAddr, r.Conn.LocalAddr()) + } + + dataAttr := proto.Data{} + if err := dataAttr.GetFrom(m); err != nil { + return err + } + + peerAddress := proto.PeerAddress{} + if err := peerAddress.GetFrom(m); err != nil { + return err + } + + msgDst := &net.UDPAddr{IP: peerAddress.IP, Port: peerAddress.Port} + if perm := a.GetPermission(msgDst); perm == nil { + return fmt.Errorf("%w: %v", errNoPermission, msgDst) + } + + l, err := a.RelaySocket.WriteTo(dataAttr, msgDst) + if l != len(dataAttr) { + return fmt.Errorf("%w %d != %d (expected) err: %v", errShortWrite, l, len(dataAttr), err) + } + return err +} + +func handleChannelBindRequest(r Request, m *stun.Message) error { + r.Log.Debugf("received ChannelBindRequest from %s", r.SrcAddr.String()) + + a := r.AllocationManager.GetAllocation(&allocation.FiveTuple{ + SrcAddr: r.SrcAddr, + DstAddr: r.Conn.LocalAddr(), + Protocol: allocation.UDP, + }) + if a == nil { + return fmt.Errorf("%w %v:%v", errNoAllocationFound, r.SrcAddr, r.Conn.LocalAddr()) + } + + badRequestMsg := buildMsg(m.TransactionID, stun.NewType(stun.MethodChannelBind, stun.ClassErrorResponse), &stun.ErrorCodeAttribute{Code: stun.CodeBadRequest}) + + messageIntegrity, hasAuth, err := authenticateRequest(r, m, stun.MethodChannelBind) + if !hasAuth { + return err + } + + var channel proto.ChannelNumber + if err = channel.GetFrom(m); err != nil { + return buildAndSendErr(r.Conn, r.SrcAddr, err, badRequestMsg...) + } + + peerAddr := proto.PeerAddress{} + if err = peerAddr.GetFrom(m); err != nil { + return buildAndSendErr(r.Conn, r.SrcAddr, err, badRequestMsg...) + } + + r.Log.Debugf("binding channel %d to %s", + channel, + fmt.Sprintf("%s:%d", peerAddr.IP.String(), peerAddr.Port)) + err = a.AddChannelBind(allocation.NewChannelBind( + channel, + &net.UDPAddr{IP: peerAddr.IP, Port: peerAddr.Port}, + r.Log, + ), r.ChannelBindTimeout) + if err != nil { + return buildAndSendErr(r.Conn, r.SrcAddr, err, badRequestMsg...) + } + + return buildAndSend(r.Conn, r.SrcAddr, buildMsg(m.TransactionID, stun.NewType(stun.MethodChannelBind, stun.ClassSuccessResponse), []stun.Setter{messageIntegrity}...)...) +} + +func handleChannelData(r Request, c *proto.ChannelData) error { + r.Log.Debugf("received ChannelData from %s", r.SrcAddr.String()) + + a := r.AllocationManager.GetAllocation(&allocation.FiveTuple{ + SrcAddr: r.SrcAddr, + DstAddr: r.Conn.LocalAddr(), + Protocol: allocation.UDP, + }) + if a == nil { + return fmt.Errorf("%w %v:%v", errNoAllocationFound, r.SrcAddr, r.Conn.LocalAddr()) + } + + channel := a.GetChannelByNumber(c.Number) + if channel == nil { + return fmt.Errorf("%w %x", errNoSuchChannelBind, uint16(c.Number)) + } + + l, err := a.RelaySocket.WriteTo(c.Data, channel.Peer) + if err != nil { + return fmt.Errorf("%w: %s", errFailedWriteSocket, err.Error()) + } else if l != len(c.Data) { + return fmt.Errorf("%w %d != %d (expected)", errShortWrite, l, len(c.Data)) + } + + return nil +} diff --git a/vendor/github.com/pion/turn/v2/internal/server/util.go b/vendor/github.com/pion/turn/v2/internal/server/util.go new file mode 100644 index 0000000..c6baffb --- /dev/null +++ b/vendor/github.com/pion/turn/v2/internal/server/util.go @@ -0,0 +1,133 @@ +package server + +import ( + "crypto/md5" //nolint:gosec,gci + "fmt" + "io" + "math/rand" + "net" + "strconv" + "time" + + "github.com/pion/stun" + "github.com/pion/turn/v2/internal/proto" +) + +const ( + maximumAllocationLifetime = time.Hour // https://tools.ietf.org/html/rfc5766#section-6.2 defines 3600 seconds recommendation + nonceLifetime = time.Hour // https://tools.ietf.org/html/rfc5766#section-4 + +) + +func randSeq(n int) string { + letters := []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") + b := make([]rune, n) + for i := range b { + b[i] = letters[rand.Intn(len(letters))] //nolint:gosec + } + return string(b) +} + +func buildNonce() (string, error) { + /* #nosec */ + h := md5.New() + if _, err := io.WriteString(h, strconv.FormatInt(time.Now().Unix(), 10)); err != nil { + return "", fmt.Errorf("%w: %v", errFailedToGenerateNonce, err) + } + if _, err := io.WriteString(h, strconv.FormatInt(rand.Int63(), 10)); err != nil { //nolint:gosec + return "", fmt.Errorf("%w: %v", errFailedToGenerateNonce, err) + } + return fmt.Sprintf("%x", h.Sum(nil)), nil +} + +func buildAndSend(conn net.PacketConn, dst net.Addr, attrs ...stun.Setter) error { + msg, err := stun.Build(attrs...) + if err != nil { + return err + } + _, err = conn.WriteTo(msg.Raw, dst) + return err +} + +// Send a STUN packet and return the original error to the caller +func buildAndSendErr(conn net.PacketConn, dst net.Addr, err error, attrs ...stun.Setter) error { + if sendErr := buildAndSend(conn, dst, attrs...); sendErr != nil { + err = fmt.Errorf("%w %v %v", errFailedToSendError, sendErr, err) + } + return err +} + +func buildMsg(transactionID [stun.TransactionIDSize]byte, msgType stun.MessageType, additional ...stun.Setter) []stun.Setter { + return append([]stun.Setter{&stun.Message{TransactionID: transactionID}, msgType}, additional...) +} + +func authenticateRequest(r Request, m *stun.Message, callingMethod stun.Method) (stun.MessageIntegrity, bool, error) { + respondWithNonce := func(responseCode stun.ErrorCode) (stun.MessageIntegrity, bool, error) { + nonce, err := buildNonce() + if err != nil { + return nil, false, err + } + + // Nonce has already been taken + if _, keyCollision := r.Nonces.LoadOrStore(nonce, time.Now()); keyCollision { + return nil, false, errDuplicatedNonce + } + + return nil, false, buildAndSend(r.Conn, r.SrcAddr, buildMsg(m.TransactionID, + stun.NewType(callingMethod, stun.ClassErrorResponse), + &stun.ErrorCodeAttribute{Code: responseCode}, + stun.NewNonce(nonce), + stun.NewRealm(r.Realm), + )...) + } + + if !m.Contains(stun.AttrMessageIntegrity) { + return respondWithNonce(stun.CodeUnauthorized) + } + + nonceAttr := &stun.Nonce{} + usernameAttr := &stun.Username{} + realmAttr := &stun.Realm{} + badRequestMsg := buildMsg(m.TransactionID, stun.NewType(callingMethod, stun.ClassErrorResponse), &stun.ErrorCodeAttribute{Code: stun.CodeBadRequest}) + + if err := nonceAttr.GetFrom(m); err != nil { + return nil, false, buildAndSendErr(r.Conn, r.SrcAddr, err, badRequestMsg...) + } + + // Assert Nonce exists and is not expired + nonceCreationTime, ok := r.Nonces.Load(string(*nonceAttr)) + if !ok || time.Since(nonceCreationTime.(time.Time)) >= nonceLifetime { + r.Nonces.Delete(nonceAttr) + return respondWithNonce(stun.CodeStaleNonce) + } + + if err := realmAttr.GetFrom(m); err != nil { + return nil, false, buildAndSendErr(r.Conn, r.SrcAddr, err, badRequestMsg...) + } else if err := usernameAttr.GetFrom(m); err != nil { + return nil, false, buildAndSendErr(r.Conn, r.SrcAddr, err, badRequestMsg...) + } + + ourKey, ok := r.AuthHandler(usernameAttr.String(), realmAttr.String(), r.SrcAddr) + if !ok { + return nil, false, buildAndSendErr(r.Conn, r.SrcAddr, fmt.Errorf("%w %s", errNoSuchUser, usernameAttr.String()), badRequestMsg...) + } + + if err := stun.MessageIntegrity(ourKey).Check(m); err != nil { + return nil, false, buildAndSendErr(r.Conn, r.SrcAddr, err, badRequestMsg...) + } + + return stun.MessageIntegrity(ourKey), true, nil +} + +func allocationLifeTime(m *stun.Message) time.Duration { + lifetimeDuration := proto.DefaultLifetime + + var lifetime proto.Lifetime + if err := lifetime.GetFrom(m); err == nil { + if lifetime.Duration < maximumAllocationLifetime { + lifetimeDuration = lifetime.Duration + } + } + + return lifetimeDuration +} diff --git a/vendor/github.com/pion/turn/v2/lt_cred.go b/vendor/github.com/pion/turn/v2/lt_cred.go new file mode 100644 index 0000000..07520b1 --- /dev/null +++ b/vendor/github.com/pion/turn/v2/lt_cred.go @@ -0,0 +1,56 @@ +package turn + +import ( //nolint:gci + "crypto/hmac" + "crypto/sha1" //nolint:gosec,gci + "encoding/base64" + "net" + "strconv" + "time" + + "github.com/pion/logging" +) + +// GenerateCredentials can be used to create credentials valid for [duration] time +func GenerateLongTermCredentials(sharedSecret string, duration time.Duration) (string, string, error) { + t := time.Now().Add(duration).Unix() + username := strconv.FormatInt(t, 10) + password, err := longTermCredentials(username, sharedSecret) + return username, password, err +} + +func longTermCredentials(username string, sharedSecret string) (string, error) { + mac := hmac.New(sha1.New, []byte(sharedSecret)) + _, err := mac.Write([]byte(username)) + if err != nil { + return "", err // Not sure if this will ever happen + } + password := mac.Sum(nil) + return base64.StdEncoding.EncodeToString(password), nil +} + +// NewAuthHandler returns a turn.AuthAuthHandler used with Long Term (or Time Windowed) Credentials. +// https://tools.ietf.org/search/rfc5389#section-10.2 +func NewLongTermAuthHandler(sharedSecret string, l logging.LeveledLogger) AuthHandler { + if l == nil { + l = logging.NewDefaultLoggerFactory().NewLogger("turn") + } + return func(username, realm string, srcAddr net.Addr) (key []byte, ok bool) { + l.Tracef("Authentication username=%q realm=%q srcAddr=%v\n", username, realm, srcAddr) + t, err := strconv.Atoi(username) + if err != nil { + l.Errorf("Invalid time-windowed username %q", username) + return nil, false + } + if int64(t) < time.Now().Unix() { + l.Errorf("Expired time-windowed username %q", username) + return nil, false + } + password, err := longTermCredentials(username, sharedSecret) + if err != nil { + l.Error(err.Error()) + return nil, false + } + return GenerateAuthKey(username, realm, password), true + } +} diff --git a/vendor/github.com/pion/turn/v2/relay_address_generator_none.go b/vendor/github.com/pion/turn/v2/relay_address_generator_none.go new file mode 100644 index 0000000..6fab061 --- /dev/null +++ b/vendor/github.com/pion/turn/v2/relay_address_generator_none.go @@ -0,0 +1,45 @@ +package turn + +import ( + "net" + "strconv" + + "github.com/pion/transport/vnet" +) + +// RelayAddressGeneratorNone returns the listener with no modifications +type RelayAddressGeneratorNone struct { + // Address is passed to Listen/ListenPacket when creating the Relay + Address string + + Net *vnet.Net +} + +// Validate is caled on server startup and confirms the RelayAddressGenerator is properly configured +func (r *RelayAddressGeneratorNone) Validate() error { + if r.Net == nil { + r.Net = vnet.NewNet(nil) + } + + switch { + case r.Address == "": + return errListeningAddressInvalid + default: + return nil + } +} + +// AllocatePacketConn generates a new PacketConn to receive traffic on and the IP/Port to populate the allocation response with +func (r *RelayAddressGeneratorNone) AllocatePacketConn(network string, requestedPort int) (net.PacketConn, net.Addr, error) { + conn, err := r.Net.ListenPacket(network, r.Address+":"+strconv.Itoa(requestedPort)) + if err != nil { + return nil, nil, err + } + + return conn, conn.LocalAddr(), nil +} + +// AllocateConn generates a new Conn to receive traffic on and the IP/Port to populate the allocation response with +func (r *RelayAddressGeneratorNone) AllocateConn(network string, requestedPort int) (net.Conn, net.Addr, error) { + return nil, nil, errTODO +} diff --git a/vendor/github.com/pion/turn/v2/relay_address_generator_range.go b/vendor/github.com/pion/turn/v2/relay_address_generator_range.go new file mode 100644 index 0000000..9f95429 --- /dev/null +++ b/vendor/github.com/pion/turn/v2/relay_address_generator_range.go @@ -0,0 +1,92 @@ +package turn + +import ( + "fmt" + "net" + + "github.com/pion/randutil" + "github.com/pion/transport/vnet" +) + +// RelayAddressGeneratorPortRange can be used to only allocate connections inside a defined port range. +// Similar to the RelayAddressGeneratorStatic a static ip address can be set. +type RelayAddressGeneratorPortRange struct { + // RelayAddress is the IP returned to the user when the relay is created + RelayAddress net.IP + + // MinPort the minimum port to allocate + MinPort uint16 + // MaxPort the maximum (inclusive) port to allocate + MaxPort uint16 + + // MaxRetries the amount of tries to allocate a random port in the defined range + MaxRetries int + + // Rand the random source of numbers + Rand randutil.MathRandomGenerator + + // Address is passed to Listen/ListenPacket when creating the Relay + Address string + + Net *vnet.Net +} + +// Validate is called on server startup and confirms the RelayAddressGenerator is properly configured +func (r *RelayAddressGeneratorPortRange) Validate() error { + if r.Net == nil { + r.Net = vnet.NewNet(nil) + } + + if r.Rand == nil { + r.Rand = randutil.NewMathRandomGenerator() + } + + if r.MaxRetries == 0 { + r.MaxRetries = 10 + } + + switch { + case r.MinPort == 0: + return errMinPortNotZero + case r.MaxPort == 0: + return errMaxPortNotZero + case r.RelayAddress == nil: + return errRelayAddressInvalid + case r.Address == "": + return errListeningAddressInvalid + default: + return nil + } +} + +// AllocatePacketConn generates a new PacketConn to receive traffic on and the IP/Port to populate the allocation response with +func (r *RelayAddressGeneratorPortRange) AllocatePacketConn(network string, requestedPort int) (net.PacketConn, net.Addr, error) { + if requestedPort != 0 { + conn, err := r.Net.ListenPacket(network, fmt.Sprintf("%s:%d", r.Address, requestedPort)) + if err != nil { + return nil, nil, err + } + relayAddr := conn.LocalAddr().(*net.UDPAddr) + relayAddr.IP = r.RelayAddress + return conn, relayAddr, nil + } + + for try := 0; try < r.MaxRetries; try++ { + port := r.MinPort + uint16(r.Rand.Intn(int((r.MaxPort+1)-r.MinPort))) + conn, err := r.Net.ListenPacket(network, fmt.Sprintf("%s:%d", r.Address, port)) + if err != nil { + continue + } + + relayAddr := conn.LocalAddr().(*net.UDPAddr) + relayAddr.IP = r.RelayAddress + return conn, relayAddr, nil + } + + return nil, nil, errMaxRetriesExceeded +} + +// AllocateConn generates a new Conn to receive traffic on and the IP/Port to populate the allocation response with +func (r *RelayAddressGeneratorPortRange) AllocateConn(network string, requestedPort int) (net.Conn, net.Addr, error) { + return nil, nil, errTODO +} diff --git a/vendor/github.com/pion/turn/v2/relay_address_generator_static.go b/vendor/github.com/pion/turn/v2/relay_address_generator_static.go new file mode 100644 index 0000000..ae921e7 --- /dev/null +++ b/vendor/github.com/pion/turn/v2/relay_address_generator_static.go @@ -0,0 +1,55 @@ +package turn + +import ( + "net" + "strconv" + + "github.com/pion/transport/vnet" +) + +// RelayAddressGeneratorStatic can be used to return static IP address each time a relay is created. +// This can be used when you have a single static IP address that you want to use +type RelayAddressGeneratorStatic struct { + // RelayAddress is the IP returned to the user when the relay is created + RelayAddress net.IP + + // Address is passed to Listen/ListenPacket when creating the Relay + Address string + + Net *vnet.Net +} + +// Validate is caled on server startup and confirms the RelayAddressGenerator is properly configured +func (r *RelayAddressGeneratorStatic) Validate() error { + if r.Net == nil { + r.Net = vnet.NewNet(nil) + } + + switch { + case r.RelayAddress == nil: + return errRelayAddressInvalid + case r.Address == "": + return errListeningAddressInvalid + default: + return nil + } +} + +// AllocatePacketConn generates a new PacketConn to receive traffic on and the IP/Port to populate the allocation response with +func (r *RelayAddressGeneratorStatic) AllocatePacketConn(network string, requestedPort int) (net.PacketConn, net.Addr, error) { + conn, err := r.Net.ListenPacket(network, r.Address+":"+strconv.Itoa(requestedPort)) + if err != nil { + return nil, nil, err + } + + // Replace actual listening IP with the user requested one of RelayAddressGeneratorStatic + relayAddr := conn.LocalAddr().(*net.UDPAddr) + relayAddr.IP = r.RelayAddress + + return conn, relayAddr, nil +} + +// AllocateConn generates a new Conn to receive traffic on and the IP/Port to populate the allocation response with +func (r *RelayAddressGeneratorStatic) AllocateConn(network string, requestedPort int) (net.Conn, net.Addr, error) { + return nil, nil, errTODO +} diff --git a/vendor/github.com/pion/turn/v2/renovate.json b/vendor/github.com/pion/turn/v2/renovate.json new file mode 100644 index 0000000..4400fd9 --- /dev/null +++ b/vendor/github.com/pion/turn/v2/renovate.json @@ -0,0 +1,15 @@ +{ + "extends": [ + "config:base" + ], + "postUpdateOptions": [ + "gomodTidy" + ], + "commitBody": "Generated by renovateBot", + "packageRules": [ + { + "packagePatterns": ["^golang.org/x/"], + "schedule": ["on the first day of the month"] + } + ] +} diff --git a/vendor/github.com/pion/turn/v2/server.go b/vendor/github.com/pion/turn/v2/server.go new file mode 100644 index 0000000..c8deba8 --- /dev/null +++ b/vendor/github.com/pion/turn/v2/server.go @@ -0,0 +1,161 @@ +// Package turn contains the public API for pion/turn, a toolkit for building TURN clients and servers +package turn + +import ( + "fmt" + "net" + "sync" + "time" + + "github.com/pion/logging" + "github.com/pion/turn/v2/internal/allocation" + "github.com/pion/turn/v2/internal/proto" + "github.com/pion/turn/v2/internal/server" +) + +const ( + inboundMTU = 1500 +) + +// Server is an instance of the Pion TURN Server +type Server struct { + log logging.LeveledLogger + authHandler AuthHandler + realm string + channelBindTimeout time.Duration + nonces *sync.Map + + packetConnConfigs []PacketConnConfig + listenerConfigs []ListenerConfig +} + +// NewServer creates the Pion TURN server +func NewServer(config ServerConfig) (*Server, error) { + if err := config.validate(); err != nil { + return nil, err + } + + loggerFactory := config.LoggerFactory + if loggerFactory == nil { + loggerFactory = logging.NewDefaultLoggerFactory() + } + + s := &Server{ + log: loggerFactory.NewLogger("turn"), + authHandler: config.AuthHandler, + realm: config.Realm, + channelBindTimeout: config.ChannelBindTimeout, + packetConnConfigs: config.PacketConnConfigs, + listenerConfigs: config.ListenerConfigs, + nonces: &sync.Map{}, + } + + if s.channelBindTimeout == 0 { + s.channelBindTimeout = proto.DefaultLifetime + } + + for i := range s.packetConnConfigs { + go func(p PacketConnConfig) { + allocationManager, err := allocation.NewManager(allocation.ManagerConfig{ + AllocatePacketConn: p.RelayAddressGenerator.AllocatePacketConn, + AllocateConn: p.RelayAddressGenerator.AllocateConn, + LeveledLogger: s.log, + }) + if err != nil { + s.log.Errorf("exit read loop on error: %s", err.Error()) + return + } + defer func() { + if err := allocationManager.Close(); err != nil { + s.log.Errorf("Failed to close AllocationManager: %s", err.Error()) + } + }() + + s.readLoop(p.PacketConn, allocationManager) + }(s.packetConnConfigs[i]) + } + + for _, listener := range s.listenerConfigs { + go func(l ListenerConfig) { + allocationManager, err := allocation.NewManager(allocation.ManagerConfig{ + AllocatePacketConn: l.RelayAddressGenerator.AllocatePacketConn, + AllocateConn: l.RelayAddressGenerator.AllocateConn, + LeveledLogger: s.log, + }) + if err != nil { + s.log.Errorf("exit read loop on error: %s", err.Error()) + return + } + defer func() { + if err := allocationManager.Close(); err != nil { + s.log.Errorf("Failed to close AllocationManager: %s", err.Error()) + } + }() + + for { + conn, err := l.Listener.Accept() + if err != nil { + s.log.Debugf("exit accept loop on error: %s", err.Error()) + return + } + + go s.readLoop(NewSTUNConn(conn), allocationManager) + } + }(listener) + } + + return s, nil +} + +// Close stops the TURN Server. It cleans up any associated state and closes all connections it is managing +func (s *Server) Close() error { + var errors []error + + for _, p := range s.packetConnConfigs { + if err := p.PacketConn.Close(); err != nil { + errors = append(errors, err) + } + } + + for _, l := range s.listenerConfigs { + if err := l.Listener.Close(); err != nil { + errors = append(errors, err) + } + } + + if len(errors) == 0 { + return nil + } + + err := errFailedToClose + for _, e := range errors { + err = fmt.Errorf("%s; Close error (%v) ", err.Error(), e) //nolint:goerr113 + } + + return err +} + +func (s *Server) readLoop(p net.PacketConn, allocationManager *allocation.Manager) { + buf := make([]byte, inboundMTU) + for { + n, addr, err := p.ReadFrom(buf) + if err != nil { + s.log.Debugf("exit read loop on error: %s", err.Error()) + return + } + + if err := server.HandleRequest(server.Request{ + Conn: p, + SrcAddr: addr, + Buff: buf[:n], + Log: s.log, + AuthHandler: s.authHandler, + Realm: s.realm, + AllocationManager: allocationManager, + ChannelBindTimeout: s.channelBindTimeout, + Nonces: s.nonces, + }); err != nil { + s.log.Errorf("error when handling datagram: %v", err) + } + } +} diff --git a/vendor/github.com/pion/turn/v2/server_config.go b/vendor/github.com/pion/turn/v2/server_config.go new file mode 100644 index 0000000..9ff3872 --- /dev/null +++ b/vendor/github.com/pion/turn/v2/server_config.go @@ -0,0 +1,116 @@ +package turn + +import ( + "crypto/md5" //nolint:gosec,gci + "fmt" + "net" + "strings" + "time" + + "github.com/pion/logging" +) + +// RelayAddressGenerator is used to generate a RelayAddress when creating an allocation. +// You can use one of the provided ones or provide your own. +type RelayAddressGenerator interface { + // Validate confirms that the RelayAddressGenerator is properly initialized + Validate() error + + // Allocate a PacketConn (UDP) RelayAddress + AllocatePacketConn(network string, requestedPort int) (net.PacketConn, net.Addr, error) + + // Allocate a Conn (TCP) RelayAddress + AllocateConn(network string, requestedPort int) (net.Conn, net.Addr, error) +} + +// PacketConnConfig is a single net.PacketConn to listen/write on. This will be used for UDP listeners +type PacketConnConfig struct { + PacketConn net.PacketConn + + // When an allocation is generated the RelayAddressGenerator + // creates the net.PacketConn and returns the IP/Port it is available at + RelayAddressGenerator RelayAddressGenerator +} + +func (c *PacketConnConfig) validate() error { + if c.PacketConn == nil { + return errConnUnset + } + if c.RelayAddressGenerator == nil { + return errRelayAddressGeneratorUnset + } + + return c.RelayAddressGenerator.Validate() +} + +// ListenerConfig is a single net.Listener to accept connections on. This will be used for TCP, TLS and DTLS listeners +type ListenerConfig struct { + Listener net.Listener + + // When an allocation is generated the RelayAddressGenerator + // creates the net.PacketConn and returns the IP/Port it is available at + RelayAddressGenerator RelayAddressGenerator +} + +func (c *ListenerConfig) validate() error { + if c.Listener == nil { + return errListenerUnset + } + + if c.RelayAddressGenerator == nil { + return errRelayAddressGeneratorUnset + } + + return c.RelayAddressGenerator.Validate() +} + +// AuthHandler is a callback used to handle incoming auth requests, allowing users to customize Pion TURN with custom behavior +type AuthHandler func(username, realm string, srcAddr net.Addr) (key []byte, ok bool) + +// GenerateAuthKey is a convince function to easily generate keys in the format used by AuthHandler +func GenerateAuthKey(username, realm, password string) []byte { + // #nosec + h := md5.New() + fmt.Fprint(h, strings.Join([]string{username, realm, password}, ":")) + return h.Sum(nil) +} + +// ServerConfig configures the Pion TURN Server +type ServerConfig struct { + // PacketConnConfigs and ListenerConfigs are a list of all the turn listeners + // Each listener can have custom behavior around the creation of Relays + PacketConnConfigs []PacketConnConfig + ListenerConfigs []ListenerConfig + + // LoggerFactory must be set for logging from this server. + LoggerFactory logging.LoggerFactory + + // Realm sets the realm for this server + Realm string + + // AuthHandler is a callback used to handle incoming auth requests, allowing users to customize Pion TURN with custom behavior + AuthHandler AuthHandler + + // ChannelBindTimeout sets the lifetime of channel binding. Defaults to 10 minutes. + ChannelBindTimeout time.Duration +} + +func (s *ServerConfig) validate() error { + if len(s.PacketConnConfigs) == 0 && len(s.ListenerConfigs) == 0 { + return errNoAvailableConns + } + + for _, s := range s.PacketConnConfigs { + if err := s.validate(); err != nil { + return err + } + } + + for _, s := range s.ListenerConfigs { + if err := s.validate(); err != nil { + return err + } + } + + return nil +} diff --git a/vendor/github.com/pion/turn/v2/stun_conn.go b/vendor/github.com/pion/turn/v2/stun_conn.go new file mode 100644 index 0000000..e1446bd --- /dev/null +++ b/vendor/github.com/pion/turn/v2/stun_conn.go @@ -0,0 +1,121 @@ +package turn + +import ( + "encoding/binary" + "errors" + "net" + "time" + + "github.com/pion/stun" + "github.com/pion/turn/v2/internal/proto" +) + +var ( + errInvalidTURNFrame = errors.New("data is not a valid TURN frame, no STUN or ChannelData found") + errIncompleteTURNFrame = errors.New("data contains incomplete STUN or TURN frame") +) + +// STUNConn wraps a net.Conn and implements +// net.PacketConn by being STUN aware and +// packetizing the stream +type STUNConn struct { + nextConn net.Conn + buff []byte +} + +const ( + stunHeaderSize = 20 + + channelDataLengthSize = 2 + channelDataNumberSize = channelDataLengthSize + channelDataHeaderSize = channelDataLengthSize + channelDataNumberSize + channelDataPadding = 4 +) + +// Given a buffer give the last offset of the TURN frame +// If the buffer isn't a valid STUN or ChannelData packet +// or the length doesn't match return false +func consumeSingleTURNFrame(p []byte) (int, error) { + // Too short to determine if ChannelData or STUN + if len(p) < 9 { + return 0, errIncompleteTURNFrame + } + + var datagramSize uint16 + if stun.IsMessage(p) { + datagramSize = binary.BigEndian.Uint16(p[2:4]) + stunHeaderSize + } else if num := binary.BigEndian.Uint16(p[0:4]); proto.ChannelNumber(num).Valid() { + datagramSize = binary.BigEndian.Uint16(p[channelDataNumberSize:channelDataHeaderSize]) + if paddingOverflow := (datagramSize + channelDataPadding) % channelDataPadding; paddingOverflow != 0 { + datagramSize = (datagramSize + channelDataPadding) - paddingOverflow + } + + datagramSize += channelDataHeaderSize + } else { + return 0, errInvalidTURNFrame + } + + if len(p) < int(datagramSize) { + return 0, errIncompleteTURNFrame + } + + return int(datagramSize), nil +} + +// ReadFrom implements ReadFrom from net.PacketConn +func (s *STUNConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) { + // First pass any buffered data from previous reads + n, err = consumeSingleTURNFrame(s.buff) + if errors.Is(err, errInvalidTURNFrame) { + return 0, nil, err + } else if err == nil { + copy(p, s.buff[:n]) + s.buff = s.buff[n:] + + return n, s.nextConn.RemoteAddr(), nil + } + + // Then read from the nextConn, appending to our buff + n, err = s.nextConn.Read(p) + if err != nil { + return 0, nil, err + } + + s.buff = append(s.buff, append([]byte{}, p[:n]...)...) + return s.ReadFrom(p) +} + +// WriteTo implements WriteTo from net.PacketConn +func (s *STUNConn) WriteTo(p []byte, addr net.Addr) (n int, err error) { + return s.nextConn.Write(p) +} + +// Close implements Close from net.PacketConn +func (s *STUNConn) Close() error { + return s.nextConn.Close() +} + +// LocalAddr implements LocalAddr from net.PacketConn +func (s *STUNConn) LocalAddr() net.Addr { + return s.nextConn.LocalAddr() +} + +// SetDeadline implements SetDeadline from net.PacketConn +func (s *STUNConn) SetDeadline(t time.Time) error { + return s.nextConn.SetDeadline(t) +} + +// SetReadDeadline implements SetReadDeadline from net.PacketConn +func (s *STUNConn) SetReadDeadline(t time.Time) error { + return s.nextConn.SetReadDeadline(t) +} + +// SetWriteDeadline implements SetWriteDeadline from net.PacketConn +func (s *STUNConn) SetWriteDeadline(t time.Time) error { + return s.nextConn.SetWriteDeadline(t) +} + +// NewSTUNConn creates a STUNConn +func NewSTUNConn(nextConn net.Conn) *STUNConn { + return &STUNConn{nextConn: nextConn} +} diff --git a/vendor/github.com/pion/udp/.gitignore b/vendor/github.com/pion/udp/.gitignore new file mode 100644 index 0000000..f7b3492 --- /dev/null +++ b/vendor/github.com/pion/udp/.gitignore @@ -0,0 +1,15 @@ +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Dependency directories (remove the comment below to include it) +# vendor/
\ No newline at end of file diff --git a/vendor/github.com/pion/udp/.golangci.yml b/vendor/github.com/pion/udp/.golangci.yml new file mode 100644 index 0000000..1387b55 --- /dev/null +++ b/vendor/github.com/pion/udp/.golangci.yml @@ -0,0 +1,12 @@ +linters-settings: + govet: + check-shadowing: true + misspell: + locale: US + +linters: + enable-all: true + disable: + +issues: + exclude-use-default: false diff --git a/vendor/github.com/pion/udp/LICENSE b/vendor/github.com/pion/udp/LICENSE new file mode 100644 index 0000000..81f990d --- /dev/null +++ b/vendor/github.com/pion/udp/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/pion/udp/README.md b/vendor/github.com/pion/udp/README.md new file mode 100644 index 0000000..f96659f --- /dev/null +++ b/vendor/github.com/pion/udp/README.md @@ -0,0 +1,39 @@ +<h1 align="center"> + <br> + Pion UDP + <br> +</h1> +<h4 align="center">A connection-oriented listener over a UDP PacketConn</h4> +<p align="center"> + <a href="https://pion.ly"><img src="https://img.shields.io/badge/pion-udp-gray.svg?longCache=true&colorB=brightgreen" alt="Pion UDP"></a> + <!--<a href="https://sourcegraph.com/github.com/pion/webrtc?badge"><img src="https://sourcegraph.com/github.com/pion/webrtc/-/badge.svg" alt="Sourcegraph Widget"></a>--> + <a href="https://pion.ly/slack"><img src="https://img.shields.io/badge/join-us%20on%20slack-gray.svg?longCache=true&logo=slack&colorB=brightgreen" alt="Slack Widget"></a> + <br> + <a href="https://travis-ci.org/pion/udp"><img src="https://travis-ci.org/pion/udp.svg?branch=master" alt="Build Status"></a> + <a href="https://pkg.go.dev/github.com/pion/udp"><img src="https://godoc.org/github.com/pion/udp?status.svg" alt="GoDoc"></a> + <a href="https://codecov.io/gh/pion/udp"><img src="https://codecov.io/gh/pion/udp/branch/master/graph/badge.svg" alt="Coverage Status"></a> + <a href="https://goreportcard.com/report/github.com/pion/udp"><img src="https://goreportcard.com/badge/github.com/pion/udp" alt="Go Report Card"></a> + <!--<a href="https://www.codacy.com/app/Sean-Der/webrtc"><img src="https://api.codacy.com/project/badge/Grade/18f4aec384894e6aac0b94effe51961d" alt="Codacy Badge"></a>--> + <a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="License: MIT"></a> +</p> +<br> + +### Roadmap +This package is used in the [DTLS](https://github.com/pion/dtls) and [SCTP](https://github.com/pion/sctp) transport to provide a connection-oriented listener over a UDP. + +### Community +Pion has an active community on the [Golang Slack](https://pion.ly/slack/). You can also use [Pion mailing list](https://groups.google.com/forum/#!forum/pion). + +We are always looking to support **your projects**. Please reach out if you have something to build! + +If you need commercial support or don't want to use public methods you can contact us at [team@pion.ly](mailto:team@pion.ly) + +### Contributing +Check out the **[contributing wiki](https://github.com/pion/webrtc/wiki/Contributing)** to join the group of amazing people making this project possible: + +* [Sean DuBois](https://github.com/Sean-Der) - *Original Author* +* [Michiel De Backker](https://github.com/backkem) - *Original Author* +* [Atsushi Watanabe](https://github.com/at-wat) - *Original Author* + +### License +MIT License - see [LICENSE](LICENSE) for full text diff --git a/vendor/github.com/pion/udp/conn.go b/vendor/github.com/pion/udp/conn.go new file mode 100644 index 0000000..9f680e8 --- /dev/null +++ b/vendor/github.com/pion/udp/conn.go @@ -0,0 +1,296 @@ +// Package udp provides a connection-oriented listener over a UDP PacketConn +package udp + +import ( + "context" + "errors" + "net" + "sync" + "sync/atomic" + "time" + + "github.com/pion/transport/deadline" + "github.com/pion/transport/packetio" +) + +const receiveMTU = 8192 +const defaultListenBacklog = 128 // same as Linux default + +var errClosedListener = errors.New("udp: listener closed") +var errListenQueueExceeded = errors.New("udp: listen queue exceeded") + +// listener augments a connection-oriented Listener over a UDP PacketConn +type listener struct { + pConn *net.UDPConn + + accepting atomic.Value // bool + acceptCh chan *Conn + doneCh chan struct{} + doneOnce sync.Once + acceptFilter func([]byte) bool + + connLock sync.Mutex + conns map[string]*Conn + connWG sync.WaitGroup + + readWG sync.WaitGroup + errClose atomic.Value // error +} + +// Accept waits for and returns the next connection to the listener. +func (l *listener) Accept() (net.Conn, error) { + select { + case c := <-l.acceptCh: + l.connWG.Add(1) + return c, nil + + case <-l.doneCh: + return nil, errClosedListener + } +} + +// Close closes the listener. +// Any blocked Accept operations will be unblocked and return errors. +func (l *listener) Close() error { + var err error + l.doneOnce.Do(func() { + l.accepting.Store(false) + close(l.doneCh) + + l.connLock.Lock() + // Close unaccepted connections + L_CLOSE: + for { + select { + case c := <-l.acceptCh: + close(c.doneCh) + delete(l.conns, c.rAddr.String()) + + default: + break L_CLOSE + } + } + nConns := len(l.conns) + l.connLock.Unlock() + + l.connWG.Done() + + if nConns == 0 { + // Wait if this is the final connection + l.readWG.Wait() + if errClose, ok := l.errClose.Load().(error); ok { + err = errClose + } + } else { + err = nil + } + }) + + return err +} + +// Addr returns the listener's network address. +func (l *listener) Addr() net.Addr { + return l.pConn.LocalAddr() +} + +// ListenConfig stores options for listening to an address. +type ListenConfig struct { + // Backlog defines the maximum length of the queue of pending + // connections. It is equivalent of the backlog argument of + // POSIX listen function. + // If a connection request arrives when the queue is full, + // the request will be silently discarded, unlike TCP. + // Set zero to use default value 128 which is same as Linux default. + Backlog int + + // AcceptFilter determines whether the new conn should be made for + // the incoming packet. If not set, any packet creates new conn. + AcceptFilter func([]byte) bool +} + +// Listen creates a new listener based on the ListenConfig. +func (lc *ListenConfig) Listen(network string, laddr *net.UDPAddr) (net.Listener, error) { + if lc.Backlog == 0 { + lc.Backlog = defaultListenBacklog + } + + conn, err := net.ListenUDP(network, laddr) + if err != nil { + return nil, err + } + + l := &listener{ + pConn: conn, + acceptCh: make(chan *Conn, lc.Backlog), + conns: make(map[string]*Conn), + doneCh: make(chan struct{}), + acceptFilter: lc.AcceptFilter, + } + l.accepting.Store(true) + l.connWG.Add(1) + l.readWG.Add(2) // wait readLoop and Close execution routine + + go l.readLoop() + go func() { + l.connWG.Wait() + if err := l.pConn.Close(); err != nil { + l.errClose.Store(err) + } + l.readWG.Done() + }() + + return l, nil +} + +// Listen creates a new listener using default ListenConfig. +func Listen(network string, laddr *net.UDPAddr) (net.Listener, error) { + return (&ListenConfig{}).Listen(network, laddr) +} + +var readBufferPool = &sync.Pool{ + New: func() interface{} { + buf := make([]byte, receiveMTU) + return &buf + }, +} + +// readLoop has to tasks: +// 1. Dispatching incoming packets to the correct Conn. +// It can therefore not be ended until all Conns are closed. +// 2. Creating a new Conn when receiving from a new remote. +func (l *listener) readLoop() { + defer l.readWG.Done() + + for { + buf := *(readBufferPool.Get().(*[]byte)) + n, raddr, err := l.pConn.ReadFrom(buf) + if err != nil { + return + } + conn, ok, err := l.getConn(raddr, buf[:n]) + if err != nil { + continue + } + if ok { + _, _ = conn.buffer.Write(buf[:n]) + } + } +} + +func (l *listener) getConn(raddr net.Addr, buf []byte) (*Conn, bool, error) { + l.connLock.Lock() + defer l.connLock.Unlock() + conn, ok := l.conns[raddr.String()] + if !ok { + if !l.accepting.Load().(bool) { + return nil, false, errClosedListener + } + if l.acceptFilter != nil { + if !l.acceptFilter(buf) { + return nil, false, nil + } + } + conn = l.newConn(raddr) + select { + case l.acceptCh <- conn: + l.conns[raddr.String()] = conn + default: + return nil, false, errListenQueueExceeded + } + } + return conn, true, nil +} + +// Conn augments a connection-oriented connection over a UDP PacketConn +type Conn struct { + listener *listener + + rAddr net.Addr + + buffer *packetio.Buffer + + doneCh chan struct{} + doneOnce sync.Once + + writeDeadline *deadline.Deadline +} + +func (l *listener) newConn(rAddr net.Addr) *Conn { + return &Conn{ + listener: l, + rAddr: rAddr, + buffer: packetio.NewBuffer(), + doneCh: make(chan struct{}), + writeDeadline: deadline.New(), + } +} + +// Read +func (c *Conn) Read(p []byte) (int, error) { + return c.buffer.Read(p) +} + +// Write writes len(p) bytes from p to the DTLS connection +func (c *Conn) Write(p []byte) (n int, err error) { + select { + case <-c.writeDeadline.Done(): + return 0, context.DeadlineExceeded + default: + } + return c.listener.pConn.WriteTo(p, c.rAddr) +} + +// Close closes the conn and releases any Read calls +func (c *Conn) Close() error { + var err error + c.doneOnce.Do(func() { + c.listener.connWG.Done() + close(c.doneCh) + c.listener.connLock.Lock() + delete(c.listener.conns, c.rAddr.String()) + nConns := len(c.listener.conns) + c.listener.connLock.Unlock() + + if nConns == 0 && !c.listener.accepting.Load().(bool) { + // Wait if this is the final connection + c.listener.readWG.Wait() + if errClose, ok := c.listener.errClose.Load().(error); ok { + err = errClose + } + } else { + err = nil + } + }) + + return err +} + +// LocalAddr implements net.Conn.LocalAddr +func (c *Conn) LocalAddr() net.Addr { + return c.listener.pConn.LocalAddr() +} + +// RemoteAddr implements net.Conn.RemoteAddr +func (c *Conn) RemoteAddr() net.Addr { + return c.rAddr +} + +// SetDeadline implements net.Conn.SetDeadline +func (c *Conn) SetDeadline(t time.Time) error { + c.writeDeadline.Set(t) + return c.SetReadDeadline(t) +} + +// SetReadDeadline implements net.Conn.SetDeadline +func (c *Conn) SetReadDeadline(t time.Time) error { + return c.buffer.SetReadDeadline(t) +} + +// SetWriteDeadline implements net.Conn.SetDeadline +func (c *Conn) SetWriteDeadline(t time.Time) error { + c.writeDeadline.Set(t) + // Write deadline of underlying connection should not be changed + // since the connection can be shared. + return nil +} diff --git a/vendor/github.com/pion/udp/go.mod b/vendor/github.com/pion/udp/go.mod new file mode 100644 index 0000000..59289b6 --- /dev/null +++ b/vendor/github.com/pion/udp/go.mod @@ -0,0 +1,5 @@ +module github.com/pion/udp + +go 1.14 + +require github.com/pion/transport v0.10.0 diff --git a/vendor/github.com/pion/udp/go.sum b/vendor/github.com/pion/udp/go.sum new file mode 100644 index 0000000..cd0abb2 --- /dev/null +++ b/vendor/github.com/pion/udp/go.sum @@ -0,0 +1,19 @@ +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms= +github.com/pion/transport v0.10.0 h1:9M12BSneJm6ggGhJyWpDveFOstJsTiQjkLf4M44rm80= +github.com/pion/transport v0.10.0/go.mod h1:BnHnUipd0rZQyTVB2SBGojFHT9CBt5C5TcsJSQGkvSE= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b h1:0mm1VjtFUOIlE1SbDlwjYaDxZVDP2S5ou6y0gSgXHu8= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/vendor/github.com/pion/webrtc/v3/.codacy.yaml b/vendor/github.com/pion/webrtc/v3/.codacy.yaml new file mode 100644 index 0000000..a8c225b --- /dev/null +++ b/vendor/github.com/pion/webrtc/v3/.codacy.yaml @@ -0,0 +1,3 @@ +--- +exclude_paths: + - examples/examples.json diff --git a/vendor/github.com/pion/webrtc/v3/.eslintrc.json b/vendor/github.com/pion/webrtc/v3/.eslintrc.json new file mode 100644 index 0000000..a755cdb --- /dev/null +++ b/vendor/github.com/pion/webrtc/v3/.eslintrc.json @@ -0,0 +1,3 @@ +{ + "extends": ["standard"] +} diff --git a/vendor/github.com/pion/webrtc/v3/.gitignore b/vendor/github.com/pion/webrtc/v3/.gitignore new file mode 100644 index 0000000..83db74b --- /dev/null +++ b/vendor/github.com/pion/webrtc/v3/.gitignore @@ -0,0 +1,24 @@ +### JetBrains IDE ### +##################### +.idea/ + +### Emacs Temporary Files ### +############################# +*~ + +### Folders ### +############### +bin/ +vendor/ +node_modules/ + +### Files ### +############# +*.ivf +*.ogg +tags +cover.out +*.sw[poe] +*.wasm +examples/sfu-ws/cert.pem +examples/sfu-ws/key.pem diff --git a/vendor/github.com/pion/webrtc/v3/.golangci.yml b/vendor/github.com/pion/webrtc/v3/.golangci.yml new file mode 100644 index 0000000..d6162c9 --- /dev/null +++ b/vendor/github.com/pion/webrtc/v3/.golangci.yml @@ -0,0 +1,89 @@ +linters-settings: + govet: + check-shadowing: true + misspell: + locale: US + exhaustive: + default-signifies-exhaustive: true + gomodguard: + blocked: + modules: + - github.com/pkg/errors: + recommendations: + - errors + +linters: + enable: + - asciicheck # Simple linter to check that your code does not contain non-ASCII identifiers + - bodyclose # checks whether HTTP response body is closed successfully + - deadcode # Finds unused code + - depguard # Go linter that checks if package imports are in a list of acceptable packages + - dogsled # Checks assignments with too many blank identifiers (e.g. x, _, _, _, := f()) + - dupl # Tool for code clone detection + - errcheck # Errcheck is a program for checking for unchecked errors in go programs. These unchecked errors can be critical bugs in some cases + - exhaustive # check exhaustiveness of enum switch statements + - exportloopref # checks for pointers to enclosing loop variables + - gci # Gci control golang package import order and make it always deterministic. + - gochecknoglobals # Checks that no globals are present in Go code + - gochecknoinits # Checks that no init functions are present in Go code + - gocognit # Computes and checks the cognitive complexity of functions + - goconst # Finds repeated strings that could be replaced by a constant + - gocritic # The most opinionated Go source code linter + - godox # Tool for detection of FIXME, TODO and other comment keywords + - goerr113 # Golang linter to check the errors handling expressions + - gofmt # Gofmt checks whether code was gofmt-ed. By default this tool runs with -s option to check for code simplification + - gofumpt # Gofumpt checks whether code was gofumpt-ed. + - goheader # Checks is file header matches to pattern + - goimports # Goimports does everything that gofmt does. Additionally it checks unused imports + - golint # Golint differs from gofmt. Gofmt reformats Go source code, whereas golint prints out style mistakes + - gomodguard # Allow and block list linter for direct Go module dependencies. This is different from depguard where there are different block types for example version constraints and module recommendations. + - goprintffuncname # Checks that printf-like functions are named with `f` at the end + - gosec # Inspects source code for security problems + - gosimple # Linter for Go source code that specializes in simplifying a code + - govet # Vet examines Go source code and reports suspicious constructs, such as Printf calls whose arguments do not align with the format string + - ineffassign # Detects when assignments to existing variables are not used + - misspell # Finds commonly misspelled English words in comments + - nakedret # Finds naked returns in functions greater than a specified function length + - noctx # noctx finds sending http request without context.Context + - scopelint # Scopelint checks for unpinned variables in go programs + - staticcheck # Staticcheck is a go vet on steroids, applying a ton of static analysis checks + - structcheck # Finds unused struct fields + - stylecheck # Stylecheck is a replacement for golint + - typecheck # Like the front-end of a Go compiler, parses and type-checks Go code + - unconvert # Remove unnecessary type conversions + - unparam # Reports unused function parameters + - unused # Checks Go code for unused constants, variables, functions and types + - varcheck # Finds unused global variables and constants + - whitespace # Tool for detection of leading and trailing whitespace + disable: + - funlen # Tool for detection of long functions + - gocyclo # Computes and checks the cyclomatic complexity of functions + - godot # Check if comments end in a period + - gomnd # An analyzer to detect magic numbers. + - lll # Reports long lines + - maligned # Tool to detect Go structs that would take less memory if their fields were sorted + - nestif # Reports deeply nested if statements + - nlreturn # nlreturn checks for a new line before return and branch statements to increase code clarity + - nolintlint # Reports ill-formed or insufficient nolint directives + - prealloc # Finds slice declarations that could potentially be preallocated + - rowserrcheck # checks whether Err of rows is checked successfully + - sqlclosecheck # Checks that sql.Rows and sql.Stmt are closed. + - testpackage # linter that makes you use a separate _test package + - wsl # Whitespace Linter - Forces you to use empty lines! + +issues: + exclude-use-default: false + exclude-rules: + # Allow complex tests, better to be self contained + - path: _test\.go + linters: + - gocognit + + # Allow complex main function in examples + - path: examples + text: "of func `main` is high" + linters: + - gocognit + +run: + skip-dirs-use-default: false diff --git a/vendor/github.com/pion/webrtc/v3/DESIGN.md b/vendor/github.com/pion/webrtc/v3/DESIGN.md new file mode 100644 index 0000000..e22b08e --- /dev/null +++ b/vendor/github.com/pion/webrtc/v3/DESIGN.md @@ -0,0 +1,43 @@ +<h1 align="center"> + Design +</h1> +WebRTC is a powerful, but complicated technology you can build amazing things with, it comes with a steep learning curve though. +Using WebRTC in the browser is easy, but outside the browser is more of a challenge. There are multiple libraries, and they all have +varying levels of quality. Most are also difficult to build, and depend on libraries that aren't available in repos or portable. + +Pion WebRTC aims to solve all that! Built in native Go you should be able to send and receive media and text from anywhere with minimal headache. +These are the design principals that drive Pion WebRTC and hopefully convince you it is worth a try. + +### Portable +Pion WebRTC is written in Go and extremely portable. Anywhere Golang runs, Pion WebRTC should work as well! Instead of dealing with complicated +cross-compiling of multiple libraries, you now can run anywhere with one `go build` + +### Flexible +When possible we leave all decisions to the user. When choice is possible (like what logging library is used) we defer to the developer. + +### Simple API +If you know how to use WebRTC in your browser, you know how to use Pion WebRTC. +We try our best just to duplicate the Javascript API, so your code can look the same everywhere. + +If this is your first time using WebRTC, don't worry! We have multiple [examples](https://github.com/pion/webrtc/tree/master/examples) and [GoDoc](https://pkg.go.dev/github.com/pion/webrtc/v3) + +### Bring your own media +Pion WebRTC doesn't make any assumptions about where your audio, video or text come from. You can use FFmpeg, GStreamer, MLT or just serve a video file. +This library only serves to transport, not create media. + +### Safe +Golang provides a great foundation to build safe network services. +Especially when running a networked service that is highly concurrent bugs can be devastating. + +### Readable +If code comes from an RFC we try to make sure everything is commented with a link to the spec. +This makes learning and debugging easier, this WebRTC library was written to also serve as a guide for others. + +### Tested +Every commit is tested via travis-ci Go provides fantastic facilities for testing, and more will be added as time goes on. + +### Shared libraries +Every Pion project is built using shared libraries, allowing others to review and reuse our libraries. + +### Community +The most important part of Pion is the community. This projects only exist because of individual contributions. We aim to be radically open and do everything we can to support those that make Pion possible. diff --git a/vendor/github.com/pion/webrtc/v3/LICENSE b/vendor/github.com/pion/webrtc/v3/LICENSE new file mode 100644 index 0000000..ab60297 --- /dev/null +++ b/vendor/github.com/pion/webrtc/v3/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/pion/webrtc/v3/README.md b/vendor/github.com/pion/webrtc/v3/README.md new file mode 100644 index 0000000..6fc98d5 --- /dev/null +++ b/vendor/github.com/pion/webrtc/v3/README.md @@ -0,0 +1,271 @@ +<h1 align="center"> + <a href="https://pion.ly"><img src="./.github/pion-gopher-webrtc.png" alt="Pion WebRTC" height="250px"></a> + <br> + Pion WebRTC + <br> +</h1> +<h4 align="center">A pure Go implementation of the WebRTC API</h4> +<p align="center"> + <a href="https://pion.ly"><img src="https://img.shields.io/badge/pion-webrtc-gray.svg?longCache=true&colorB=brightgreen" alt="Pion webrtc"></a> + <a href="https://sourcegraph.com/github.com/pion/webrtc?badge"><img src="https://sourcegraph.com/github.com/pion/webrtc/-/badge.svg" alt="Sourcegraph Widget"></a> + <a href="https://pion.ly/slack"><img src="https://img.shields.io/badge/join-us%20on%20slack-gray.svg?longCache=true&logo=slack&colorB=brightgreen" alt="Slack Widget"></a> + <a href="https://twitter.com/_pion?ref_src=twsrc%5Etfw"><img src="https://img.shields.io/twitter/url.svg?label=Follow%20%40_pion&style=social&url=https%3A%2F%2Ftwitter.com%2F_pion" alt="Twitter Widget"></a> + <a href="https://github.com/pion/awesome-pion" alt="Awesome Pion"><img src="https://cdn.rawgit.com/sindresorhus/awesome/d7305f38d29fed78fa85652e3a63e154dd8e8829/media/badge.svg"></a> + <br> + <a href="https://travis-ci.org/pion/webrtc"><img src="https://travis-ci.org/pion/webrtc.svg?branch=master" alt="Build Status"></a> + <a href="https://pkg.go.dev/github.com/pion/webrtc/v3"><img src="https://pkg.go.dev/badge/github.com/pion/webrtc/v3" alt="PkgGoDev"></a> + <a href="https://codecov.io/gh/pion/webrtc"><img src="https://codecov.io/gh/pion/webrtc/branch/master/graph/badge.svg" alt="Coverage Status"></a> + <a href="https://goreportcard.com/report/github.com/pion/webrtc"><img src="https://goreportcard.com/badge/github.com/pion/webrtc" alt="Go Report Card"></a> + <a href="https://www.codacy.com/app/Sean-Der/webrtc"><img src="https://api.codacy.com/project/badge/Grade/18f4aec384894e6aac0b94effe51961d" alt="Codacy Badge"></a> + <a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="License: MIT"></a> +</p> +<br> + +### New Release + +Pion WebRTC v3.0.0 has been released! See the [release notes](https://github.com/pion/webrtc/wiki/Release-WebRTC@v3.0.0) to learn about new features and breaking changes. + +If you aren't able to upgrade yet check the [tags](https://github.com/pion/webrtc/tags) for the latest `v2` release. + +We would love your feedback! Please create GitHub issues or join [the Slack channel](https://pion.ly/slack) to follow development and speak with the maintainers. + +---- + +### Usage +[Go Modules](https://blog.golang.org/using-go-modules) are mandatory for using Pion WebRTC. So make sure you set `export GO111MODULE=on`, and explicitly specify `/v2` or `/v3` when importing. + + +**[example applications](examples/README.md)** contains code samples of common things people build with Pion WebRTC. + +**[example-webrtc-applications](https://github.com/pion/example-webrtc-applications)** contains more full featured examples that use 3rd party libraries. + +**[awesome-pion](https://github.com/pion/awesome-pion)** contains projects that have used Pion, and serve as real world examples of usage. + +**[GoDoc](https://pkg.go.dev/github.com/pion/webrtc/v3)** is an auto generated API reference. All our Public APIs are commented. + +**[FAQ](https://github.com/pion/webrtc/wiki/FAQ)** has answers to common questions. If you have a question not covered please ask in [Slack](https://pion.ly/slack) we are always looking to expand it. + +Now go build something awesome! Here are some **ideas** to get your creative juices flowing: +* Send a video file to multiple browser in real time for perfectly synchronized movie watching. +* Send a webcam on an embedded device to your browser with no additional server required! +* Securely send data between two servers, without using pub/sub. +* Record your webcam and do special effects server side. +* Build a conferencing application that processes audio/video and make decisions off of it. +* Remotely control a robots and stream its cameras in realtime. + +### Want to learn more about WebRTC? +Check out [WebRTC for the Curious](https://webrtcforthecurious.com). A book about WebRTC in depth, not just about the APIs. +Learn the full details of ICE, SCTP, DTLS, SRTP, and how they work together to make up the WebRTC stack. + +This is also a great resource if you are trying to debug. Learn the tools of the trade and how to approach WebRTC issues. + +This book is vendor agnostic and will not have any Pion specific information. + +### Features +#### PeerConnection API +* Go implementation of [webrtc-pc](https://w3c.github.io/webrtc-pc/) and [webrtc-stats](https://www.w3.org/TR/webrtc-stats/) +* DataChannels +* Send/Receive audio and video +* Renegotiation +* Plan-B and Unified Plan +* [SettingEngine](https://pkg.go.dev/github.com/pion/webrtc/v3#SettingEngine) for Pion specific extensions + + +#### Connectivity +* Full ICE Agent +* ICE Restart +* Trickle ICE +* STUN +* TURN (UDP, TCP, DTLS and TLS) +* mDNS candidates + +#### DataChannels +* Ordered/Unordered +* Lossy/Lossless + +#### Media +* API with direct RTP/RTCP access +* Opus, PCM, H264, VP8 and VP9 packetizer +* API also allows developer to pass their own packetizer +* IVF, Ogg, H264 and Matroska provided for easy sending and saving +* [getUserMedia](https://github.com/pion/mediadevices) implementation (Requires Cgo) +* Easy integration with x264, libvpx, GStreamer and ffmpeg. +* [Simulcast](https://github.com/pion/webrtc/tree/master/examples/simulcast) +* [SVC](https://github.com/pion/rtp/blob/master/codecs/vp9_packet.go#L138) +* [NACK](https://github.com/pion/interceptor/pull/4) +* Full loss recovery and congestion control is not complete, see [pion/interceptor](https://github.com/pion/interceptor) for progress + * See [ion](https://github.com/pion/ion-sfu/tree/master/pkg/buffer) for how an implementor can do it today + +#### Security +* TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 and TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA for DTLS v1.2 +* SRTP_AEAD_AES_256_GCM and SRTP_AES128_CM_HMAC_SHA1_80 for SRTP +* Hardware acceleration available for GCM suites + +#### Pure Go +* No Cgo usage +* Wide platform support + * Windows, macOS, Linux, FreeBSD + * iOS, Android + * [WASM](https://github.com/pion/webrtc/wiki/WebAssembly-Development-and-Testing) see [examples](examples/README.md#webassembly) + * 386, amd64, arm, mips, ppc64 +* Easy to build *Numbers generated on Intel(R) Core(TM) i5-2520M CPU @ 2.50GHz* + * **Time to build examples/play-from-disk** - 0.66s user 0.20s system 306% cpu 0.279 total + * **Time to run entire test suite** - 25.60s user 9.40s system 45% cpu 1:16.69 total +* Tools to measure performance [provided](https://github.com/pion/rtsp-bench) + + +### Roadmap +The library is in active development, please refer to the [roadmap](https://github.com/pion/webrtc/issues/9) to track our major milestones. +We also maintain a list of [Big Ideas](https://github.com/pion/webrtc/wiki/Big-Ideas) these are things we want to build but don't have a clear plan or the resources yet. +If you are looking to get involved this is a great place to get started! We would also love to hear your ideas! Even if you can't implement it yourself, it could inspire others. + +### Community +Pion has an active community on the [Slack](https://pion.ly/slack). + +Follow the [Pion Twitter](https://twitter.com/_pion) for project updates and important WebRTC news. + +We are always looking to support **your projects**. Please reach out if you have something to build! +If you need commercial support or don't want to use public methods you can contact us at [team@pion.ly](mailto:team@pion.ly) + +### Contributing +Check out the **[contributing wiki](https://github.com/pion/webrtc/wiki/Contributing)** to join the group of amazing people making this project possible: + +* [John Bradley](https://github.com/kc5nra) - *Original Author* +* [Michael Melvin Santry](https://github.com/santrym) - *Mascot* +* [Raphael Randschau](https://github.com/nicolai86) - *STUN* +* [Sean DuBois](https://github.com/Sean-Der) - *Original Author* +* [Michiel De Backker](https://github.com/backkem) - *SDP, Public API, Project Management* +* [Brendan Rius](https://github.com/brendanrius) - *Cleanup* +* [Konstantin Itskov](https://github.com/trivigy) - *SDP Parsing* +* [chenkaiC4](https://github.com/chenkaiC4) - *Fix GolangCI Linter* +* [Ronan J](https://github.com/ronanj) - *Fix STCP PPID* +* [wattanakorn495](https://github.com/wattanakorn495) +* [Max Hawkins](https://github.com/maxhawkins) - *RTCP* +* [Justin Okamoto](https://github.com/justinokamoto) - *Fix Docs* +* [leeoxiang](https://github.com/notedit) - *Implement Janus examples* +* [Denis](https://github.com/Hixon10) - *Adding docker-compose to pion-to-pion example* +* [earle](https://github.com/aguilEA) - *Generate DTLS fingerprint in Go* +* [Jake B](https://github.com/silbinarywolf) - *Fix Windows installation instructions* +* [Michael MacDonald](https://github.com/mjmac) - *Plan B compatibility, Remote TURN/Trickle-ICE, Logging framework* +* [Oleg Kovalov](https://github.com/cristaloleg) *Use wildcards instead of hardcoding travis-ci config* +* [Woodrow Douglass](https://github.com/wdouglass) *RTCP, RTP improvements, G.722 support, Bugfixes* +* [Tobias Fridén](https://github.com/tobiasfriden) *SRTP authentication verification* +* [Yutaka Takeda](https://github.com/enobufs) *Fix ICE connection timeout* +* [Hugo Arregui](https://github.com/hugoArregui) *Fix connection timeout* +* [Rob Deutsch](https://github.com/rob-deutsch) *RTPReceiver graceful shutdown* +* [Jin Lei](https://github.com/jinleileiking) - *SFU example use http* +* [Will Watson](https://github.com/wwatson) - *Enable gocritic* +* [Luke Curley](https://github.com/kixelated) +* [Antoine Baché](https://github.com/Antonito) - *OGG Opus export* +* [frank](https://github.com/feixiao) - *Building examples on OSX* +* [mxmCherry](https://github.com/mxmCherry) +* [Alex Browne](https://github.com/albrow) - *JavaScript/Wasm bindings* +* [adwpc](https://github.com/adwpc) - *SFU example with websocket* +* [imalic3](https://github.com/imalic3) - *SFU websocket example with datachannel broadcast* +* [Žiga Željko](https://github.com/zigazeljko) +* [Simonacca Fotokite](https://github.com/simonacca-fotokite) +* [Marouane](https://github.com/nindolabs) *Fix Offer bundle generation* +* [Christopher Fry](https://github.com/christopherfry) +* [Adam Kiss](https://github.com/masterada) +* [xsbchen](https://github.com/xsbchen) +* [Alex Harford](https://github.com/alexjh) +* [Aleksandr Razumov](https://github.com/ernado) +* [mchlrhw](https://github.com/mchlrhw) +* [AlexWoo(武杰)](https://github.com/AlexWoo) *Fix RemoteDescription parsing for certificate fingerprint* +* [Cecylia Bocovich](https://github.com/cohosh) +* [Slugalisk](https://github.com/slugalisk) +* [Agugua Kenechukwu](https://github.com/spaceCh1mp) +* [Ato Araki](https://github.com/atotto) +* [Rafael Viscarra](https://github.com/rviscarra) +* [Mike Coleman](https://github.com/fivebats) +* [Suhas Gaddam](https://github.com/suhasgaddam) +* [Atsushi Watanabe](https://github.com/at-wat) +* [Robert Eperjesi](https://github.com/epes) +* [Aaron France](https://github.com/AeroNotix) +* [Gareth Hayes](https://github.com/gazhayes) +* [Sebastian Waisbrot](https://github.com/seppo0010) +* [Masataka Hisasue](https://github.com/sylba2050) - *Fix Docs* +* [Hongchao Ma(马洪超)](https://github.com/hcm007) +* [Aaron France](https://github.com/AeroNotix) +* [Chris Hiszpanski](https://github.com/thinkski) - *Fix Answer bundle generation* +* [Vicken Simonian](https://github.com/vsimon) +* [Guilherme Souza](https://github.com/gqgs) +* [Andrew N. Shalaev](https://github.com/isqad) +* [David Hamilton](https://github.com/dihamilton) +* [Ilya Mayorov](https://github.com/faroyam) +* [Patrick Lange](https://github.com/langep) +* [cyannuk](https://github.com/cyannuk) +* [Lukas Herman](https://github.com/lherman-cs) +* [Konstantin Chugalinskiy](https://github.com/kchugalinskiy) +* [Bao Nguyen](https://github.com/sysbot) +* [Luke S](https://github.com/encounter) +* [Hendrik Hofstadt](https://github.com/hendrikhofstadt) +* [Clayton McCray](https://github.com/ClaytonMcCray) +* [lawl](https://github.com/lawl) +* [Jorropo](https://github.com/Jorropo) +* [Akil](https://github.com/akilude) +* [Quentin Renard](https://github.com/asticode) +* [opennota](https://github.com/opennota) +* [Simon Eisenmann](https://github.com/longsleep) +* [Ben Weitzman](https://github.com/benweitzman) +* [Masahiro Nakamura](https://github.com/tsuu32) +* [Tarrence van As](https://github.com/tarrencev) +* [Yuki Igarashi](https://github.com/bonprosoft) +* [Egon Elbre](https://github.com/egonelbre) +* [Jerko Steiner](https://github.com/jeremija) +* [Roman Romanenko](https://github.com/r-novel) +* [YongXin SHI](https://github.com/a-wing) +* [Magnus Wahlstrand](https://github.com/kyeett) +* [Chad Retz](https://github.com/cretz) +* [Simone Gotti](https://github.com/sgotti) +* [Cedric Fung](https://github.com/cedricfung) +* [Norman Rasmussen](https://github.com/normanr) - *Fix Empty DataChannel messages* +* [salmān aljammāz](https://github.com/saljam) +* [cnderrauber](https://github.com/cnderrauber) +* [Juliusz Chroboczek](https://github.com/jech) +* [John Berthels](https://github.com/jbert) +* [Somers Matthews](https://github.com/somersbmatthews) +* [Vitaliy F](https://github.com/funvit) +* [Ivan Egorov](https://github.com/vany-egorov) +* [Nick Mykins](https://github.com/nmyk) +* [Jason Brady](https://github.com/jbrady42) +* [krishna chiatanya](https://github.com/kittuov) +* [JacobZwang](https://github.com/JacobZwang) +* [박종훈](https://github.com/JonghunBok) +* [Sam Lancia](https://github.com/nerd2) +* [Henry](https://github.com/cryptix) +* [Jeff Tchang](https://github.com/tachang) +* [JooYoung Lim](https://github.com/DevRockstarZ) +* [Sidney San Martín](https://github.com/s4y) +* [soolaugust](https://github.com/soolaugust) +* [Kuzmin Vladimir](https://github.com/tekig) +* [Alessandro Ros](https://github.com/aler9) +* [Thomas Miller](https://github.com/tmiv) +* [yoko(q191201771)](https://github.com/q191201771) +* [Joshua Obasaju](https://github.com/obasajujoshua31) +* [Mission Liao](https://github.com/mission-liao) +* [Hanjun Kim](https://github.com/hallazzang) +* [ZHENK](https://github.com/scorpionknifes) +* [Rahul Nakre](https://github.com/rahulnakre) +* [OrlandoCo](https://github.com/OrlandoCo) +* [Assad Obaid](https://github.com/assadobaid) +* [Jamie Good](https://github.com/jamiegood) - *Bug fix in jsfiddle example* +* [Artur Shellunts](https://github.com/ashellunts) +* [Sean Knight](https://github.com/SeanKnight) +* [o0olele](https://github.com/o0olele) +* [Bo Shi](https://github.com/bshimc) +* [Suzuki Takeo](https://github.com/BambooTuna) +* [baiyufei](https://github.com/baiyufei) +* [pascal-ace](https://github.com/pascal-ace) +* [Threadnaught](https://github.com/Threadnaught) +* [Dean Eigenmann](https://github.com/decanus) +* [Cameron Elliott](https://github.com/cameronelliott) +* [Pascal Benoit](https://github.com/pascal-ace) +* [Mats](https://github.com/Mindgamesnl) +* [donotanswer](https://github.com/f-viktor) +* [Reese](https://github.com/figadore) +* [David Zhao](https://github.com/davidzhao) +* [Nam V. Do](https://github.com/namvdo) + +### License +MIT License - see [LICENSE](LICENSE) for full text diff --git a/vendor/github.com/pion/webrtc/v3/api.go b/vendor/github.com/pion/webrtc/v3/api.go new file mode 100644 index 0000000..60ac728 --- /dev/null +++ b/vendor/github.com/pion/webrtc/v3/api.go @@ -0,0 +1,73 @@ +// +build !js + +package webrtc + +import ( + "github.com/pion/interceptor" + "github.com/pion/logging" +) + +// API bundles the global functions of the WebRTC and ORTC API. +// Some of these functions are also exported globally using the +// defaultAPI object. Note that the global version of the API +// may be phased out in the future. +type API struct { + settingEngine *SettingEngine + mediaEngine *MediaEngine + interceptor interceptor.Interceptor +} + +// NewAPI Creates a new API object for keeping semi-global settings to WebRTC objects +func NewAPI(options ...func(*API)) *API { + a := &API{} + + for _, o := range options { + o(a) + } + + if a.settingEngine == nil { + a.settingEngine = &SettingEngine{} + } + + if a.settingEngine.LoggerFactory == nil { + a.settingEngine.LoggerFactory = logging.NewDefaultLoggerFactory() + } + + if a.mediaEngine == nil { + a.mediaEngine = &MediaEngine{} + } + + if a.interceptor == nil { + a.interceptor = &interceptor.NoOp{} + } + + return a +} + +// WithMediaEngine allows providing a MediaEngine to the API. +// Settings can be changed after passing the engine to an API. +func WithMediaEngine(m *MediaEngine) func(a *API) { + return func(a *API) { + if m != nil { + a.mediaEngine = m + } else { + a.mediaEngine = &MediaEngine{} + } + } +} + +// WithSettingEngine allows providing a SettingEngine to the API. +// Settings should not be changed after passing the engine to an API. +func WithSettingEngine(s SettingEngine) func(a *API) { + return func(a *API) { + a.settingEngine = &s + } +} + +// WithInterceptorRegistry allows providing Interceptors to the API. +// Settings should not be changed after passing the registry to an API. +func WithInterceptorRegistry(interceptorRegistry *interceptor.Registry) func(a *API) { + return func(a *API) { + a.interceptor = interceptorRegistry.Build() + } +} diff --git a/vendor/github.com/pion/webrtc/v3/api_js.go b/vendor/github.com/pion/webrtc/v3/api_js.go new file mode 100644 index 0000000..964b7b0 --- /dev/null +++ b/vendor/github.com/pion/webrtc/v3/api_js.go @@ -0,0 +1,31 @@ +// +build js,wasm + +package webrtc + +// API bundles the global funcions of the WebRTC and ORTC API. +type API struct { + settingEngine *SettingEngine +} + +// NewAPI Creates a new API object for keeping semi-global settings to WebRTC objects +func NewAPI(options ...func(*API)) *API { + a := &API{} + + for _, o := range options { + o(a) + } + + if a.settingEngine == nil { + a.settingEngine = &SettingEngine{} + } + + return a +} + +// WithSettingEngine allows providing a SettingEngine to the API. +// Settings should not be changed after passing the engine to an API. +func WithSettingEngine(s SettingEngine) func(a *API) { + return func(a *API) { + a.settingEngine = &s + } +} diff --git a/vendor/github.com/pion/webrtc/v3/atomicbool.go b/vendor/github.com/pion/webrtc/v3/atomicbool.go new file mode 100644 index 0000000..c5ace62 --- /dev/null +++ b/vendor/github.com/pion/webrtc/v3/atomicbool.go @@ -0,0 +1,20 @@ +package webrtc + +import "sync/atomic" + +type atomicBool struct { + val int32 +} + +func (b *atomicBool) set(value bool) { // nolint: unparam + var i int32 + if value { + i = 1 + } + + atomic.StoreInt32(&(b.val), i) +} + +func (b *atomicBool) get() bool { + return atomic.LoadInt32(&(b.val)) != 0 +} diff --git a/vendor/github.com/pion/webrtc/v3/bundlepolicy.go b/vendor/github.com/pion/webrtc/v3/bundlepolicy.go new file mode 100644 index 0000000..6d39a27 --- /dev/null +++ b/vendor/github.com/pion/webrtc/v3/bundlepolicy.go @@ -0,0 +1,78 @@ +package webrtc + +import ( + "encoding/json" +) + +// BundlePolicy affects which media tracks are negotiated if the remote +// endpoint is not bundle-aware, and what ICE candidates are gathered. If the +// remote endpoint is bundle-aware, all media tracks and data channels are +// bundled onto the same transport. +type BundlePolicy int + +const ( + // BundlePolicyBalanced indicates to gather ICE candidates for each + // media type in use (audio, video, and data). If the remote endpoint is + // not bundle-aware, negotiate only one audio and video track on separate + // transports. + BundlePolicyBalanced BundlePolicy = iota + 1 + + // BundlePolicyMaxCompat indicates to gather ICE candidates for each + // track. If the remote endpoint is not bundle-aware, negotiate all media + // tracks on separate transports. + BundlePolicyMaxCompat + + // BundlePolicyMaxBundle indicates to gather ICE candidates for only + // one track. If the remote endpoint is not bundle-aware, negotiate only + // one media track. + BundlePolicyMaxBundle +) + +// This is done this way because of a linter. +const ( + bundlePolicyBalancedStr = "balanced" + bundlePolicyMaxCompatStr = "max-compat" + bundlePolicyMaxBundleStr = "max-bundle" +) + +func newBundlePolicy(raw string) BundlePolicy { + switch raw { + case bundlePolicyBalancedStr: + return BundlePolicyBalanced + case bundlePolicyMaxCompatStr: + return BundlePolicyMaxCompat + case bundlePolicyMaxBundleStr: + return BundlePolicyMaxBundle + default: + return BundlePolicy(Unknown) + } +} + +func (t BundlePolicy) String() string { + switch t { + case BundlePolicyBalanced: + return bundlePolicyBalancedStr + case BundlePolicyMaxCompat: + return bundlePolicyMaxCompatStr + case BundlePolicyMaxBundle: + return bundlePolicyMaxBundleStr + default: + return ErrUnknownType.Error() + } +} + +// UnmarshalJSON parses the JSON-encoded data and stores the result +func (t *BundlePolicy) UnmarshalJSON(b []byte) error { + var val string + if err := json.Unmarshal(b, &val); err != nil { + return err + } + + *t = newBundlePolicy(val) + return nil +} + +// MarshalJSON returns the JSON encoding +func (t BundlePolicy) MarshalJSON() ([]byte, error) { + return json.Marshal(t.String()) +} diff --git a/vendor/github.com/pion/webrtc/v3/certificate.go b/vendor/github.com/pion/webrtc/v3/certificate.go new file mode 100644 index 0000000..bf106d6 --- /dev/null +++ b/vendor/github.com/pion/webrtc/v3/certificate.go @@ -0,0 +1,185 @@ +// +build !js + +package webrtc + +import ( + "crypto" + "crypto/ecdsa" + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "crypto/x509/pkix" + "encoding/base64" + "encoding/hex" + "fmt" + "math/big" + "time" + + "github.com/pion/dtls/v2/pkg/crypto/fingerprint" + "github.com/pion/webrtc/v3/pkg/rtcerr" +) + +// Certificate represents a x509Cert used to authenticate WebRTC communications. +type Certificate struct { + privateKey crypto.PrivateKey + x509Cert *x509.Certificate + statsID string +} + +// NewCertificate generates a new x509 compliant Certificate to be used +// by DTLS for encrypting data sent over the wire. This method differs from +// GenerateCertificate by allowing to specify a template x509.Certificate to +// be used in order to define certificate parameters. +func NewCertificate(key crypto.PrivateKey, tpl x509.Certificate) (*Certificate, error) { + var err error + var certDER []byte + switch sk := key.(type) { + case *rsa.PrivateKey: + pk := sk.Public() + tpl.SignatureAlgorithm = x509.SHA256WithRSA + certDER, err = x509.CreateCertificate(rand.Reader, &tpl, &tpl, pk, sk) + if err != nil { + return nil, &rtcerr.UnknownError{Err: err} + } + case *ecdsa.PrivateKey: + pk := sk.Public() + tpl.SignatureAlgorithm = x509.ECDSAWithSHA256 + certDER, err = x509.CreateCertificate(rand.Reader, &tpl, &tpl, pk, sk) + if err != nil { + return nil, &rtcerr.UnknownError{Err: err} + } + default: + return nil, &rtcerr.NotSupportedError{Err: ErrPrivateKeyType} + } + + cert, err := x509.ParseCertificate(certDER) + if err != nil { + return nil, &rtcerr.UnknownError{Err: err} + } + + return &Certificate{privateKey: key, x509Cert: cert, statsID: fmt.Sprintf("certificate-%d", time.Now().UnixNano())}, nil +} + +// Equals determines if two certificates are identical by comparing both the +// secretKeys and x509Certificates. +func (c Certificate) Equals(o Certificate) bool { + switch cSK := c.privateKey.(type) { + case *rsa.PrivateKey: + if oSK, ok := o.privateKey.(*rsa.PrivateKey); ok { + if cSK.N.Cmp(oSK.N) != 0 { + return false + } + return c.x509Cert.Equal(o.x509Cert) + } + return false + case *ecdsa.PrivateKey: + if oSK, ok := o.privateKey.(*ecdsa.PrivateKey); ok { + if cSK.X.Cmp(oSK.X) != 0 || cSK.Y.Cmp(oSK.Y) != 0 { + return false + } + return c.x509Cert.Equal(o.x509Cert) + } + return false + default: + return false + } +} + +// Expires returns the timestamp after which this certificate is no longer valid. +func (c Certificate) Expires() time.Time { + if c.x509Cert == nil { + return time.Time{} + } + return c.x509Cert.NotAfter +} + +// GetFingerprints returns the list of certificate fingerprints, one of which +// is computed with the digest algorithm used in the certificate signature. +func (c Certificate) GetFingerprints() ([]DTLSFingerprint, error) { + fingerprintAlgorithms := []crypto.Hash{crypto.SHA256} + res := make([]DTLSFingerprint, len(fingerprintAlgorithms)) + + i := 0 + for _, algo := range fingerprintAlgorithms { + name, err := fingerprint.StringFromHash(algo) + if err != nil { + return nil, fmt.Errorf("%w: %v", ErrFailedToGenerateCertificateFingerprint, err) + } + value, err := fingerprint.Fingerprint(c.x509Cert, algo) + if err != nil { + return nil, fmt.Errorf("%w: %v", ErrFailedToGenerateCertificateFingerprint, err) + } + res[i] = DTLSFingerprint{ + Algorithm: name, + Value: value, + } + } + + return res[:i+1], nil +} + +// GenerateCertificate causes the creation of an X.509 certificate and +// corresponding private key. +func GenerateCertificate(secretKey crypto.PrivateKey) (*Certificate, error) { + origin := make([]byte, 16) + /* #nosec */ + if _, err := rand.Read(origin); err != nil { + return nil, &rtcerr.UnknownError{Err: err} + } + + // Max random value, a 130-bits integer, i.e 2^130 - 1 + maxBigInt := new(big.Int) + /* #nosec */ + maxBigInt.Exp(big.NewInt(2), big.NewInt(130), nil).Sub(maxBigInt, big.NewInt(1)) + /* #nosec */ + serialNumber, err := rand.Int(rand.Reader, maxBigInt) + if err != nil { + return nil, &rtcerr.UnknownError{Err: err} + } + + return NewCertificate(secretKey, x509.Certificate{ + ExtKeyUsage: []x509.ExtKeyUsage{ + x509.ExtKeyUsageClientAuth, + x509.ExtKeyUsageServerAuth, + }, + BasicConstraintsValid: true, + NotBefore: time.Now(), + KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, + NotAfter: time.Now().AddDate(0, 1, 0), + SerialNumber: serialNumber, + Version: 2, + Subject: pkix.Name{CommonName: hex.EncodeToString(origin)}, + IsCA: true, + }) +} + +// CertificateFromX509 creates a new WebRTC Certificate from a given PrivateKey and Certificate +// +// This can be used if you want to share a certificate across multiple PeerConnections +func CertificateFromX509(privateKey crypto.PrivateKey, certificate *x509.Certificate) Certificate { + return Certificate{privateKey, certificate, fmt.Sprintf("certificate-%d", time.Now().UnixNano())} +} + +func (c Certificate) collectStats(report *statsReportCollector) error { + report.Collecting() + + fingerPrintAlgo, err := c.GetFingerprints() + if err != nil { + return err + } + + base64Certificate := base64.RawURLEncoding.EncodeToString(c.x509Cert.Raw) + + stats := CertificateStats{ + Timestamp: statsTimestampFrom(time.Now()), + Type: StatsTypeCertificate, + ID: c.statsID, + Fingerprint: fingerPrintAlgo[0].Value, + FingerprintAlgorithm: fingerPrintAlgo[0].Algorithm, + Base64Certificate: base64Certificate, + IssuerCertificateID: c.x509Cert.Issuer.String(), + } + + report.Collect(stats.ID, stats) + return nil +} diff --git a/vendor/github.com/pion/webrtc/v3/codecov.yml b/vendor/github.com/pion/webrtc/v3/codecov.yml new file mode 100644 index 0000000..085200a --- /dev/null +++ b/vendor/github.com/pion/webrtc/v3/codecov.yml @@ -0,0 +1,20 @@ +# +# DO NOT EDIT THIS FILE +# +# It is automatically copied from https://github.com/pion/.goassets repository. +# + +coverage: + status: + project: + default: + # Allow decreasing 2% of total coverage to avoid noise. + threshold: 2% + patch: + default: + target: 70% + only_pulls: true + +ignore: + - "examples/*" + - "examples/**/*" diff --git a/vendor/github.com/pion/webrtc/v3/configuration.go b/vendor/github.com/pion/webrtc/v3/configuration.go new file mode 100644 index 0000000..712bab9 --- /dev/null +++ b/vendor/github.com/pion/webrtc/v3/configuration.go @@ -0,0 +1,51 @@ +// +build !js + +package webrtc + +// A Configuration defines how peer-to-peer communication via PeerConnection +// is established or re-established. +// Configurations may be set up once and reused across multiple connections. +// Configurations are treated as readonly. As long as they are unmodified, +// they are safe for concurrent use. +type Configuration struct { + // ICEServers defines a slice describing servers available to be used by + // ICE, such as STUN and TURN servers. + ICEServers []ICEServer `json:"iceServers,omitempty"` + + // ICETransportPolicy indicates which candidates the ICEAgent is allowed + // to use. + ICETransportPolicy ICETransportPolicy `json:"iceTransportPolicy,omitempty"` + + // BundlePolicy indicates which media-bundling policy to use when gathering + // ICE candidates. + BundlePolicy BundlePolicy `json:"bundlePolicy,omitempty"` + + // RTCPMuxPolicy indicates which rtcp-mux policy to use when gathering ICE + // candidates. + RTCPMuxPolicy RTCPMuxPolicy `json:"rtcpMuxPolicy,omitempty"` + + // PeerIdentity sets the target peer identity for the PeerConnection. + // The PeerConnection will not establish a connection to a remote peer + // unless it can be successfully authenticated with the provided name. + PeerIdentity string `json:"peerIdentity,omitempty"` + + // Certificates describes a set of certificates that the PeerConnection + // uses to authenticate. Valid values for this parameter are created + // through calls to the GenerateCertificate function. Although any given + // DTLS connection will use only one certificate, this attribute allows the + // caller to provide multiple certificates that support different + // algorithms. The final certificate will be selected based on the DTLS + // handshake, which establishes which certificates are allowed. The + // PeerConnection implementation selects which of the certificates is + // used for a given connection; how certificates are selected is outside + // the scope of this specification. If this value is absent, then a default + // set of certificates is generated for each PeerConnection instance. + Certificates []Certificate `json:"certificates,omitempty"` + + // ICECandidatePoolSize describes the size of the prefetched ICE pool. + ICECandidatePoolSize uint8 `json:"iceCandidatePoolSize,omitempty"` + + // SDPSemantics controls the type of SDP offers accepted by and + // SDP answers generated by the PeerConnection. + SDPSemantics SDPSemantics `json:"sdpSemantics,omitempty"` +} diff --git a/vendor/github.com/pion/webrtc/v3/configuration_common.go b/vendor/github.com/pion/webrtc/v3/configuration_common.go new file mode 100644 index 0000000..92fc228 --- /dev/null +++ b/vendor/github.com/pion/webrtc/v3/configuration_common.go @@ -0,0 +1,24 @@ +package webrtc + +import "strings" + +// getICEServers side-steps the strict parsing mode of the ice package +// (as defined in https://tools.ietf.org/html/rfc7064) by copying and then +// stripping any erroneous queries from "stun(s):" URLs before parsing. +func (c Configuration) getICEServers() []ICEServer { + iceServers := append([]ICEServer{}, c.ICEServers...) + + for iceServersIndex := range iceServers { + iceServers[iceServersIndex].URLs = append([]string{}, iceServers[iceServersIndex].URLs...) + + for urlsIndex, rawURL := range iceServers[iceServersIndex].URLs { + if strings.HasPrefix(rawURL, "stun") { + // strip the query from "stun(s):" if present + parts := strings.Split(rawURL, "?") + rawURL = parts[0] + } + iceServers[iceServersIndex].URLs[urlsIndex] = rawURL + } + } + return iceServers +} diff --git a/vendor/github.com/pion/webrtc/v3/configuration_js.go b/vendor/github.com/pion/webrtc/v3/configuration_js.go new file mode 100644 index 0000000..44ab9f3 --- /dev/null +++ b/vendor/github.com/pion/webrtc/v3/configuration_js.go @@ -0,0 +1,35 @@ +// +build js,wasm + +package webrtc + +// Configuration defines a set of parameters to configure how the +// peer-to-peer communication via PeerConnection is established or +// re-established. +type Configuration struct { + // ICEServers defines a slice describing servers available to be used by + // ICE, such as STUN and TURN servers. + ICEServers []ICEServer + + // ICETransportPolicy indicates which candidates the ICEAgent is allowed + // to use. + ICETransportPolicy ICETransportPolicy + + // BundlePolicy indicates which media-bundling policy to use when gathering + // ICE candidates. + BundlePolicy BundlePolicy + + // RTCPMuxPolicy indicates which rtcp-mux policy to use when gathering ICE + // candidates. + RTCPMuxPolicy RTCPMuxPolicy + + // PeerIdentity sets the target peer identity for the PeerConnection. + // The PeerConnection will not establish a connection to a remote peer + // unless it can be successfully authenticated with the provided name. + PeerIdentity string + + // Certificates are not supported in the JavaScript/Wasm bindings. + // Certificates []Certificate + + // ICECandidatePoolSize describes the size of the prefetched ICE pool. + ICECandidatePoolSize uint8 +} diff --git a/vendor/github.com/pion/webrtc/v3/constants.go b/vendor/github.com/pion/webrtc/v3/constants.go new file mode 100644 index 0000000..79d759a --- /dev/null +++ b/vendor/github.com/pion/webrtc/v3/constants.go @@ -0,0 +1,25 @@ +package webrtc + +const ( + // Unknown defines default public constant to use for "enum" like struct + // comparisons when no value was defined. + Unknown = iota + unknownStr = "unknown" + ssrcStr = "ssrc" + + // Equal to UDP MTU + receiveMTU = 1460 + + // simulcastProbeCount is the amount of RTP Packets + // that handleUndeclaredSSRC will read and try to dispatch from + // mid and rid values + simulcastProbeCount = 10 + + // simulcastMaxProbeRoutines is how many active routines can be used to probe + // If the total amount of incoming SSRCes exceeds this new requests will be ignored + simulcastMaxProbeRoutines = 25 + + mediaSectionApplication = "application" + + rtpOutboundMTU = 1200 +) diff --git a/vendor/github.com/pion/webrtc/v3/datachannel.go b/vendor/github.com/pion/webrtc/v3/datachannel.go new file mode 100644 index 0000000..f391e7f --- /dev/null +++ b/vendor/github.com/pion/webrtc/v3/datachannel.go @@ -0,0 +1,597 @@ +// +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 + + // 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() + 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, 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, 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{ + 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 { + d.mu.Lock() + if d.sctpTransport != nil { + // already open + d.mu.Unlock() + return nil + } + d.sctpTransport = sctpTransport + + if err := d.ensureSCTP(); err != nil { + d.mu.Unlock() + return err + } + + 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 { + err := d.sctpTransport.generateAndSetDataChannelID(d.sctpTransport.dtlsTransport.role(), &d.id) + if err != nil { + return err + } + } + + dc, err := datachannel.Dial(d.sctpTransport.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.handleOpen(dc) + return nil +} + +func (d *DataChannel) ensureSCTP() error { + if d.sctpTransport == nil { + return errSCTPNotEstablished + } + + d.sctpTransport.lock.RLock() + defer d.sctpTransport.lock.RUnlock() + if d.sctpTransport.association == nil { + return errSCTPNotEstablished + } + 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 + d.mu.RUnlock() + + if handler != nil { + go d.openHandlerOnce.Do(func() { + handler() + d.checkDetachAfterOpen() + }) + } +} + +// OnClose sets an event handler which is invoked when +// the underlying data transport has been closed. +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 + d.mu.RUnlock() + + if handler == nil { + return + } + handler(msg) +} + +func (d *DataChannel) handleOpen(dc *datachannel.DataChannel) { + d.mu.Lock() + d.dataChannel = dc + d.mu.Unlock() + d.setReadyState(DataChannelStateOpen) + + d.onOpen() + + d.mu.Lock() + defer d.mu.Unlock() + + if !d.api.settingEngine.detach.DataChannels { + 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 + 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() { + for { + buffer := rlBufPool.Get().([]byte) + n, isString, err := d.dataChannel.ReadDataChannel(buffer) + if err != nil { + rlBufPool.Put(buffer) // nolint:staticcheck + d.setReadyState(DataChannelStateClosed) + if 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 { + d.mu.Lock() + 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 represents 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 := d.readyState.Load(); v != nil { + return v.(DataChannelState) + } + 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) +} diff --git a/vendor/github.com/pion/webrtc/v3/datachannel_js.go b/vendor/github.com/pion/webrtc/v3/datachannel_js.go new file mode 100644 index 0000000..7aa6d99 --- /dev/null +++ b/vendor/github.com/pion/webrtc/v3/datachannel_js.go @@ -0,0 +1,319 @@ +// +build js,wasm + +package webrtc + +import ( + "fmt" + "syscall/js" + + "github.com/pion/datachannel" +) + +const dataChannelBufferSize = 16384 // Lowest common denominator among browsers + +// 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 { + // Pointer to the underlying JavaScript RTCPeerConnection object. + underlying js.Value + + // Keep track of handlers/callbacks so we can call Release as required by the + // syscall/js API. Initially nil. + onOpenHandler *js.Func + onCloseHandler *js.Func + onMessageHandler *js.Func + onBufferedAmountLow *js.Func + + // A reference to the associated api object used by this datachannel + api *API +} + +// 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()) { + if d.onOpenHandler != nil { + oldHandler := d.onOpenHandler + defer oldHandler.Release() + } + onOpenHandler := js.FuncOf(func(this js.Value, args []js.Value) interface{} { + go f() + return js.Undefined() + }) + d.onOpenHandler = &onOpenHandler + d.underlying.Set("onopen", onOpenHandler) +} + +// OnClose sets an event handler which is invoked when +// the underlying data transport has been closed. +func (d *DataChannel) OnClose(f func()) { + if d.onCloseHandler != nil { + oldHandler := d.onCloseHandler + defer oldHandler.Release() + } + onCloseHandler := js.FuncOf(func(this js.Value, args []js.Value) interface{} { + go f() + return js.Undefined() + }) + d.onCloseHandler = &onCloseHandler + d.underlying.Set("onclose", onCloseHandler) +} + +// OnMessage sets an event handler which is invoked on a binary message arrival +// from a remote peer. Note that browsers may place limitations on message size. +func (d *DataChannel) OnMessage(f func(msg DataChannelMessage)) { + if d.onMessageHandler != nil { + oldHandler := d.onMessageHandler + defer oldHandler.Release() + } + onMessageHandler := js.FuncOf(func(this js.Value, args []js.Value) interface{} { + // pion/webrtc/projects/15 + data := args[0].Get("data") + go func() { + // valueToDataChannelMessage may block when handling 'Blob' data + // so we need to call it from a new routine. See: + // https://pkg.go.dev/syscall/js#FuncOf + msg := valueToDataChannelMessage(data) + f(msg) + }() + return js.Undefined() + }) + d.onMessageHandler = &onMessageHandler + d.underlying.Set("onmessage", onMessageHandler) +} + +// Send sends the binary message to the DataChannel peer +func (d *DataChannel) Send(data []byte) (err error) { + defer func() { + if e := recover(); e != nil { + err = recoveryToError(e) + } + }() + array := js.Global().Get("Uint8Array").New(len(data)) + js.CopyBytesToJS(array, data) + d.underlying.Call("send", array) + return nil +} + +// SendText sends the text message to the DataChannel peer +func (d *DataChannel) SendText(s string) (err error) { + defer func() { + if e := recover(); e != nil { + err = recoveryToError(e) + } + }() + d.underlying.Call("send", s) + 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 reffer 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) { + if !d.api.settingEngine.detach.DataChannels { + return nil, fmt.Errorf("enable detaching by calling webrtc.DetachDataChannels()") + } + + detached := newDetachedDataChannel(d) + return detached, 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() (err error) { + defer func() { + if e := recover(); e != nil { + err = recoveryToError(e) + } + }() + + d.underlying.Call("close") + + // Release any handlers as required by the syscall/js API. + if d.onOpenHandler != nil { + d.onOpenHandler.Release() + } + if d.onCloseHandler != nil { + d.onCloseHandler.Release() + } + if d.onMessageHandler != nil { + d.onMessageHandler.Release() + } + if d.onBufferedAmountLow != nil { + d.onBufferedAmountLow.Release() + } + + return nil +} + +// 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 { + return d.underlying.Get("label").String() +} + +// Ordered represents if the DataChannel is ordered, and false if +// out-of-order delivery is allowed. +func (d *DataChannel) Ordered() bool { + ordered := d.underlying.Get("ordered") + if jsValueIsUndefined(ordered) { + return true // default is true + } + return ordered.Bool() +} + +// MaxPacketLifeTime represents the length of the time window (msec) during +// which transmissions and retransmissions may occur in unreliable mode. +func (d *DataChannel) MaxPacketLifeTime() *uint16 { + if !jsValueIsUndefined(d.underlying.Get("maxPacketLifeTime")) { + return valueToUint16Pointer(d.underlying.Get("maxPacketLifeTime")) + } else { + // See https://bugs.chromium.org/p/chromium/issues/detail?id=696681 + // Chrome calls this "maxRetransmitTime" + return valueToUint16Pointer(d.underlying.Get("maxRetransmitTime")) + } +} + +// MaxRetransmits represents the maximum number of retransmissions that are +// attempted in unreliable mode. +func (d *DataChannel) MaxRetransmits() *uint16 { + return valueToUint16Pointer(d.underlying.Get("maxRetransmits")) +} + +// Protocol represents the name of the sub-protocol used with this +// DataChannel. +func (d *DataChannel) Protocol() string { + return d.underlying.Get("protocol").String() +} + +// Negotiated represents whether this DataChannel was negotiated by the +// application (true), or not (false). +func (d *DataChannel) Negotiated() bool { + return d.underlying.Get("negotiated").Bool() +} + +// 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. 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 { + return valueToUint16Pointer(d.underlying.Get("id")) +} + +// ReadyState represents the state of the DataChannel object. +func (d *DataChannel) ReadyState() DataChannelState { + return newDataChannelState(d.underlying.Get("readyState").String()) +} + +// 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 { + return uint64(d.underlying.Get("bufferedAmount").Int()) +} + +// 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. +func (d *DataChannel) BufferedAmountLowThreshold() uint64 { + return uint64(d.underlying.Get("bufferedAmountLowThreshold").Int()) +} + +// SetBufferedAmountLowThreshold is used to update the threshold. +// See BufferedAmountLowThreshold(). +func (d *DataChannel) SetBufferedAmountLowThreshold(th uint64) { + d.underlying.Set("bufferedAmountLowThreshold", 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()) { + if d.onBufferedAmountLow != nil { + oldHandler := d.onBufferedAmountLow + defer oldHandler.Release() + } + onBufferedAmountLow := js.FuncOf(func(this js.Value, args []js.Value) interface{} { + go f() + return js.Undefined() + }) + d.onBufferedAmountLow = &onBufferedAmountLow + d.underlying.Set("onbufferedamountlow", onBufferedAmountLow) +} + +// valueToDataChannelMessage converts the given value to a DataChannelMessage. +// val should be obtained from MessageEvent.data where MessageEvent is received +// via the RTCDataChannel.onmessage callback. +func valueToDataChannelMessage(val js.Value) DataChannelMessage { + // If val is of type string, the conversion is straightforward. + if val.Type() == js.TypeString { + return DataChannelMessage{ + IsString: true, + Data: []byte(val.String()), + } + } + + // For other types, we need to first determine val.constructor.name. + constructorName := val.Get("constructor").Get("name").String() + var data []byte + switch constructorName { + case "Uint8Array": + // We can easily convert Uint8Array to []byte + data = uint8ArrayValueToBytes(val) + case "Blob": + // Convert the Blob to an ArrayBuffer and then convert the ArrayBuffer + // to a Uint8Array. + // See: https://developer.mozilla.org/en-US/docs/Web/API/Blob + + // The JavaScript API for reading from the Blob is asynchronous. We use a + // channel to signal when reading is done. + reader := js.Global().Get("FileReader").New() + doneChan := make(chan struct{}) + reader.Call("addEventListener", "loadend", js.FuncOf(func(this js.Value, args []js.Value) interface{} { + go func() { + // Signal that the FileReader is done reading/loading by sending through + // the doneChan. + doneChan <- struct{}{} + }() + return js.Undefined() + })) + + reader.Call("readAsArrayBuffer", val) + + // Wait for the FileReader to finish reading/loading. + <-doneChan + + // At this point buffer.result is a typed array, which we know how to + // handle. + buffer := reader.Get("result") + uint8Array := js.Global().Get("Uint8Array").New(buffer) + data = uint8ArrayValueToBytes(uint8Array) + default: + // Assume we have an ArrayBufferView type which we can convert to a + // Uint8Array in JavaScript. + // See: https://developer.mozilla.org/en-US/docs/Web/API/ArrayBufferView + uint8Array := js.Global().Get("Uint8Array").New(val) + data = uint8ArrayValueToBytes(uint8Array) + } + + return DataChannelMessage{ + IsString: false, + Data: data, + } +} diff --git a/vendor/github.com/pion/webrtc/v3/datachannel_js_detach.go b/vendor/github.com/pion/webrtc/v3/datachannel_js_detach.go new file mode 100644 index 0000000..dd0069e --- /dev/null +++ b/vendor/github.com/pion/webrtc/v3/datachannel_js_detach.go @@ -0,0 +1,71 @@ +// +build js,wasm + +package webrtc + +import ( + "errors" +) + +type detachedDataChannel struct { + dc *DataChannel + + read chan DataChannelMessage + done chan struct{} +} + +func newDetachedDataChannel(dc *DataChannel) *detachedDataChannel { + read := make(chan DataChannelMessage) + done := make(chan struct{}) + + // Wire up callbacks + dc.OnMessage(func(msg DataChannelMessage) { + read <- msg // pion/webrtc/projects/15 + }) + + // pion/webrtc/projects/15 + + return &detachedDataChannel{ + dc: dc, + read: read, + done: done, + } +} + +func (c *detachedDataChannel) Read(p []byte) (int, error) { + n, _, err := c.ReadDataChannel(p) + return n, err +} + +func (c *detachedDataChannel) ReadDataChannel(p []byte) (int, bool, error) { + select { + case <-c.done: + return 0, false, errors.New("Reader closed") + case msg := <-c.read: + n := copy(p, msg.Data) + if n < len(msg.Data) { + return n, msg.IsString, errors.New("Read buffer to small") + } + return n, msg.IsString, nil + } +} + +func (c *detachedDataChannel) Write(p []byte) (n int, err error) { + return c.WriteDataChannel(p, false) +} + +func (c *detachedDataChannel) WriteDataChannel(p []byte, isString bool) (n int, err error) { + if isString { + err = c.dc.SendText(string(p)) + return len(p), err + } + + err = c.dc.Send(p) + + return len(p), err +} + +func (c *detachedDataChannel) Close() error { + close(c.done) + + return c.dc.Close() +} diff --git a/vendor/github.com/pion/webrtc/v3/datachannelinit.go b/vendor/github.com/pion/webrtc/v3/datachannelinit.go new file mode 100644 index 0000000..a4320e4 --- /dev/null +++ b/vendor/github.com/pion/webrtc/v3/datachannelinit.go @@ -0,0 +1,33 @@ +package webrtc + +// DataChannelInit can be used to configure properties of the underlying +// channel such as data reliability. +type DataChannelInit struct { + // Ordered indicates if data is allowed to be delivered out of order. The + // default value of true, guarantees that data will be delivered in order. + Ordered *bool + + // MaxPacketLifeTime limits the time (in milliseconds) during which the + // channel will transmit or retransmit data if not acknowledged. This value + // may be clamped if it exceeds the maximum value supported. + MaxPacketLifeTime *uint16 + + // MaxRetransmits limits the number of times a channel will retransmit data + // if not successfully delivered. This value may be clamped if it exceeds + // the maximum value supported. + MaxRetransmits *uint16 + + // Protocol describes the subprotocol name used for this channel. + Protocol *string + + // Negotiated describes if the data channel is created by the local peer or + // the remote peer. The default value of false tells the user agent to + // announce the channel in-band and instruct the other peer to dispatch a + // corresponding DataChannel. If set to true, it is up to the application + // to negotiate the channel and create an DataChannel with the same id + // at the other peer. + Negotiated *bool + + // ID overrides the default selection of ID for this channel. + ID *uint16 +} diff --git a/vendor/github.com/pion/webrtc/v3/datachannelmessage.go b/vendor/github.com/pion/webrtc/v3/datachannelmessage.go new file mode 100644 index 0000000..1e3c63b --- /dev/null +++ b/vendor/github.com/pion/webrtc/v3/datachannelmessage.go @@ -0,0 +1,10 @@ +package webrtc + +// DataChannelMessage represents a message received from the +// data channel. IsString will be set to true if the incoming +// message is of the string type. Otherwise the message is of +// a binary type. +type DataChannelMessage struct { + IsString bool + Data []byte +} diff --git a/vendor/github.com/pion/webrtc/v3/datachannelparameters.go b/vendor/github.com/pion/webrtc/v3/datachannelparameters.go new file mode 100644 index 0000000..d67a63b --- /dev/null +++ b/vendor/github.com/pion/webrtc/v3/datachannelparameters.go @@ -0,0 +1,12 @@ +package webrtc + +// DataChannelParameters describes the configuration of the DataChannel. +type DataChannelParameters struct { + Label string `json:"label"` + Protocol string `json:"protocol"` + ID *uint16 `json:"id"` + Ordered bool `json:"ordered"` + MaxPacketLifeTime *uint16 `json:"maxPacketLifeTime"` + MaxRetransmits *uint16 `json:"maxRetransmits"` + Negotiated bool `json:"negotiated"` +} diff --git a/vendor/github.com/pion/webrtc/v3/datachannelstate.go b/vendor/github.com/pion/webrtc/v3/datachannelstate.go new file mode 100644 index 0000000..a2c7b95 --- /dev/null +++ b/vendor/github.com/pion/webrtc/v3/datachannelstate.go @@ -0,0 +1,61 @@ +package webrtc + +// DataChannelState indicates the state of a data channel. +type DataChannelState int + +const ( + // DataChannelStateConnecting indicates that the data channel is being + // established. This is the initial state of DataChannel, whether created + // with CreateDataChannel, or dispatched as a part of an DataChannelEvent. + DataChannelStateConnecting DataChannelState = iota + 1 + + // DataChannelStateOpen indicates that the underlying data transport is + // established and communication is possible. + DataChannelStateOpen + + // DataChannelStateClosing indicates that the procedure to close down the + // underlying data transport has started. + DataChannelStateClosing + + // DataChannelStateClosed indicates that the underlying data transport + // has been closed or could not be established. + DataChannelStateClosed +) + +// This is done this way because of a linter. +const ( + dataChannelStateConnectingStr = "connecting" + dataChannelStateOpenStr = "open" + dataChannelStateClosingStr = "closing" + dataChannelStateClosedStr = "closed" +) + +func newDataChannelState(raw string) DataChannelState { + switch raw { + case dataChannelStateConnectingStr: + return DataChannelStateConnecting + case dataChannelStateOpenStr: + return DataChannelStateOpen + case dataChannelStateClosingStr: + return DataChannelStateClosing + case dataChannelStateClosedStr: + return DataChannelStateClosed + default: + return DataChannelState(Unknown) + } +} + +func (t DataChannelState) String() string { + switch t { + case DataChannelStateConnecting: + return dataChannelStateConnectingStr + case DataChannelStateOpen: + return dataChannelStateOpenStr + case DataChannelStateClosing: + return dataChannelStateClosingStr + case DataChannelStateClosed: + return dataChannelStateClosedStr + default: + return ErrUnknownType.Error() + } +} diff --git a/vendor/github.com/pion/webrtc/v3/dtlsfingerprint.go b/vendor/github.com/pion/webrtc/v3/dtlsfingerprint.go new file mode 100644 index 0000000..db13d3e --- /dev/null +++ b/vendor/github.com/pion/webrtc/v3/dtlsfingerprint.go @@ -0,0 +1,14 @@ +package webrtc + +// DTLSFingerprint specifies the hash function algorithm and certificate +// fingerprint as described in https://tools.ietf.org/html/rfc4572. +type DTLSFingerprint struct { + // Algorithm specifies one of the the hash function algorithms defined in + // the 'Hash function Textual Names' registry. + Algorithm string `json:"algorithm"` + + // Value specifies the value of the certificate fingerprint in lowercase + // hex string as expressed utilizing the syntax of 'fingerprint' in + // https://tools.ietf.org/html/rfc4572#section-5. + Value string `json:"value"` +} diff --git a/vendor/github.com/pion/webrtc/v3/dtlsparameters.go b/vendor/github.com/pion/webrtc/v3/dtlsparameters.go new file mode 100644 index 0000000..4b4b568 --- /dev/null +++ b/vendor/github.com/pion/webrtc/v3/dtlsparameters.go @@ -0,0 +1,7 @@ +package webrtc + +// DTLSParameters holds information relating to DTLS configuration. +type DTLSParameters struct { + Role DTLSRole `json:"role"` + Fingerprints []DTLSFingerprint `json:"fingerprints"` +} diff --git a/vendor/github.com/pion/webrtc/v3/dtlsrole.go b/vendor/github.com/pion/webrtc/v3/dtlsrole.go new file mode 100644 index 0000000..6e67f60 --- /dev/null +++ b/vendor/github.com/pion/webrtc/v3/dtlsrole.go @@ -0,0 +1,92 @@ +package webrtc + +import ( + "github.com/pion/sdp/v3" +) + +// DTLSRole indicates the role of the DTLS transport. +type DTLSRole byte + +const ( + // DTLSRoleAuto defines the DTLS role is determined based on + // the resolved ICE role: the ICE controlled role acts as the DTLS + // client and the ICE controlling role acts as the DTLS server. + DTLSRoleAuto DTLSRole = iota + 1 + + // DTLSRoleClient defines the DTLS client role. + DTLSRoleClient + + // DTLSRoleServer defines the DTLS server role. + DTLSRoleServer +) + +const ( + // https://tools.ietf.org/html/rfc5763 + /* + The answerer MUST use either a + setup attribute value of setup:active or setup:passive. Note that + if the answerer uses setup:passive, then the DTLS handshake will + not begin until the answerer is received, which adds additional + latency. setup:active allows the answer and the DTLS handshake to + occur in parallel. Thus, setup:active is RECOMMENDED. + */ + defaultDtlsRoleAnswer = DTLSRoleClient + /* + The endpoint that is the offerer MUST use the setup attribute + value of setup:actpass and be prepared to receive a client_hello + before it receives the answer. + */ + defaultDtlsRoleOffer = DTLSRoleAuto +) + +func (r DTLSRole) String() string { + switch r { + case DTLSRoleAuto: + return "auto" + case DTLSRoleClient: + return "client" + case DTLSRoleServer: + return "server" + default: + return unknownStr + } +} + +// Iterate a SessionDescription from a remote to determine if an explicit +// role can been determined from it. The decision is made from the first role we we parse. +// If no role can be found we return DTLSRoleAuto +func dtlsRoleFromRemoteSDP(sessionDescription *sdp.SessionDescription) DTLSRole { + if sessionDescription == nil { + return DTLSRoleAuto + } + + for _, mediaSection := range sessionDescription.MediaDescriptions { + for _, attribute := range mediaSection.Attributes { + if attribute.Key == "setup" { + switch attribute.Value { + case sdp.ConnectionRoleActive.String(): + return DTLSRoleClient + case sdp.ConnectionRolePassive.String(): + return DTLSRoleServer + default: + return DTLSRoleAuto + } + } + } + } + + return DTLSRoleAuto +} + +func connectionRoleFromDtlsRole(d DTLSRole) sdp.ConnectionRole { + switch d { + case DTLSRoleClient: + return sdp.ConnectionRoleActive + case DTLSRoleServer: + return sdp.ConnectionRolePassive + case DTLSRoleAuto: + return sdp.ConnectionRoleActpass + default: + return sdp.ConnectionRole(0) + } +} diff --git a/vendor/github.com/pion/webrtc/v3/dtlstransport.go b/vendor/github.com/pion/webrtc/v3/dtlstransport.go new file mode 100644 index 0000000..cc25889 --- /dev/null +++ b/vendor/github.com/pion/webrtc/v3/dtlstransport.go @@ -0,0 +1,430 @@ +// +build !js + +package webrtc + +import ( + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/tls" + "crypto/x509" + "errors" + "fmt" + "strings" + "sync" + "sync/atomic" + "time" + + "github.com/pion/dtls/v2" + "github.com/pion/dtls/v2/pkg/crypto/fingerprint" + "github.com/pion/logging" + "github.com/pion/srtp/v2" + "github.com/pion/webrtc/v3/internal/mux" + "github.com/pion/webrtc/v3/internal/util" + "github.com/pion/webrtc/v3/pkg/rtcerr" +) + +// DTLSTransport allows an application access to information about the DTLS +// transport over which RTP and RTCP packets are sent and received by +// RTPSender and RTPReceiver, as well other data such as SCTP packets sent +// and received by data channels. +type DTLSTransport struct { + lock sync.RWMutex + + iceTransport *ICETransport + certificates []Certificate + remoteParameters DTLSParameters + remoteCertificate []byte + state DTLSTransportState + srtpProtectionProfile srtp.ProtectionProfile + + onStateChangeHandler func(DTLSTransportState) + + conn *dtls.Conn + + srtpSession, srtcpSession atomic.Value + srtpEndpoint, srtcpEndpoint *mux.Endpoint + simulcastStreams []*srtp.ReadStreamSRTP + srtpReady chan struct{} + + dtlsMatcher mux.MatchFunc + + api *API + log logging.LeveledLogger +} + +// NewDTLSTransport creates a new DTLSTransport. +// This constructor is part of the ORTC API. It is not +// meant to be used together with the basic WebRTC API. +func (api *API) NewDTLSTransport(transport *ICETransport, certificates []Certificate) (*DTLSTransport, error) { + t := &DTLSTransport{ + iceTransport: transport, + api: api, + state: DTLSTransportStateNew, + dtlsMatcher: mux.MatchDTLS, + srtpReady: make(chan struct{}), + log: api.settingEngine.LoggerFactory.NewLogger("DTLSTransport"), + } + + if len(certificates) > 0 { + now := time.Now() + for _, x509Cert := range certificates { + if !x509Cert.Expires().IsZero() && now.After(x509Cert.Expires()) { + return nil, &rtcerr.InvalidAccessError{Err: ErrCertificateExpired} + } + t.certificates = append(t.certificates, x509Cert) + } + } else { + sk, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + return nil, &rtcerr.UnknownError{Err: err} + } + certificate, err := GenerateCertificate(sk) + if err != nil { + return nil, err + } + t.certificates = []Certificate{*certificate} + } + + return t, nil +} + +// ICETransport returns the currently-configured *ICETransport or nil +// if one has not been configured +func (t *DTLSTransport) ICETransport() *ICETransport { + t.lock.RLock() + defer t.lock.RUnlock() + return t.iceTransport +} + +// onStateChange requires the caller holds the lock +func (t *DTLSTransport) onStateChange(state DTLSTransportState) { + t.state = state + handler := t.onStateChangeHandler + if handler != nil { + handler(state) + } +} + +// OnStateChange sets a handler that is fired when the DTLS +// connection state changes. +func (t *DTLSTransport) OnStateChange(f func(DTLSTransportState)) { + t.lock.Lock() + defer t.lock.Unlock() + t.onStateChangeHandler = f +} + +// State returns the current dtls transport state. +func (t *DTLSTransport) State() DTLSTransportState { + t.lock.RLock() + defer t.lock.RUnlock() + return t.state +} + +// GetLocalParameters returns the DTLS parameters of the local DTLSTransport upon construction. +func (t *DTLSTransport) GetLocalParameters() (DTLSParameters, error) { + fingerprints := []DTLSFingerprint{} + + for _, c := range t.certificates { + prints, err := c.GetFingerprints() + if err != nil { + return DTLSParameters{}, err + } + + fingerprints = append(fingerprints, prints...) + } + + return DTLSParameters{ + Role: DTLSRoleAuto, // always returns the default role + Fingerprints: fingerprints, + }, nil +} + +// GetRemoteCertificate returns the certificate chain in use by the remote side +// returns an empty list prior to selection of the remote certificate +func (t *DTLSTransport) GetRemoteCertificate() []byte { + t.lock.RLock() + defer t.lock.RUnlock() + return t.remoteCertificate +} + +func (t *DTLSTransport) startSRTP() error { + srtpConfig := &srtp.Config{ + Profile: t.srtpProtectionProfile, + BufferFactory: t.api.settingEngine.BufferFactory, + LoggerFactory: t.api.settingEngine.LoggerFactory, + } + if t.api.settingEngine.replayProtection.SRTP != nil { + srtpConfig.RemoteOptions = append( + srtpConfig.RemoteOptions, + srtp.SRTPReplayProtection(*t.api.settingEngine.replayProtection.SRTP), + ) + } + + if t.api.settingEngine.disableSRTPReplayProtection { + srtpConfig.RemoteOptions = append( + srtpConfig.RemoteOptions, + srtp.SRTPNoReplayProtection(), + ) + } + + if t.api.settingEngine.replayProtection.SRTCP != nil { + srtpConfig.RemoteOptions = append( + srtpConfig.RemoteOptions, + srtp.SRTCPReplayProtection(*t.api.settingEngine.replayProtection.SRTCP), + ) + } + + if t.api.settingEngine.disableSRTCPReplayProtection { + srtpConfig.RemoteOptions = append( + srtpConfig.RemoteOptions, + srtp.SRTCPNoReplayProtection(), + ) + } + + connState := t.conn.ConnectionState() + err := srtpConfig.ExtractSessionKeysFromDTLS(&connState, t.role() == DTLSRoleClient) + if err != nil { + return fmt.Errorf("%w: %v", errDtlsKeyExtractionFailed, err) + } + + srtpSession, err := srtp.NewSessionSRTP(t.srtpEndpoint, srtpConfig) + if err != nil { + return fmt.Errorf("%w: %v", errFailedToStartSRTP, err) + } + + srtcpSession, err := srtp.NewSessionSRTCP(t.srtcpEndpoint, srtpConfig) + if err != nil { + return fmt.Errorf("%w: %v", errFailedToStartSRTCP, err) + } + + t.srtpSession.Store(srtpSession) + t.srtcpSession.Store(srtcpSession) + close(t.srtpReady) + return nil +} + +func (t *DTLSTransport) getSRTPSession() (*srtp.SessionSRTP, error) { + if value := t.srtpSession.Load(); value != nil { + return value.(*srtp.SessionSRTP), nil + } + + return nil, errDtlsTransportNotStarted +} + +func (t *DTLSTransport) getSRTCPSession() (*srtp.SessionSRTCP, error) { + if value := t.srtcpSession.Load(); value != nil { + return value.(*srtp.SessionSRTCP), nil + } + + return nil, errDtlsTransportNotStarted +} + +func (t *DTLSTransport) role() DTLSRole { + // If remote has an explicit role use the inverse + switch t.remoteParameters.Role { + case DTLSRoleClient: + return DTLSRoleServer + case DTLSRoleServer: + return DTLSRoleClient + default: + } + + // If SettingEngine has an explicit role + switch t.api.settingEngine.answeringDTLSRole { + case DTLSRoleServer: + return DTLSRoleServer + case DTLSRoleClient: + return DTLSRoleClient + default: + } + + // Remote was auto and no explicit role was configured via SettingEngine + if t.iceTransport.Role() == ICERoleControlling { + return DTLSRoleServer + } + return defaultDtlsRoleAnswer +} + +// Start DTLS transport negotiation with the parameters of the remote DTLS transport +func (t *DTLSTransport) Start(remoteParameters DTLSParameters) error { + // Take lock and prepare connection, we must not hold the lock + // when connecting + prepareTransport := func() (DTLSRole, *dtls.Config, error) { + t.lock.Lock() + defer t.lock.Unlock() + + if err := t.ensureICEConn(); err != nil { + return DTLSRole(0), nil, err + } + + if t.state != DTLSTransportStateNew { + return DTLSRole(0), nil, &rtcerr.InvalidStateError{Err: fmt.Errorf("%w: %s", errInvalidDTLSStart, t.state)} + } + + t.srtpEndpoint = t.iceTransport.NewEndpoint(mux.MatchSRTP) + t.srtcpEndpoint = t.iceTransport.NewEndpoint(mux.MatchSRTCP) + t.remoteParameters = remoteParameters + + cert := t.certificates[0] + t.onStateChange(DTLSTransportStateConnecting) + + return t.role(), &dtls.Config{ + Certificates: []tls.Certificate{ + { + Certificate: [][]byte{cert.x509Cert.Raw}, + PrivateKey: cert.privateKey, + }, + }, + SRTPProtectionProfiles: []dtls.SRTPProtectionProfile{dtls.SRTP_AEAD_AES_128_GCM, dtls.SRTP_AES128_CM_HMAC_SHA1_80}, + ClientAuth: dtls.RequireAnyClientCert, + LoggerFactory: t.api.settingEngine.LoggerFactory, + InsecureSkipVerify: true, + }, nil + } + + var dtlsConn *dtls.Conn + dtlsEndpoint := t.iceTransport.NewEndpoint(mux.MatchDTLS) + role, dtlsConfig, err := prepareTransport() + if err != nil { + return err + } + + if t.api.settingEngine.replayProtection.DTLS != nil { + dtlsConfig.ReplayProtectionWindow = int(*t.api.settingEngine.replayProtection.DTLS) + } + + // Connect as DTLS Client/Server, function is blocking and we + // must not hold the DTLSTransport lock + if role == DTLSRoleClient { + dtlsConn, err = dtls.Client(dtlsEndpoint, dtlsConfig) + } else { + dtlsConn, err = dtls.Server(dtlsEndpoint, dtlsConfig) + } + + // Re-take the lock, nothing beyond here is blocking + t.lock.Lock() + defer t.lock.Unlock() + + if err != nil { + t.onStateChange(DTLSTransportStateFailed) + return err + } + + srtpProfile, ok := dtlsConn.SelectedSRTPProtectionProfile() + if !ok { + t.onStateChange(DTLSTransportStateFailed) + return ErrNoSRTPProtectionProfile + } + + switch srtpProfile { + case dtls.SRTP_AEAD_AES_128_GCM: + t.srtpProtectionProfile = srtp.ProtectionProfileAeadAes128Gcm + case dtls.SRTP_AES128_CM_HMAC_SHA1_80: + t.srtpProtectionProfile = srtp.ProtectionProfileAes128CmHmacSha1_80 + default: + t.onStateChange(DTLSTransportStateFailed) + return ErrNoSRTPProtectionProfile + } + + if t.api.settingEngine.disableCertificateFingerprintVerification { + return nil + } + + // Check the fingerprint if a certificate was exchanged + remoteCerts := dtlsConn.ConnectionState().PeerCertificates + if len(remoteCerts) == 0 { + t.onStateChange(DTLSTransportStateFailed) + return errNoRemoteCertificate + } + t.remoteCertificate = remoteCerts[0] + + parsedRemoteCert, err := x509.ParseCertificate(t.remoteCertificate) + if err != nil { + if closeErr := dtlsConn.Close(); closeErr != nil { + t.log.Error(err.Error()) + } + + t.onStateChange(DTLSTransportStateFailed) + return err + } + + if err = t.validateFingerPrint(parsedRemoteCert); err != nil { + if closeErr := dtlsConn.Close(); closeErr != nil { + t.log.Error(err.Error()) + } + + t.onStateChange(DTLSTransportStateFailed) + return err + } + + t.conn = dtlsConn + t.onStateChange(DTLSTransportStateConnected) + + return t.startSRTP() +} + +// Stop stops and closes the DTLSTransport object. +func (t *DTLSTransport) Stop() error { + t.lock.Lock() + defer t.lock.Unlock() + + // Try closing everything and collect the errors + var closeErrs []error + + if srtpSessionValue := t.srtpSession.Load(); srtpSessionValue != nil { + closeErrs = append(closeErrs, srtpSessionValue.(*srtp.SessionSRTP).Close()) + } + + if srtcpSessionValue := t.srtcpSession.Load(); srtcpSessionValue != nil { + closeErrs = append(closeErrs, srtcpSessionValue.(*srtp.SessionSRTCP).Close()) + } + + for i := range t.simulcastStreams { + closeErrs = append(closeErrs, t.simulcastStreams[i].Close()) + } + + if t.conn != nil { + // dtls connection may be closed on sctp close. + if err := t.conn.Close(); err != nil && !errors.Is(err, dtls.ErrConnClosed) { + closeErrs = append(closeErrs, err) + } + } + t.onStateChange(DTLSTransportStateClosed) + return util.FlattenErrs(closeErrs) +} + +func (t *DTLSTransport) validateFingerPrint(remoteCert *x509.Certificate) error { + for _, fp := range t.remoteParameters.Fingerprints { + hashAlgo, err := fingerprint.HashFromString(fp.Algorithm) + if err != nil { + return err + } + + remoteValue, err := fingerprint.Fingerprint(remoteCert, hashAlgo) + if err != nil { + return err + } + + if strings.EqualFold(remoteValue, fp.Value) { + return nil + } + } + + return errNoMatchingCertificateFingerprint +} + +func (t *DTLSTransport) ensureICEConn() error { + if t.iceTransport == nil || t.iceTransport.State() == ICETransportStateNew { + return errICEConnectionNotStarted + } + + return nil +} + +func (t *DTLSTransport) storeSimulcastStream(s *srtp.ReadStreamSRTP) { + t.lock.Lock() + defer t.lock.Unlock() + + t.simulcastStreams = append(t.simulcastStreams, s) +} diff --git a/vendor/github.com/pion/webrtc/v3/dtlstransportstate.go b/vendor/github.com/pion/webrtc/v3/dtlstransportstate.go new file mode 100644 index 0000000..900b50b --- /dev/null +++ b/vendor/github.com/pion/webrtc/v3/dtlstransportstate.go @@ -0,0 +1,71 @@ +package webrtc + +// DTLSTransportState indicates the DTLS transport establishment state. +type DTLSTransportState int + +const ( + // DTLSTransportStateNew indicates that DTLS has not started negotiating + // yet. + DTLSTransportStateNew DTLSTransportState = iota + 1 + + // DTLSTransportStateConnecting indicates that DTLS is in the process of + // negotiating a secure connection and verifying the remote fingerprint. + DTLSTransportStateConnecting + + // DTLSTransportStateConnected indicates that DTLS has completed + // negotiation of a secure connection and verified the remote fingerprint. + DTLSTransportStateConnected + + // DTLSTransportStateClosed indicates that the transport has been closed + // intentionally as the result of receipt of a close_notify alert, or + // calling close(). + DTLSTransportStateClosed + + // DTLSTransportStateFailed indicates that the transport has failed as + // the result of an error (such as receipt of an error alert or failure to + // validate the remote fingerprint). + DTLSTransportStateFailed +) + +// This is done this way because of a linter. +const ( + dtlsTransportStateNewStr = "new" + dtlsTransportStateConnectingStr = "connecting" + dtlsTransportStateConnectedStr = "connected" + dtlsTransportStateClosedStr = "closed" + dtlsTransportStateFailedStr = "failed" +) + +func newDTLSTransportState(raw string) DTLSTransportState { + switch raw { + case dtlsTransportStateNewStr: + return DTLSTransportStateNew + case dtlsTransportStateConnectingStr: + return DTLSTransportStateConnecting + case dtlsTransportStateConnectedStr: + return DTLSTransportStateConnected + case dtlsTransportStateClosedStr: + return DTLSTransportStateClosed + case dtlsTransportStateFailedStr: + return DTLSTransportStateFailed + default: + return DTLSTransportState(Unknown) + } +} + +func (t DTLSTransportState) String() string { + switch t { + case DTLSTransportStateNew: + return dtlsTransportStateNewStr + case DTLSTransportStateConnecting: + return dtlsTransportStateConnectingStr + case DTLSTransportStateConnected: + return dtlsTransportStateConnectedStr + case DTLSTransportStateClosed: + return dtlsTransportStateClosedStr + case DTLSTransportStateFailed: + return dtlsTransportStateFailedStr + default: + return ErrUnknownType.Error() + } +} diff --git a/vendor/github.com/pion/webrtc/v3/errors.go b/vendor/github.com/pion/webrtc/v3/errors.go new file mode 100644 index 0000000..a9daf16 --- /dev/null +++ b/vendor/github.com/pion/webrtc/v3/errors.go @@ -0,0 +1,220 @@ +package webrtc + +import ( + "errors" +) + +var ( + // ErrUnknownType indicates an error with Unknown info. + ErrUnknownType = errors.New("unknown") + + // ErrConnectionClosed indicates an operation executed after connection + // has already been closed. + ErrConnectionClosed = errors.New("connection closed") + + // ErrDataChannelNotOpen indicates an operation executed when the data + // channel is not (yet) open. + ErrDataChannelNotOpen = errors.New("data channel not open") + + // ErrCertificateExpired indicates that an x509 certificate has expired. + ErrCertificateExpired = errors.New("x509Cert expired") + + // ErrNoTurnCredentials indicates that a TURN server URL was provided + // without required credentials. + ErrNoTurnCredentials = errors.New("turn server credentials required") + + // ErrTurnCredentials indicates that provided TURN credentials are partial + // or malformed. + ErrTurnCredentials = errors.New("invalid turn server credentials") + + // ErrExistingTrack indicates that a track already exists. + ErrExistingTrack = errors.New("track already exists") + + // ErrPrivateKeyType indicates that a particular private key encryption + // chosen to generate a certificate is not supported. + ErrPrivateKeyType = errors.New("private key type not supported") + + // ErrModifyingPeerIdentity indicates that an attempt to modify + // PeerIdentity was made after PeerConnection has been initialized. + ErrModifyingPeerIdentity = errors.New("peerIdentity cannot be modified") + + // ErrModifyingCertificates indicates that an attempt to modify + // Certificates was made after PeerConnection has been initialized. + ErrModifyingCertificates = errors.New("certificates cannot be modified") + + // ErrModifyingBundlePolicy indicates that an attempt to modify + // BundlePolicy was made after PeerConnection has been initialized. + ErrModifyingBundlePolicy = errors.New("bundle policy cannot be modified") + + // ErrModifyingRTCPMuxPolicy indicates that an attempt to modify + // RTCPMuxPolicy was made after PeerConnection has been initialized. + ErrModifyingRTCPMuxPolicy = errors.New("rtcp mux policy cannot be modified") + + // ErrModifyingICECandidatePoolSize indicates that an attempt to modify + // ICECandidatePoolSize was made after PeerConnection has been initialized. + ErrModifyingICECandidatePoolSize = errors.New("ice candidate pool size cannot be modified") + + // ErrStringSizeLimit indicates that the character size limit of string is + // exceeded. The limit is hardcoded to 65535 according to specifications. + ErrStringSizeLimit = errors.New("data channel label exceeds size limit") + + // ErrMaxDataChannelID indicates that the maximum number ID that could be + // specified for a data channel has been exceeded. + ErrMaxDataChannelID = errors.New("maximum number ID for datachannel specified") + + // ErrNegotiatedWithoutID indicates that an attempt to create a data channel + // was made while setting the negotiated option to true without providing + // the negotiated channel ID. + ErrNegotiatedWithoutID = errors.New("negotiated set without channel id") + + // ErrRetransmitsOrPacketLifeTime indicates that an attempt to create a data + // channel was made with both options MaxPacketLifeTime and MaxRetransmits + // set together. Such configuration is not supported by the specification + // and is mutually exclusive. + ErrRetransmitsOrPacketLifeTime = errors.New("both MaxPacketLifeTime and MaxRetransmits was set") + + // ErrCodecNotFound is returned when a codec search to the Media Engine fails + ErrCodecNotFound = errors.New("codec not found") + + // ErrNoRemoteDescription indicates that an operation was rejected because + // the remote description is not set + ErrNoRemoteDescription = errors.New("remote description is not set") + + // ErrIncorrectSDPSemantics indicates that the PeerConnection was configured to + // generate SDP Answers with different SDP Semantics than the received Offer + ErrIncorrectSDPSemantics = errors.New("offer SDP semantics does not match configuration") + + // ErrIncorrectSignalingState indicates that the signaling state of PeerConnection is not correct + ErrIncorrectSignalingState = errors.New("operation can not be run in current signaling state") + + // ErrProtocolTooLarge indicates that value given for a DataChannelInit protocol is + // longer then 65535 bytes + ErrProtocolTooLarge = errors.New("protocol is larger then 65535 bytes") + + // ErrSenderNotCreatedByConnection indicates RemoveTrack was called with a RtpSender not created + // by this PeerConnection + ErrSenderNotCreatedByConnection = errors.New("RtpSender not created by this PeerConnection") + + // ErrSessionDescriptionNoFingerprint indicates SetRemoteDescription was called with a SessionDescription that has no + // fingerprint + ErrSessionDescriptionNoFingerprint = errors.New("SetRemoteDescription called with no fingerprint") + + // ErrSessionDescriptionInvalidFingerprint indicates SetRemoteDescription was called with a SessionDescription that + // has an invalid fingerprint + ErrSessionDescriptionInvalidFingerprint = errors.New("SetRemoteDescription called with an invalid fingerprint") + + // ErrSessionDescriptionConflictingFingerprints indicates SetRemoteDescription was called with a SessionDescription that + // has an conflicting fingerprints + ErrSessionDescriptionConflictingFingerprints = errors.New("SetRemoteDescription called with multiple conflicting fingerprint") + + // ErrSessionDescriptionMissingIceUfrag indicates SetRemoteDescription was called with a SessionDescription that + // is missing an ice-ufrag value + ErrSessionDescriptionMissingIceUfrag = errors.New("SetRemoteDescription called with no ice-ufrag") + + // ErrSessionDescriptionMissingIcePwd indicates SetRemoteDescription was called with a SessionDescription that + // is missing an ice-pwd value + ErrSessionDescriptionMissingIcePwd = errors.New("SetRemoteDescription called with no ice-pwd") + + // ErrSessionDescriptionConflictingIceUfrag indicates SetRemoteDescription was called with a SessionDescription that + // contains multiple conflicting ice-ufrag values + ErrSessionDescriptionConflictingIceUfrag = errors.New("SetRemoteDescription called with multiple conflicting ice-ufrag values") + + // ErrSessionDescriptionConflictingIcePwd indicates SetRemoteDescription was called with a SessionDescription that + // contains multiple conflicting ice-pwd values + ErrSessionDescriptionConflictingIcePwd = errors.New("SetRemoteDescription called with multiple conflicting ice-pwd values") + + // ErrNoSRTPProtectionProfile indicates that the DTLS handshake completed and no SRTP Protection Profile was chosen + ErrNoSRTPProtectionProfile = errors.New("DTLS Handshake completed and no SRTP Protection Profile was chosen") + + // ErrFailedToGenerateCertificateFingerprint indicates that we failed to generate the fingerprint used for comparing certificates + ErrFailedToGenerateCertificateFingerprint = errors.New("failed to generate certificate fingerprint") + + // ErrNoCodecsAvailable indicates that operation isn't possible because the MediaEngine has no codecs available + ErrNoCodecsAvailable = errors.New("operation failed no codecs are available") + + // ErrUnsupportedCodec indicates the remote peer doesn't support the requested codec + ErrUnsupportedCodec = errors.New("unable to start track, codec is not supported by remote") + + // ErrUnbindFailed indicates that a TrackLocal was not able to be unbind + ErrUnbindFailed = errors.New("failed to unbind TrackLocal from PeerConnection") + + // ErrNoPayloaderForCodec indicates that the requested codec does not have a payloader + ErrNoPayloaderForCodec = errors.New("the requested codec does not have a payloader") + + // ErrRegisterHeaderExtensionInvalidDirection indicates that a extension was registered with a direction besides `sendonly` or `recvonly` + ErrRegisterHeaderExtensionInvalidDirection = errors.New("a header extension must be registered as 'recvonly', 'sendonly' or both") + + // ErrSimulcastProbeOverflow indicates that too many Simulcast probe streams are in flight and the requested SSRC was ignored + ErrSimulcastProbeOverflow = errors.New("simulcast probe limit has been reached, new SSRC has been discarded") + + errDetachNotEnabled = errors.New("enable detaching by calling webrtc.DetachDataChannels()") + errDetachBeforeOpened = errors.New("datachannel not opened yet, try calling Detach from OnOpen") + errDtlsTransportNotStarted = errors.New("the DTLS transport has not started yet") + errDtlsKeyExtractionFailed = errors.New("failed extracting keys from DTLS for SRTP") + errFailedToStartSRTP = errors.New("failed to start SRTP") + errFailedToStartSRTCP = errors.New("failed to start SRTCP") + errInvalidDTLSStart = errors.New("attempted to start DTLSTransport that is not in new state") + errNoRemoteCertificate = errors.New("peer didn't provide certificate via DTLS") + errIdentityProviderNotImplemented = errors.New("identity provider is not implemented") + errNoMatchingCertificateFingerprint = errors.New("remote certificate does not match any fingerprint") + + errICEConnectionNotStarted = errors.New("ICE connection not started") + errICECandidateTypeUnknown = errors.New("unknown candidate type") + errICEInvalidConvertCandidateType = errors.New("cannot convert ice.CandidateType into webrtc.ICECandidateType, invalid type") + errICEAgentNotExist = errors.New("ICEAgent does not exist") + errICECandiatesCoversionFailed = errors.New("unable to convert ICE candidates to ICECandidates") + errICERoleUnknown = errors.New("unknown ICE Role") + errICEProtocolUnknown = errors.New("unknown protocol") + errICEGathererNotStarted = errors.New("gatherer not started") + + errNetworkTypeUnknown = errors.New("unknown network type") + + errSDPDoesNotMatchOffer = errors.New("new sdp does not match previous offer") + errSDPDoesNotMatchAnswer = errors.New("new sdp does not match previous answer") + errPeerConnSDPTypeInvalidValue = errors.New("provided value is not a valid enum value of type SDPType") + errPeerConnStateChangeInvalid = errors.New("invalid state change op") + errPeerConnStateChangeUnhandled = errors.New("unhandled state change op") + errPeerConnSDPTypeInvalidValueSetLocalDescription = errors.New("invalid SDP type supplied to SetLocalDescription()") + errPeerConnRemoteDescriptionWithoutMidValue = errors.New("remoteDescription contained media section without mid value") + errPeerConnRemoteDescriptionNil = errors.New("remoteDescription has not been set yet") + errPeerConnSingleMediaSectionHasExplicitSSRC = errors.New("single media section has an explicit SSRC") + errPeerConnRemoteSSRCAddTransceiver = errors.New("could not add transceiver for remote SSRC") + errPeerConnSimulcastMidRTPExtensionRequired = errors.New("mid RTP Extensions required for Simulcast") + errPeerConnSimulcastStreamIDRTPExtensionRequired = errors.New("stream id RTP Extensions required for Simulcast") + errPeerConnSimulcastIncomingSSRCFailed = errors.New("incoming SSRC failed Simulcast probing") + errPeerConnAddTransceiverFromKindOnlyAcceptsOne = errors.New("AddTransceiverFromKind only accepts one RtpTransceiverInit") + errPeerConnAddTransceiverFromTrackOnlyAcceptsOne = errors.New("AddTransceiverFromTrack only accepts one RtpTransceiverInit") + errPeerConnAddTransceiverFromKindSupport = errors.New("AddTransceiverFromKind currently only supports recvonly") + errPeerConnAddTransceiverFromTrackSupport = errors.New("AddTransceiverFromTrack currently only supports sendonly and sendrecv") + errPeerConnSetIdentityProviderNotImplemented = errors.New("TODO SetIdentityProvider") + errPeerConnWriteRTCPOpenWriteStream = errors.New("WriteRTCP failed to open WriteStream") + errPeerConnTranscieverMidNil = errors.New("cannot find transceiver with mid") + + errRTPReceiverDTLSTransportNil = errors.New("DTLSTransport must not be nil") + errRTPReceiverReceiveAlreadyCalled = errors.New("Receive has already been called") + errRTPReceiverWithSSRCTrackStreamNotFound = errors.New("unable to find stream for Track with SSRC") + errRTPReceiverForSSRCTrackStreamNotFound = errors.New("no trackStreams found for SSRC") + errRTPReceiverForRIDTrackStreamNotFound = errors.New("no trackStreams found for RID") + + errRTPSenderTrackNil = errors.New("Track must not be nil") + errRTPSenderDTLSTransportNil = errors.New("DTLSTransport must not be nil") + errRTPSenderSendAlreadyCalled = errors.New("Send has already been called") + + errRTPTransceiverCannotChangeMid = errors.New("errRTPSenderTrackNil") + errRTPTransceiverSetSendingInvalidState = errors.New("invalid state change in RTPTransceiver.setSending") + + errSCTPTransportDTLS = errors.New("DTLS not established") + + errSDPZeroTransceivers = errors.New("addTransceiverSDP() called with 0 transceivers") + errSDPMediaSectionMediaDataChanInvalid = errors.New("invalid Media Section. Media + DataChannel both enabled") + errSDPMediaSectionMultipleTrackInvalid = errors.New("invalid Media Section. Can not have multiple tracks in one MediaSection in UnifiedPlan") + + errSettingEngineSetAnsweringDTLSRole = errors.New("SetAnsweringDTLSRole must DTLSRoleClient or DTLSRoleServer") + + errSignalingStateCannotRollback = errors.New("can't rollback from stable state") + errSignalingStateProposedTransitionInvalid = errors.New("invalid proposed signaling state transition") + + errStatsICECandidateStateInvalid = errors.New("cannot convert to StatsICECandidatePairStateSucceeded invalid ice candidate state") + + errICETransportNotInNew = errors.New("ICETransport can only be called in ICETransportStateNew") +) diff --git a/vendor/github.com/pion/webrtc/v3/gathering_complete_promise.go b/vendor/github.com/pion/webrtc/v3/gathering_complete_promise.go new file mode 100644 index 0000000..a4d52f9 --- /dev/null +++ b/vendor/github.com/pion/webrtc/v3/gathering_complete_promise.go @@ -0,0 +1,24 @@ +package webrtc + +import ( + "context" +) + +// GatheringCompletePromise is a Pion specific helper function that returns a channel that is closed when gathering is complete. +// This function may be helpful in cases where you are unable to trickle your ICE Candidates. +// +// It is better to not use this function, and instead trickle candidates. If you use this function you will see longer connection startup times. +// When the call is connected you will see no impact however. +func GatheringCompletePromise(pc *PeerConnection) (gatherComplete <-chan struct{}) { + gatheringComplete, done := context.WithCancel(context.Background()) + + // It's possible to miss the GatherComplete event since setGatherCompleteHandler is an atomic operation and the + // promise might have been created after the gathering is finished. Therefore, we need to check if the ICE gathering + // state has changed to complete so that we don't block the caller forever. + pc.setGatherCompleteHandler(func() { done() }) + if pc.ICEGatheringState() == ICEGatheringStateComplete { + done() + } + + return gatheringComplete.Done() +} diff --git a/vendor/github.com/pion/webrtc/v3/go.mod b/vendor/github.com/pion/webrtc/v3/go.mod new file mode 100644 index 0000000..19d5a7e --- /dev/null +++ b/vendor/github.com/pion/webrtc/v3/go.mod @@ -0,0 +1,23 @@ +module github.com/pion/webrtc/v3 + +go 1.12 + +require ( + github.com/onsi/ginkgo v1.14.2 // indirect + github.com/onsi/gomega v1.10.3 // indirect + github.com/pion/datachannel v1.4.21 + github.com/pion/dtls/v2 v2.0.8 + github.com/pion/ice/v2 v2.0.15 + github.com/pion/interceptor v0.0.10 + github.com/pion/logging v0.2.2 + github.com/pion/randutil v0.1.0 + github.com/pion/rtcp v1.2.6 + github.com/pion/rtp v1.6.2 + github.com/pion/sctp v1.7.11 + github.com/pion/sdp/v3 v3.0.4 + github.com/pion/srtp/v2 v2.0.2 + github.com/pion/transport v0.12.2 + github.com/sclevine/agouti v3.0.0+incompatible + github.com/stretchr/testify v1.7.0 + golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 +) diff --git a/vendor/github.com/pion/webrtc/v3/go.sum b/vendor/github.com/pion/webrtc/v3/go.sum new file mode 100644 index 0000000..397bbfd --- /dev/null +++ b/vendor/github.com/pion/webrtc/v3/go.sum @@ -0,0 +1,151 @@ +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/uuid v1.1.5 h1:kxhtnfFVi+rYdOALN0B3k9UT86zVJKfBimRaciULW4I= +github.com/google/uuid v1.1.5/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.14.2 h1:8mVmC9kjFFmA8H4pKMUhcblgifdkOIXPvbhN1T36q1M= +github.com/onsi/ginkgo v1.14.2/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.10.3 h1:gph6h/qe9GSUw1NhH1gp+qb+h8rXD8Cy60Z32Qw3ELA= +github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc= +github.com/pion/datachannel v1.4.21 h1:3ZvhNyfmxsAqltQrApLPQMhSFNA+aT87RqyCq4OXmf0= +github.com/pion/datachannel v1.4.21/go.mod h1:oiNyP4gHx2DIwRzX/MFyH0Rz/Gz05OgBlayAI2hAWjg= +github.com/pion/dtls/v2 v2.0.4 h1:WuUcqi6oYMu/noNTz92QrF1DaFj4eXbhQ6dzaaAwOiI= +github.com/pion/dtls/v2 v2.0.4/go.mod h1:qAkFscX0ZHoI1E07RfYPoRw3manThveu+mlTDdOxoGI= +github.com/pion/dtls/v2 v2.0.8 h1:reGe8rNIMfO/UAeFLqO61tl64t154Qfkr4U3Gzu1tsg= +github.com/pion/dtls/v2 v2.0.8/go.mod h1:QuDII+8FVvk9Dp5t5vYIMTo7hh7uBkra+8QIm7QGm10= +github.com/pion/ice/v2 v2.0.15 h1:KZrwa2ciL9od8+TUVJiYTNsCW9J5lktBjGwW1MacEnQ= +github.com/pion/ice/v2 v2.0.15/go.mod h1:ZIiVGevpgAxF/cXiIVmuIUtCb3Xs4gCzCbXB6+nFkSI= +github.com/pion/interceptor v0.0.10 h1:dXFyFWRJFwmzQqyn0U8dUAbOJu+JJnMVAqxmvTu30B4= +github.com/pion/interceptor v0.0.10/go.mod h1:qzeuWuD/ZXvPqOnxNcnhWfkCZ2e1kwwslicyyPnhoK4= +github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY= +github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms= +github.com/pion/mdns v0.0.4 h1:O4vvVqr4DGX63vzmO6Fw9vpy3lfztVWHGCQfyw0ZLSY= +github.com/pion/mdns v0.0.4/go.mod h1:R1sL0p50l42S5lJs91oNdUL58nm0QHrhxnSegr++qC0= +github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA= +github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8= +github.com/pion/rtcp v1.2.6 h1:1zvwBbyd0TeEuuWftrd/4d++m+/kZSeiguxU61LFWpo= +github.com/pion/rtcp v1.2.6/go.mod h1:52rMNPWFsjr39z9B9MhnkqhPLoeHTv1aN63o/42bWE0= +github.com/pion/rtp v1.6.2 h1:iGBerLX6JiDjB9NXuaPzHyxHFG9JsIEdgwTC0lp5n/U= +github.com/pion/rtp v1.6.2/go.mod h1:bDb5n+BFZxXx0Ea7E5qe+klMuqiBrP+w8XSjiWtCUko= +github.com/pion/sctp v1.7.10/go.mod h1:EhpTUQu1/lcK3xI+eriS6/96fWetHGCvBi9MSsnaBN0= +github.com/pion/sctp v1.7.11 h1:UCnj7MsobLKLuP/Hh+JMiI/6W5Bs/VF45lWKgHFjSIE= +github.com/pion/sctp v1.7.11/go.mod h1:EhpTUQu1/lcK3xI+eriS6/96fWetHGCvBi9MSsnaBN0= +github.com/pion/sdp/v3 v3.0.4 h1:2Kf+dgrzJflNCSw3TV5v2VLeI0s/qkzy2r5jlR0wzf8= +github.com/pion/sdp/v3 v3.0.4/go.mod h1:bNiSknmJE0HYBprTHXKPQ3+JjacTv5uap92ueJZKsRk= +github.com/pion/srtp/v2 v2.0.2 h1:664iGzVmaY7KYS5M0gleY0DscRo9ReDfTxQrq4UgGoU= +github.com/pion/srtp/v2 v2.0.2/go.mod h1:VEyLv4CuxrwGY8cxM+Ng3bmVy8ckz/1t6A0q/msKOw0= +github.com/pion/stun v0.3.5 h1:uLUCBCkQby4S1cf6CGuR9QrVOKcvUwFeemaC865QHDg= +github.com/pion/stun v0.3.5/go.mod h1:gDMim+47EeEtfWogA37n6qXZS88L5V6LqFcf+DZA2UA= +github.com/pion/transport v0.8.10/go.mod h1:tBmha/UCjpum5hqTWhfAEs3CO4/tHSg0MYRhSzR+CZ8= +github.com/pion/transport v0.10.0/go.mod h1:BnHnUipd0rZQyTVB2SBGojFHT9CBt5C5TcsJSQGkvSE= +github.com/pion/transport v0.10.1/go.mod h1:PBis1stIILMiis0PewDw91WJeLJkyIMcEk+DwKOzf4A= +github.com/pion/transport v0.12.1/go.mod h1:N3+vZQD9HlDP5GWkZ85LohxNsDcNgofQmyL6ojX5d8Q= +github.com/pion/transport v0.12.2 h1:WYEjhloRHt1R86LhUKjC5y+P52Y11/QqEUalvtzVoys= +github.com/pion/transport v0.12.2/go.mod h1:N3+vZQD9HlDP5GWkZ85LohxNsDcNgofQmyL6ojX5d8Q= +github.com/pion/turn/v2 v2.0.5 h1:iwMHqDfPEDEOFzwWKT56eFmh6DYC6o/+xnLAEzgISbA= +github.com/pion/turn/v2 v2.0.5/go.mod h1:APg43CFyt/14Uy7heYUOGWdkem/Wu4PhCO/bjyrTqMw= +github.com/pion/udp v0.1.0 h1:uGxQsNyrqG3GLINv36Ff60covYmfrLoxzwnCsIYspXI= +github.com/pion/udp v0.1.0/go.mod h1:BPELIjbwE9PRbd/zxI/KYBnbo7B6+oA6YuEaNE8lths= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/sclevine/agouti v3.0.0+incompatible h1:8IBJS6PWz3uTlMP3YBIR5f+KAldcGuOeFkFbUWfBgK4= +github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897 h1:pLI5jrR7OSLijeIDcmRxNmw2api+jEfxLoykJVice/E= +golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad h1:DN0cp81fZ3njFcrLCytUHRSUkqBjfTo4Tx9RJTWs0EY= +golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20191126235420-ef20fe5d7933/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201201195509-5d6afe98e0b7 h1:3uJsdck53FDIpWwLeAXlia9p4C8j0BO2xZrqzKpL0D8= +golang.org/x/net v0.0.0-20201201195509-5d6afe98e0b7/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210119194325-5f4716e94777 h1:003p0dJM77cxMSyCPFphvZf/Y5/NXf5fzg6ufd1/Oew= +golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f h1:+Nyd8tzPX9R7BWHguqsrbFdRx3WQ/1ib8I44HXV5yTA= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/vendor/github.com/pion/webrtc/v3/ice_go.go b/vendor/github.com/pion/webrtc/v3/ice_go.go new file mode 100644 index 0000000..897ed0d --- /dev/null +++ b/vendor/github.com/pion/webrtc/v3/ice_go.go @@ -0,0 +1,10 @@ +// +build !js + +package webrtc + +// NewICETransport creates a new NewICETransport. +// This constructor is part of the ORTC API. It is not +// meant to be used together with the basic WebRTC API. +func (api *API) NewICETransport(gatherer *ICEGatherer) *ICETransport { + return NewICETransport(gatherer, api.settingEngine.LoggerFactory) +} diff --git a/vendor/github.com/pion/webrtc/v3/icecandidate.go b/vendor/github.com/pion/webrtc/v3/icecandidate.go new file mode 100644 index 0000000..1b0fbfc --- /dev/null +++ b/vendor/github.com/pion/webrtc/v3/icecandidate.go @@ -0,0 +1,169 @@ +package webrtc + +import ( + "fmt" + + "github.com/pion/ice/v2" +) + +// ICECandidate represents a ice candidate +type ICECandidate struct { + statsID string + Foundation string `json:"foundation"` + Priority uint32 `json:"priority"` + Address string `json:"address"` + Protocol ICEProtocol `json:"protocol"` + Port uint16 `json:"port"` + Typ ICECandidateType `json:"type"` + Component uint16 `json:"component"` + RelatedAddress string `json:"relatedAddress"` + RelatedPort uint16 `json:"relatedPort"` + TCPType string `json:"tcpType"` +} + +// Conversion for package ice + +func newICECandidatesFromICE(iceCandidates []ice.Candidate) ([]ICECandidate, error) { + candidates := []ICECandidate{} + + for _, i := range iceCandidates { + c, err := newICECandidateFromICE(i) + if err != nil { + return nil, err + } + candidates = append(candidates, c) + } + + return candidates, nil +} + +func newICECandidateFromICE(i ice.Candidate) (ICECandidate, error) { + typ, err := convertTypeFromICE(i.Type()) + if err != nil { + return ICECandidate{}, err + } + protocol, err := NewICEProtocol(i.NetworkType().NetworkShort()) + if err != nil { + return ICECandidate{}, err + } + + c := ICECandidate{ + statsID: i.ID(), + Foundation: i.Foundation(), + Priority: i.Priority(), + Address: i.Address(), + Protocol: protocol, + Port: uint16(i.Port()), + Component: i.Component(), + Typ: typ, + TCPType: i.TCPType().String(), + } + + if i.RelatedAddress() != nil { + c.RelatedAddress = i.RelatedAddress().Address + c.RelatedPort = uint16(i.RelatedAddress().Port) + } + + return c, nil +} + +func (c ICECandidate) toICE() (ice.Candidate, error) { + candidateID := c.statsID + switch c.Typ { + case ICECandidateTypeHost: + config := ice.CandidateHostConfig{ + CandidateID: candidateID, + Network: c.Protocol.String(), + Address: c.Address, + Port: int(c.Port), + Component: c.Component, + TCPType: ice.NewTCPType(c.TCPType), + Foundation: c.Foundation, + Priority: c.Priority, + } + return ice.NewCandidateHost(&config) + case ICECandidateTypeSrflx: + config := ice.CandidateServerReflexiveConfig{ + CandidateID: candidateID, + Network: c.Protocol.String(), + Address: c.Address, + Port: int(c.Port), + Component: c.Component, + Foundation: c.Foundation, + Priority: c.Priority, + RelAddr: c.RelatedAddress, + RelPort: int(c.RelatedPort), + } + return ice.NewCandidateServerReflexive(&config) + case ICECandidateTypePrflx: + config := ice.CandidatePeerReflexiveConfig{ + CandidateID: candidateID, + Network: c.Protocol.String(), + Address: c.Address, + Port: int(c.Port), + Component: c.Component, + Foundation: c.Foundation, + Priority: c.Priority, + RelAddr: c.RelatedAddress, + RelPort: int(c.RelatedPort), + } + return ice.NewCandidatePeerReflexive(&config) + case ICECandidateTypeRelay: + config := ice.CandidateRelayConfig{ + CandidateID: candidateID, + Network: c.Protocol.String(), + Address: c.Address, + Port: int(c.Port), + Component: c.Component, + Foundation: c.Foundation, + Priority: c.Priority, + RelAddr: c.RelatedAddress, + RelPort: int(c.RelatedPort), + } + return ice.NewCandidateRelay(&config) + default: + return nil, fmt.Errorf("%w: %s", errICECandidateTypeUnknown, c.Typ) + } +} + +func convertTypeFromICE(t ice.CandidateType) (ICECandidateType, error) { + switch t { + case ice.CandidateTypeHost: + return ICECandidateTypeHost, nil + case ice.CandidateTypeServerReflexive: + return ICECandidateTypeSrflx, nil + case ice.CandidateTypePeerReflexive: + return ICECandidateTypePrflx, nil + case ice.CandidateTypeRelay: + return ICECandidateTypeRelay, nil + default: + return ICECandidateType(t), fmt.Errorf("%w: %s", errICECandidateTypeUnknown, t) + } +} + +func (c ICECandidate) String() string { + ic, err := c.toICE() + if err != nil { + return fmt.Sprintf("%#v failed to convert to ICE: %s", c, err) + } + return ic.String() +} + +// ToJSON returns an ICECandidateInit +// as indicated by the spec https://w3c.github.io/webrtc-pc/#dom-rtcicecandidate-tojson +func (c ICECandidate) ToJSON() ICECandidateInit { + zeroVal := uint16(0) + emptyStr := "" + candidateStr := "" + + candidate, err := c.toICE() + if err == nil { + candidateStr = candidate.Marshal() + } + + return ICECandidateInit{ + Candidate: fmt.Sprintf("candidate:%s", candidateStr), + SDPMid: &emptyStr, + SDPMLineIndex: &zeroVal, + } +} diff --git a/vendor/github.com/pion/webrtc/v3/icecandidateinit.go b/vendor/github.com/pion/webrtc/v3/icecandidateinit.go new file mode 100644 index 0000000..31ebb4b --- /dev/null +++ b/vendor/github.com/pion/webrtc/v3/icecandidateinit.go @@ -0,0 +1,9 @@ +package webrtc + +// ICECandidateInit is used to serialize ice candidates +type ICECandidateInit struct { + Candidate string `json:"candidate"` + SDPMid *string `json:"sdpMid"` + SDPMLineIndex *uint16 `json:"sdpMLineIndex"` + UsernameFragment *string `json:"usernameFragment"` +} diff --git a/vendor/github.com/pion/webrtc/v3/icecandidatepair.go b/vendor/github.com/pion/webrtc/v3/icecandidatepair.go new file mode 100644 index 0000000..7350fbe --- /dev/null +++ b/vendor/github.com/pion/webrtc/v3/icecandidatepair.go @@ -0,0 +1,29 @@ +package webrtc + +import "fmt" + +// ICECandidatePair represents an ICE Candidate pair +type ICECandidatePair struct { + statsID string + Local *ICECandidate + Remote *ICECandidate +} + +func newICECandidatePairStatsID(localID, remoteID string) string { + return fmt.Sprintf("%s-%s", localID, remoteID) +} + +func (p *ICECandidatePair) String() string { + return fmt.Sprintf("(local) %s <-> (remote) %s", p.Local, p.Remote) +} + +// NewICECandidatePair returns an initialized *ICECandidatePair +// for the given pair of ICECandidate instances +func NewICECandidatePair(local, remote *ICECandidate) *ICECandidatePair { + statsID := newICECandidatePairStatsID(local.statsID, remote.statsID) + return &ICECandidatePair{ + statsID: statsID, + Local: local, + Remote: remote, + } +} diff --git a/vendor/github.com/pion/webrtc/v3/icecandidatetype.go b/vendor/github.com/pion/webrtc/v3/icecandidatetype.go new file mode 100644 index 0000000..e57bf14 --- /dev/null +++ b/vendor/github.com/pion/webrtc/v3/icecandidatetype.go @@ -0,0 +1,94 @@ +package webrtc + +import ( + "fmt" + + "github.com/pion/ice/v2" +) + +// ICECandidateType represents the type of the ICE candidate used. +type ICECandidateType int + +const ( + // ICECandidateTypeHost indicates that the candidate is of Host type as + // described in https://tools.ietf.org/html/rfc8445#section-5.1.1.1. A + // candidate obtained by binding to a specific port from an IP address on + // the host. This includes IP addresses on physical interfaces and logical + // ones, such as ones obtained through VPNs. + ICECandidateTypeHost ICECandidateType = iota + 1 + + // ICECandidateTypeSrflx indicates the the candidate is of Server + // Reflexive type as described + // https://tools.ietf.org/html/rfc8445#section-5.1.1.2. A candidate type + // whose IP address and port are a binding allocated by a NAT for an ICE + // agent after it sends a packet through the NAT to a server, such as a + // STUN server. + ICECandidateTypeSrflx + + // ICECandidateTypePrflx indicates that the candidate is of Peer + // Reflexive type. A candidate type whose IP address and port are a binding + // allocated by a NAT for an ICE agent after it sends a packet through the + // NAT to its peer. + ICECandidateTypePrflx + + // ICECandidateTypeRelay indicates the the candidate is of Relay type as + // described in https://tools.ietf.org/html/rfc8445#section-5.1.1.2. A + // candidate type obtained from a relay server, such as a TURN server. + ICECandidateTypeRelay +) + +// This is done this way because of a linter. +const ( + iceCandidateTypeHostStr = "host" + iceCandidateTypeSrflxStr = "srflx" + iceCandidateTypePrflxStr = "prflx" + iceCandidateTypeRelayStr = "relay" +) + +// NewICECandidateType takes a string and converts it into ICECandidateType +func NewICECandidateType(raw string) (ICECandidateType, error) { + switch raw { + case iceCandidateTypeHostStr: + return ICECandidateTypeHost, nil + case iceCandidateTypeSrflxStr: + return ICECandidateTypeSrflx, nil + case iceCandidateTypePrflxStr: + return ICECandidateTypePrflx, nil + case iceCandidateTypeRelayStr: + return ICECandidateTypeRelay, nil + default: + return ICECandidateType(Unknown), fmt.Errorf("%w: %s", errICECandidateTypeUnknown, raw) + } +} + +func (t ICECandidateType) String() string { + switch t { + case ICECandidateTypeHost: + return iceCandidateTypeHostStr + case ICECandidateTypeSrflx: + return iceCandidateTypeSrflxStr + case ICECandidateTypePrflx: + return iceCandidateTypePrflxStr + case ICECandidateTypeRelay: + return iceCandidateTypeRelayStr + default: + return ErrUnknownType.Error() + } +} + +func getCandidateType(candidateType ice.CandidateType) (ICECandidateType, error) { + switch candidateType { + case ice.CandidateTypeHost: + return ICECandidateTypeHost, nil + case ice.CandidateTypeServerReflexive: + return ICECandidateTypeSrflx, nil + case ice.CandidateTypePeerReflexive: + return ICECandidateTypePrflx, nil + case ice.CandidateTypeRelay: + return ICECandidateTypeRelay, nil + default: + // NOTE: this should never happen[tm] + err := fmt.Errorf("%w: %s", errICEInvalidConvertCandidateType, candidateType.String()) + return ICECandidateType(Unknown), err + } +} diff --git a/vendor/github.com/pion/webrtc/v3/icecomponent.go b/vendor/github.com/pion/webrtc/v3/icecomponent.go new file mode 100644 index 0000000..1f03ec5 --- /dev/null +++ b/vendor/github.com/pion/webrtc/v3/icecomponent.go @@ -0,0 +1,47 @@ +package webrtc + +// ICEComponent describes if the ice transport is used for RTP +// (or RTCP multiplexing). +type ICEComponent int + +const ( + // ICEComponentRTP indicates that the ICE Transport is used for RTP (or + // RTCP multiplexing), as defined in + // https://tools.ietf.org/html/rfc5245#section-4.1.1.1. Protocols + // multiplexed with RTP (e.g. data channel) share its component ID. This + // represents the component-id value 1 when encoded in candidate-attribute. + ICEComponentRTP ICEComponent = iota + 1 + + // ICEComponentRTCP indicates that the ICE Transport is used for RTCP as + // defined by https://tools.ietf.org/html/rfc5245#section-4.1.1.1. This + // represents the component-id value 2 when encoded in candidate-attribute. + ICEComponentRTCP +) + +// This is done this way because of a linter. +const ( + iceComponentRTPStr = "rtp" + iceComponentRTCPStr = "rtcp" +) + +func newICEComponent(raw string) ICEComponent { + switch raw { + case iceComponentRTPStr: + return ICEComponentRTP + case iceComponentRTCPStr: + return ICEComponentRTCP + default: + return ICEComponent(Unknown) + } +} + +func (t ICEComponent) String() string { + switch t { + case ICEComponentRTP: + return iceComponentRTPStr + case ICEComponentRTCP: + return iceComponentRTCPStr + default: + return ErrUnknownType.Error() + } +} diff --git a/vendor/github.com/pion/webrtc/v3/iceconnectionstate.go b/vendor/github.com/pion/webrtc/v3/iceconnectionstate.go new file mode 100644 index 0000000..22fd269 --- /dev/null +++ b/vendor/github.com/pion/webrtc/v3/iceconnectionstate.go @@ -0,0 +1,94 @@ +package webrtc + +// ICEConnectionState indicates signaling state of the ICE Connection. +type ICEConnectionState int + +const ( + // ICEConnectionStateNew indicates that any of the ICETransports are + // in the "new" state and none of them are in the "checking", "disconnected" + // or "failed" state, or all ICETransports are in the "closed" state, or + // there are no transports. + ICEConnectionStateNew ICEConnectionState = iota + 1 + + // ICEConnectionStateChecking indicates that any of the ICETransports + // are in the "checking" state and none of them are in the "disconnected" + // or "failed" state. + ICEConnectionStateChecking + + // ICEConnectionStateConnected indicates that all ICETransports are + // in the "connected", "completed" or "closed" state and at least one of + // them is in the "connected" state. + ICEConnectionStateConnected + + // ICEConnectionStateCompleted indicates that all ICETransports are + // in the "completed" or "closed" state and at least one of them is in the + // "completed" state. + ICEConnectionStateCompleted + + // ICEConnectionStateDisconnected indicates that any of the + // ICETransports are in the "disconnected" state and none of them are + // in the "failed" state. + ICEConnectionStateDisconnected + + // ICEConnectionStateFailed indicates that any of the ICETransports + // are in the "failed" state. + ICEConnectionStateFailed + + // ICEConnectionStateClosed indicates that the PeerConnection's + // isClosed is true. + ICEConnectionStateClosed +) + +// This is done this way because of a linter. +const ( + iceConnectionStateNewStr = "new" + iceConnectionStateCheckingStr = "checking" + iceConnectionStateConnectedStr = "connected" + iceConnectionStateCompletedStr = "completed" + iceConnectionStateDisconnectedStr = "disconnected" + iceConnectionStateFailedStr = "failed" + iceConnectionStateClosedStr = "closed" +) + +// NewICEConnectionState takes a string and converts it to ICEConnectionState +func NewICEConnectionState(raw string) ICEConnectionState { + switch raw { + case iceConnectionStateNewStr: + return ICEConnectionStateNew + case iceConnectionStateCheckingStr: + return ICEConnectionStateChecking + case iceConnectionStateConnectedStr: + return ICEConnectionStateConnected + case iceConnectionStateCompletedStr: + return ICEConnectionStateCompleted + case iceConnectionStateDisconnectedStr: + return ICEConnectionStateDisconnected + case iceConnectionStateFailedStr: + return ICEConnectionStateFailed + case iceConnectionStateClosedStr: + return ICEConnectionStateClosed + default: + return ICEConnectionState(Unknown) + } +} + +func (c ICEConnectionState) String() string { + switch c { + case ICEConnectionStateNew: + return iceConnectionStateNewStr + case ICEConnectionStateChecking: + return iceConnectionStateCheckingStr + case ICEConnectionStateConnected: + return iceConnectionStateConnectedStr + case ICEConnectionStateCompleted: + return iceConnectionStateCompletedStr + case ICEConnectionStateDisconnected: + return iceConnectionStateDisconnectedStr + case ICEConnectionStateFailed: + return iceConnectionStateFailedStr + case ICEConnectionStateClosed: + return iceConnectionStateClosedStr + default: + return ErrUnknownType.Error() + } +} diff --git a/vendor/github.com/pion/webrtc/v3/icecredentialtype.go b/vendor/github.com/pion/webrtc/v3/icecredentialtype.go new file mode 100644 index 0000000..3967c16 --- /dev/null +++ b/vendor/github.com/pion/webrtc/v3/icecredentialtype.go @@ -0,0 +1,43 @@ +package webrtc + +// ICECredentialType indicates the type of credentials used to connect to +// an ICE server. +type ICECredentialType int + +const ( + // ICECredentialTypePassword describes username and password based + // credentials as described in https://tools.ietf.org/html/rfc5389. + ICECredentialTypePassword ICECredentialType = iota + + // ICECredentialTypeOauth describes token based credential as described + // in https://tools.ietf.org/html/rfc7635. + ICECredentialTypeOauth +) + +// This is done this way because of a linter. +const ( + iceCredentialTypePasswordStr = "password" + iceCredentialTypeOauthStr = "oauth" +) + +func newICECredentialType(raw string) ICECredentialType { + switch raw { + case iceCredentialTypePasswordStr: + return ICECredentialTypePassword + case iceCredentialTypeOauthStr: + return ICECredentialTypeOauth + default: + return ICECredentialType(Unknown) + } +} + +func (t ICECredentialType) String() string { + switch t { + case ICECredentialTypePassword: + return iceCredentialTypePasswordStr + case ICECredentialTypeOauth: + return iceCredentialTypeOauthStr + default: + return ErrUnknownType.Error() + } +} diff --git a/vendor/github.com/pion/webrtc/v3/icegatherer.go b/vendor/github.com/pion/webrtc/v3/icegatherer.go new file mode 100644 index 0000000..31c64ee --- /dev/null +++ b/vendor/github.com/pion/webrtc/v3/icegatherer.go @@ -0,0 +1,367 @@ +// +build !js + +package webrtc + +import ( + "sync" + "sync/atomic" + + "github.com/pion/ice/v2" + "github.com/pion/logging" +) + +// ICEGatherer gathers local host, server reflexive and relay +// candidates, as well as enabling the retrieval of local Interactive +// Connectivity Establishment (ICE) parameters which can be +// exchanged in signaling. +type ICEGatherer struct { + lock sync.RWMutex + log logging.LeveledLogger + state ICEGathererState + + validatedServers []*ice.URL + gatherPolicy ICETransportPolicy + + agent *ice.Agent + + onLocalCandidateHandler atomic.Value // func(candidate *ICECandidate) + onStateChangeHandler atomic.Value // func(state ICEGathererState) + + // Used for GatheringCompletePromise + onGatheringCompleteHandler atomic.Value // func() + + api *API +} + +// NewICEGatherer creates a new NewICEGatherer. +// This constructor is part of the ORTC API. It is not +// meant to be used together with the basic WebRTC API. +func (api *API) NewICEGatherer(opts ICEGatherOptions) (*ICEGatherer, error) { + var validatedServers []*ice.URL + if len(opts.ICEServers) > 0 { + for _, server := range opts.ICEServers { + url, err := server.urls() + if err != nil { + return nil, err + } + validatedServers = append(validatedServers, url...) + } + } + + return &ICEGatherer{ + state: ICEGathererStateNew, + gatherPolicy: opts.ICEGatherPolicy, + validatedServers: validatedServers, + api: api, + log: api.settingEngine.LoggerFactory.NewLogger("ice"), + }, nil +} + +func (g *ICEGatherer) createAgent() error { + g.lock.Lock() + defer g.lock.Unlock() + + if g.agent != nil || g.State() != ICEGathererStateNew { + return nil + } + + candidateTypes := []ice.CandidateType{} + if g.api.settingEngine.candidates.ICELite { + candidateTypes = append(candidateTypes, ice.CandidateTypeHost) + } else if g.gatherPolicy == ICETransportPolicyRelay { + candidateTypes = append(candidateTypes, ice.CandidateTypeRelay) + } + + var nat1To1CandiTyp ice.CandidateType + switch g.api.settingEngine.candidates.NAT1To1IPCandidateType { + case ICECandidateTypeHost: + nat1To1CandiTyp = ice.CandidateTypeHost + case ICECandidateTypeSrflx: + nat1To1CandiTyp = ice.CandidateTypeServerReflexive + default: + nat1To1CandiTyp = ice.CandidateTypeUnspecified + } + + mDNSMode := g.api.settingEngine.candidates.MulticastDNSMode + if mDNSMode != ice.MulticastDNSModeDisabled && mDNSMode != ice.MulticastDNSModeQueryAndGather { + // If enum is in state we don't recognized default to MulticastDNSModeQueryOnly + mDNSMode = ice.MulticastDNSModeQueryOnly + } + + config := &ice.AgentConfig{ + Lite: g.api.settingEngine.candidates.ICELite, + Urls: g.validatedServers, + PortMin: g.api.settingEngine.ephemeralUDP.PortMin, + PortMax: g.api.settingEngine.ephemeralUDP.PortMax, + DisconnectedTimeout: g.api.settingEngine.timeout.ICEDisconnectedTimeout, + FailedTimeout: g.api.settingEngine.timeout.ICEFailedTimeout, + KeepaliveInterval: g.api.settingEngine.timeout.ICEKeepaliveInterval, + LoggerFactory: g.api.settingEngine.LoggerFactory, + CandidateTypes: candidateTypes, + HostAcceptanceMinWait: g.api.settingEngine.timeout.ICEHostAcceptanceMinWait, + SrflxAcceptanceMinWait: g.api.settingEngine.timeout.ICESrflxAcceptanceMinWait, + PrflxAcceptanceMinWait: g.api.settingEngine.timeout.ICEPrflxAcceptanceMinWait, + RelayAcceptanceMinWait: g.api.settingEngine.timeout.ICERelayAcceptanceMinWait, + InterfaceFilter: g.api.settingEngine.candidates.InterfaceFilter, + NAT1To1IPs: g.api.settingEngine.candidates.NAT1To1IPs, + NAT1To1IPCandidateType: nat1To1CandiTyp, + Net: g.api.settingEngine.vnet, + MulticastDNSMode: mDNSMode, + MulticastDNSHostName: g.api.settingEngine.candidates.MulticastDNSHostName, + LocalUfrag: g.api.settingEngine.candidates.UsernameFragment, + LocalPwd: g.api.settingEngine.candidates.Password, + TCPMux: g.api.settingEngine.iceTCPMux, + ProxyDialer: g.api.settingEngine.iceProxyDialer, + } + + requestedNetworkTypes := g.api.settingEngine.candidates.ICENetworkTypes + if len(requestedNetworkTypes) == 0 { + requestedNetworkTypes = supportedNetworkTypes() + } + + for _, typ := range requestedNetworkTypes { + config.NetworkTypes = append(config.NetworkTypes, ice.NetworkType(typ)) + } + + agent, err := ice.NewAgent(config) + if err != nil { + return err + } + + g.agent = agent + return nil +} + +// Gather ICE candidates. +func (g *ICEGatherer) Gather() error { + if err := g.createAgent(); err != nil { + return err + } + + g.lock.Lock() + agent := g.agent + g.lock.Unlock() + + g.setState(ICEGathererStateGathering) + if err := agent.OnCandidate(func(candidate ice.Candidate) { + onLocalCandidateHandler := func(*ICECandidate) {} + if handler, ok := g.onLocalCandidateHandler.Load().(func(candidate *ICECandidate)); ok && handler != nil { + onLocalCandidateHandler = handler + } + + onGatheringCompleteHandler := func() {} + if handler, ok := g.onGatheringCompleteHandler.Load().(func()); ok && handler != nil { + onGatheringCompleteHandler = handler + } + + if candidate != nil { + c, err := newICECandidateFromICE(candidate) + if err != nil { + g.log.Warnf("Failed to convert ice.Candidate: %s", err) + return + } + onLocalCandidateHandler(&c) + } else { + g.setState(ICEGathererStateComplete) + + onGatheringCompleteHandler() + onLocalCandidateHandler(nil) + } + }); err != nil { + return err + } + return agent.GatherCandidates() +} + +// Close prunes all local candidates, and closes the ports. +func (g *ICEGatherer) Close() error { + g.lock.Lock() + defer g.lock.Unlock() + + if g.agent == nil { + return nil + } else if err := g.agent.Close(); err != nil { + return err + } + + g.agent = nil + g.setState(ICEGathererStateClosed) + + return nil +} + +// GetLocalParameters returns the ICE parameters of the ICEGatherer. +func (g *ICEGatherer) GetLocalParameters() (ICEParameters, error) { + if err := g.createAgent(); err != nil { + return ICEParameters{}, err + } + + frag, pwd, err := g.agent.GetLocalUserCredentials() + if err != nil { + return ICEParameters{}, err + } + + return ICEParameters{ + UsernameFragment: frag, + Password: pwd, + ICELite: false, + }, nil +} + +// GetLocalCandidates returns the sequence of valid local candidates associated with the ICEGatherer. +func (g *ICEGatherer) GetLocalCandidates() ([]ICECandidate, error) { + if err := g.createAgent(); err != nil { + return nil, err + } + iceCandidates, err := g.agent.GetLocalCandidates() + if err != nil { + return nil, err + } + + return newICECandidatesFromICE(iceCandidates) +} + +// OnLocalCandidate sets an event handler which fires when a new local ICE candidate is available +// Take note that the handler is gonna be called with a nil pointer when gathering is finished. +func (g *ICEGatherer) OnLocalCandidate(f func(*ICECandidate)) { + g.onLocalCandidateHandler.Store(f) +} + +// OnStateChange fires any time the ICEGatherer changes +func (g *ICEGatherer) OnStateChange(f func(ICEGathererState)) { + g.onStateChangeHandler.Store(f) +} + +// State indicates the current state of the ICE gatherer. +func (g *ICEGatherer) State() ICEGathererState { + return atomicLoadICEGathererState(&g.state) +} + +func (g *ICEGatherer) setState(s ICEGathererState) { + atomicStoreICEGathererState(&g.state, s) + + if handler, ok := g.onStateChangeHandler.Load().(func(state ICEGathererState)); ok && handler != nil { + handler(s) + } +} + +func (g *ICEGatherer) getAgent() *ice.Agent { + g.lock.RLock() + defer g.lock.RUnlock() + return g.agent +} + +func (g *ICEGatherer) collectStats(collector *statsReportCollector) { + agent := g.getAgent() + if agent == nil { + return + } + + collector.Collecting() + go func(collector *statsReportCollector, agent *ice.Agent) { + for _, candidatePairStats := range agent.GetCandidatePairsStats() { + collector.Collecting() + + state, err := toStatsICECandidatePairState(candidatePairStats.State) + if err != nil { + g.log.Error(err.Error()) + } + + pairID := newICECandidatePairStatsID(candidatePairStats.LocalCandidateID, + candidatePairStats.RemoteCandidateID) + + stats := ICECandidatePairStats{ + Timestamp: statsTimestampFrom(candidatePairStats.Timestamp), + Type: StatsTypeCandidatePair, + ID: pairID, + // TransportID: + LocalCandidateID: candidatePairStats.LocalCandidateID, + RemoteCandidateID: candidatePairStats.RemoteCandidateID, + State: state, + Nominated: candidatePairStats.Nominated, + PacketsSent: candidatePairStats.PacketsSent, + PacketsReceived: candidatePairStats.PacketsReceived, + BytesSent: candidatePairStats.BytesSent, + BytesReceived: candidatePairStats.BytesReceived, + LastPacketSentTimestamp: statsTimestampFrom(candidatePairStats.LastPacketSentTimestamp), + LastPacketReceivedTimestamp: statsTimestampFrom(candidatePairStats.LastPacketReceivedTimestamp), + FirstRequestTimestamp: statsTimestampFrom(candidatePairStats.FirstRequestTimestamp), + LastRequestTimestamp: statsTimestampFrom(candidatePairStats.LastRequestTimestamp), + LastResponseTimestamp: statsTimestampFrom(candidatePairStats.LastResponseTimestamp), + TotalRoundTripTime: candidatePairStats.TotalRoundTripTime, + CurrentRoundTripTime: candidatePairStats.CurrentRoundTripTime, + AvailableOutgoingBitrate: candidatePairStats.AvailableOutgoingBitrate, + AvailableIncomingBitrate: candidatePairStats.AvailableIncomingBitrate, + CircuitBreakerTriggerCount: candidatePairStats.CircuitBreakerTriggerCount, + RequestsReceived: candidatePairStats.RequestsReceived, + RequestsSent: candidatePairStats.RequestsSent, + ResponsesReceived: candidatePairStats.ResponsesReceived, + ResponsesSent: candidatePairStats.ResponsesSent, + RetransmissionsReceived: candidatePairStats.RetransmissionsReceived, + RetransmissionsSent: candidatePairStats.RetransmissionsSent, + ConsentRequestsSent: candidatePairStats.ConsentRequestsSent, + ConsentExpiredTimestamp: statsTimestampFrom(candidatePairStats.ConsentExpiredTimestamp), + } + collector.Collect(stats.ID, stats) + } + + for _, candidateStats := range agent.GetLocalCandidatesStats() { + collector.Collecting() + + networkType, err := getNetworkType(candidateStats.NetworkType) + if err != nil { + g.log.Error(err.Error()) + } + + candidateType, err := getCandidateType(candidateStats.CandidateType) + if err != nil { + g.log.Error(err.Error()) + } + + stats := ICECandidateStats{ + Timestamp: statsTimestampFrom(candidateStats.Timestamp), + ID: candidateStats.ID, + Type: StatsTypeLocalCandidate, + NetworkType: networkType, + IP: candidateStats.IP, + Port: int32(candidateStats.Port), + Protocol: networkType.Protocol(), + CandidateType: candidateType, + Priority: int32(candidateStats.Priority), + URL: candidateStats.URL, + RelayProtocol: candidateStats.RelayProtocol, + Deleted: candidateStats.Deleted, + } + collector.Collect(stats.ID, stats) + } + + for _, candidateStats := range agent.GetRemoteCandidatesStats() { + collector.Collecting() + networkType, err := getNetworkType(candidateStats.NetworkType) + if err != nil { + g.log.Error(err.Error()) + } + + candidateType, err := getCandidateType(candidateStats.CandidateType) + if err != nil { + g.log.Error(err.Error()) + } + + stats := ICECandidateStats{ + Timestamp: statsTimestampFrom(candidateStats.Timestamp), + ID: candidateStats.ID, + Type: StatsTypeRemoteCandidate, + NetworkType: networkType, + IP: candidateStats.IP, + Port: int32(candidateStats.Port), + Protocol: networkType.Protocol(), + CandidateType: candidateType, + Priority: int32(candidateStats.Priority), + URL: candidateStats.URL, + RelayProtocol: candidateStats.RelayProtocol, + } + collector.Collect(stats.ID, stats) + } + collector.Done() + }(collector, agent) +} diff --git a/vendor/github.com/pion/webrtc/v3/icegathererstate.go b/vendor/github.com/pion/webrtc/v3/icegathererstate.go new file mode 100644 index 0000000..80dc77a --- /dev/null +++ b/vendor/github.com/pion/webrtc/v3/icegathererstate.go @@ -0,0 +1,48 @@ +package webrtc + +import ( + "sync/atomic" +) + +// ICEGathererState represents the current state of the ICE gatherer. +type ICEGathererState uint32 + +const ( + // ICEGathererStateNew indicates object has been created but + // gather() has not been called. + ICEGathererStateNew ICEGathererState = iota + 1 + + // ICEGathererStateGathering indicates gather() has been called, + // and the ICEGatherer is in the process of gathering candidates. + ICEGathererStateGathering + + // ICEGathererStateComplete indicates the ICEGatherer has completed gathering. + ICEGathererStateComplete + + // ICEGathererStateClosed indicates the closed state can only be entered + // when the ICEGatherer has been closed intentionally by calling close(). + ICEGathererStateClosed +) + +func (s ICEGathererState) String() string { + switch s { + case ICEGathererStateNew: + return "new" + case ICEGathererStateGathering: + return "gathering" + case ICEGathererStateComplete: + return "complete" + case ICEGathererStateClosed: + return "closed" + default: + return unknownStr + } +} + +func atomicStoreICEGathererState(state *ICEGathererState, newState ICEGathererState) { + atomic.StoreUint32((*uint32)(state), uint32(newState)) +} + +func atomicLoadICEGathererState(state *ICEGathererState) ICEGathererState { + return ICEGathererState(atomic.LoadUint32((*uint32)(state))) +} diff --git a/vendor/github.com/pion/webrtc/v3/icegatheringstate.go b/vendor/github.com/pion/webrtc/v3/icegatheringstate.go new file mode 100644 index 0000000..21361f9 --- /dev/null +++ b/vendor/github.com/pion/webrtc/v3/icegatheringstate.go @@ -0,0 +1,53 @@ +package webrtc + +// ICEGatheringState describes the state of the candidate gathering process. +type ICEGatheringState int + +const ( + // ICEGatheringStateNew indicates that any of the ICETransports are + // in the "new" gathering state and none of the transports are in the + // "gathering" state, or there are no transports. + ICEGatheringStateNew ICEGatheringState = iota + 1 + + // ICEGatheringStateGathering indicates that any of the ICETransports + // are in the "gathering" state. + ICEGatheringStateGathering + + // ICEGatheringStateComplete indicates that at least one ICETransport + // exists, and all ICETransports are in the "completed" gathering state. + ICEGatheringStateComplete +) + +// This is done this way because of a linter. +const ( + iceGatheringStateNewStr = "new" + iceGatheringStateGatheringStr = "gathering" + iceGatheringStateCompleteStr = "complete" +) + +// NewICEGatheringState takes a string and converts it to ICEGatheringState +func NewICEGatheringState(raw string) ICEGatheringState { + switch raw { + case iceGatheringStateNewStr: + return ICEGatheringStateNew + case iceGatheringStateGatheringStr: + return ICEGatheringStateGathering + case iceGatheringStateCompleteStr: + return ICEGatheringStateComplete + default: + return ICEGatheringState(Unknown) + } +} + +func (t ICEGatheringState) String() string { + switch t { + case ICEGatheringStateNew: + return iceGatheringStateNewStr + case ICEGatheringStateGathering: + return iceGatheringStateGatheringStr + case ICEGatheringStateComplete: + return iceGatheringStateCompleteStr + default: + return ErrUnknownType.Error() + } +} diff --git a/vendor/github.com/pion/webrtc/v3/icegatheroptions.go b/vendor/github.com/pion/webrtc/v3/icegatheroptions.go new file mode 100644 index 0000000..88421c7 --- /dev/null +++ b/vendor/github.com/pion/webrtc/v3/icegatheroptions.go @@ -0,0 +1,7 @@ +package webrtc + +// ICEGatherOptions provides options relating to the gathering of ICE candidates. +type ICEGatherOptions struct { + ICEServers []ICEServer + ICEGatherPolicy ICETransportPolicy +} diff --git a/vendor/github.com/pion/webrtc/v3/iceparameters.go b/vendor/github.com/pion/webrtc/v3/iceparameters.go new file mode 100644 index 0000000..0c03a88 --- /dev/null +++ b/vendor/github.com/pion/webrtc/v3/iceparameters.go @@ -0,0 +1,9 @@ +package webrtc + +// ICEParameters includes the ICE username fragment +// and password and other ICE-related parameters. +type ICEParameters struct { + UsernameFragment string `json:"usernameFragment"` + Password string `json:"password"` + ICELite bool `json:"iceLite"` +} diff --git a/vendor/github.com/pion/webrtc/v3/iceprotocol.go b/vendor/github.com/pion/webrtc/v3/iceprotocol.go new file mode 100644 index 0000000..f9eb0cf --- /dev/null +++ b/vendor/github.com/pion/webrtc/v3/iceprotocol.go @@ -0,0 +1,47 @@ +package webrtc + +import ( + "fmt" + "strings" +) + +// ICEProtocol indicates the transport protocol type that is used in the +// ice.URL structure. +type ICEProtocol int + +const ( + // ICEProtocolUDP indicates the URL uses a UDP transport. + ICEProtocolUDP ICEProtocol = iota + 1 + + // ICEProtocolTCP indicates the URL uses a TCP transport. + ICEProtocolTCP +) + +// This is done this way because of a linter. +const ( + iceProtocolUDPStr = "udp" + iceProtocolTCPStr = "tcp" +) + +// NewICEProtocol takes a string and converts it to ICEProtocol +func NewICEProtocol(raw string) (ICEProtocol, error) { + switch { + case strings.EqualFold(iceProtocolUDPStr, raw): + return ICEProtocolUDP, nil + case strings.EqualFold(iceProtocolTCPStr, raw): + return ICEProtocolTCP, nil + default: + return ICEProtocol(Unknown), fmt.Errorf("%w: %s", errICEProtocolUnknown, raw) + } +} + +func (t ICEProtocol) String() string { + switch t { + case ICEProtocolUDP: + return iceProtocolUDPStr + case ICEProtocolTCP: + return iceProtocolTCPStr + default: + return ErrUnknownType.Error() + } +} diff --git a/vendor/github.com/pion/webrtc/v3/icerole.go b/vendor/github.com/pion/webrtc/v3/icerole.go new file mode 100644 index 0000000..1118786 --- /dev/null +++ b/vendor/github.com/pion/webrtc/v3/icerole.go @@ -0,0 +1,45 @@ +package webrtc + +// ICERole describes the role ice.Agent is playing in selecting the +// preferred the candidate pair. +type ICERole int + +const ( + // ICERoleControlling indicates that the ICE agent that is responsible + // for selecting the final choice of candidate pairs and signaling them + // through STUN and an updated offer, if needed. In any session, one agent + // is always controlling. The other is the controlled agent. + ICERoleControlling ICERole = iota + 1 + + // ICERoleControlled indicates that an ICE agent that waits for the + // controlling agent to select the final choice of candidate pairs. + ICERoleControlled +) + +// This is done this way because of a linter. +const ( + iceRoleControllingStr = "controlling" + iceRoleControlledStr = "controlled" +) + +func newICERole(raw string) ICERole { + switch raw { + case iceRoleControllingStr: + return ICERoleControlling + case iceRoleControlledStr: + return ICERoleControlled + default: + return ICERole(Unknown) + } +} + +func (t ICERole) String() string { + switch t { + case ICERoleControlling: + return iceRoleControllingStr + case ICERoleControlled: + return iceRoleControlledStr + default: + return ErrUnknownType.Error() + } +} diff --git a/vendor/github.com/pion/webrtc/v3/iceserver.go b/vendor/github.com/pion/webrtc/v3/iceserver.go new file mode 100644 index 0000000..76146ec --- /dev/null +++ b/vendor/github.com/pion/webrtc/v3/iceserver.go @@ -0,0 +1,68 @@ +// +build !js + +package webrtc + +import ( + "github.com/pion/ice/v2" + "github.com/pion/webrtc/v3/pkg/rtcerr" +) + +// ICEServer describes a single STUN and TURN server that can be used by +// the ICEAgent to establish a connection with a peer. +type ICEServer struct { + URLs []string `json:"urls"` + Username string `json:"username,omitempty"` + Credential interface{} `json:"credential,omitempty"` + CredentialType ICECredentialType `json:"credentialType,omitempty"` +} + +func (s ICEServer) parseURL(i int) (*ice.URL, error) { + return ice.ParseURL(s.URLs[i]) +} + +func (s ICEServer) validate() error { + _, err := s.urls() + return err +} + +func (s ICEServer) urls() ([]*ice.URL, error) { + urls := []*ice.URL{} + + for i := range s.URLs { + url, err := s.parseURL(i) + if err != nil { + return nil, &rtcerr.InvalidAccessError{Err: err} + } + + if url.Scheme == ice.SchemeTypeTURN || url.Scheme == ice.SchemeTypeTURNS { + // https://www.w3.org/TR/webrtc/#set-the-configuration (step #11.3.2) + if s.Username == "" || s.Credential == nil { + return nil, &rtcerr.InvalidAccessError{Err: ErrNoTurnCredentials} + } + url.Username = s.Username + + switch s.CredentialType { + case ICECredentialTypePassword: + // https://www.w3.org/TR/webrtc/#set-the-configuration (step #11.3.3) + password, ok := s.Credential.(string) + if !ok { + return nil, &rtcerr.InvalidAccessError{Err: ErrTurnCredentials} + } + url.Password = password + + case ICECredentialTypeOauth: + // https://www.w3.org/TR/webrtc/#set-the-configuration (step #11.3.4) + if _, ok := s.Credential.(OAuthCredential); !ok { + return nil, &rtcerr.InvalidAccessError{Err: ErrTurnCredentials} + } + + default: + return nil, &rtcerr.InvalidAccessError{Err: ErrTurnCredentials} + } + } + + urls = append(urls, url) + } + + return urls, nil +} diff --git a/vendor/github.com/pion/webrtc/v3/iceserver_js.go b/vendor/github.com/pion/webrtc/v3/iceserver_js.go new file mode 100644 index 0000000..07b2dcb --- /dev/null +++ b/vendor/github.com/pion/webrtc/v3/iceserver_js.go @@ -0,0 +1,42 @@ +// +build js,wasm + +package webrtc + +import ( + "errors" + + "github.com/pion/ice/v2" +) + +// ICEServer describes a single STUN and TURN server that can be used by +// the ICEAgent to establish a connection with a peer. +type ICEServer struct { + URLs []string + Username string + // Note: TURN is not supported in the WASM bindings yet + Credential interface{} + CredentialType ICECredentialType +} + +func (s ICEServer) parseURL(i int) (*ice.URL, error) { + return ice.ParseURL(s.URLs[i]) +} + +func (s ICEServer) validate() ([]*ice.URL, error) { + urls := []*ice.URL{} + + for i := range s.URLs { + url, err := s.parseURL(i) + if err != nil { + return nil, err + } + + if url.Scheme == ice.SchemeTypeTURN || url.Scheme == ice.SchemeTypeTURNS { + return nil, errors.New("TURN is not currently supported in the JavaScript/Wasm bindings") + } + + urls = append(urls, url) + } + + return urls, nil +} diff --git a/vendor/github.com/pion/webrtc/v3/icetcp.go b/vendor/github.com/pion/webrtc/v3/icetcp.go new file mode 100644 index 0000000..a5f1d09 --- /dev/null +++ b/vendor/github.com/pion/webrtc/v3/icetcp.go @@ -0,0 +1,18 @@ +package webrtc + +import ( + "net" + + "github.com/pion/ice/v2" + "github.com/pion/logging" +) + +// NewICETCPMux creates a new instance of ice.TCPMuxDefault. It enables use of +// passive ICE TCP candidates. +func NewICETCPMux(logger logging.LeveledLogger, listener net.Listener, readBufferSize int) ice.TCPMux { + return ice.NewTCPMuxDefault(ice.TCPMuxParams{ + Listener: listener, + Logger: logger, + ReadBufferSize: readBufferSize, + }) +} diff --git a/vendor/github.com/pion/webrtc/v3/icetransport.go b/vendor/github.com/pion/webrtc/v3/icetransport.go new file mode 100644 index 0000000..ba20ec1 --- /dev/null +++ b/vendor/github.com/pion/webrtc/v3/icetransport.go @@ -0,0 +1,368 @@ +// +build !js + +package webrtc + +import ( + "context" + "fmt" + "sync" + "sync/atomic" + "time" + + "github.com/pion/ice/v2" + "github.com/pion/logging" + "github.com/pion/webrtc/v3/internal/mux" +) + +// ICETransport allows an application access to information about the ICE +// transport over which packets are sent and received. +type ICETransport struct { + lock sync.RWMutex + + role ICERole + // Component ICEComponent + // State ICETransportState + // gatheringState ICEGathererState + + onConnectionStateChangeHandler atomic.Value // func(ICETransportState) + onSelectedCandidatePairChangeHandler atomic.Value // func(*ICECandidatePair) + + state atomic.Value // ICETransportState + + gatherer *ICEGatherer + conn *ice.Conn + mux *mux.Mux + + ctx context.Context + ctxCancel func() + + loggerFactory logging.LoggerFactory + + log logging.LeveledLogger +} + +// func (t *ICETransport) GetLocalCandidates() []ICECandidate { +// +// } +// +// func (t *ICETransport) GetRemoteCandidates() []ICECandidate { +// +// } +// +// func (t *ICETransport) GetSelectedCandidatePair() ICECandidatePair { +// +// } +// +// func (t *ICETransport) GetLocalParameters() ICEParameters { +// +// } +// +// func (t *ICETransport) GetRemoteParameters() ICEParameters { +// +// } + +// NewICETransport creates a new NewICETransport. +func NewICETransport(gatherer *ICEGatherer, loggerFactory logging.LoggerFactory) *ICETransport { + iceTransport := &ICETransport{ + gatherer: gatherer, + loggerFactory: loggerFactory, + log: loggerFactory.NewLogger("ortc"), + } + iceTransport.setState(ICETransportStateNew) + return iceTransport +} + +// Start incoming connectivity checks based on its configured role. +func (t *ICETransport) Start(gatherer *ICEGatherer, params ICEParameters, role *ICERole) error { + t.lock.Lock() + defer t.lock.Unlock() + + if t.State() != ICETransportStateNew { + return errICETransportNotInNew + } + + if gatherer != nil { + t.gatherer = gatherer + } + + if err := t.ensureGatherer(); err != nil { + return err + } + + agent := t.gatherer.getAgent() + if agent == nil { + return fmt.Errorf("%w: unable to start ICETransport", errICEAgentNotExist) + } + + if err := agent.OnConnectionStateChange(func(iceState ice.ConnectionState) { + state := newICETransportStateFromICE(iceState) + + t.setState(state) + t.onConnectionStateChange(state) + }); err != nil { + return err + } + if err := agent.OnSelectedCandidatePairChange(func(local, remote ice.Candidate) { + candidates, err := newICECandidatesFromICE([]ice.Candidate{local, remote}) + if err != nil { + t.log.Warnf("%w: %s", errICECandiatesCoversionFailed, err) + return + } + t.onSelectedCandidatePairChange(NewICECandidatePair(&candidates[0], &candidates[1])) + }); err != nil { + return err + } + + if role == nil { + controlled := ICERoleControlled + role = &controlled + } + t.role = *role + + t.ctx, t.ctxCancel = context.WithCancel(context.Background()) + + // Drop the lock here to allow ICE candidates to be + // added so that the agent can complete a connection + t.lock.Unlock() + + var iceConn *ice.Conn + var err error + switch *role { + case ICERoleControlling: + iceConn, err = agent.Dial(t.ctx, + params.UsernameFragment, + params.Password) + + case ICERoleControlled: + iceConn, err = agent.Accept(t.ctx, + params.UsernameFragment, + params.Password) + + default: + err = errICERoleUnknown + } + + // Reacquire the lock to set the connection/mux + t.lock.Lock() + if err != nil { + return err + } + + t.conn = iceConn + + config := mux.Config{ + Conn: t.conn, + BufferSize: receiveMTU, + LoggerFactory: t.loggerFactory, + } + t.mux = mux.NewMux(config) + + return nil +} + +// restart is not exposed currently because ORTC has users create a whole new ICETransport +// so for now lets keep it private so we don't cause ORTC users to depend on non-standard APIs +func (t *ICETransport) restart() error { + t.lock.Lock() + defer t.lock.Unlock() + + agent := t.gatherer.getAgent() + if agent == nil { + return fmt.Errorf("%w: unable to restart ICETransport", errICEAgentNotExist) + } + + if err := agent.Restart(t.gatherer.api.settingEngine.candidates.UsernameFragment, t.gatherer.api.settingEngine.candidates.Password); err != nil { + return err + } + return t.gatherer.Gather() +} + +// Stop irreversibly stops the ICETransport. +func (t *ICETransport) Stop() error { + t.lock.Lock() + defer t.lock.Unlock() + + t.setState(ICETransportStateClosed) + + if t.ctxCancel != nil { + t.ctxCancel() + } + + if t.mux != nil { + return t.mux.Close() + } else if t.gatherer != nil { + return t.gatherer.Close() + } + return nil +} + +// OnSelectedCandidatePairChange sets a handler that is invoked when a new +// ICE candidate pair is selected +func (t *ICETransport) OnSelectedCandidatePairChange(f func(*ICECandidatePair)) { + t.onSelectedCandidatePairChangeHandler.Store(f) +} + +func (t *ICETransport) onSelectedCandidatePairChange(pair *ICECandidatePair) { + handler := t.onSelectedCandidatePairChangeHandler.Load() + if handler != nil { + handler.(func(*ICECandidatePair))(pair) + } +} + +// OnConnectionStateChange sets a handler that is fired when the ICE +// connection state changes. +func (t *ICETransport) OnConnectionStateChange(f func(ICETransportState)) { + t.onConnectionStateChangeHandler.Store(f) +} + +func (t *ICETransport) onConnectionStateChange(state ICETransportState) { + handler := t.onConnectionStateChangeHandler.Load() + if handler != nil { + handler.(func(ICETransportState))(state) + } +} + +// Role indicates the current role of the ICE transport. +func (t *ICETransport) Role() ICERole { + t.lock.RLock() + defer t.lock.RUnlock() + + return t.role +} + +// SetRemoteCandidates sets the sequence of candidates associated with the remote ICETransport. +func (t *ICETransport) SetRemoteCandidates(remoteCandidates []ICECandidate) error { + t.lock.RLock() + defer t.lock.RUnlock() + + if err := t.ensureGatherer(); err != nil { + return err + } + + agent := t.gatherer.getAgent() + if agent == nil { + return fmt.Errorf("%w: unable to set remote candidates", errICEAgentNotExist) + } + + for _, c := range remoteCandidates { + i, err := c.toICE() + if err != nil { + return err + } + + if err = agent.AddRemoteCandidate(i); err != nil { + return err + } + } + + return nil +} + +// AddRemoteCandidate adds a candidate associated with the remote ICETransport. +func (t *ICETransport) AddRemoteCandidate(remoteCandidate *ICECandidate) error { + t.lock.RLock() + defer t.lock.RUnlock() + + var ( + c ice.Candidate + err error + ) + + if err = t.ensureGatherer(); err != nil { + return err + } + + if remoteCandidate != nil { + if c, err = remoteCandidate.toICE(); err != nil { + return err + } + } + + agent := t.gatherer.getAgent() + if agent == nil { + return fmt.Errorf("%w: unable to add remote candidates", errICEAgentNotExist) + } + + return agent.AddRemoteCandidate(c) +} + +// State returns the current ice transport state. +func (t *ICETransport) State() ICETransportState { + if v := t.state.Load(); v != nil { + return v.(ICETransportState) + } + return ICETransportState(0) +} + +func (t *ICETransport) setState(i ICETransportState) { + t.state.Store(i) +} + +// NewEndpoint registers a new endpoint on the underlying mux. +func (t *ICETransport) NewEndpoint(f mux.MatchFunc) *mux.Endpoint { + t.lock.Lock() + defer t.lock.Unlock() + return t.mux.NewEndpoint(f) +} + +func (t *ICETransport) ensureGatherer() error { + if t.gatherer == nil { + return errICEGathererNotStarted + } else if t.gatherer.getAgent() == nil { + if err := t.gatherer.createAgent(); err != nil { + return err + } + } + + return nil +} + +func (t *ICETransport) collectStats(collector *statsReportCollector) { + t.lock.Lock() + conn := t.conn + t.lock.Unlock() + + collector.Collecting() + + stats := TransportStats{ + Timestamp: statsTimestampFrom(time.Now()), + Type: StatsTypeTransport, + ID: "iceTransport", + } + + if conn != nil { + stats.BytesSent = conn.BytesSent() + stats.BytesReceived = conn.BytesReceived() + } + + collector.Collect(stats.ID, stats) +} + +func (t *ICETransport) haveRemoteCredentialsChange(newUfrag, newPwd string) bool { + t.lock.Lock() + defer t.lock.Unlock() + + agent := t.gatherer.getAgent() + if agent == nil { + return false + } + + uFrag, uPwd, err := agent.GetRemoteUserCredentials() + if err != nil { + return false + } + + return uFrag != newUfrag || uPwd != newPwd +} + +func (t *ICETransport) setRemoteCredentials(newUfrag, newPwd string) error { + t.lock.Lock() + defer t.lock.Unlock() + + agent := t.gatherer.getAgent() + if agent == nil { + return fmt.Errorf("%w: unable to SetRemoteCredentials", errICEAgentNotExist) + } + + return agent.SetRemoteCredentials(newUfrag, newPwd) +} diff --git a/vendor/github.com/pion/webrtc/v3/icetransportpolicy.go b/vendor/github.com/pion/webrtc/v3/icetransportpolicy.go new file mode 100644 index 0000000..16273f5 --- /dev/null +++ b/vendor/github.com/pion/webrtc/v3/icetransportpolicy.go @@ -0,0 +1,65 @@ +package webrtc + +import ( + "encoding/json" +) + +// ICETransportPolicy defines the ICE candidate policy surface the +// permitted candidates. Only these candidates are used for connectivity checks. +type ICETransportPolicy int + +// ICEGatherPolicy is the ORTC equivalent of ICETransportPolicy +type ICEGatherPolicy = ICETransportPolicy + +const ( + // ICETransportPolicyAll indicates any type of candidate is used. + ICETransportPolicyAll ICETransportPolicy = iota + + // ICETransportPolicyRelay indicates only media relay candidates such + // as candidates passing through a TURN server are used. + ICETransportPolicyRelay +) + +// This is done this way because of a linter. +const ( + iceTransportPolicyRelayStr = "relay" + iceTransportPolicyAllStr = "all" +) + +// NewICETransportPolicy takes a string and converts it to ICETransportPolicy +func NewICETransportPolicy(raw string) ICETransportPolicy { + switch raw { + case iceTransportPolicyRelayStr: + return ICETransportPolicyRelay + case iceTransportPolicyAllStr: + return ICETransportPolicyAll + default: + return ICETransportPolicy(Unknown) + } +} + +func (t ICETransportPolicy) String() string { + switch t { + case ICETransportPolicyRelay: + return iceTransportPolicyRelayStr + case ICETransportPolicyAll: + return iceTransportPolicyAllStr + default: + return ErrUnknownType.Error() + } +} + +// UnmarshalJSON parses the JSON-encoded data and stores the result +func (t *ICETransportPolicy) UnmarshalJSON(b []byte) error { + var val string + if err := json.Unmarshal(b, &val); err != nil { + return err + } + *t = NewICETransportPolicy(val) + return nil +} + +// MarshalJSON returns the JSON encoding +func (t ICETransportPolicy) MarshalJSON() ([]byte, error) { + return json.Marshal(t.String()) +} diff --git a/vendor/github.com/pion/webrtc/v3/icetransportstate.go b/vendor/github.com/pion/webrtc/v3/icetransportstate.go new file mode 100644 index 0000000..da93e44 --- /dev/null +++ b/vendor/github.com/pion/webrtc/v3/icetransportstate.go @@ -0,0 +1,107 @@ +package webrtc + +import "github.com/pion/ice/v2" + +// ICETransportState represents the current state of the ICE transport. +type ICETransportState int + +const ( + // ICETransportStateNew indicates the ICETransport is waiting + // for remote candidates to be supplied. + ICETransportStateNew = iota + 1 + + // ICETransportStateChecking indicates the ICETransport has + // received at least one remote candidate, and a local and remote + // ICECandidateComplete dictionary was not added as the last candidate. + ICETransportStateChecking + + // ICETransportStateConnected indicates the ICETransport has + // received a response to an outgoing connectivity check, or has + // received incoming DTLS/media after a successful response to an + // incoming connectivity check, but is still checking other candidate + // pairs to see if there is a better connection. + ICETransportStateConnected + + // ICETransportStateCompleted indicates the ICETransport tested + // all appropriate candidate pairs and at least one functioning + // candidate pair has been found. + ICETransportStateCompleted + + // ICETransportStateFailed indicates the ICETransport the last + // candidate was added and all appropriate candidate pairs have either + // failed connectivity checks or have lost consent. + ICETransportStateFailed + + // ICETransportStateDisconnected indicates the ICETransport has received + // at least one local and remote candidate, but the final candidate was + // received yet and all appropriate candidate pairs thus far have been + // tested and failed. + ICETransportStateDisconnected + + // ICETransportStateClosed indicates the ICETransport has shut down + // and is no longer responding to STUN requests. + ICETransportStateClosed +) + +func (c ICETransportState) String() string { + switch c { + case ICETransportStateNew: + return "new" + case ICETransportStateChecking: + return "checking" + case ICETransportStateConnected: + return "connected" + case ICETransportStateCompleted: + return "completed" + case ICETransportStateFailed: + return "failed" + case ICETransportStateDisconnected: + return "disconnected" + case ICETransportStateClosed: + return "closed" + default: + return unknownStr + } +} + +func newICETransportStateFromICE(i ice.ConnectionState) ICETransportState { + switch i { + case ice.ConnectionStateNew: + return ICETransportStateNew + case ice.ConnectionStateChecking: + return ICETransportStateChecking + case ice.ConnectionStateConnected: + return ICETransportStateConnected + case ice.ConnectionStateCompleted: + return ICETransportStateCompleted + case ice.ConnectionStateFailed: + return ICETransportStateFailed + case ice.ConnectionStateDisconnected: + return ICETransportStateDisconnected + case ice.ConnectionStateClosed: + return ICETransportStateClosed + default: + return ICETransportState(Unknown) + } +} + +func (c ICETransportState) toICE() ice.ConnectionState { + switch c { + case ICETransportStateNew: + return ice.ConnectionStateNew + case ICETransportStateChecking: + return ice.ConnectionStateChecking + case ICETransportStateConnected: + return ice.ConnectionStateConnected + case ICETransportStateCompleted: + return ice.ConnectionStateCompleted + case ICETransportStateFailed: + return ice.ConnectionStateFailed + case ICETransportStateDisconnected: + return ice.ConnectionStateDisconnected + case ICETransportStateClosed: + return ice.ConnectionStateClosed + default: + return ice.ConnectionState(Unknown) + } +} diff --git a/vendor/github.com/pion/webrtc/v3/interceptor.go b/vendor/github.com/pion/webrtc/v3/interceptor.go new file mode 100644 index 0000000..eff9496 --- /dev/null +++ b/vendor/github.com/pion/webrtc/v3/interceptor.go @@ -0,0 +1,107 @@ +// +build !js + +package webrtc + +import ( + "sync/atomic" + + "github.com/pion/interceptor" + "github.com/pion/interceptor/pkg/nack" + "github.com/pion/interceptor/pkg/report" + "github.com/pion/rtp" +) + +// RegisterDefaultInterceptors will register some useful interceptors. +// If you want to customize which interceptors are loaded, you should copy the +// code from this method and remove unwanted interceptors. +func RegisterDefaultInterceptors(mediaEngine *MediaEngine, interceptorRegistry *interceptor.Registry) error { + if err := ConfigureNack(mediaEngine, interceptorRegistry); err != nil { + return err + } + + if err := ConfigureRTCPReports(interceptorRegistry); err != nil { + return err + } + + return nil +} + +// ConfigureRTCPReports will setup everything necessary for generating Sender and Receiver Reports +func ConfigureRTCPReports(interceptorRegistry *interceptor.Registry) error { + reciver, err := report.NewReceiverInterceptor() + if err != nil { + return err + } + + sender, err := report.NewSenderInterceptor() + if err != nil { + return err + } + + interceptorRegistry.Add(reciver) + interceptorRegistry.Add(sender) + return nil +} + +// ConfigureNack will setup everything necessary for handling generating/responding to nack messages. +func ConfigureNack(mediaEngine *MediaEngine, interceptorRegistry *interceptor.Registry) error { + generator, err := nack.NewGeneratorInterceptor() + if err != nil { + return err + } + + responder, err := nack.NewResponderInterceptor() + if err != nil { + return err + } + + mediaEngine.RegisterFeedback(RTCPFeedback{Type: "nack"}, RTPCodecTypeVideo) + mediaEngine.RegisterFeedback(RTCPFeedback{Type: "nack", Parameter: "pli"}, RTPCodecTypeVideo) + interceptorRegistry.Add(responder) + interceptorRegistry.Add(generator) + return nil +} + +type interceptorToTrackLocalWriter struct{ interceptor atomic.Value } // interceptor.RTPWriter } + +func (i *interceptorToTrackLocalWriter) WriteRTP(header *rtp.Header, payload []byte) (int, error) { + if writer, ok := i.interceptor.Load().(interceptor.RTPWriter); ok && writer != nil { + return writer.Write(header, payload, interceptor.Attributes{}) + } + + return 0, nil +} + +func (i *interceptorToTrackLocalWriter) Write(b []byte) (int, error) { + packet := &rtp.Packet{} + if err := packet.Unmarshal(b); err != nil { + return 0, err + } + + return i.WriteRTP(&packet.Header, packet.Payload) +} + +func createStreamInfo(id string, ssrc SSRC, payloadType PayloadType, codec RTPCodecCapability, webrtcHeaderExtensions []RTPHeaderExtensionParameter) interceptor.StreamInfo { + headerExtensions := make([]interceptor.RTPHeaderExtension, 0, len(webrtcHeaderExtensions)) + for _, h := range webrtcHeaderExtensions { + headerExtensions = append(headerExtensions, interceptor.RTPHeaderExtension{ID: h.ID, URI: h.URI}) + } + + feedbacks := make([]interceptor.RTCPFeedback, 0, len(codec.RTCPFeedback)) + for _, f := range codec.RTCPFeedback { + feedbacks = append(feedbacks, interceptor.RTCPFeedback{Type: f.Type, Parameter: f.Parameter}) + } + + return interceptor.StreamInfo{ + ID: id, + Attributes: interceptor.Attributes{}, + SSRC: uint32(ssrc), + PayloadType: uint8(payloadType), + RTPHeaderExtensions: headerExtensions, + MimeType: codec.MimeType, + ClockRate: codec.ClockRate, + Channels: codec.Channels, + SDPFmtpLine: codec.SDPFmtpLine, + RTCPFeedback: feedbacks, + } +} diff --git a/vendor/github.com/pion/webrtc/v3/internal/mux/endpoint.go b/vendor/github.com/pion/webrtc/v3/internal/mux/endpoint.go new file mode 100644 index 0000000..afccc07 --- /dev/null +++ b/vendor/github.com/pion/webrtc/v3/internal/mux/endpoint.go @@ -0,0 +1,75 @@ +package mux + +import ( + "errors" + "io" + "net" + "time" + + "github.com/pion/ice/v2" + "github.com/pion/transport/packetio" +) + +// Endpoint implements net.Conn. It is used to read muxed packets. +type Endpoint struct { + mux *Mux + buffer *packetio.Buffer +} + +// Close unregisters the endpoint from the Mux +func (e *Endpoint) Close() (err error) { + err = e.close() + if err != nil { + return err + } + + e.mux.RemoveEndpoint(e) + return nil +} + +func (e *Endpoint) close() error { + return e.buffer.Close() +} + +// Read reads a packet of len(p) bytes from the underlying conn +// that are matched by the associated MuxFunc +func (e *Endpoint) Read(p []byte) (int, error) { + return e.buffer.Read(p) +} + +// Write writes len(p) bytes to the underlying conn +func (e *Endpoint) Write(p []byte) (int, error) { + n, err := e.mux.nextConn.Write(p) + if errors.Is(err, ice.ErrNoCandidatePairs) { + return 0, nil + } else if errors.Is(err, ice.ErrClosed) { + return 0, io.ErrClosedPipe + } + + return n, err +} + +// LocalAddr is a stub +func (e *Endpoint) LocalAddr() net.Addr { + return e.mux.nextConn.LocalAddr() +} + +// RemoteAddr is a stub +func (e *Endpoint) RemoteAddr() net.Addr { + return e.mux.nextConn.RemoteAddr() +} + +// SetDeadline is a stub +func (e *Endpoint) SetDeadline(t time.Time) error { + return nil +} + +// SetReadDeadline is a stub +func (e *Endpoint) SetReadDeadline(t time.Time) error { + return nil +} + +// SetWriteDeadline is a stub +func (e *Endpoint) SetWriteDeadline(t time.Time) error { + return nil +} diff --git a/vendor/github.com/pion/webrtc/v3/internal/mux/mux.go b/vendor/github.com/pion/webrtc/v3/internal/mux/mux.go new file mode 100644 index 0000000..83aaa26 --- /dev/null +++ b/vendor/github.com/pion/webrtc/v3/internal/mux/mux.go @@ -0,0 +1,145 @@ +// Package mux multiplexes packets on a single socket (RFC7983) +package mux + +import ( + "net" + "sync" + + "github.com/pion/logging" + "github.com/pion/transport/packetio" +) + +// The maximum amount of data that can be buffered before returning errors. +const maxBufferSize = 1000 * 1000 // 1MB + +// Config collects the arguments to mux.Mux construction into +// a single structure +type Config struct { + Conn net.Conn + BufferSize int + LoggerFactory logging.LoggerFactory +} + +// Mux allows multiplexing +type Mux struct { + lock sync.RWMutex + nextConn net.Conn + endpoints map[*Endpoint]MatchFunc + bufferSize int + closedCh chan struct{} + + log logging.LeveledLogger +} + +// NewMux creates a new Mux +func NewMux(config Config) *Mux { + m := &Mux{ + nextConn: config.Conn, + endpoints: make(map[*Endpoint]MatchFunc), + bufferSize: config.BufferSize, + closedCh: make(chan struct{}), + log: config.LoggerFactory.NewLogger("mux"), + } + + go m.readLoop() + + return m +} + +// NewEndpoint creates a new Endpoint +func (m *Mux) NewEndpoint(f MatchFunc) *Endpoint { + e := &Endpoint{ + mux: m, + buffer: packetio.NewBuffer(), + } + + // Set a maximum size of the buffer in bytes. + // NOTE: We actually won't get anywhere close to this limit. + // SRTP will constantly read from the endpoint and drop packets if it's full. + e.buffer.SetLimitSize(maxBufferSize) + + m.lock.Lock() + m.endpoints[e] = f + m.lock.Unlock() + + return e +} + +// RemoveEndpoint removes an endpoint from the Mux +func (m *Mux) RemoveEndpoint(e *Endpoint) { + m.lock.Lock() + defer m.lock.Unlock() + delete(m.endpoints, e) +} + +// Close closes the Mux and all associated Endpoints. +func (m *Mux) Close() error { + m.lock.Lock() + for e := range m.endpoints { + err := e.close() + if err != nil { + return err + } + + delete(m.endpoints, e) + } + m.lock.Unlock() + + err := m.nextConn.Close() + if err != nil { + return err + } + + // Wait for readLoop to end + <-m.closedCh + + return nil +} + +func (m *Mux) readLoop() { + defer func() { + close(m.closedCh) + }() + + buf := make([]byte, m.bufferSize) + for { + n, err := m.nextConn.Read(buf) + if err != nil { + return + } + + err = m.dispatch(buf[:n]) + if err != nil { + return + } + } +} + +func (m *Mux) dispatch(buf []byte) error { + var endpoint *Endpoint + + m.lock.Lock() + for e, f := range m.endpoints { + if f(buf) { + endpoint = e + break + } + } + m.lock.Unlock() + + if endpoint == nil { + if len(buf) > 0 { + m.log.Warnf("Warning: mux: no endpoint for packet starting with %d\n", buf[0]) + } else { + m.log.Warnf("Warning: mux: no endpoint for zero length packet") + } + return nil + } + + _, err := endpoint.buffer.Write(buf) + if err != nil { + return err + } + + return nil +} diff --git a/vendor/github.com/pion/webrtc/v3/internal/mux/muxfunc.go b/vendor/github.com/pion/webrtc/v3/internal/mux/muxfunc.go new file mode 100644 index 0000000..2ab585b --- /dev/null +++ b/vendor/github.com/pion/webrtc/v3/internal/mux/muxfunc.go @@ -0,0 +1,101 @@ +package mux + +import ( + "bytes" + "encoding/binary" +) + +// MatchFunc allows custom logic for mapping packets to an Endpoint +type MatchFunc func([]byte) bool + +// MatchAll always returns true +func MatchAll(b []byte) bool { + return true +} + +// MatchNone always returns false +func MatchNone(b []byte) bool { + return false +} + +// MatchRange is a MatchFunc that accepts packets with the first byte in [lower..upper] +func MatchRange(lower, upper byte) MatchFunc { + return func(buf []byte) bool { + if len(buf) < 1 { + return false + } + b := buf[0] + return b >= lower && b <= upper + } +} + +// MatchFuncs as described in RFC7983 +// https://tools.ietf.org/html/rfc7983 +// +----------------+ +// | [0..3] -+--> forward to STUN +// | | +// | [16..19] -+--> forward to ZRTP +// | | +// packet --> | [20..63] -+--> forward to DTLS +// | | +// | [64..79] -+--> forward to TURN Channel +// | | +// | [128..191] -+--> forward to RTP/RTCP +// +----------------+ + +// MatchSTUN is a MatchFunc that accepts packets with the first byte in [0..3] +// as defied in RFC7983 +func MatchSTUN(b []byte) bool { + return MatchRange(0, 3)(b) +} + +// MatchZRTP is a MatchFunc that accepts packets with the first byte in [16..19] +// as defied in RFC7983 +func MatchZRTP(b []byte) bool { + return MatchRange(16, 19)(b) +} + +// MatchDTLS is a MatchFunc that accepts packets with the first byte in [20..63] +// as defied in RFC7983 +func MatchDTLS(b []byte) bool { + return MatchRange(20, 63)(b) +} + +// MatchTURN is a MatchFunc that accepts packets with the first byte in [64..79] +// as defied in RFC7983 +func MatchTURN(b []byte) bool { + return MatchRange(64, 79)(b) +} + +// MatchSRTPOrSRTCP is a MatchFunc that accepts packets with the first byte in [128..191] +// as defied in RFC7983 +func MatchSRTPOrSRTCP(b []byte) bool { + return MatchRange(128, 191)(b) +} + +func isRTCP(buf []byte) bool { + // Not long enough to determine RTP/RTCP + if len(buf) < 4 { + return false + } + + var rtcpPacketType uint8 + r := bytes.NewReader([]byte{buf[1]}) + if err := binary.Read(r, binary.BigEndian, &rtcpPacketType); err != nil { + return false + } else if rtcpPacketType >= 192 && rtcpPacketType <= 223 { + return true + } + + return false +} + +// MatchSRTP is a MatchFunc that only matches SRTP and not SRTCP +func MatchSRTP(buf []byte) bool { + return MatchSRTPOrSRTCP(buf) && !isRTCP(buf) +} + +// MatchSRTCP is a MatchFunc that only matches SRTCP and not SRTP +func MatchSRTCP(buf []byte) bool { + return MatchSRTPOrSRTCP(buf) && isRTCP(buf) +} diff --git a/vendor/github.com/pion/webrtc/v3/internal/util/util.go b/vendor/github.com/pion/webrtc/v3/internal/util/util.go new file mode 100644 index 0000000..6e12b64 --- /dev/null +++ b/vendor/github.com/pion/webrtc/v3/internal/util/util.go @@ -0,0 +1,72 @@ +// Package util provides auxiliary functions internally used in webrtc package +package util + +import ( + "errors" + "strings" + + "github.com/pion/randutil" +) + +const ( + runesAlpha = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" +) + +// Use global random generator to properly seed by crypto grade random. +var globalMathRandomGenerator = randutil.NewMathRandomGenerator() // nolint:gochecknoglobals + +// MathRandAlpha generates a mathmatical random alphabet sequence of the requested length. +func MathRandAlpha(n int) string { + return globalMathRandomGenerator.GenerateString(n, runesAlpha) +} + +// RandUint32 generates a mathmatical random uint32. +func RandUint32() uint32 { + return globalMathRandomGenerator.Uint32() +} + +// FlattenErrs flattens multiple errors into one +func FlattenErrs(errs []error) error { + errs2 := []error{} + for _, e := range errs { + if e != nil { + errs2 = append(errs2, e) + } + } + if len(errs2) == 0 { + return nil + } + return multiError(errs2) +} + +type multiError []error + +func (me multiError) Error() string { + var errstrings []string + + for _, err := range me { + if err != nil { + errstrings = append(errstrings, err.Error()) + } + } + + if len(errstrings) == 0 { + return "multiError must contain multiple error but is empty" + } + + return strings.Join(errstrings, "\n") +} + +func (me multiError) Is(err error) bool { + for _, e := range me { + if errors.Is(e, err) { + return true + } + if me2, ok := e.(multiError); ok { + if me2.Is(err) { + return true + } + } + } + return false +} diff --git a/vendor/github.com/pion/webrtc/v3/js_compare.go b/vendor/github.com/pion/webrtc/v3/js_compare.go new file mode 100644 index 0000000..31e3980 --- /dev/null +++ b/vendor/github.com/pion/webrtc/v3/js_compare.go @@ -0,0 +1,13 @@ +// +build js,go1.14 + +package webrtc + +import "syscall/js" + +func jsValueIsUndefined(v js.Value) bool { + return v.IsUndefined() +} + +func jsValueIsNull(v js.Value) bool { + return v.IsNull() +} diff --git a/vendor/github.com/pion/webrtc/v3/js_compare_legacy.go b/vendor/github.com/pion/webrtc/v3/js_compare_legacy.go new file mode 100644 index 0000000..e2e247a --- /dev/null +++ b/vendor/github.com/pion/webrtc/v3/js_compare_legacy.go @@ -0,0 +1,13 @@ +// +build js,!go1.14 + +package webrtc + +import "syscall/js" + +func jsValueIsUndefined(v js.Value) bool { + return v == js.Undefined() +} + +func jsValueIsNull(v js.Value) bool { + return v == js.Null() +} diff --git a/vendor/github.com/pion/webrtc/v3/js_utils.go b/vendor/github.com/pion/webrtc/v3/js_utils.go new file mode 100644 index 0000000..25e8939 --- /dev/null +++ b/vendor/github.com/pion/webrtc/v3/js_utils.go @@ -0,0 +1,168 @@ +// +build js,wasm + +package webrtc + +import ( + "fmt" + "syscall/js" +) + +// awaitPromise accepts a js.Value representing a Promise. If the promise +// resolves, it returns (result, nil). If the promise rejects, it returns +// (js.Undefined, error). awaitPromise has a synchronous-like API but does not +// block the JavaScript event loop. +func awaitPromise(promise js.Value) (js.Value, error) { + resultsChan := make(chan js.Value) + errChan := make(chan js.Error) + + thenFunc := js.FuncOf(func(this js.Value, args []js.Value) interface{} { + go func() { + resultsChan <- args[0] + }() + return js.Undefined() + }) + defer thenFunc.Release() + promise.Call("then", thenFunc) + + catchFunc := js.FuncOf(func(this js.Value, args []js.Value) interface{} { + go func() { + errChan <- js.Error{args[0]} + }() + return js.Undefined() + }) + defer catchFunc.Release() + promise.Call("catch", catchFunc) + + select { + case result := <-resultsChan: + return result, nil + case err := <-errChan: + return js.Undefined(), err + } +} + +func valueToUint16Pointer(val js.Value) *uint16 { + if jsValueIsNull(val) || jsValueIsUndefined(val) { + return nil + } + convertedVal := uint16(val.Int()) + return &convertedVal +} + +func valueToStringPointer(val js.Value) *string { + if jsValueIsNull(val) || jsValueIsUndefined(val) { + return nil + } + stringVal := val.String() + return &stringVal +} + +func stringToValueOrUndefined(val string) js.Value { + if val == "" { + return js.Undefined() + } + return js.ValueOf(val) +} + +func uint8ToValueOrUndefined(val uint8) js.Value { + if val == 0 { + return js.Undefined() + } + return js.ValueOf(val) +} + +func interfaceToValueOrUndefined(val interface{}) js.Value { + if val == nil { + return js.Undefined() + } + return js.ValueOf(val) +} + +func valueToStringOrZero(val js.Value) string { + if jsValueIsUndefined(val) || jsValueIsNull(val) { + return "" + } + return val.String() +} + +func valueToUint8OrZero(val js.Value) uint8 { + if jsValueIsUndefined(val) || jsValueIsNull(val) { + return 0 + } + return uint8(val.Int()) +} + +func valueToUint16OrZero(val js.Value) uint16 { + if jsValueIsNull(val) || jsValueIsUndefined(val) { + return 0 + } + return uint16(val.Int()) +} + +func valueToUint32OrZero(val js.Value) uint32 { + if jsValueIsNull(val) || jsValueIsUndefined(val) { + return 0 + } + return uint32(val.Int()) +} + +func valueToStrings(val js.Value) []string { + result := make([]string, val.Length()) + for i := 0; i < val.Length(); i++ { + result[i] = val.Index(i).String() + } + return result +} + +func stringPointerToValue(val *string) js.Value { + if val == nil { + return js.Undefined() + } + return js.ValueOf(*val) +} + +func uint16PointerToValue(val *uint16) js.Value { + if val == nil { + return js.Undefined() + } + return js.ValueOf(*val) +} + +func boolPointerToValue(val *bool) js.Value { + if val == nil { + return js.Undefined() + } + return js.ValueOf(*val) +} + +func stringsToValue(strings []string) js.Value { + val := make([]interface{}, len(strings)) + for i, s := range strings { + val[i] = s + } + return js.ValueOf(val) +} + +func stringEnumToValueOrUndefined(s string) js.Value { + if s == "unknown" { + return js.Undefined() + } + return js.ValueOf(s) +} + +// Converts the return value of recover() to an error. +func recoveryToError(e interface{}) error { + switch e := e.(type) { + case error: + return e + default: + return fmt.Errorf("recovered with non-error value: (%T) %s", e, e) + } +} + +func uint8ArrayValueToBytes(val js.Value) []byte { + result := make([]byte, val.Length()) + js.CopyBytesToGo(result, val) + + return result +} diff --git a/vendor/github.com/pion/webrtc/v3/mediaengine.go b/vendor/github.com/pion/webrtc/v3/mediaengine.go new file mode 100644 index 0000000..bc3ed99 --- /dev/null +++ b/vendor/github.com/pion/webrtc/v3/mediaengine.go @@ -0,0 +1,533 @@ +// +build !js + +package webrtc + +import ( + "fmt" + "strconv" + "strings" + "time" + + "github.com/pion/rtp" + "github.com/pion/rtp/codecs" + "github.com/pion/sdp/v3" +) + +const ( + // MimeTypeH264 H264 MIME type. + // Note: Matching should be case insensitive. + MimeTypeH264 = "video/H264" + // MimeTypeOpus Opus MIME type + // Note: Matching should be case insensitive. + MimeTypeOpus = "audio/opus" + // MimeTypeVP8 VP8 MIME type + // Note: Matching should be case insensitive. + MimeTypeVP8 = "video/VP8" + // MimeTypeVP9 VP9 MIME type + // Note: Matching should be case insensitive. + MimeTypeVP9 = "video/VP9" + // MimeTypeG722 G722 MIME type + // Note: Matching should be case insensitive. + MimeTypeG722 = "audio/G722" + // MimeTypePCMU PCMU MIME type + // Note: Matching should be case insensitive. + MimeTypePCMU = "audio/PCMU" + // MimeTypePCMA PCMA MIME type + // Note: Matching should be case insensitive. + MimeTypePCMA = "audio/PCMA" +) + +type mediaEngineHeaderExtension struct { + uri string + isAudio, isVideo bool + + // If set only Transceivers of this direction are allowed + allowedDirections []RTPTransceiverDirection +} + +// A MediaEngine defines the codecs supported by a PeerConnection, and the +// configuration of those codecs. A MediaEngine must not be shared between +// PeerConnections. +type MediaEngine struct { + // If we have attempted to negotiate a codec type yet. + negotiatedVideo, negotiatedAudio bool + + videoCodecs, audioCodecs []RTPCodecParameters + negotiatedVideoCodecs, negotiatedAudioCodecs []RTPCodecParameters + + headerExtensions []mediaEngineHeaderExtension + negotiatedHeaderExtensions map[int]mediaEngineHeaderExtension +} + +// RegisterDefaultCodecs registers the default codecs supported by Pion WebRTC. +// RegisterDefaultCodecs is not safe for concurrent use. +func (m *MediaEngine) RegisterDefaultCodecs() error { + // Default Pion Audio Codecs + for _, codec := range []RTPCodecParameters{ + { + RTPCodecCapability: RTPCodecCapability{MimeTypeOpus, 48000, 2, "minptime=10;useinbandfec=1", nil}, + PayloadType: 111, + }, + { + RTPCodecCapability: RTPCodecCapability{MimeTypeG722, 8000, 0, "", nil}, + PayloadType: 9, + }, + { + RTPCodecCapability: RTPCodecCapability{MimeTypePCMU, 8000, 0, "", nil}, + PayloadType: 0, + }, + { + RTPCodecCapability: RTPCodecCapability{MimeTypePCMA, 8000, 0, "", nil}, + PayloadType: 8, + }, + } { + if err := m.RegisterCodec(codec, RTPCodecTypeAudio); err != nil { + return err + } + } + + videoRTCPFeedback := []RTCPFeedback{{"goog-remb", ""}, {"ccm", "fir"}, {"nack", ""}, {"nack", "pli"}} + for _, codec := range []RTPCodecParameters{ + { + RTPCodecCapability: RTPCodecCapability{MimeTypeVP8, 90000, 0, "", videoRTCPFeedback}, + PayloadType: 96, + }, + { + RTPCodecCapability: RTPCodecCapability{"video/rtx", 90000, 0, "apt=96", nil}, + PayloadType: 97, + }, + + { + RTPCodecCapability: RTPCodecCapability{MimeTypeVP9, 90000, 0, "profile-id=0", videoRTCPFeedback}, + PayloadType: 98, + }, + { + RTPCodecCapability: RTPCodecCapability{"video/rtx", 90000, 0, "apt=98", nil}, + PayloadType: 99, + }, + + { + RTPCodecCapability: RTPCodecCapability{MimeTypeVP9, 90000, 0, "profile-id=1", videoRTCPFeedback}, + PayloadType: 100, + }, + { + RTPCodecCapability: RTPCodecCapability{"video/rtx", 90000, 0, "apt=100", nil}, + PayloadType: 101, + }, + + { + RTPCodecCapability: RTPCodecCapability{MimeTypeH264, 90000, 0, "level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001f", videoRTCPFeedback}, + PayloadType: 102, + }, + { + RTPCodecCapability: RTPCodecCapability{"video/rtx", 90000, 0, "apt=102", nil}, + PayloadType: 121, + }, + + { + RTPCodecCapability: RTPCodecCapability{MimeTypeH264, 90000, 0, "level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42001f", videoRTCPFeedback}, + PayloadType: 127, + }, + { + RTPCodecCapability: RTPCodecCapability{"video/rtx", 90000, 0, "apt=127", nil}, + PayloadType: 120, + }, + + { + RTPCodecCapability: RTPCodecCapability{MimeTypeH264, 90000, 0, "level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f", videoRTCPFeedback}, + PayloadType: 125, + }, + { + RTPCodecCapability: RTPCodecCapability{"video/rtx", 90000, 0, "apt=125", nil}, + PayloadType: 107, + }, + + { + RTPCodecCapability: RTPCodecCapability{MimeTypeH264, 90000, 0, "level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42e01f", videoRTCPFeedback}, + PayloadType: 108, + }, + { + RTPCodecCapability: RTPCodecCapability{"video/rtx", 90000, 0, "apt=108", nil}, + PayloadType: 109, + }, + + { + RTPCodecCapability: RTPCodecCapability{MimeTypeH264, 90000, 0, "level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42001f", videoRTCPFeedback}, + PayloadType: 127, + }, + { + RTPCodecCapability: RTPCodecCapability{"video/rtx", 90000, 0, "apt=127", nil}, + PayloadType: 120, + }, + + { + RTPCodecCapability: RTPCodecCapability{MimeTypeH264, 90000, 0, "level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=640032", videoRTCPFeedback}, + PayloadType: 123, + }, + { + RTPCodecCapability: RTPCodecCapability{"video/rtx", 90000, 0, "apt=123", nil}, + PayloadType: 118, + }, + + { + RTPCodecCapability: RTPCodecCapability{"video/ulpfec", 90000, 0, "", nil}, + PayloadType: 116, + }, + } { + if err := m.RegisterCodec(codec, RTPCodecTypeVideo); err != nil { + return err + } + } + + return nil +} + +// addCodec will append codec if it not exists +func (m *MediaEngine) addCodec(codecs []RTPCodecParameters, codec RTPCodecParameters) []RTPCodecParameters { + for _, c := range codecs { + if c.MimeType == codec.MimeType && c.PayloadType == codec.PayloadType { + return codecs + } + } + return append(codecs, codec) +} + +// RegisterCodec adds codec to the MediaEngine +// These are the list of codecs supported by this PeerConnection. +// RegisterCodec is not safe for concurrent use. +func (m *MediaEngine) RegisterCodec(codec RTPCodecParameters, typ RTPCodecType) error { + codec.statsID = fmt.Sprintf("RTPCodec-%d", time.Now().UnixNano()) + switch typ { + case RTPCodecTypeAudio: + m.audioCodecs = m.addCodec(m.audioCodecs, codec) + case RTPCodecTypeVideo: + m.videoCodecs = m.addCodec(m.videoCodecs, codec) + default: + return ErrUnknownType + } + return nil +} + +// RegisterHeaderExtension adds a header extension to the MediaEngine +// To determine the negotiated value use `GetHeaderExtensionID` after signaling is complete +func (m *MediaEngine) RegisterHeaderExtension(extension RTPHeaderExtensionCapability, typ RTPCodecType, allowedDirections ...RTPTransceiverDirection) error { + if m.negotiatedHeaderExtensions == nil { + m.negotiatedHeaderExtensions = map[int]mediaEngineHeaderExtension{} + } + + if len(allowedDirections) == 0 { + allowedDirections = []RTPTransceiverDirection{RTPTransceiverDirectionRecvonly, RTPTransceiverDirectionSendonly} + } + + for _, direction := range allowedDirections { + if direction != RTPTransceiverDirectionRecvonly && direction != RTPTransceiverDirectionSendonly { + return ErrRegisterHeaderExtensionInvalidDirection + } + } + + extensionIndex := -1 + for i := range m.headerExtensions { + if extension.URI == m.headerExtensions[i].uri { + extensionIndex = i + } + } + + if extensionIndex == -1 { + m.headerExtensions = append(m.headerExtensions, mediaEngineHeaderExtension{}) + extensionIndex = len(m.headerExtensions) - 1 + } + + if typ == RTPCodecTypeAudio { + m.headerExtensions[extensionIndex].isAudio = true + } else if typ == RTPCodecTypeVideo { + m.headerExtensions[extensionIndex].isVideo = true + } + + m.headerExtensions[extensionIndex].uri = extension.URI + m.headerExtensions[extensionIndex].allowedDirections = allowedDirections + + return nil +} + +// RegisterFeedback adds feedback mechanism to already registered codecs. +func (m *MediaEngine) RegisterFeedback(feedback RTCPFeedback, typ RTPCodecType) { + switch typ { + case RTPCodecTypeVideo: + for i, v := range m.videoCodecs { + v.RTCPFeedback = append(v.RTCPFeedback, feedback) + m.videoCodecs[i] = v + } + case RTPCodecTypeAudio: + for i, v := range m.audioCodecs { + v.RTCPFeedback = append(v.RTCPFeedback, feedback) + m.audioCodecs[i] = v + } + } +} + +// getHeaderExtensionID returns the negotiated ID for a header extension. +// If the Header Extension isn't enabled ok will be false +func (m *MediaEngine) getHeaderExtensionID(extension RTPHeaderExtensionCapability) (val int, audioNegotiated, videoNegotiated bool) { + if m.negotiatedHeaderExtensions == nil { + return 0, false, false + } + + for id, h := range m.negotiatedHeaderExtensions { + if extension.URI == h.uri { + return id, h.isAudio, h.isVideo + } + } + + return +} + +// copy copies any user modifiable state of the MediaEngine +// all internal state is reset +func (m *MediaEngine) copy() *MediaEngine { + cloned := &MediaEngine{ + videoCodecs: append([]RTPCodecParameters{}, m.videoCodecs...), + audioCodecs: append([]RTPCodecParameters{}, m.audioCodecs...), + headerExtensions: append([]mediaEngineHeaderExtension{}, m.headerExtensions...), + } + if len(m.headerExtensions) > 0 { + cloned.negotiatedHeaderExtensions = map[int]mediaEngineHeaderExtension{} + } + return cloned +} + +func (m *MediaEngine) getCodecByPayload(payloadType PayloadType) (RTPCodecParameters, RTPCodecType, error) { + for _, codec := range m.negotiatedVideoCodecs { + if codec.PayloadType == payloadType { + return codec, RTPCodecTypeVideo, nil + } + } + for _, codec := range m.negotiatedAudioCodecs { + if codec.PayloadType == payloadType { + return codec, RTPCodecTypeAudio, nil + } + } + + return RTPCodecParameters{}, 0, ErrCodecNotFound +} + +func (m *MediaEngine) collectStats(collector *statsReportCollector) { + statsLoop := func(codecs []RTPCodecParameters) { + for _, codec := range codecs { + collector.Collecting() + stats := CodecStats{ + Timestamp: statsTimestampFrom(time.Now()), + Type: StatsTypeCodec, + ID: codec.statsID, + PayloadType: codec.PayloadType, + MimeType: codec.MimeType, + ClockRate: codec.ClockRate, + Channels: uint8(codec.Channels), + SDPFmtpLine: codec.SDPFmtpLine, + } + + collector.Collect(stats.ID, stats) + } + } + + statsLoop(m.videoCodecs) + statsLoop(m.audioCodecs) +} + +// Look up a codec and enable if it exists +func (m *MediaEngine) matchRemoteCodec(remoteCodec RTPCodecParameters, typ RTPCodecType) (codecMatchType, error) { + codecs := m.videoCodecs + if typ == RTPCodecTypeAudio { + codecs = m.audioCodecs + } + + if strings.HasPrefix(remoteCodec.RTPCodecCapability.SDPFmtpLine, "apt=") { + payloadType, err := strconv.Atoi(strings.TrimPrefix(remoteCodec.RTPCodecCapability.SDPFmtpLine, "apt=")) + if err != nil { + return codecMatchNone, err + } + + if _, _, err = m.getCodecByPayload(PayloadType(payloadType)); err != nil { + return codecMatchNone, nil // not an error, we just ignore this codec we don't support + } + } + + _, matchType := codecParametersFuzzySearch(remoteCodec, codecs) + return matchType, nil +} + +// Look up a header extension and enable if it exists +func (m *MediaEngine) updateHeaderExtension(id int, extension string, typ RTPCodecType) error { + if m.negotiatedHeaderExtensions == nil { + return nil + } + + for _, localExtension := range m.headerExtensions { + if localExtension.uri == extension { + h := mediaEngineHeaderExtension{uri: extension, allowedDirections: localExtension.allowedDirections} + if existingValue, ok := m.negotiatedHeaderExtensions[id]; ok { + h = existingValue + } + + switch { + case localExtension.isAudio && typ == RTPCodecTypeAudio: + h.isAudio = true + case localExtension.isVideo && typ == RTPCodecTypeVideo: + h.isVideo = true + } + + m.negotiatedHeaderExtensions[id] = h + } + } + return nil +} + +func (m *MediaEngine) pushCodecs(codecs []RTPCodecParameters, typ RTPCodecType) { + for _, codec := range codecs { + if typ == RTPCodecTypeAudio { + m.negotiatedAudioCodecs = m.addCodec(m.negotiatedAudioCodecs, codec) + } else if typ == RTPCodecTypeVideo { + m.negotiatedVideoCodecs = m.addCodec(m.negotiatedVideoCodecs, codec) + } + } +} + +// Update the MediaEngine from a remote description +func (m *MediaEngine) updateFromRemoteDescription(desc sdp.SessionDescription) error { + for _, media := range desc.MediaDescriptions { + var typ RTPCodecType + switch { + case !m.negotiatedAudio && strings.EqualFold(media.MediaName.Media, "audio"): + m.negotiatedAudio = true + typ = RTPCodecTypeAudio + case !m.negotiatedVideo && strings.EqualFold(media.MediaName.Media, "video"): + m.negotiatedVideo = true + typ = RTPCodecTypeVideo + default: + continue + } + + codecs, err := codecsFromMediaDescription(media) + if err != nil { + return err + } + + exactMatches := make([]RTPCodecParameters, 0, len(codecs)) + partialMatches := make([]RTPCodecParameters, 0, len(codecs)) + + for _, codec := range codecs { + matchType, mErr := m.matchRemoteCodec(codec, typ) + if mErr != nil { + return mErr + } + + if matchType == codecMatchExact { + exactMatches = append(exactMatches, codec) + } else if matchType == codecMatchPartial { + partialMatches = append(partialMatches, codec) + } + } + + // use exact matches when they exist, otherwise fall back to partial + switch { + case len(exactMatches) > 0: + m.pushCodecs(exactMatches, typ) + case len(partialMatches) > 0: + m.pushCodecs(partialMatches, typ) + default: + // no match, not negotiated + continue + } + + extensions, err := rtpExtensionsFromMediaDescription(media) + if err != nil { + return err + } + + for extension, id := range extensions { + if err = m.updateHeaderExtension(id, extension, typ); err != nil { + return err + } + } + } + return nil +} + +func (m *MediaEngine) getCodecsByKind(typ RTPCodecType) []RTPCodecParameters { + if typ == RTPCodecTypeVideo { + if m.negotiatedVideo { + return m.negotiatedVideoCodecs + } + + return m.videoCodecs + } else if typ == RTPCodecTypeAudio { + if m.negotiatedAudio { + return m.negotiatedAudioCodecs + } + + return m.audioCodecs + } + + return nil +} + +func (m *MediaEngine) getRTPParametersByKind(typ RTPCodecType, directions []RTPTransceiverDirection) RTPParameters { + headerExtensions := make([]RTPHeaderExtensionParameter, 0) + + if m.negotiatedVideo && typ == RTPCodecTypeVideo || + m.negotiatedAudio && typ == RTPCodecTypeAudio { + for id, e := range m.negotiatedHeaderExtensions { + if haveRTPTransceiverDirectionIntersection(e.allowedDirections, directions) && (e.isAudio && typ == RTPCodecTypeAudio || e.isVideo && typ == RTPCodecTypeVideo) { + headerExtensions = append(headerExtensions, RTPHeaderExtensionParameter{ID: id, URI: e.uri}) + } + } + } else { + for id, e := range m.headerExtensions { + if haveRTPTransceiverDirectionIntersection(e.allowedDirections, directions) && (e.isAudio && typ == RTPCodecTypeAudio || e.isVideo && typ == RTPCodecTypeVideo) { + headerExtensions = append(headerExtensions, RTPHeaderExtensionParameter{ID: id + 1, URI: e.uri}) + } + } + } + + return RTPParameters{ + HeaderExtensions: headerExtensions, + Codecs: m.getCodecsByKind(typ), + } +} + +func (m *MediaEngine) getRTPParametersByPayloadType(payloadType PayloadType) (RTPParameters, error) { + codec, typ, err := m.getCodecByPayload(payloadType) + if err != nil { + return RTPParameters{}, err + } + + headerExtensions := make([]RTPHeaderExtensionParameter, 0) + for id, e := range m.negotiatedHeaderExtensions { + if e.isAudio && typ == RTPCodecTypeAudio || e.isVideo && typ == RTPCodecTypeVideo { + headerExtensions = append(headerExtensions, RTPHeaderExtensionParameter{ID: id, URI: e.uri}) + } + } + + return RTPParameters{ + HeaderExtensions: headerExtensions, + Codecs: []RTPCodecParameters{codec}, + }, nil +} + +func payloaderForCodec(codec RTPCodecCapability) (rtp.Payloader, error) { + switch strings.ToLower(codec.MimeType) { + case strings.ToLower(MimeTypeH264): + return &codecs.H264Payloader{}, nil + case strings.ToLower(MimeTypeOpus): + return &codecs.OpusPayloader{}, nil + case strings.ToLower(MimeTypeVP8): + return &codecs.VP8Payloader{}, nil + case strings.ToLower(MimeTypeVP9): + return &codecs.VP9Payloader{}, nil + case strings.ToLower(MimeTypeG722): + return &codecs.G722Payloader{}, nil + case strings.ToLower(MimeTypePCMU), strings.ToLower(MimeTypePCMA): + return &codecs.G711Payloader{}, nil + default: + return nil, ErrNoPayloaderForCodec + } +} diff --git a/vendor/github.com/pion/webrtc/v3/networktype.go b/vendor/github.com/pion/webrtc/v3/networktype.go new file mode 100644 index 0000000..e5dd840 --- /dev/null +++ b/vendor/github.com/pion/webrtc/v3/networktype.go @@ -0,0 +1,104 @@ +package webrtc + +import ( + "fmt" + + "github.com/pion/ice/v2" +) + +func supportedNetworkTypes() []NetworkType { + return []NetworkType{ + NetworkTypeUDP4, + NetworkTypeUDP6, + // NetworkTypeTCP4, // Not supported yet + // NetworkTypeTCP6, // Not supported yet + } +} + +// NetworkType represents the type of network +type NetworkType int + +const ( + // NetworkTypeUDP4 indicates UDP over IPv4. + NetworkTypeUDP4 NetworkType = iota + 1 + + // NetworkTypeUDP6 indicates UDP over IPv6. + NetworkTypeUDP6 + + // NetworkTypeTCP4 indicates TCP over IPv4. + NetworkTypeTCP4 + + // NetworkTypeTCP6 indicates TCP over IPv6. + NetworkTypeTCP6 +) + +// This is done this way because of a linter. +const ( + networkTypeUDP4Str = "udp4" + networkTypeUDP6Str = "udp6" + networkTypeTCP4Str = "tcp4" + networkTypeTCP6Str = "tcp6" +) + +func (t NetworkType) String() string { + switch t { + case NetworkTypeUDP4: + return networkTypeUDP4Str + case NetworkTypeUDP6: + return networkTypeUDP6Str + case NetworkTypeTCP4: + return networkTypeTCP4Str + case NetworkTypeTCP6: + return networkTypeTCP6Str + default: + return ErrUnknownType.Error() + } +} + +// Protocol returns udp or tcp +func (t NetworkType) Protocol() string { + switch t { + case NetworkTypeUDP4: + return "udp" + case NetworkTypeUDP6: + return "udp" + case NetworkTypeTCP4: + return "tcp" + case NetworkTypeTCP6: + return "tcp" + default: + return ErrUnknownType.Error() + } +} + +// NewNetworkType allows create network type from string +// It will be useful for getting custom network types from external config. +func NewNetworkType(raw string) (NetworkType, error) { + switch raw { + case networkTypeUDP4Str: + return NetworkTypeUDP4, nil + case networkTypeUDP6Str: + return NetworkTypeUDP6, nil + case networkTypeTCP4Str: + return NetworkTypeTCP4, nil + case networkTypeTCP6Str: + return NetworkTypeTCP6, nil + default: + return NetworkType(Unknown), fmt.Errorf("%w: %s", errNetworkTypeUnknown, raw) + } +} + +func getNetworkType(iceNetworkType ice.NetworkType) (NetworkType, error) { + switch iceNetworkType { + case ice.NetworkTypeUDP4: + return NetworkTypeUDP4, nil + case ice.NetworkTypeUDP6: + return NetworkTypeUDP6, nil + case ice.NetworkTypeTCP4: + return NetworkTypeTCP4, nil + case ice.NetworkTypeTCP6: + return NetworkTypeTCP6, nil + default: + return NetworkType(Unknown), fmt.Errorf("%w: %s", errNetworkTypeUnknown, iceNetworkType.String()) + } +} diff --git a/vendor/github.com/pion/webrtc/v3/oauthcredential.go b/vendor/github.com/pion/webrtc/v3/oauthcredential.go new file mode 100644 index 0000000..46170c7 --- /dev/null +++ b/vendor/github.com/pion/webrtc/v3/oauthcredential.go @@ -0,0 +1,15 @@ +package webrtc + +// OAuthCredential represents OAuth credential information which is used by +// the STUN/TURN client to connect to an ICE server as defined in +// https://tools.ietf.org/html/rfc7635. Note that the kid parameter is not +// located in OAuthCredential, but in ICEServer's username member. +type OAuthCredential struct { + // MACKey is a base64-url encoded format. It is used in STUN message + // integrity hash calculation. + MACKey string + + // AccessToken is a base64-encoded format. This is an encrypted + // self-contained token that is opaque to the application. + AccessToken string +} diff --git a/vendor/github.com/pion/webrtc/v3/offeransweroptions.go b/vendor/github.com/pion/webrtc/v3/offeransweroptions.go new file mode 100644 index 0000000..2a34aed --- /dev/null +++ b/vendor/github.com/pion/webrtc/v3/offeransweroptions.go @@ -0,0 +1,26 @@ +package webrtc + +// OfferAnswerOptions is a base structure which describes the options that +// can be used to control the offer/answer creation process. +type OfferAnswerOptions struct { + // VoiceActivityDetection allows the application to provide information + // about whether it wishes voice detection feature to be enabled or disabled. + VoiceActivityDetection bool +} + +// AnswerOptions structure describes the options used to control the answer +// creation process. +type AnswerOptions struct { + OfferAnswerOptions +} + +// OfferOptions structure describes the options used to control the offer +// creation process +type OfferOptions struct { + OfferAnswerOptions + + // ICERestart forces the underlying ice gathering process to be restarted. + // When this value is true, the generated description will have ICE + // credentials that are different from the current credentials + ICERestart bool +} diff --git a/vendor/github.com/pion/webrtc/v3/operations.go b/vendor/github.com/pion/webrtc/v3/operations.go new file mode 100644 index 0000000..82bc832 --- /dev/null +++ b/vendor/github.com/pion/webrtc/v3/operations.go @@ -0,0 +1,87 @@ +package webrtc + +import ( + "sync" +) + +// Operation is a function +type operation func() + +// Operations is a task executor. +type operations struct { + mu sync.Mutex + busy bool + ops []operation +} + +func newOperations() *operations { + return &operations{} +} + +// Enqueue adds a new action to be executed. If there are no actions scheduled, +// the execution will start immediately in a new goroutine. +func (o *operations) Enqueue(op operation) { + if op == nil { + return + } + + o.mu.Lock() + running := o.busy + o.ops = append(o.ops, op) + o.busy = true + o.mu.Unlock() + + if !running { + go o.start() + } +} + +// IsEmpty checks if there are tasks in the queue +func (o *operations) IsEmpty() bool { + o.mu.Lock() + defer o.mu.Unlock() + return len(o.ops) == 0 +} + +// Done blocks until all currently enqueued operations are finished executing. +// For more complex synchronization, use Enqueue directly. +func (o *operations) Done() { + var wg sync.WaitGroup + wg.Add(1) + o.Enqueue(func() { + wg.Done() + }) + wg.Wait() +} + +func (o *operations) pop() func() { + o.mu.Lock() + defer o.mu.Unlock() + if len(o.ops) == 0 { + return nil + } + + fn := o.ops[0] + o.ops = o.ops[1:] + return fn +} + +func (o *operations) start() { + defer func() { + o.mu.Lock() + defer o.mu.Unlock() + if len(o.ops) == 0 { + o.busy = false + return + } + // either a new operation was enqueued while we + // were busy, or an operation panicked + go o.start() + }() + + fn := o.pop() + for fn != nil { + fn() + fn = o.pop() + } +} diff --git a/vendor/github.com/pion/webrtc/v3/package.json b/vendor/github.com/pion/webrtc/v3/package.json new file mode 100644 index 0000000..429f3ef --- /dev/null +++ b/vendor/github.com/pion/webrtc/v3/package.json @@ -0,0 +1,11 @@ +{ + "name": "webrtc", + "repository": "git@github.com:pion/webrtc.git", + "private": true, + "devDependencies": { + "wrtc": "0.4.7" + }, + "dependencies": { + "request": "2.88.2" + } +} diff --git a/vendor/github.com/pion/webrtc/v3/peerconnection.go b/vendor/github.com/pion/webrtc/v3/peerconnection.go new file mode 100644 index 0000000..3f02bba --- /dev/null +++ b/vendor/github.com/pion/webrtc/v3/peerconnection.go @@ -0,0 +1,2323 @@ +// +build !js + +package webrtc + +import ( + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "errors" + "fmt" + "io" + "strconv" + "strings" + "sync" + "sync/atomic" + "time" + + "github.com/pion/ice/v2" + "github.com/pion/interceptor" + "github.com/pion/logging" + "github.com/pion/rtcp" + "github.com/pion/sdp/v3" + "github.com/pion/webrtc/v3/internal/util" + "github.com/pion/webrtc/v3/pkg/rtcerr" +) + +// PeerConnection represents a WebRTC connection that establishes a +// peer-to-peer communications with another PeerConnection instance in a +// browser, or to another endpoint implementing the required protocols. +type PeerConnection struct { + statsID string + mu sync.RWMutex + + // ops is an operations queue which will ensure the enqueued actions are + // executed in order. It is used for asynchronously, but serially processing + // remote and local descriptions + ops *operations + + configuration Configuration + + currentLocalDescription *SessionDescription + pendingLocalDescription *SessionDescription + currentRemoteDescription *SessionDescription + pendingRemoteDescription *SessionDescription + signalingState SignalingState + iceConnectionState ICEConnectionState + connectionState PeerConnectionState + + idpLoginURL *string + + isClosed *atomicBool + isNegotiationNeeded *atomicBool + negotiationNeededState negotiationNeededState + + lastOffer string + lastAnswer string + + // a value containing the last known greater mid value + // we internally generate mids as numbers. Needed since JSEP + // requires that when reusing a media section a new unique mid + // should be defined (see JSEP 3.4.1). + greaterMid int + + rtpTransceivers []*RTPTransceiver + + onSignalingStateChangeHandler func(SignalingState) + onICEConnectionStateChangeHandler func(ICEConnectionState) + onConnectionStateChangeHandler func(PeerConnectionState) + onTrackHandler func(*TrackRemote, *RTPReceiver) + onDataChannelHandler func(*DataChannel) + onNegotiationNeededHandler atomic.Value // func() + + iceGatherer *ICEGatherer + iceTransport *ICETransport + dtlsTransport *DTLSTransport + sctpTransport *SCTPTransport + + // A reference to the associated API state used by this connection + api *API + log logging.LeveledLogger + + interceptorRTCPWriter interceptor.RTCPWriter +} + +// NewPeerConnection creates a PeerConnection with the default codecs and +// interceptors. See RegisterDefaultCodecs and RegisterDefaultInterceptors. +// +// If you wish to customize the set of available codecs or the set of +// active interceptors, create a MediaEngine and call api.NewPeerConnection +// instead of this function. +func NewPeerConnection(configuration Configuration) (*PeerConnection, error) { + m := &MediaEngine{} + if err := m.RegisterDefaultCodecs(); err != nil { + return nil, err + } + + i := &interceptor.Registry{} + if err := RegisterDefaultInterceptors(m, i); err != nil { + return nil, err + } + + api := NewAPI(WithMediaEngine(m), WithInterceptorRegistry(i)) + return api.NewPeerConnection(configuration) +} + +// NewPeerConnection creates a new PeerConnection with the provided configuration against the received API object +func (api *API) NewPeerConnection(configuration Configuration) (*PeerConnection, error) { + // https://w3c.github.io/webrtc-pc/#constructor (Step #2) + // Some variables defined explicitly despite their implicit zero values to + // allow better readability to understand what is happening. + pc := &PeerConnection{ + statsID: fmt.Sprintf("PeerConnection-%d", time.Now().UnixNano()), + configuration: Configuration{ + ICEServers: []ICEServer{}, + ICETransportPolicy: ICETransportPolicyAll, + BundlePolicy: BundlePolicyBalanced, + RTCPMuxPolicy: RTCPMuxPolicyRequire, + Certificates: []Certificate{}, + ICECandidatePoolSize: 0, + }, + ops: newOperations(), + isClosed: &atomicBool{}, + isNegotiationNeeded: &atomicBool{}, + negotiationNeededState: negotiationNeededStateEmpty, + lastOffer: "", + lastAnswer: "", + greaterMid: -1, + signalingState: SignalingStateStable, + iceConnectionState: ICEConnectionStateNew, + connectionState: PeerConnectionStateNew, + + api: api, + log: api.settingEngine.LoggerFactory.NewLogger("pc"), + } + + if !api.settingEngine.disableMediaEngineCopy { + pc.api = &API{ + settingEngine: api.settingEngine, + mediaEngine: api.mediaEngine.copy(), + interceptor: api.interceptor, + } + } + + var err error + if err = pc.initConfiguration(configuration); err != nil { + return nil, err + } + + pc.iceGatherer, err = pc.createICEGatherer() + if err != nil { + return nil, err + } + + // Create the ice transport + iceTransport := pc.createICETransport() + pc.iceTransport = iceTransport + + // Create the DTLS transport + dtlsTransport, err := pc.api.NewDTLSTransport(pc.iceTransport, pc.configuration.Certificates) + if err != nil { + return nil, err + } + pc.dtlsTransport = dtlsTransport + + // Create the SCTP transport + pc.sctpTransport = pc.api.NewSCTPTransport(pc.dtlsTransport) + + // Wire up the on datachannel handler + pc.sctpTransport.OnDataChannel(func(d *DataChannel) { + pc.mu.RLock() + handler := pc.onDataChannelHandler + pc.mu.RUnlock() + if handler != nil { + handler(d) + } + }) + + pc.interceptorRTCPWriter = api.interceptor.BindRTCPWriter(interceptor.RTCPWriterFunc(pc.writeRTCP)) + + return pc, nil +} + +// initConfiguration defines validation of the specified Configuration and +// its assignment to the internal configuration variable. This function differs +// from its SetConfiguration counterpart because most of the checks do not +// include verification statements related to the existing state. Thus the +// function describes only minor verification of some the struct variables. +func (pc *PeerConnection) initConfiguration(configuration Configuration) error { + if configuration.PeerIdentity != "" { + pc.configuration.PeerIdentity = configuration.PeerIdentity + } + + // https://www.w3.org/TR/webrtc/#constructor (step #3) + if len(configuration.Certificates) > 0 { + now := time.Now() + for _, x509Cert := range configuration.Certificates { + if !x509Cert.Expires().IsZero() && now.After(x509Cert.Expires()) { + return &rtcerr.InvalidAccessError{Err: ErrCertificateExpired} + } + pc.configuration.Certificates = append(pc.configuration.Certificates, x509Cert) + } + } else { + sk, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + return &rtcerr.UnknownError{Err: err} + } + certificate, err := GenerateCertificate(sk) + if err != nil { + return err + } + pc.configuration.Certificates = []Certificate{*certificate} + } + + if configuration.BundlePolicy != BundlePolicy(Unknown) { + pc.configuration.BundlePolicy = configuration.BundlePolicy + } + + if configuration.RTCPMuxPolicy != RTCPMuxPolicy(Unknown) { + pc.configuration.RTCPMuxPolicy = configuration.RTCPMuxPolicy + } + + if configuration.ICECandidatePoolSize != 0 { + pc.configuration.ICECandidatePoolSize = configuration.ICECandidatePoolSize + } + + if configuration.ICETransportPolicy != ICETransportPolicy(Unknown) { + pc.configuration.ICETransportPolicy = configuration.ICETransportPolicy + } + + if configuration.SDPSemantics != SDPSemantics(Unknown) { + pc.configuration.SDPSemantics = configuration.SDPSemantics + } + + sanitizedICEServers := configuration.getICEServers() + if len(sanitizedICEServers) > 0 { + for _, server := range sanitizedICEServers { + if err := server.validate(); err != nil { + return err + } + } + pc.configuration.ICEServers = sanitizedICEServers + } + + return nil +} + +// OnSignalingStateChange sets an event handler which is invoked when the +// peer connection's signaling state changes +func (pc *PeerConnection) OnSignalingStateChange(f func(SignalingState)) { + pc.mu.Lock() + defer pc.mu.Unlock() + pc.onSignalingStateChangeHandler = f +} + +func (pc *PeerConnection) onSignalingStateChange(newState SignalingState) { + pc.mu.RLock() + handler := pc.onSignalingStateChangeHandler + pc.mu.RUnlock() + + pc.log.Infof("signaling state changed to %s", newState) + if handler != nil { + go handler(newState) + } +} + +// OnDataChannel sets an event handler which is invoked when a data +// channel message arrives from a remote peer. +func (pc *PeerConnection) OnDataChannel(f func(*DataChannel)) { + pc.mu.Lock() + defer pc.mu.Unlock() + pc.onDataChannelHandler = f +} + +// OnNegotiationNeeded sets an event handler which is invoked when +// a change has occurred which requires session negotiation +func (pc *PeerConnection) OnNegotiationNeeded(f func()) { + pc.onNegotiationNeededHandler.Store(f) +} + +func (pc *PeerConnection) onNegotiationNeeded() { + // https://w3c.github.io/webrtc-pc/#updating-the-negotiation-needed-flag + // non-canon step 1 + pc.mu.Lock() + defer pc.mu.Unlock() + if pc.negotiationNeededState == negotiationNeededStateRun { + pc.negotiationNeededState = negotiationNeededStateQueue + return + } else if pc.negotiationNeededState == negotiationNeededStateQueue { + return + } + + pc.negotiationNeededState = negotiationNeededStateRun + + pc.ops.Enqueue(pc.negotiationNeededOp) +} + +func (pc *PeerConnection) negotiationNeededOp() { + // Don't run NegotiatedNeeded checks if OnNegotiationNeeded is not set + if handler := pc.onNegotiationNeededHandler.Load(); handler == nil { + return + } + + // https://www.w3.org/TR/webrtc/#updating-the-negotiation-needed-flag + // Step 2.1 + if pc.isClosed.get() { + return + } + // non-canon step 2.2 + if !pc.ops.IsEmpty() { + pc.ops.Enqueue(pc.negotiationNeededOp) + return + } + + // non-canon, run again if there was a request + defer func() { + pc.mu.Lock() + if pc.negotiationNeededState == negotiationNeededStateQueue { + defer pc.onNegotiationNeeded() + } + pc.negotiationNeededState = negotiationNeededStateEmpty + pc.mu.Unlock() + }() + + // Step 2.3 + if pc.SignalingState() != SignalingStateStable { + return + } + + // Step 2.4 + if !pc.checkNegotiationNeeded() { + pc.isNegotiationNeeded.set(false) + return + } + + // Step 2.5 + if pc.isNegotiationNeeded.get() { + return + } + + // Step 2.6 + pc.isNegotiationNeeded.set(true) + + // Step 2.7 + if handler, ok := pc.onNegotiationNeededHandler.Load().(func()); ok && handler != nil { + handler() + } +} + +func (pc *PeerConnection) checkNegotiationNeeded() bool { //nolint:gocognit + // To check if negotiation is needed for connection, perform the following checks: + // Skip 1, 2 steps + // Step 3 + pc.mu.Lock() + defer pc.mu.Unlock() + + localDesc := pc.currentLocalDescription + remoteDesc := pc.currentRemoteDescription + + if localDesc == nil { + return true + } + + pc.sctpTransport.lock.Lock() + lenDataChannel := len(pc.sctpTransport.dataChannels) + pc.sctpTransport.lock.Unlock() + + if lenDataChannel != 0 && haveDataChannel(localDesc) == nil { + return true + } + + for _, t := range pc.rtpTransceivers { + // https://www.w3.org/TR/webrtc/#dfn-update-the-negotiation-needed-flag + // Step 5.1 + // if t.stopping && !t.stopped { + // return true + // } + m := getByMid(t.Mid(), localDesc) + // Step 5.2 + if !t.stopped && m == nil { + return true + } + if !t.stopped && m != nil { + // Step 5.3.1 + if t.Direction() == RTPTransceiverDirectionSendrecv || t.Direction() == RTPTransceiverDirectionSendonly { + descMsid, okMsid := m.Attribute(sdp.AttrKeyMsid) + track := t.Sender().Track() + if !okMsid || descMsid != track.StreamID()+" "+track.ID() { + return true + } + } + switch localDesc.Type { + case SDPTypeOffer: + // Step 5.3.2 + rm := getByMid(t.Mid(), remoteDesc) + if rm == nil { + return true + } + + if getPeerDirection(m) != t.Direction() && getPeerDirection(rm) != t.Direction().Revers() { + return true + } + case SDPTypeAnswer: + // Step 5.3.3 + if _, ok := m.Attribute(t.Direction().String()); !ok { + return true + } + default: + } + } + // Step 5.4 + if t.stopped && t.Mid() != "" { + if getByMid(t.Mid(), localDesc) != nil || getByMid(t.Mid(), remoteDesc) != nil { + return true + } + } + } + // Step 6 + return false +} + +// OnICECandidate sets an event handler which is invoked when a new ICE +// candidate is found. +// Take note that the handler is gonna be called with a nil pointer when +// gathering is finished. +func (pc *PeerConnection) OnICECandidate(f func(*ICECandidate)) { + pc.iceGatherer.OnLocalCandidate(f) +} + +// OnICEGatheringStateChange sets an event handler which is invoked when the +// ICE candidate gathering state has changed. +func (pc *PeerConnection) OnICEGatheringStateChange(f func(ICEGathererState)) { + pc.iceGatherer.OnStateChange(f) +} + +// OnTrack sets an event handler which is called when remote track +// arrives from a remote peer. +func (pc *PeerConnection) OnTrack(f func(*TrackRemote, *RTPReceiver)) { + pc.mu.Lock() + defer pc.mu.Unlock() + pc.onTrackHandler = f +} + +func (pc *PeerConnection) onTrack(t *TrackRemote, r *RTPReceiver) { + pc.mu.RLock() + handler := pc.onTrackHandler + pc.mu.RUnlock() + + pc.log.Debugf("got new track: %+v", t) + if t != nil { + if handler != nil { + go handler(t, r) + } else { + pc.log.Warnf("OnTrack unset, unable to handle incoming media streams") + } + } +} + +// OnICEConnectionStateChange sets an event handler which is called +// when an ICE connection state is changed. +func (pc *PeerConnection) OnICEConnectionStateChange(f func(ICEConnectionState)) { + pc.mu.Lock() + defer pc.mu.Unlock() + pc.onICEConnectionStateChangeHandler = f +} + +func (pc *PeerConnection) onICEConnectionStateChange(cs ICEConnectionState) { + pc.mu.Lock() + pc.iceConnectionState = cs + handler := pc.onICEConnectionStateChangeHandler + pc.mu.Unlock() + + pc.log.Infof("ICE connection state changed: %s", cs) + if handler != nil { + go handler(cs) + } +} + +// OnConnectionStateChange sets an event handler which is called +// when the PeerConnectionState has changed +func (pc *PeerConnection) OnConnectionStateChange(f func(PeerConnectionState)) { + pc.mu.Lock() + defer pc.mu.Unlock() + pc.onConnectionStateChangeHandler = f +} + +// SetConfiguration updates the configuration of this PeerConnection object. +func (pc *PeerConnection) SetConfiguration(configuration Configuration) error { //nolint:gocognit + // https://www.w3.org/TR/webrtc/#dom-rtcpeerconnection-setconfiguration (step #2) + if pc.isClosed.get() { + return &rtcerr.InvalidStateError{Err: ErrConnectionClosed} + } + + // https://www.w3.org/TR/webrtc/#set-the-configuration (step #3) + if configuration.PeerIdentity != "" { + if configuration.PeerIdentity != pc.configuration.PeerIdentity { + return &rtcerr.InvalidModificationError{Err: ErrModifyingPeerIdentity} + } + pc.configuration.PeerIdentity = configuration.PeerIdentity + } + + // https://www.w3.org/TR/webrtc/#set-the-configuration (step #4) + if len(configuration.Certificates) > 0 { + if len(configuration.Certificates) != len(pc.configuration.Certificates) { + return &rtcerr.InvalidModificationError{Err: ErrModifyingCertificates} + } + + for i, certificate := range configuration.Certificates { + if !pc.configuration.Certificates[i].Equals(certificate) { + return &rtcerr.InvalidModificationError{Err: ErrModifyingCertificates} + } + } + pc.configuration.Certificates = configuration.Certificates + } + + // https://www.w3.org/TR/webrtc/#set-the-configuration (step #5) + if configuration.BundlePolicy != BundlePolicy(Unknown) { + if configuration.BundlePolicy != pc.configuration.BundlePolicy { + return &rtcerr.InvalidModificationError{Err: ErrModifyingBundlePolicy} + } + pc.configuration.BundlePolicy = configuration.BundlePolicy + } + + // https://www.w3.org/TR/webrtc/#set-the-configuration (step #6) + if configuration.RTCPMuxPolicy != RTCPMuxPolicy(Unknown) { + if configuration.RTCPMuxPolicy != pc.configuration.RTCPMuxPolicy { + return &rtcerr.InvalidModificationError{Err: ErrModifyingRTCPMuxPolicy} + } + pc.configuration.RTCPMuxPolicy = configuration.RTCPMuxPolicy + } + + // https://www.w3.org/TR/webrtc/#set-the-configuration (step #7) + if configuration.ICECandidatePoolSize != 0 { + if pc.configuration.ICECandidatePoolSize != configuration.ICECandidatePoolSize && + pc.LocalDescription() != nil { + return &rtcerr.InvalidModificationError{Err: ErrModifyingICECandidatePoolSize} + } + pc.configuration.ICECandidatePoolSize = configuration.ICECandidatePoolSize + } + + // https://www.w3.org/TR/webrtc/#set-the-configuration (step #8) + if configuration.ICETransportPolicy != ICETransportPolicy(Unknown) { + pc.configuration.ICETransportPolicy = configuration.ICETransportPolicy + } + + // https://www.w3.org/TR/webrtc/#set-the-configuration (step #11) + if len(configuration.ICEServers) > 0 { + // https://www.w3.org/TR/webrtc/#set-the-configuration (step #11.3) + for _, server := range configuration.ICEServers { + if err := server.validate(); err != nil { + return err + } + } + pc.configuration.ICEServers = configuration.ICEServers + } + return nil +} + +// GetConfiguration returns a Configuration object representing the current +// configuration of this PeerConnection object. The returned object is a +// copy and direct mutation on it will not take affect until SetConfiguration +// has been called with Configuration passed as its only argument. +// https://www.w3.org/TR/webrtc/#dom-rtcpeerconnection-getconfiguration +func (pc *PeerConnection) GetConfiguration() Configuration { + return pc.configuration +} + +func (pc *PeerConnection) getStatsID() string { + pc.mu.RLock() + defer pc.mu.RUnlock() + return pc.statsID +} + +func (pc *PeerConnection) hasLocalDescriptionChanged(desc *SessionDescription) bool { + for _, t := range pc.GetTransceivers() { + m := getByMid(t.Mid(), desc) + if m == nil { + return true + } + + if getPeerDirection(m) != t.Direction() { + return true + } + } + + return false +} + +var errExcessiveRetries = errors.New("excessive retries in CreateOffer") + +// CreateOffer starts the PeerConnection and generates the localDescription +// https://w3c.github.io/webrtc-pc/#dom-rtcpeerconnection-createoffer +func (pc *PeerConnection) CreateOffer(options *OfferOptions) (SessionDescription, error) { //nolint:gocognit + useIdentity := pc.idpLoginURL != nil + switch { + case useIdentity: + return SessionDescription{}, errIdentityProviderNotImplemented + case pc.isClosed.get(): + return SessionDescription{}, &rtcerr.InvalidStateError{Err: ErrConnectionClosed} + } + + if options != nil && options.ICERestart { + if err := pc.iceTransport.restart(); err != nil { + return SessionDescription{}, err + } + } + + var ( + d *sdp.SessionDescription + offer SessionDescription + err error + ) + + // This may be necessary to recompute if, for example, createOffer was called when only an + // audio RTCRtpTransceiver was added to connection, but while performing the in-parallel + // steps to create an offer, a video RTCRtpTransceiver was added, requiring additional + // inspection of video system resources. + count := 0 + for { + // We cache current transceivers to ensure they aren't + // mutated during offer generation. We later check if they have + // been mutated and recompute the offer if necessary. + currentTransceivers := pc.GetTransceivers() + + // in-parallel steps to create an offer + // https://w3c.github.io/webrtc-pc/#dfn-in-parallel-steps-to-create-an-offer + isPlanB := pc.configuration.SDPSemantics == SDPSemanticsPlanB + if pc.currentRemoteDescription != nil { + isPlanB = descriptionIsPlanB(pc.RemoteDescription()) + } + + // include unmatched local transceivers + if !isPlanB { + // update the greater mid if the remote description provides a greater one + if pc.currentRemoteDescription != nil { + var numericMid int + for _, media := range pc.currentRemoteDescription.parsed.MediaDescriptions { + mid := getMidValue(media) + if mid == "" { + continue + } + numericMid, err = strconv.Atoi(mid) + if err != nil { + continue + } + if numericMid > pc.greaterMid { + pc.greaterMid = numericMid + } + } + } + for _, t := range currentTransceivers { + if t.Mid() != "" { + continue + } + pc.greaterMid++ + err = t.setMid(strconv.Itoa(pc.greaterMid)) + if err != nil { + return SessionDescription{}, err + } + } + } + + if pc.currentRemoteDescription == nil { + d, err = pc.generateUnmatchedSDP(currentTransceivers, useIdentity) + } else { + d, err = pc.generateMatchedSDP(currentTransceivers, useIdentity, true /*includeUnmatched */, connectionRoleFromDtlsRole(defaultDtlsRoleOffer)) + } + + if err != nil { + return SessionDescription{}, err + } + + sdpBytes, err := d.Marshal() + if err != nil { + return SessionDescription{}, err + } + + offer = SessionDescription{ + Type: SDPTypeOffer, + SDP: string(sdpBytes), + parsed: d, + } + + // Verify local media hasn't changed during offer + // generation. Recompute if necessary + if isPlanB || !pc.hasLocalDescriptionChanged(&offer) { + break + } + count++ + if count >= 128 { + return SessionDescription{}, errExcessiveRetries + } + } + + pc.lastOffer = offer.SDP + return offer, nil +} + +func (pc *PeerConnection) createICEGatherer() (*ICEGatherer, error) { + g, err := pc.api.NewICEGatherer(ICEGatherOptions{ + ICEServers: pc.configuration.getICEServers(), + ICEGatherPolicy: pc.configuration.ICETransportPolicy, + }) + if err != nil { + return nil, err + } + + return g, nil +} + +// Update the PeerConnectionState given the state of relevant transports +// https://www.w3.org/TR/webrtc/#rtcpeerconnectionstate-enum +func (pc *PeerConnection) updateConnectionState(iceConnectionState ICEConnectionState, dtlsTransportState DTLSTransportState) { + pc.mu.Lock() + defer pc.mu.Unlock() + + connectionState := PeerConnectionStateNew + switch { + // The RTCPeerConnection object's [[IsClosed]] slot is true. + case pc.isClosed.get(): + connectionState = PeerConnectionStateClosed + + // Any of the RTCIceTransports or RTCDtlsTransports are in a "failed" state. + case iceConnectionState == ICEConnectionStateFailed || dtlsTransportState == DTLSTransportStateFailed: + connectionState = PeerConnectionStateFailed + + // Any of the RTCIceTransports or RTCDtlsTransports are in the "disconnected" + // state and none of them are in the "failed" or "connecting" or "checking" state. */ + case iceConnectionState == ICEConnectionStateDisconnected: + connectionState = PeerConnectionStateDisconnected + + // All RTCIceTransports and RTCDtlsTransports are in the "connected", "completed" or "closed" + // state and at least one of them is in the "connected" or "completed" state. + case iceConnectionState == ICEConnectionStateConnected && dtlsTransportState == DTLSTransportStateConnected: + connectionState = PeerConnectionStateConnected + + // Any of the RTCIceTransports or RTCDtlsTransports are in the "connecting" or + // "checking" state and none of them is in the "failed" state. + case iceConnectionState == ICEConnectionStateChecking && dtlsTransportState == DTLSTransportStateConnecting: + connectionState = PeerConnectionStateConnecting + } + + if pc.connectionState == connectionState { + return + } + + pc.log.Infof("peer connection state changed: %s", connectionState) + pc.connectionState = connectionState + handler := pc.onConnectionStateChangeHandler + if handler != nil { + go handler(connectionState) + } +} + +func (pc *PeerConnection) createICETransport() *ICETransport { + t := pc.api.NewICETransport(pc.iceGatherer) + t.OnConnectionStateChange(func(state ICETransportState) { + var cs ICEConnectionState + switch state { + case ICETransportStateNew: + cs = ICEConnectionStateNew + case ICETransportStateChecking: + cs = ICEConnectionStateChecking + case ICETransportStateConnected: + cs = ICEConnectionStateConnected + case ICETransportStateCompleted: + cs = ICEConnectionStateCompleted + case ICETransportStateFailed: + cs = ICEConnectionStateFailed + case ICETransportStateDisconnected: + cs = ICEConnectionStateDisconnected + case ICETransportStateClosed: + cs = ICEConnectionStateClosed + default: + pc.log.Warnf("OnConnectionStateChange: unhandled ICE state: %s", state) + return + } + pc.onICEConnectionStateChange(cs) + pc.updateConnectionState(cs, pc.dtlsTransport.State()) + }) + + return t +} + +// CreateAnswer starts the PeerConnection and generates the localDescription +func (pc *PeerConnection) CreateAnswer(options *AnswerOptions) (SessionDescription, error) { + useIdentity := pc.idpLoginURL != nil + switch { + case pc.RemoteDescription() == nil: + return SessionDescription{}, &rtcerr.InvalidStateError{Err: ErrNoRemoteDescription} + case useIdentity: + return SessionDescription{}, errIdentityProviderNotImplemented + case pc.isClosed.get(): + return SessionDescription{}, &rtcerr.InvalidStateError{Err: ErrConnectionClosed} + case pc.signalingState.Get() != SignalingStateHaveRemoteOffer && pc.signalingState.Get() != SignalingStateHaveLocalPranswer: + return SessionDescription{}, &rtcerr.InvalidStateError{Err: ErrIncorrectSignalingState} + } + + connectionRole := connectionRoleFromDtlsRole(pc.api.settingEngine.answeringDTLSRole) + if connectionRole == sdp.ConnectionRole(0) { + connectionRole = connectionRoleFromDtlsRole(defaultDtlsRoleAnswer) + } + + currentTransceivers := pc.GetTransceivers() + d, err := pc.generateMatchedSDP(currentTransceivers, useIdentity, false /*includeUnmatched */, connectionRole) + if err != nil { + return SessionDescription{}, err + } + + sdpBytes, err := d.Marshal() + if err != nil { + return SessionDescription{}, err + } + + desc := SessionDescription{ + Type: SDPTypeAnswer, + SDP: string(sdpBytes), + parsed: d, + } + pc.lastAnswer = desc.SDP + return desc, nil +} + +// 4.4.1.6 Set the SessionDescription +func (pc *PeerConnection) setDescription(sd *SessionDescription, op stateChangeOp) error { //nolint:gocognit + switch { + case pc.isClosed.get(): + return &rtcerr.InvalidStateError{Err: ErrConnectionClosed} + case NewSDPType(sd.Type.String()) == SDPType(Unknown): + return &rtcerr.TypeError{Err: fmt.Errorf("%w: '%d' is not a valid enum value of type SDPType", errPeerConnSDPTypeInvalidValue, sd.Type)} + } + + nextState, err := func() (SignalingState, error) { + pc.mu.Lock() + defer pc.mu.Unlock() + + cur := pc.SignalingState() + setLocal := stateChangeOpSetLocal + setRemote := stateChangeOpSetRemote + newSDPDoesNotMatchOffer := &rtcerr.InvalidModificationError{Err: errSDPDoesNotMatchOffer} + newSDPDoesNotMatchAnswer := &rtcerr.InvalidModificationError{Err: errSDPDoesNotMatchAnswer} + + var nextState SignalingState + var err error + switch op { + case setLocal: + switch sd.Type { + // stable->SetLocal(offer)->have-local-offer + case SDPTypeOffer: + if sd.SDP != pc.lastOffer { + return nextState, newSDPDoesNotMatchOffer + } + nextState, err = checkNextSignalingState(cur, SignalingStateHaveLocalOffer, setLocal, sd.Type) + if err == nil { + pc.pendingLocalDescription = sd + } + // have-remote-offer->SetLocal(answer)->stable + // have-local-pranswer->SetLocal(answer)->stable + case SDPTypeAnswer: + if sd.SDP != pc.lastAnswer { + return nextState, newSDPDoesNotMatchAnswer + } + nextState, err = checkNextSignalingState(cur, SignalingStateStable, setLocal, sd.Type) + if err == nil { + pc.currentLocalDescription = sd + pc.currentRemoteDescription = pc.pendingRemoteDescription + pc.pendingRemoteDescription = nil + pc.pendingLocalDescription = nil + } + case SDPTypeRollback: + nextState, err = checkNextSignalingState(cur, SignalingStateStable, setLocal, sd.Type) + if err == nil { + pc.pendingLocalDescription = nil + } + // have-remote-offer->SetLocal(pranswer)->have-local-pranswer + case SDPTypePranswer: + if sd.SDP != pc.lastAnswer { + return nextState, newSDPDoesNotMatchAnswer + } + nextState, err = checkNextSignalingState(cur, SignalingStateHaveLocalPranswer, setLocal, sd.Type) + if err == nil { + pc.pendingLocalDescription = sd + } + default: + return nextState, &rtcerr.OperationError{Err: fmt.Errorf("%w: %s(%s)", errPeerConnStateChangeInvalid, op, sd.Type)} + } + case setRemote: + switch sd.Type { + // stable->SetRemote(offer)->have-remote-offer + case SDPTypeOffer: + nextState, err = checkNextSignalingState(cur, SignalingStateHaveRemoteOffer, setRemote, sd.Type) + if err == nil { + pc.pendingRemoteDescription = sd + } + // have-local-offer->SetRemote(answer)->stable + // have-remote-pranswer->SetRemote(answer)->stable + case SDPTypeAnswer: + nextState, err = checkNextSignalingState(cur, SignalingStateStable, setRemote, sd.Type) + if err == nil { + pc.currentRemoteDescription = sd + pc.currentLocalDescription = pc.pendingLocalDescription + pc.pendingRemoteDescription = nil + pc.pendingLocalDescription = nil + } + case SDPTypeRollback: + nextState, err = checkNextSignalingState(cur, SignalingStateStable, setRemote, sd.Type) + if err == nil { + pc.pendingRemoteDescription = nil + } + // have-local-offer->SetRemote(pranswer)->have-remote-pranswer + case SDPTypePranswer: + nextState, err = checkNextSignalingState(cur, SignalingStateHaveRemotePranswer, setRemote, sd.Type) + if err == nil { + pc.pendingRemoteDescription = sd + } + default: + return nextState, &rtcerr.OperationError{Err: fmt.Errorf("%w: %s(%s)", errPeerConnStateChangeInvalid, op, sd.Type)} + } + default: + return nextState, &rtcerr.OperationError{Err: fmt.Errorf("%w: %q", errPeerConnStateChangeUnhandled, op)} + } + + return nextState, err + }() + + if err == nil { + pc.signalingState.Set(nextState) + if pc.signalingState.Get() == SignalingStateStable { + pc.isNegotiationNeeded.set(false) + pc.onNegotiationNeeded() + } + pc.onSignalingStateChange(nextState) + } + return err +} + +// SetLocalDescription sets the SessionDescription of the local peer +func (pc *PeerConnection) SetLocalDescription(desc SessionDescription) error { + if pc.isClosed.get() { + return &rtcerr.InvalidStateError{Err: ErrConnectionClosed} + } + + haveLocalDescription := pc.currentLocalDescription != nil + + // JSEP 5.4 + if desc.SDP == "" { + switch desc.Type { + case SDPTypeAnswer, SDPTypePranswer: + desc.SDP = pc.lastAnswer + case SDPTypeOffer: + desc.SDP = pc.lastOffer + default: + return &rtcerr.InvalidModificationError{ + Err: fmt.Errorf("%w: %s", errPeerConnSDPTypeInvalidValueSetLocalDescription, desc.Type), + } + } + } + + desc.parsed = &sdp.SessionDescription{} + if err := desc.parsed.Unmarshal([]byte(desc.SDP)); err != nil { + return err + } + if err := pc.setDescription(&desc, stateChangeOpSetLocal); err != nil { + return err + } + + currentTransceivers := append([]*RTPTransceiver{}, pc.GetTransceivers()...) + + weAnswer := desc.Type == SDPTypeAnswer + remoteDesc := pc.RemoteDescription() + if weAnswer && remoteDesc != nil { + if err := pc.startRTPSenders(currentTransceivers); err != nil { + return err + } + pc.ops.Enqueue(func() { + pc.startRTP(haveLocalDescription, remoteDesc, currentTransceivers) + }) + } + + if pc.iceGatherer.State() == ICEGathererStateNew { + return pc.iceGatherer.Gather() + } + return nil +} + +// LocalDescription returns PendingLocalDescription if it is not null and +// otherwise it returns CurrentLocalDescription. This property is used to +// determine if SetLocalDescription has already been called. +// https://www.w3.org/TR/webrtc/#dom-rtcpeerconnection-localdescription +func (pc *PeerConnection) LocalDescription() *SessionDescription { + if pendingLocalDescription := pc.PendingLocalDescription(); pendingLocalDescription != nil { + return pendingLocalDescription + } + return pc.CurrentLocalDescription() +} + +// SetRemoteDescription sets the SessionDescription of the remote peer +// nolint: gocyclo +func (pc *PeerConnection) SetRemoteDescription(desc SessionDescription) error { //nolint:gocognit + if pc.isClosed.get() { + return &rtcerr.InvalidStateError{Err: ErrConnectionClosed} + } + + isRenegotation := pc.currentRemoteDescription != nil + + if _, err := desc.Unmarshal(); err != nil { + return err + } + if err := pc.setDescription(&desc, stateChangeOpSetRemote); err != nil { + return err + } + + if err := pc.api.mediaEngine.updateFromRemoteDescription(*desc.parsed); err != nil { + return err + } + + var t *RTPTransceiver + localTransceivers := append([]*RTPTransceiver{}, pc.GetTransceivers()...) + detectedPlanB := descriptionIsPlanB(pc.RemoteDescription()) + weOffer := desc.Type == SDPTypeAnswer + + if !weOffer && !detectedPlanB { + for _, media := range pc.RemoteDescription().parsed.MediaDescriptions { + midValue := getMidValue(media) + if midValue == "" { + return errPeerConnRemoteDescriptionWithoutMidValue + } + + if media.MediaName.Media == mediaSectionApplication { + continue + } + + kind := NewRTPCodecType(media.MediaName.Media) + direction := getPeerDirection(media) + if kind == 0 || direction == RTPTransceiverDirection(Unknown) { + continue + } + + t, localTransceivers = findByMid(midValue, localTransceivers) + if t == nil { + t, localTransceivers = satisfyTypeAndDirection(kind, direction, localTransceivers) + } else if direction == RTPTransceiverDirectionInactive { + if err := t.Stop(); err != nil { + return err + } + } + + if t == nil { + receiver, err := pc.api.NewRTPReceiver(kind, pc.dtlsTransport) + if err != nil { + return err + } + + localDirection := RTPTransceiverDirectionRecvonly + if direction == RTPTransceiverDirectionRecvonly { + localDirection = RTPTransceiverDirectionSendonly + } + + t = pc.newRTPTransceiver(receiver, nil, localDirection, kind) + + pc.onNegotiationNeeded() + } else if direction == RTPTransceiverDirectionRecvonly { + if t.Direction() == RTPTransceiverDirectionSendrecv { + t.setDirection(RTPTransceiverDirectionSendonly) + } + } + + if t.Mid() == "" { + if err := t.setMid(midValue); err != nil { + return err + } + } + } + } + + remoteUfrag, remotePwd, candidates, err := extractICEDetails(desc.parsed) + if err != nil { + return err + } + + if isRenegotation && pc.iceTransport.haveRemoteCredentialsChange(remoteUfrag, remotePwd) { + // An ICE Restart only happens implicitly for a SetRemoteDescription of type offer + if !weOffer { + if err = pc.iceTransport.restart(); err != nil { + return err + } + } + + if err = pc.iceTransport.setRemoteCredentials(remoteUfrag, remotePwd); err != nil { + return err + } + } + + for i := range candidates { + if err = pc.iceTransport.AddRemoteCandidate(&candidates[i]); err != nil { + return err + } + } + + currentTransceivers := append([]*RTPTransceiver{}, pc.GetTransceivers()...) + + if isRenegotation { + if weOffer { + if err = pc.startRTPSenders(currentTransceivers); err != nil { + return err + } + pc.ops.Enqueue(func() { + pc.startRTP(true, &desc, currentTransceivers) + }) + } + return nil + } + + remoteIsLite := false + for _, a := range desc.parsed.Attributes { + if strings.TrimSpace(a.Key) == sdp.AttrKeyICELite { + remoteIsLite = true + } + } + + fingerprint, fingerprintHash, err := extractFingerprint(desc.parsed) + if err != nil { + return err + } + + iceRole := ICERoleControlled + // If one of the agents is lite and the other one is not, the lite agent must be the controlling agent. + // If both or neither agents are lite the offering agent is controlling. + // RFC 8445 S6.1.1 + if (weOffer && remoteIsLite == pc.api.settingEngine.candidates.ICELite) || (remoteIsLite && !pc.api.settingEngine.candidates.ICELite) { + iceRole = ICERoleControlling + } + + // Start the networking in a new routine since it will block until + // the connection is actually established. + if weOffer { + if err := pc.startRTPSenders(currentTransceivers); err != nil { + return err + } + } + + pc.ops.Enqueue(func() { + pc.startTransports(iceRole, dtlsRoleFromRemoteSDP(desc.parsed), remoteUfrag, remotePwd, fingerprint, fingerprintHash) + if weOffer { + pc.startRTP(false, &desc, currentTransceivers) + } + }) + return nil +} + +func (pc *PeerConnection) startReceiver(incoming trackDetails, receiver *RTPReceiver) { + encodings := []RTPDecodingParameters{} + if incoming.ssrc != 0 { + encodings = append(encodings, RTPDecodingParameters{RTPCodingParameters{SSRC: incoming.ssrc}}) + } + for _, rid := range incoming.rids { + encodings = append(encodings, RTPDecodingParameters{RTPCodingParameters{RID: rid}}) + } + + if err := receiver.Receive(RTPReceiveParameters{Encodings: encodings}); err != nil { + pc.log.Warnf("RTPReceiver Receive failed %s", err) + return + } + + // set track id and label early so they can be set as new track information + // is received from the SDP. + for i := range receiver.tracks { + receiver.tracks[i].track.mu.Lock() + receiver.tracks[i].track.id = incoming.id + receiver.tracks[i].track.streamID = incoming.streamID + receiver.tracks[i].track.mu.Unlock() + } + + // We can't block and wait for a single SSRC + if incoming.ssrc == 0 { + return + } + + go func() { + if err := receiver.Track().determinePayloadType(); err != nil { + pc.log.Warnf("Could not determine PayloadType for SSRC %d", receiver.Track().SSRC()) + return + } + + params, err := pc.api.mediaEngine.getRTPParametersByPayloadType(receiver.Track().PayloadType()) + if err != nil { + pc.log.Warnf("no codec could be found for payloadType %d", receiver.Track().PayloadType()) + return + } + + receiver.Track().mu.Lock() + receiver.Track().kind = receiver.kind + receiver.Track().codec = params.Codecs[0] + receiver.Track().params = params + receiver.Track().mu.Unlock() + + pc.onTrack(receiver.Track(), receiver) + }() +} + +// startRTPReceivers opens knows inbound SRTP streams from the RemoteDescription +func (pc *PeerConnection) startRTPReceivers(incomingTracks []trackDetails, currentTransceivers []*RTPTransceiver) { //nolint:gocognit + localTransceivers := append([]*RTPTransceiver{}, currentTransceivers...) + + remoteIsPlanB := false + switch pc.configuration.SDPSemantics { + case SDPSemanticsPlanB: + remoteIsPlanB = true + case SDPSemanticsUnifiedPlanWithFallback: + remoteIsPlanB = descriptionIsPlanB(pc.RemoteDescription()) + default: + // none + } + + // Ensure we haven't already started a transceiver for this ssrc + for i := range incomingTracks { + if len(incomingTracks) <= i { + break + } + incomingTrack := incomingTracks[i] + + for _, t := range localTransceivers { + if (t.Receiver()) == nil || t.Receiver().Track() == nil || t.Receiver().Track().ssrc != incomingTrack.ssrc { + continue + } + + incomingTracks = filterTrackWithSSRC(incomingTracks, incomingTrack.ssrc) + } + } + + unhandledTracks := incomingTracks[:0] + for i := range incomingTracks { + trackHandled := false + for j := range localTransceivers { + t := localTransceivers[j] + incomingTrack := incomingTracks[i] + + if t.Mid() != incomingTrack.mid { + continue + } + + if (incomingTrack.kind != t.kind) || + (t.Direction() != RTPTransceiverDirectionRecvonly && t.Direction() != RTPTransceiverDirectionSendrecv) || + (t.Receiver()) == nil || + (t.Receiver().haveReceived()) { + continue + } + + pc.startReceiver(incomingTrack, t.Receiver()) + trackHandled = true + break + } + + if !trackHandled { + unhandledTracks = append(unhandledTracks, incomingTracks[i]) + } + } + + if remoteIsPlanB { + for _, incoming := range unhandledTracks { + t, err := pc.AddTransceiverFromKind(incoming.kind, RtpTransceiverInit{ + Direction: RTPTransceiverDirectionSendrecv, + }) + if err != nil { + pc.log.Warnf("Could not add transceiver for remote SSRC %d: %s", incoming.ssrc, err) + continue + } + pc.startReceiver(incoming, t.Receiver()) + } + } +} + +// startRTPSenders starts all outbound RTP streams +func (pc *PeerConnection) startRTPSenders(currentTransceivers []*RTPTransceiver) error { + for _, transceiver := range currentTransceivers { + if transceiver.Sender() != nil && transceiver.Sender().isNegotiated() && !transceiver.Sender().hasSent() { + err := transceiver.Sender().Send(RTPSendParameters{ + Encodings: []RTPEncodingParameters{ + { + RTPCodingParameters{ + SSRC: transceiver.Sender().ssrc, + PayloadType: transceiver.Sender().payloadType, + }, + }, + }, + }) + if err != nil { + return err + } + } + } + + return nil +} + +// Start SCTP subsystem +func (pc *PeerConnection) startSCTP() { + // Start sctp + if err := pc.sctpTransport.Start(SCTPCapabilities{ + MaxMessageSize: 0, + }); err != nil { + pc.log.Warnf("Failed to start SCTP: %s", err) + if err = pc.sctpTransport.Stop(); err != nil { + pc.log.Warnf("Failed to stop SCTPTransport: %s", err) + } + + return + } + + // DataChannels that need to be opened now that SCTP is available + // make a copy we may have incoming DataChannels mutating this while we open + pc.sctpTransport.lock.RLock() + dataChannels := append([]*DataChannel{}, pc.sctpTransport.dataChannels...) + pc.sctpTransport.lock.RUnlock() + + var openedDCCount uint32 + for _, d := range dataChannels { + if d.ReadyState() == DataChannelStateConnecting { + err := d.open(pc.sctpTransport) + if err != nil { + pc.log.Warnf("failed to open data channel: %s", err) + continue + } + openedDCCount++ + } + } + + pc.sctpTransport.lock.Lock() + pc.sctpTransport.dataChannelsOpened += openedDCCount + pc.sctpTransport.lock.Unlock() +} + +func (pc *PeerConnection) handleUndeclaredSSRC(rtpStream io.Reader, ssrc SSRC) error { //nolint:gocognit + remoteDescription := pc.RemoteDescription() + if remoteDescription == nil { + return errPeerConnRemoteDescriptionNil + } + + // If the remote SDP was only one media section the ssrc doesn't have to be explicitly declared + if len(remoteDescription.parsed.MediaDescriptions) == 1 { + onlyMediaSection := remoteDescription.parsed.MediaDescriptions[0] + for _, a := range onlyMediaSection.Attributes { + if a.Key == ssrcStr { + return errPeerConnSingleMediaSectionHasExplicitSSRC + } + } + + incoming := trackDetails{ + ssrc: ssrc, + kind: RTPCodecTypeVideo, + } + if onlyMediaSection.MediaName.Media == RTPCodecTypeAudio.String() { + incoming.kind = RTPCodecTypeAudio + } + + t, err := pc.AddTransceiverFromKind(incoming.kind, RtpTransceiverInit{ + Direction: RTPTransceiverDirectionSendrecv, + }) + if err != nil { + return fmt.Errorf("%w: %d: %s", errPeerConnRemoteSSRCAddTransceiver, ssrc, err) + } + pc.startReceiver(incoming, t.Receiver()) + return nil + } + + midExtensionID, audioSupported, videoSupported := pc.api.mediaEngine.getHeaderExtensionID(RTPHeaderExtensionCapability{sdp.SDESMidURI}) + if !audioSupported && !videoSupported { + return errPeerConnSimulcastMidRTPExtensionRequired + } + + streamIDExtensionID, audioSupported, videoSupported := pc.api.mediaEngine.getHeaderExtensionID(RTPHeaderExtensionCapability{sdp.SDESRTPStreamIDURI}) + if !audioSupported && !videoSupported { + return errPeerConnSimulcastStreamIDRTPExtensionRequired + } + + b := make([]byte, receiveMTU) + var mid, rid string + for readCount := 0; readCount <= simulcastProbeCount; readCount++ { + i, err := rtpStream.Read(b) + if err != nil { + return err + } + + maybeMid, maybeRid, payloadType, err := handleUnknownRTPPacket(b[:i], uint8(midExtensionID), uint8(streamIDExtensionID)) + if err != nil { + return err + } + + if maybeMid != "" { + mid = maybeMid + } + if maybeRid != "" { + rid = maybeRid + } + + if mid == "" || rid == "" { + continue + } + + params, err := pc.api.mediaEngine.getRTPParametersByPayloadType(payloadType) + if err != nil { + return err + } + + for _, t := range pc.GetTransceivers() { + if t.Mid() != mid || t.Receiver() == nil { + continue + } + + track, err := t.Receiver().receiveForRid(rid, params, ssrc) + if err != nil { + return err + } + pc.onTrack(track, t.Receiver()) + return nil + } + } + + return errPeerConnSimulcastIncomingSSRCFailed +} + +// undeclaredMediaProcessor handles RTP/RTCP packets that don't match any a:ssrc lines +func (pc *PeerConnection) undeclaredMediaProcessor() { + go func() { + var simulcastRoutineCount uint64 + for { + srtpSession, err := pc.dtlsTransport.getSRTPSession() + if err != nil { + pc.log.Warnf("undeclaredMediaProcessor failed to open SrtpSession: %v", err) + return + } + + stream, ssrc, err := srtpSession.AcceptStream() + if err != nil { + pc.log.Warnf("Failed to accept RTP %v", err) + return + } + + if pc.isClosed.get() { + if err = stream.Close(); err != nil { + pc.log.Warnf("Failed to close RTP stream %v", err) + } + continue + } + + if atomic.AddUint64(&simulcastRoutineCount, 1) >= simulcastMaxProbeRoutines { + atomic.AddUint64(&simulcastRoutineCount, ^uint64(0)) + pc.log.Warn(ErrSimulcastProbeOverflow.Error()) + continue + } + + go func(rtpStream io.Reader, ssrc SSRC) { + pc.dtlsTransport.storeSimulcastStream(stream) + + if err := pc.handleUndeclaredSSRC(rtpStream, ssrc); err != nil { + pc.log.Errorf("Incoming unhandled RTP ssrc(%d), OnTrack will not be fired. %v", ssrc, err) + } + atomic.AddUint64(&simulcastRoutineCount, ^uint64(0)) + }(stream, SSRC(ssrc)) + } + }() + + go func() { + for { + srtcpSession, err := pc.dtlsTransport.getSRTCPSession() + if err != nil { + pc.log.Warnf("undeclaredMediaProcessor failed to open SrtcpSession: %v", err) + return + } + + _, ssrc, err := srtcpSession.AcceptStream() + if err != nil { + pc.log.Warnf("Failed to accept RTCP %v", err) + return + } + pc.log.Warnf("Incoming unhandled RTCP ssrc(%d), OnTrack will not be fired", ssrc) + } + }() +} + +// RemoteDescription returns pendingRemoteDescription if it is not null and +// otherwise it returns currentRemoteDescription. This property is used to +// determine if setRemoteDescription has already been called. +// https://www.w3.org/TR/webrtc/#dom-rtcpeerconnection-remotedescription +func (pc *PeerConnection) RemoteDescription() *SessionDescription { + pc.mu.RLock() + defer pc.mu.RUnlock() + + if pc.pendingRemoteDescription != nil { + return pc.pendingRemoteDescription + } + return pc.currentRemoteDescription +} + +// AddICECandidate accepts an ICE candidate string and adds it +// to the existing set of candidates. +func (pc *PeerConnection) AddICECandidate(candidate ICECandidateInit) error { + if pc.RemoteDescription() == nil { + return &rtcerr.InvalidStateError{Err: ErrNoRemoteDescription} + } + + candidateValue := strings.TrimPrefix(candidate.Candidate, "candidate:") + + var iceCandidate *ICECandidate + if candidateValue != "" { + candidate, err := ice.UnmarshalCandidate(candidateValue) + if err != nil { + return err + } + + c, err := newICECandidateFromICE(candidate) + if err != nil { + return err + } + iceCandidate = &c + } + + return pc.iceTransport.AddRemoteCandidate(iceCandidate) +} + +// ICEConnectionState returns the ICE connection state of the +// PeerConnection instance. +func (pc *PeerConnection) ICEConnectionState() ICEConnectionState { + pc.mu.RLock() + defer pc.mu.RUnlock() + + return pc.iceConnectionState +} + +// GetSenders returns the RTPSender that are currently attached to this PeerConnection +func (pc *PeerConnection) GetSenders() []*RTPSender { + pc.mu.Lock() + defer pc.mu.Unlock() + + result := []*RTPSender{} + for _, transceiver := range pc.rtpTransceivers { + if transceiver.Sender() != nil { + result = append(result, transceiver.Sender()) + } + } + return result +} + +// GetReceivers returns the RTPReceivers that are currently attached to this PeerConnection +func (pc *PeerConnection) GetReceivers() (receivers []*RTPReceiver) { + pc.mu.Lock() + defer pc.mu.Unlock() + + for _, transceiver := range pc.rtpTransceivers { + if transceiver.Receiver() != nil { + receivers = append(receivers, transceiver.Receiver()) + } + } + return +} + +// GetTransceivers returns the RtpTransceiver that are currently attached to this PeerConnection +func (pc *PeerConnection) GetTransceivers() []*RTPTransceiver { + pc.mu.Lock() + defer pc.mu.Unlock() + + return pc.rtpTransceivers +} + +// AddTrack adds a Track to the PeerConnection +func (pc *PeerConnection) AddTrack(track TrackLocal) (*RTPSender, error) { + if pc.isClosed.get() { + return nil, &rtcerr.InvalidStateError{Err: ErrConnectionClosed} + } + + var transceiver *RTPTransceiver + for _, t := range pc.GetTransceivers() { + if !t.stopped && t.kind == track.Kind() && t.Sender() == nil { + transceiver = t + break + } + } + if transceiver != nil { + sender, err := pc.api.NewRTPSender(track, pc.dtlsTransport) + if err != nil { + return nil, err + } + transceiver.setSender(sender) + // we still need to call setSendingTrack to ensure direction has changed + if err := transceiver.setSendingTrack(track); err != nil { + return nil, err + } + pc.onNegotiationNeeded() + + return sender, nil + } + + transceiver, err := pc.AddTransceiverFromTrack(track) + if err != nil { + return nil, err + } + + return transceiver.Sender(), nil +} + +// RemoveTrack removes a Track from the PeerConnection +func (pc *PeerConnection) RemoveTrack(sender *RTPSender) error { + if pc.isClosed.get() { + return &rtcerr.InvalidStateError{Err: ErrConnectionClosed} + } + + var transceiver *RTPTransceiver + for _, t := range pc.GetTransceivers() { + if t.Sender() == sender { + transceiver = t + break + } + } + + if transceiver == nil { + return &rtcerr.InvalidAccessError{Err: ErrSenderNotCreatedByConnection} + } else if err := sender.Stop(); err != nil { + return err + } + + if err := transceiver.setSendingTrack(nil); err != nil { + return err + } + + pc.onNegotiationNeeded() + + return nil +} + +// AddTransceiverFromKind Create a new RtpTransceiver(SendRecv or RecvOnly) and add it to the set of transceivers. +func (pc *PeerConnection) AddTransceiverFromKind(kind RTPCodecType, init ...RtpTransceiverInit) (*RTPTransceiver, error) { + if pc.isClosed.get() { + return nil, &rtcerr.InvalidStateError{Err: ErrConnectionClosed} + } + + direction := RTPTransceiverDirectionSendrecv + if len(init) > 1 { + return nil, errPeerConnAddTransceiverFromKindOnlyAcceptsOne + } else if len(init) == 1 { + direction = init[0].Direction + } + + switch direction { + case RTPTransceiverDirectionSendrecv: + codecs := pc.api.mediaEngine.getCodecsByKind(kind) + if len(codecs) == 0 { + return nil, ErrNoCodecsAvailable + } + + track, err := NewTrackLocalStaticSample(codecs[0].RTPCodecCapability, util.MathRandAlpha(16), util.MathRandAlpha(16)) + if err != nil { + return nil, err + } + + return pc.AddTransceiverFromTrack(track, init...) + case RTPTransceiverDirectionRecvonly: + receiver, err := pc.api.NewRTPReceiver(kind, pc.dtlsTransport) + if err != nil { + return nil, err + } + + t := pc.newRTPTransceiver( + receiver, + nil, + RTPTransceiverDirectionRecvonly, + kind, + ) + + pc.onNegotiationNeeded() + + return t, nil + default: + return nil, errPeerConnAddTransceiverFromKindSupport + } +} + +// AddTransceiverFromTrack Create a new RtpTransceiver(SendRecv or SendOnly) and add it to the set of transceivers. +func (pc *PeerConnection) AddTransceiverFromTrack(track TrackLocal, init ...RtpTransceiverInit) (*RTPTransceiver, error) { + if pc.isClosed.get() { + return nil, &rtcerr.InvalidStateError{Err: ErrConnectionClosed} + } + + direction := RTPTransceiverDirectionSendrecv + if len(init) > 1 { + return nil, errPeerConnAddTransceiverFromTrackOnlyAcceptsOne + } else if len(init) == 1 { + direction = init[0].Direction + } + + switch direction { + case RTPTransceiverDirectionSendrecv: + receiver, err := pc.api.NewRTPReceiver(track.Kind(), pc.dtlsTransport) + if err != nil { + return nil, err + } + + sender, err := pc.api.NewRTPSender(track, pc.dtlsTransport) + if err != nil { + return nil, err + } + + t := pc.newRTPTransceiver( + receiver, + sender, + RTPTransceiverDirectionSendrecv, + track.Kind(), + ) + + pc.onNegotiationNeeded() + + return t, nil + + case RTPTransceiverDirectionSendonly: + sender, err := pc.api.NewRTPSender(track, pc.dtlsTransport) + if err != nil { + return nil, err + } + + t := pc.newRTPTransceiver( + nil, + sender, + RTPTransceiverDirectionSendonly, + track.Kind(), + ) + + pc.onNegotiationNeeded() + + return t, nil + default: + return nil, errPeerConnAddTransceiverFromTrackSupport + } +} + +// CreateDataChannel creates a new DataChannel object with the given label +// and optional DataChannelInit used to configure properties of the +// underlying channel such as data reliability. +func (pc *PeerConnection) CreateDataChannel(label string, options *DataChannelInit) (*DataChannel, error) { + // https://w3c.github.io/webrtc-pc/#peer-to-peer-data-api (Step #2) + if pc.isClosed.get() { + return nil, &rtcerr.InvalidStateError{Err: ErrConnectionClosed} + } + + params := &DataChannelParameters{ + Label: label, + Ordered: true, + } + + // https://w3c.github.io/webrtc-pc/#peer-to-peer-data-api (Step #19) + if options != nil { + params.ID = options.ID + } + + if options != nil { + // Ordered indicates if data is allowed to be delivered out of order. The + // default value of true, guarantees that data will be delivered in order. + // https://w3c.github.io/webrtc-pc/#peer-to-peer-data-api (Step #9) + if options.Ordered != nil { + params.Ordered = *options.Ordered + } + + // https://w3c.github.io/webrtc-pc/#peer-to-peer-data-api (Step #7) + if options.MaxPacketLifeTime != nil { + params.MaxPacketLifeTime = options.MaxPacketLifeTime + } + + // https://w3c.github.io/webrtc-pc/#peer-to-peer-data-api (Step #8) + if options.MaxRetransmits != nil { + params.MaxRetransmits = options.MaxRetransmits + } + + // https://w3c.github.io/webrtc-pc/#peer-to-peer-data-api (Step #10) + if options.Protocol != nil { + params.Protocol = *options.Protocol + } + + // https://w3c.github.io/webrtc-pc/#peer-to-peer-data-api (Step #11) + if len(params.Protocol) > 65535 { + return nil, &rtcerr.TypeError{Err: ErrProtocolTooLarge} + } + + // https://w3c.github.io/webrtc-pc/#peer-to-peer-data-api (Step #12) + if options.Negotiated != nil { + params.Negotiated = *options.Negotiated + } + } + + d, err := pc.api.newDataChannel(params, pc.log) + if err != nil { + return nil, err + } + + // https://w3c.github.io/webrtc-pc/#peer-to-peer-data-api (Step #16) + if d.maxPacketLifeTime != nil && d.maxRetransmits != nil { + return nil, &rtcerr.TypeError{Err: ErrRetransmitsOrPacketLifeTime} + } + + pc.sctpTransport.lock.Lock() + pc.sctpTransport.dataChannels = append(pc.sctpTransport.dataChannels, d) + pc.sctpTransport.dataChannelsRequested++ + pc.sctpTransport.lock.Unlock() + + // If SCTP already connected open all the channels + if pc.sctpTransport.State() == SCTPTransportStateConnected { + if err = d.open(pc.sctpTransport); err != nil { + return nil, err + } + } + + pc.onNegotiationNeeded() + + return d, nil +} + +// SetIdentityProvider is used to configure an identity provider to generate identity assertions +func (pc *PeerConnection) SetIdentityProvider(provider string) error { + return errPeerConnSetIdentityProviderNotImplemented +} + +// WriteRTCP sends a user provided RTCP packet to the connected peer. If no peer is connected the +// packet is discarded. It also runs any configured interceptors. +func (pc *PeerConnection) WriteRTCP(pkts []rtcp.Packet) error { + _, err := pc.interceptorRTCPWriter.Write(pkts, make(interceptor.Attributes)) + return err +} + +func (pc *PeerConnection) writeRTCP(pkts []rtcp.Packet, _ interceptor.Attributes) (int, error) { + raw, err := rtcp.Marshal(pkts) + if err != nil { + return 0, err + } + + srtcpSession, err := pc.dtlsTransport.getSRTCPSession() + if err != nil { + return 0, nil + } + + writeStream, err := srtcpSession.OpenWriteStream() + if err != nil { + return 0, fmt.Errorf("%w: %v", errPeerConnWriteRTCPOpenWriteStream, err) + } + + if n, err := writeStream.Write(raw); err != nil { + return n, err + } + return 0, nil +} + +// Close ends the PeerConnection +func (pc *PeerConnection) Close() error { + // https://www.w3.org/TR/webrtc/#dom-rtcpeerconnection-close (step #1) + if pc.isClosed.get() { + return nil + } + + // https://www.w3.org/TR/webrtc/#dom-rtcpeerconnection-close (step #2) + pc.isClosed.set(true) + + // https://www.w3.org/TR/webrtc/#dom-rtcpeerconnection-close (step #3) + pc.signalingState.Set(SignalingStateClosed) + + // Try closing everything and collect the errors + // Shutdown strategy: + // 1. All Conn close by closing their underlying Conn. + // 2. A Mux stops this chain. It won't close the underlying + // Conn if one of the endpoints is closed down. To + // continue the chain the Mux has to be closed. + closeErrs := make([]error, 4) + + closeErrs = append(closeErrs, pc.api.interceptor.Close()) + + // https://www.w3.org/TR/webrtc/#dom-rtcpeerconnection-close (step #4) + for _, t := range pc.GetTransceivers() { + if !t.stopped { + closeErrs = append(closeErrs, t.Stop()) + } + } + + // https://www.w3.org/TR/webrtc/#dom-rtcpeerconnection-close (step #5) + pc.sctpTransport.lock.Lock() + for _, d := range pc.sctpTransport.dataChannels { + d.setReadyState(DataChannelStateClosed) + } + pc.sctpTransport.lock.Unlock() + + // https://www.w3.org/TR/webrtc/#dom-rtcpeerconnection-close (step #6) + if pc.sctpTransport != nil { + closeErrs = append(closeErrs, pc.sctpTransport.Stop()) + } + + // https://www.w3.org/TR/webrtc/#dom-rtcpeerconnection-close (step #7) + closeErrs = append(closeErrs, pc.dtlsTransport.Stop()) + + // https://www.w3.org/TR/webrtc/#dom-rtcpeerconnection-close (step #8, #9, #10) + if pc.iceTransport != nil { + closeErrs = append(closeErrs, pc.iceTransport.Stop()) + } + + // https://www.w3.org/TR/webrtc/#dom-rtcpeerconnection-close (step #11) + pc.updateConnectionState(pc.ICEConnectionState(), pc.dtlsTransport.State()) + + return util.FlattenErrs(closeErrs) +} + +func (pc *PeerConnection) newRTPTransceiver( + receiver *RTPReceiver, + sender *RTPSender, + direction RTPTransceiverDirection, + kind RTPCodecType, +) *RTPTransceiver { + t := &RTPTransceiver{kind: kind} + t.setReceiver(receiver) + t.setSender(sender) + t.setDirection(direction) + + pc.mu.Lock() + pc.rtpTransceivers = append(pc.rtpTransceivers, t) + pc.mu.Unlock() + + return t +} + +// CurrentLocalDescription represents the local description that was +// successfully negotiated the last time the PeerConnection transitioned +// into the stable state plus any local candidates that have been generated +// by the ICEAgent since the offer or answer was created. +func (pc *PeerConnection) CurrentLocalDescription() *SessionDescription { + pc.mu.Lock() + defer pc.mu.Unlock() + return populateLocalCandidates(pc.currentLocalDescription, pc.iceGatherer, pc.ICEGatheringState()) +} + +// PendingLocalDescription represents a local description that is in the +// process of being negotiated plus any local candidates that have been +// generated by the ICEAgent since the offer or answer was created. If the +// PeerConnection is in the stable state, the value is null. +func (pc *PeerConnection) PendingLocalDescription() *SessionDescription { + pc.mu.Lock() + defer pc.mu.Unlock() + return populateLocalCandidates(pc.pendingLocalDescription, pc.iceGatherer, pc.ICEGatheringState()) +} + +// CurrentRemoteDescription represents the last remote description that was +// successfully negotiated the last time the PeerConnection transitioned +// into the stable state plus any remote candidates that have been supplied +// via AddICECandidate() since the offer or answer was created. +func (pc *PeerConnection) CurrentRemoteDescription() *SessionDescription { + return pc.currentRemoteDescription +} + +// PendingRemoteDescription represents a remote description that is in the +// process of being negotiated, complete with any remote candidates that +// have been supplied via AddICECandidate() since the offer or answer was +// created. If the PeerConnection is in the stable state, the value is +// null. +func (pc *PeerConnection) PendingRemoteDescription() *SessionDescription { + return pc.pendingRemoteDescription +} + +// SignalingState attribute returns the signaling state of the +// PeerConnection instance. +func (pc *PeerConnection) SignalingState() SignalingState { + return pc.signalingState.Get() +} + +// ICEGatheringState attribute returns the ICE gathering state of the +// PeerConnection instance. +func (pc *PeerConnection) ICEGatheringState() ICEGatheringState { + if pc.iceGatherer == nil { + return ICEGatheringStateNew + } + + switch pc.iceGatherer.State() { + case ICEGathererStateNew: + return ICEGatheringStateNew + case ICEGathererStateGathering: + return ICEGatheringStateGathering + default: + return ICEGatheringStateComplete + } +} + +// ConnectionState attribute returns the connection state of the +// PeerConnection instance. +func (pc *PeerConnection) ConnectionState() PeerConnectionState { + pc.mu.Lock() + defer pc.mu.Unlock() + + return pc.connectionState +} + +// GetStats return data providing statistics about the overall connection +func (pc *PeerConnection) GetStats() StatsReport { + var ( + dataChannelsAccepted uint32 + dataChannelsClosed uint32 + dataChannelsOpened uint32 + dataChannelsRequested uint32 + ) + statsCollector := newStatsReportCollector() + statsCollector.Collecting() + + pc.mu.Lock() + if pc.iceGatherer != nil { + pc.iceGatherer.collectStats(statsCollector) + } + if pc.iceTransport != nil { + pc.iceTransport.collectStats(statsCollector) + } + + pc.sctpTransport.lock.Lock() + dataChannels := append([]*DataChannel{}, pc.sctpTransport.dataChannels...) + dataChannelsAccepted = pc.sctpTransport.dataChannelsAccepted + dataChannelsOpened = pc.sctpTransport.dataChannelsOpened + dataChannelsRequested = pc.sctpTransport.dataChannelsRequested + pc.sctpTransport.lock.Unlock() + + for _, d := range dataChannels { + state := d.ReadyState() + if state != DataChannelStateConnecting && state != DataChannelStateOpen { + dataChannelsClosed++ + } + + d.collectStats(statsCollector) + } + pc.sctpTransport.collectStats(statsCollector) + + stats := PeerConnectionStats{ + Timestamp: statsTimestampNow(), + Type: StatsTypePeerConnection, + ID: pc.statsID, + DataChannelsAccepted: dataChannelsAccepted, + DataChannelsClosed: dataChannelsClosed, + DataChannelsOpened: dataChannelsOpened, + DataChannelsRequested: dataChannelsRequested, + } + + statsCollector.Collect(stats.ID, stats) + + certificates := pc.configuration.Certificates + for _, certificate := range certificates { + if err := certificate.collectStats(statsCollector); err != nil { + continue + } + } + pc.mu.Unlock() + + pc.api.mediaEngine.collectStats(statsCollector) + + return statsCollector.Ready() +} + +// Start all transports. PeerConnection now has enough state +func (pc *PeerConnection) startTransports(iceRole ICERole, dtlsRole DTLSRole, remoteUfrag, remotePwd, fingerprint, fingerprintHash string) { + // Start the ice transport + err := pc.iceTransport.Start( + pc.iceGatherer, + ICEParameters{ + UsernameFragment: remoteUfrag, + Password: remotePwd, + ICELite: false, + }, + &iceRole, + ) + if err != nil { + pc.log.Warnf("Failed to start manager: %s", err) + return + } + + // Start the dtls transport + err = pc.dtlsTransport.Start(DTLSParameters{ + Role: dtlsRole, + Fingerprints: []DTLSFingerprint{{Algorithm: fingerprintHash, Value: fingerprint}}, + }) + pc.updateConnectionState(pc.ICEConnectionState(), pc.dtlsTransport.State()) + if err != nil { + pc.log.Warnf("Failed to start manager: %s", err) + return + } +} + +func (pc *PeerConnection) startRTP(isRenegotiation bool, remoteDesc *SessionDescription, currentTransceivers []*RTPTransceiver) { + trackDetails := trackDetailsFromSDP(pc.log, remoteDesc.parsed) + if isRenegotiation { + for _, t := range currentTransceivers { + if t.Receiver() == nil || t.Receiver().Track() == nil { + continue + } + + t.Receiver().Track().mu.Lock() + ssrc := t.Receiver().Track().ssrc + + if details := trackDetailsForSSRC(trackDetails, ssrc); details != nil { + t.Receiver().Track().id = details.id + t.Receiver().Track().streamID = details.streamID + t.Receiver().Track().mu.Unlock() + continue + } + + t.Receiver().Track().mu.Unlock() + + if err := t.Receiver().Stop(); err != nil { + pc.log.Warnf("Failed to stop RtpReceiver: %s", err) + continue + } + + receiver, err := pc.api.NewRTPReceiver(t.Receiver().kind, pc.dtlsTransport) + if err != nil { + pc.log.Warnf("Failed to create new RtpReceiver: %s", err) + continue + } + t.setReceiver(receiver) + } + } + + pc.startRTPReceivers(trackDetails, currentTransceivers) + if haveApplicationMediaSection(remoteDesc.parsed) { + pc.startSCTP() + } + + if !isRenegotiation { + pc.undeclaredMediaProcessor() + } +} + +// generateUnmatchedSDP generates an SDP that doesn't take remote state into account +// This is used for the initial call for CreateOffer +func (pc *PeerConnection) generateUnmatchedSDP(transceivers []*RTPTransceiver, useIdentity bool) (*sdp.SessionDescription, error) { + d, err := sdp.NewJSEPSessionDescription(useIdentity) + if err != nil { + return nil, err + } + + iceParams, err := pc.iceGatherer.GetLocalParameters() + if err != nil { + return nil, err + } + + candidates, err := pc.iceGatherer.GetLocalCandidates() + if err != nil { + return nil, err + } + + isPlanB := pc.configuration.SDPSemantics == SDPSemanticsPlanB + mediaSections := []mediaSection{} + + // Needed for pc.sctpTransport.dataChannelsRequested + pc.sctpTransport.lock.Lock() + defer pc.sctpTransport.lock.Unlock() + + if isPlanB { + video := make([]*RTPTransceiver, 0) + audio := make([]*RTPTransceiver, 0) + + for _, t := range transceivers { + if t.kind == RTPCodecTypeVideo { + video = append(video, t) + } else if t.kind == RTPCodecTypeAudio { + audio = append(audio, t) + } + if t.Sender() != nil { + t.Sender().setNegotiated() + } + } + + if len(video) > 0 { + mediaSections = append(mediaSections, mediaSection{id: "video", transceivers: video}) + } + if len(audio) > 0 { + mediaSections = append(mediaSections, mediaSection{id: "audio", transceivers: audio}) + } + + if pc.sctpTransport.dataChannelsRequested != 0 { + mediaSections = append(mediaSections, mediaSection{id: "data", data: true}) + } + } else { + for _, t := range transceivers { + if t.Sender() != nil { + t.Sender().setNegotiated() + } + mediaSections = append(mediaSections, mediaSection{id: t.Mid(), transceivers: []*RTPTransceiver{t}}) + } + + if pc.sctpTransport.dataChannelsRequested != 0 { + mediaSections = append(mediaSections, mediaSection{id: strconv.Itoa(len(mediaSections)), data: true}) + } + } + + dtlsFingerprints, err := pc.configuration.Certificates[0].GetFingerprints() + if err != nil { + return nil, err + } + + return populateSDP(d, isPlanB, dtlsFingerprints, pc.api.settingEngine.sdpMediaLevelFingerprints, pc.api.settingEngine.candidates.ICELite, pc.api.mediaEngine, connectionRoleFromDtlsRole(defaultDtlsRoleOffer), candidates, iceParams, mediaSections, pc.ICEGatheringState()) +} + +// generateMatchedSDP generates a SDP and takes the remote state into account +// this is used everytime we have a RemoteDescription +// nolint: gocyclo +func (pc *PeerConnection) generateMatchedSDP(transceivers []*RTPTransceiver, useIdentity bool, includeUnmatched bool, connectionRole sdp.ConnectionRole) (*sdp.SessionDescription, error) { //nolint:gocognit + d, err := sdp.NewJSEPSessionDescription(useIdentity) + if err != nil { + return nil, err + } + + iceParams, err := pc.iceGatherer.GetLocalParameters() + if err != nil { + return nil, err + } + + candidates, err := pc.iceGatherer.GetLocalCandidates() + if err != nil { + return nil, err + } + + var t *RTPTransceiver + localTransceivers := append([]*RTPTransceiver{}, transceivers...) + detectedPlanB := descriptionIsPlanB(pc.RemoteDescription()) + mediaSections := []mediaSection{} + alreadyHaveApplicationMediaSection := false + + for _, media := range pc.RemoteDescription().parsed.MediaDescriptions { + midValue := getMidValue(media) + if midValue == "" { + return nil, errPeerConnRemoteDescriptionWithoutMidValue + } + + if media.MediaName.Media == mediaSectionApplication { + mediaSections = append(mediaSections, mediaSection{id: midValue, data: true}) + alreadyHaveApplicationMediaSection = true + continue + } + + kind := NewRTPCodecType(media.MediaName.Media) + direction := getPeerDirection(media) + if kind == 0 || direction == RTPTransceiverDirection(Unknown) { + continue + } + + sdpSemantics := pc.configuration.SDPSemantics + + switch { + case sdpSemantics == SDPSemanticsPlanB || sdpSemantics == SDPSemanticsUnifiedPlanWithFallback && detectedPlanB: + if !detectedPlanB { + return nil, &rtcerr.TypeError{Err: ErrIncorrectSDPSemantics} + } + // If we're responding to a plan-b offer, then we should try to fill up this + // media entry with all matching local transceivers + mediaTransceivers := []*RTPTransceiver{} + for { + // keep going until we can't get any more + t, localTransceivers = satisfyTypeAndDirection(kind, direction, localTransceivers) + if t == nil { + if len(mediaTransceivers) == 0 { + t = &RTPTransceiver{kind: kind} + t.setDirection(RTPTransceiverDirectionInactive) + mediaTransceivers = append(mediaTransceivers, t) + } + break + } + if t.Sender() != nil { + t.Sender().setNegotiated() + } + mediaTransceivers = append(mediaTransceivers, t) + } + mediaSections = append(mediaSections, mediaSection{id: midValue, transceivers: mediaTransceivers}) + case sdpSemantics == SDPSemanticsUnifiedPlan || sdpSemantics == SDPSemanticsUnifiedPlanWithFallback: + if detectedPlanB { + return nil, &rtcerr.TypeError{Err: ErrIncorrectSDPSemantics} + } + t, localTransceivers = findByMid(midValue, localTransceivers) + if t == nil { + return nil, fmt.Errorf("%w: %q", errPeerConnTranscieverMidNil, midValue) + } + if t.Sender() != nil { + t.Sender().setNegotiated() + } + mediaTransceivers := []*RTPTransceiver{t} + mediaSections = append(mediaSections, mediaSection{id: midValue, transceivers: mediaTransceivers, ridMap: getRids(media)}) + } + } + + // If we are offering also include unmatched local transceivers + if includeUnmatched { + if !detectedPlanB { + for _, t := range localTransceivers { + if t.Sender() != nil { + t.Sender().setNegotiated() + } + mediaSections = append(mediaSections, mediaSection{id: t.Mid(), transceivers: []*RTPTransceiver{t}}) + } + } + + if pc.sctpTransport.dataChannelsRequested != 0 && !alreadyHaveApplicationMediaSection { + if detectedPlanB { + mediaSections = append(mediaSections, mediaSection{id: "data", data: true}) + } else { + mediaSections = append(mediaSections, mediaSection{id: strconv.Itoa(len(mediaSections)), data: true}) + } + } + } + + if pc.configuration.SDPSemantics == SDPSemanticsUnifiedPlanWithFallback && detectedPlanB { + pc.log.Info("Plan-B Offer detected; responding with Plan-B Answer") + } + + dtlsFingerprints, err := pc.configuration.Certificates[0].GetFingerprints() + if err != nil { + return nil, err + } + + return populateSDP(d, detectedPlanB, dtlsFingerprints, pc.api.settingEngine.sdpMediaLevelFingerprints, pc.api.settingEngine.candidates.ICELite, pc.api.mediaEngine, connectionRole, candidates, iceParams, mediaSections, pc.ICEGatheringState()) +} + +func (pc *PeerConnection) setGatherCompleteHandler(handler func()) { + pc.iceGatherer.onGatheringCompleteHandler.Store(handler) +} + +// SCTP returns the SCTPTransport for this PeerConnection +// +// The SCTP transport over which SCTP data is sent and received. If SCTP has not been negotiated, the value is nil. +// https://www.w3.org/TR/webrtc/#attributes-15 +func (pc *PeerConnection) SCTP() *SCTPTransport { + return pc.sctpTransport +} diff --git a/vendor/github.com/pion/webrtc/v3/peerconnection_js.go b/vendor/github.com/pion/webrtc/v3/peerconnection_js.go new file mode 100644 index 0000000..c293194 --- /dev/null +++ b/vendor/github.com/pion/webrtc/v3/peerconnection_js.go @@ -0,0 +1,676 @@ +// +build js,wasm + +// Package webrtc implements the WebRTC 1.0 as defined in W3C WebRTC specification document. +package webrtc + +import ( + "syscall/js" + + "github.com/pion/ice/v2" + "github.com/pion/webrtc/v3/pkg/rtcerr" +) + +// PeerConnection represents a WebRTC connection that establishes a +// peer-to-peer communications with another PeerConnection instance in a +// browser, or to another endpoint implementing the required protocols. +type PeerConnection struct { + // Pointer to the underlying JavaScript RTCPeerConnection object. + underlying js.Value + + // Keep track of handlers/callbacks so we can call Release as required by the + // syscall/js API. Initially nil. + onSignalingStateChangeHandler *js.Func + onDataChannelHandler *js.Func + onNegotiationNeededHandler *js.Func + onConnectionStateChangeHandler *js.Func + onICEConnectionStateChangeHandler *js.Func + onICECandidateHandler *js.Func + onICEGatheringStateChangeHandler *js.Func + + // Used by GatheringCompletePromise + onGatherCompleteHandler func() + + // A reference to the associated API state used by this connection + api *API +} + +// NewPeerConnection creates a peerconnection. +func NewPeerConnection(configuration Configuration) (*PeerConnection, error) { + api := NewAPI() + return api.NewPeerConnection(configuration) +} + +// NewPeerConnection creates a new PeerConnection with the provided configuration against the received API object +func (api *API) NewPeerConnection(configuration Configuration) (_ *PeerConnection, err error) { + defer func() { + if e := recover(); e != nil { + err = recoveryToError(e) + } + }() + configMap := configurationToValue(configuration) + underlying := js.Global().Get("window").Get("RTCPeerConnection").New(configMap) + return &PeerConnection{ + underlying: underlying, + api: api, + }, nil +} + +func (pc *PeerConnection) JSValue() js.Value { + return pc.underlying +} + +// OnSignalingStateChange sets an event handler which is invoked when the +// peer connection's signaling state changes +func (pc *PeerConnection) OnSignalingStateChange(f func(SignalingState)) { + if pc.onSignalingStateChangeHandler != nil { + oldHandler := pc.onSignalingStateChangeHandler + defer oldHandler.Release() + } + onSignalingStateChangeHandler := js.FuncOf(func(this js.Value, args []js.Value) interface{} { + state := newSignalingState(args[0].String()) + go f(state) + return js.Undefined() + }) + pc.onSignalingStateChangeHandler = &onSignalingStateChangeHandler + pc.underlying.Set("onsignalingstatechange", onSignalingStateChangeHandler) +} + +// OnDataChannel sets an event handler which is invoked when a data +// channel message arrives from a remote peer. +func (pc *PeerConnection) OnDataChannel(f func(*DataChannel)) { + if pc.onDataChannelHandler != nil { + oldHandler := pc.onDataChannelHandler + defer oldHandler.Release() + } + onDataChannelHandler := js.FuncOf(func(this js.Value, args []js.Value) interface{} { + // pion/webrtc/projects/15 + // This reference to the underlying DataChannel doesn't know + // about any other references to the same DataChannel. This might result in + // memory leaks where we don't clean up handler functions. Could possibly fix + // by keeping a mutex-protected list of all DataChannel references as a + // property of this PeerConnection, but at the cost of additional overhead. + dataChannel := &DataChannel{ + underlying: args[0].Get("channel"), + api: pc.api, + } + go f(dataChannel) + return js.Undefined() + }) + pc.onDataChannelHandler = &onDataChannelHandler + pc.underlying.Set("ondatachannel", onDataChannelHandler) +} + +// OnNegotiationNeeded sets an event handler which is invoked when +// a change has occurred which requires session negotiation +func (pc *PeerConnection) OnNegotiationNeeded(f func()) { + if pc.onNegotiationNeededHandler != nil { + oldHandler := pc.onNegotiationNeededHandler + defer oldHandler.Release() + } + onNegotiationNeededHandler := js.FuncOf(func(this js.Value, args []js.Value) interface{} { + go f() + return js.Undefined() + }) + pc.onNegotiationNeededHandler = &onNegotiationNeededHandler + pc.underlying.Set("onnegotiationneeded", onNegotiationNeededHandler) +} + +// OnICEConnectionStateChange sets an event handler which is called +// when an ICE connection state is changed. +func (pc *PeerConnection) OnICEConnectionStateChange(f func(ICEConnectionState)) { + if pc.onICEConnectionStateChangeHandler != nil { + oldHandler := pc.onICEConnectionStateChangeHandler + defer oldHandler.Release() + } + onICEConnectionStateChangeHandler := js.FuncOf(func(this js.Value, args []js.Value) interface{} { + connectionState := NewICEConnectionState(pc.underlying.Get("iceConnectionState").String()) + go f(connectionState) + return js.Undefined() + }) + pc.onICEConnectionStateChangeHandler = &onICEConnectionStateChangeHandler + pc.underlying.Set("oniceconnectionstatechange", onICEConnectionStateChangeHandler) +} + +// OnConnectionStateChange sets an event handler which is called +// when an PeerConnectionState is changed. +func (pc *PeerConnection) OnConnectionStateChange(f func(PeerConnectionState)) { + if pc.onConnectionStateChangeHandler != nil { + oldHandler := pc.onConnectionStateChangeHandler + defer oldHandler.Release() + } + onConnectionStateChangeHandler := js.FuncOf(func(this js.Value, args []js.Value) interface{} { + connectionState := newPeerConnectionState(pc.underlying.Get("connectionState").String()) + go f(connectionState) + return js.Undefined() + }) + pc.onConnectionStateChangeHandler = &onConnectionStateChangeHandler + pc.underlying.Set("onconnectionstatechange", onConnectionStateChangeHandler) +} + +func (pc *PeerConnection) checkConfiguration(configuration Configuration) error { + // https://www.w3.org/TR/webrtc/#dom-rtcpeerconnection-setconfiguration (step #2) + if pc.ConnectionState() == PeerConnectionStateClosed { + return &rtcerr.InvalidStateError{Err: ErrConnectionClosed} + } + + existingConfig := pc.GetConfiguration() + // https://www.w3.org/TR/webrtc/#set-the-configuration (step #3) + if configuration.PeerIdentity != "" { + if configuration.PeerIdentity != existingConfig.PeerIdentity { + return &rtcerr.InvalidModificationError{Err: ErrModifyingPeerIdentity} + } + } + + // https://github.com/pion/webrtc/issues/513 + // https://www.w3.org/TR/webrtc/#set-the-configuration (step #4) + // if len(configuration.Certificates) > 0 { + // if len(configuration.Certificates) != len(existingConfiguration.Certificates) { + // return &rtcerr.InvalidModificationError{Err: ErrModifyingCertificates} + // } + + // for i, certificate := range configuration.Certificates { + // if !pc.configuration.Certificates[i].Equals(certificate) { + // return &rtcerr.InvalidModificationError{Err: ErrModifyingCertificates} + // } + // } + // pc.configuration.Certificates = configuration.Certificates + // } + + // https://www.w3.org/TR/webrtc/#set-the-configuration (step #5) + if configuration.BundlePolicy != BundlePolicy(Unknown) { + if configuration.BundlePolicy != existingConfig.BundlePolicy { + return &rtcerr.InvalidModificationError{Err: ErrModifyingBundlePolicy} + } + } + + // https://www.w3.org/TR/webrtc/#set-the-configuration (step #6) + if configuration.RTCPMuxPolicy != RTCPMuxPolicy(Unknown) { + if configuration.RTCPMuxPolicy != existingConfig.RTCPMuxPolicy { + return &rtcerr.InvalidModificationError{Err: ErrModifyingRTCPMuxPolicy} + } + } + + // https://www.w3.org/TR/webrtc/#set-the-configuration (step #7) + if configuration.ICECandidatePoolSize != 0 { + if configuration.ICECandidatePoolSize != existingConfig.ICECandidatePoolSize && + pc.LocalDescription() != nil { + return &rtcerr.InvalidModificationError{Err: ErrModifyingICECandidatePoolSize} + } + } + + // https://www.w3.org/TR/webrtc/#set-the-configuration (step #11) + if len(configuration.ICEServers) > 0 { + // https://www.w3.org/TR/webrtc/#set-the-configuration (step #11.3) + for _, server := range configuration.ICEServers { + if _, err := server.validate(); err != nil { + return err + } + } + } + return nil +} + +// SetConfiguration updates the configuration of this PeerConnection object. +func (pc *PeerConnection) SetConfiguration(configuration Configuration) (err error) { + defer func() { + if e := recover(); e != nil { + err = recoveryToError(e) + } + }() + if err := pc.checkConfiguration(configuration); err != nil { + return err + } + configMap := configurationToValue(configuration) + pc.underlying.Call("setConfiguration", configMap) + return nil +} + +// GetConfiguration returns a Configuration object representing the current +// configuration of this PeerConnection object. The returned object is a +// copy and direct mutation on it will not take affect until SetConfiguration +// has been called with Configuration passed as its only argument. +// https://www.w3.org/TR/webrtc/#dom-rtcpeerconnection-getconfiguration +func (pc *PeerConnection) GetConfiguration() Configuration { + return valueToConfiguration(pc.underlying.Call("getConfiguration")) +} + +// CreateOffer starts the PeerConnection and generates the localDescription +func (pc *PeerConnection) CreateOffer(options *OfferOptions) (_ SessionDescription, err error) { + defer func() { + if e := recover(); e != nil { + err = recoveryToError(e) + } + }() + promise := pc.underlying.Call("createOffer", offerOptionsToValue(options)) + desc, err := awaitPromise(promise) + if err != nil { + return SessionDescription{}, err + } + return *valueToSessionDescription(desc), nil +} + +// CreateAnswer starts the PeerConnection and generates the localDescription +func (pc *PeerConnection) CreateAnswer(options *AnswerOptions) (_ SessionDescription, err error) { + defer func() { + if e := recover(); e != nil { + err = recoveryToError(e) + } + }() + promise := pc.underlying.Call("createAnswer", answerOptionsToValue(options)) + desc, err := awaitPromise(promise) + if err != nil { + return SessionDescription{}, err + } + return *valueToSessionDescription(desc), nil +} + +// SetLocalDescription sets the SessionDescription of the local peer +func (pc *PeerConnection) SetLocalDescription(desc SessionDescription) (err error) { + defer func() { + if e := recover(); e != nil { + err = recoveryToError(e) + } + }() + promise := pc.underlying.Call("setLocalDescription", sessionDescriptionToValue(&desc)) + _, err = awaitPromise(promise) + return err +} + +// LocalDescription returns PendingLocalDescription if it is not null and +// otherwise it returns CurrentLocalDescription. This property is used to +// determine if setLocalDescription has already been called. +// https://www.w3.org/TR/webrtc/#dom-rtcpeerconnection-localdescription +func (pc *PeerConnection) LocalDescription() *SessionDescription { + return valueToSessionDescription(pc.underlying.Get("localDescription")) +} + +// SetRemoteDescription sets the SessionDescription of the remote peer +func (pc *PeerConnection) SetRemoteDescription(desc SessionDescription) (err error) { + defer func() { + if e := recover(); e != nil { + err = recoveryToError(e) + } + }() + promise := pc.underlying.Call("setRemoteDescription", sessionDescriptionToValue(&desc)) + _, err = awaitPromise(promise) + return err +} + +// RemoteDescription returns PendingRemoteDescription if it is not null and +// otherwise it returns CurrentRemoteDescription. This property is used to +// determine if setRemoteDescription has already been called. +// https://www.w3.org/TR/webrtc/#dom-rtcpeerconnection-remotedescription +func (pc *PeerConnection) RemoteDescription() *SessionDescription { + return valueToSessionDescription(pc.underlying.Get("remoteDescription")) +} + +// AddICECandidate accepts an ICE candidate string and adds it +// to the existing set of candidates +func (pc *PeerConnection) AddICECandidate(candidate ICECandidateInit) (err error) { + defer func() { + if e := recover(); e != nil { + err = recoveryToError(e) + } + }() + promise := pc.underlying.Call("addIceCandidate", iceCandidateInitToValue(candidate)) + _, err = awaitPromise(promise) + return err +} + +// ICEConnectionState returns the ICE connection state of the +// PeerConnection instance. +func (pc *PeerConnection) ICEConnectionState() ICEConnectionState { + return NewICEConnectionState(pc.underlying.Get("iceConnectionState").String()) +} + +// OnICECandidate sets an event handler which is invoked when a new ICE +// candidate is found. +func (pc *PeerConnection) OnICECandidate(f func(candidate *ICECandidate)) { + if pc.onICECandidateHandler != nil { + oldHandler := pc.onICECandidateHandler + defer oldHandler.Release() + } + onICECandidateHandler := js.FuncOf(func(this js.Value, args []js.Value) interface{} { + candidate := valueToICECandidate(args[0].Get("candidate")) + if candidate == nil && pc.onGatherCompleteHandler != nil { + go pc.onGatherCompleteHandler() + } + + go f(candidate) + return js.Undefined() + }) + pc.onICECandidateHandler = &onICECandidateHandler + pc.underlying.Set("onicecandidate", onICECandidateHandler) +} + +// OnICEGatheringStateChange sets an event handler which is invoked when the +// ICE candidate gathering state has changed. +func (pc *PeerConnection) OnICEGatheringStateChange(f func()) { + if pc.onICEGatheringStateChangeHandler != nil { + oldHandler := pc.onICEGatheringStateChangeHandler + defer oldHandler.Release() + } + onICEGatheringStateChangeHandler := js.FuncOf(func(this js.Value, args []js.Value) interface{} { + go f() + return js.Undefined() + }) + pc.onICEGatheringStateChangeHandler = &onICEGatheringStateChangeHandler + pc.underlying.Set("onicegatheringstatechange", onICEGatheringStateChangeHandler) +} + +// CreateDataChannel creates a new DataChannel object with the given label +// and optional DataChannelInit used to configure properties of the +// underlying channel such as data reliability. +func (pc *PeerConnection) CreateDataChannel(label string, options *DataChannelInit) (_ *DataChannel, err error) { + defer func() { + if e := recover(); e != nil { + err = recoveryToError(e) + } + }() + channel := pc.underlying.Call("createDataChannel", label, dataChannelInitToValue(options)) + return &DataChannel{ + underlying: channel, + api: pc.api, + }, nil +} + +// SetIdentityProvider is used to configure an identity provider to generate identity assertions +func (pc *PeerConnection) SetIdentityProvider(provider string) (err error) { + defer func() { + if e := recover(); e != nil { + err = recoveryToError(e) + } + }() + pc.underlying.Call("setIdentityProvider", provider) + return nil +} + +// Close ends the PeerConnection +func (pc *PeerConnection) Close() (err error) { + defer func() { + if e := recover(); e != nil { + err = recoveryToError(e) + } + }() + + pc.underlying.Call("close") + + // Release any handlers as required by the syscall/js API. + if pc.onSignalingStateChangeHandler != nil { + pc.onSignalingStateChangeHandler.Release() + } + if pc.onDataChannelHandler != nil { + pc.onDataChannelHandler.Release() + } + if pc.onNegotiationNeededHandler != nil { + pc.onNegotiationNeededHandler.Release() + } + if pc.onConnectionStateChangeHandler != nil { + pc.onConnectionStateChangeHandler.Release() + } + if pc.onICEConnectionStateChangeHandler != nil { + pc.onICEConnectionStateChangeHandler.Release() + } + if pc.onICECandidateHandler != nil { + pc.onICECandidateHandler.Release() + } + if pc.onICEGatheringStateChangeHandler != nil { + pc.onICEGatheringStateChangeHandler.Release() + } + + return nil +} + +// CurrentLocalDescription represents the local description that was +// successfully negotiated the last time the PeerConnection transitioned +// into the stable state plus any local candidates that have been generated +// by the ICEAgent since the offer or answer was created. +func (pc *PeerConnection) CurrentLocalDescription() *SessionDescription { + desc := pc.underlying.Get("currentLocalDescription") + return valueToSessionDescription(desc) +} + +// PendingLocalDescription represents a local description that is in the +// process of being negotiated plus any local candidates that have been +// generated by the ICEAgent since the offer or answer was created. If the +// PeerConnection is in the stable state, the value is null. +func (pc *PeerConnection) PendingLocalDescription() *SessionDescription { + desc := pc.underlying.Get("pendingLocalDescription") + return valueToSessionDescription(desc) +} + +// CurrentRemoteDescription represents the last remote description that was +// successfully negotiated the last time the PeerConnection transitioned +// into the stable state plus any remote candidates that have been supplied +// via AddICECandidate() since the offer or answer was created. +func (pc *PeerConnection) CurrentRemoteDescription() *SessionDescription { + desc := pc.underlying.Get("currentRemoteDescription") + return valueToSessionDescription(desc) +} + +// PendingRemoteDescription represents a remote description that is in the +// process of being negotiated, complete with any remote candidates that +// have been supplied via AddICECandidate() since the offer or answer was +// created. If the PeerConnection is in the stable state, the value is +// null. +func (pc *PeerConnection) PendingRemoteDescription() *SessionDescription { + desc := pc.underlying.Get("pendingRemoteDescription") + return valueToSessionDescription(desc) +} + +// SignalingState returns the signaling state of the PeerConnection instance. +func (pc *PeerConnection) SignalingState() SignalingState { + rawState := pc.underlying.Get("signalingState").String() + return newSignalingState(rawState) +} + +// ICEGatheringState attribute the ICE gathering state of the PeerConnection +// instance. +func (pc *PeerConnection) ICEGatheringState() ICEGatheringState { + rawState := pc.underlying.Get("iceGatheringState").String() + return NewICEGatheringState(rawState) +} + +// ConnectionState attribute the connection state of the PeerConnection +// instance. +func (pc *PeerConnection) ConnectionState() PeerConnectionState { + rawState := pc.underlying.Get("connectionState").String() + return newPeerConnectionState(rawState) +} + +func (pc *PeerConnection) setGatherCompleteHandler(handler func()) { + pc.onGatherCompleteHandler = handler + + // If no onIceCandidate handler has been set provide an empty one + // otherwise our onGatherCompleteHandler will not be executed + if pc.onICECandidateHandler == nil { + pc.OnICECandidate(func(i *ICECandidate) {}) + } +} + +// Converts a Configuration to js.Value so it can be passed +// through to the JavaScript WebRTC API. Any zero values are converted to +// js.Undefined(), which will result in the default value being used. +func configurationToValue(configuration Configuration) js.Value { + return js.ValueOf(map[string]interface{}{ + "iceServers": iceServersToValue(configuration.ICEServers), + "iceTransportPolicy": stringEnumToValueOrUndefined(configuration.ICETransportPolicy.String()), + "bundlePolicy": stringEnumToValueOrUndefined(configuration.BundlePolicy.String()), + "rtcpMuxPolicy": stringEnumToValueOrUndefined(configuration.RTCPMuxPolicy.String()), + "peerIdentity": stringToValueOrUndefined(configuration.PeerIdentity), + "iceCandidatePoolSize": uint8ToValueOrUndefined(configuration.ICECandidatePoolSize), + + // Note: Certificates are not currently supported. + // "certificates": configuration.Certificates, + }) +} + +func iceServersToValue(iceServers []ICEServer) js.Value { + if len(iceServers) == 0 { + return js.Undefined() + } + maps := make([]interface{}, len(iceServers)) + for i, server := range iceServers { + maps[i] = iceServerToValue(server) + } + return js.ValueOf(maps) +} + +func iceServerToValue(server ICEServer) js.Value { + return js.ValueOf(map[string]interface{}{ + "urls": stringsToValue(server.URLs), // required + "username": stringToValueOrUndefined(server.Username), + // Note: credential and credentialType are not currently supported. + // "credential": interfaceToValueOrUndefined(server.Credential), + // "credentialType": stringEnumToValueOrUndefined(server.CredentialType.String()), + }) +} + +func valueToConfiguration(configValue js.Value) Configuration { + if jsValueIsNull(configValue) || jsValueIsUndefined(configValue) { + return Configuration{} + } + return Configuration{ + ICEServers: valueToICEServers(configValue.Get("iceServers")), + ICETransportPolicy: NewICETransportPolicy(valueToStringOrZero(configValue.Get("iceTransportPolicy"))), + BundlePolicy: newBundlePolicy(valueToStringOrZero(configValue.Get("bundlePolicy"))), + RTCPMuxPolicy: newRTCPMuxPolicy(valueToStringOrZero(configValue.Get("rtcpMuxPolicy"))), + PeerIdentity: valueToStringOrZero(configValue.Get("peerIdentity")), + ICECandidatePoolSize: valueToUint8OrZero(configValue.Get("iceCandidatePoolSize")), + + // Note: Certificates are not supported. + // Certificates []Certificate + } +} + +func valueToICEServers(iceServersValue js.Value) []ICEServer { + if jsValueIsNull(iceServersValue) || jsValueIsUndefined(iceServersValue) { + return nil + } + iceServers := make([]ICEServer, iceServersValue.Length()) + for i := 0; i < iceServersValue.Length(); i++ { + iceServers[i] = valueToICEServer(iceServersValue.Index(i)) + } + return iceServers +} + +func valueToICEServer(iceServerValue js.Value) ICEServer { + return ICEServer{ + URLs: valueToStrings(iceServerValue.Get("urls")), // required + Username: valueToStringOrZero(iceServerValue.Get("username")), + // Note: Credential and CredentialType are not currently supported. + // Credential: iceServerValue.Get("credential"), + // CredentialType: newICECredentialType(valueToStringOrZero(iceServerValue.Get("credentialType"))), + } +} + +func valueToICECandidate(val js.Value) *ICECandidate { + if jsValueIsNull(val) || jsValueIsUndefined(val) { + return nil + } + if jsValueIsUndefined(val.Get("protocol")) && !jsValueIsUndefined(val.Get("candidate")) { + // Missing some fields, assume it's Firefox and parse SDP candidate. + c, err := ice.UnmarshalCandidate(val.Get("candidate").String()) + if err != nil { + return nil + } + + iceCandidate, err := newICECandidateFromICE(c) + if err != nil { + return nil + } + + return &iceCandidate + } + protocol, _ := NewICEProtocol(val.Get("protocol").String()) + candidateType, _ := NewICECandidateType(val.Get("type").String()) + return &ICECandidate{ + Foundation: val.Get("foundation").String(), + Priority: valueToUint32OrZero(val.Get("priority")), + Address: val.Get("address").String(), + Protocol: protocol, + Port: valueToUint16OrZero(val.Get("port")), + Typ: candidateType, + Component: stringToComponentIDOrZero(val.Get("component").String()), + RelatedAddress: val.Get("relatedAddress").String(), + RelatedPort: valueToUint16OrZero(val.Get("relatedPort")), + } +} + +func stringToComponentIDOrZero(val string) uint16 { + // See: https://developer.mozilla.org/en-US/docs/Web/API/RTCIceComponent + switch val { + case "rtp": + return 1 + case "rtcp": + return 2 + } + return 0 +} + +func sessionDescriptionToValue(desc *SessionDescription) js.Value { + if desc == nil { + return js.Undefined() + } + return js.ValueOf(map[string]interface{}{ + "type": desc.Type.String(), + "sdp": desc.SDP, + }) +} + +func valueToSessionDescription(descValue js.Value) *SessionDescription { + if jsValueIsNull(descValue) || jsValueIsUndefined(descValue) { + return nil + } + return &SessionDescription{ + Type: NewSDPType(descValue.Get("type").String()), + SDP: descValue.Get("sdp").String(), + } +} + +func offerOptionsToValue(offerOptions *OfferOptions) js.Value { + if offerOptions == nil { + return js.Undefined() + } + return js.ValueOf(map[string]interface{}{ + "iceRestart": offerOptions.ICERestart, + "voiceActivityDetection": offerOptions.VoiceActivityDetection, + }) +} + +func answerOptionsToValue(answerOptions *AnswerOptions) js.Value { + if answerOptions == nil { + return js.Undefined() + } + return js.ValueOf(map[string]interface{}{ + "voiceActivityDetection": answerOptions.VoiceActivityDetection, + }) +} + +func iceCandidateInitToValue(candidate ICECandidateInit) js.Value { + return js.ValueOf(map[string]interface{}{ + "candidate": candidate.Candidate, + "sdpMid": stringPointerToValue(candidate.SDPMid), + "sdpMLineIndex": uint16PointerToValue(candidate.SDPMLineIndex), + "usernameFragment": stringPointerToValue(candidate.UsernameFragment), + }) +} + +func dataChannelInitToValue(options *DataChannelInit) js.Value { + if options == nil { + return js.Undefined() + } + + maxPacketLifeTime := uint16PointerToValue(options.MaxPacketLifeTime) + return js.ValueOf(map[string]interface{}{ + "ordered": boolPointerToValue(options.Ordered), + "maxPacketLifeTime": maxPacketLifeTime, + // See https://bugs.chromium.org/p/chromium/issues/detail?id=696681 + // Chrome calls this "maxRetransmitTime" + "maxRetransmitTime": maxPacketLifeTime, + "maxRetransmits": uint16PointerToValue(options.MaxRetransmits), + "protocol": stringPointerToValue(options.Protocol), + "negotiated": boolPointerToValue(options.Negotiated), + "id": uint16PointerToValue(options.ID), + }) +} diff --git a/vendor/github.com/pion/webrtc/v3/peerconnectionstate.go b/vendor/github.com/pion/webrtc/v3/peerconnectionstate.go new file mode 100644 index 0000000..66ac20e --- /dev/null +++ b/vendor/github.com/pion/webrtc/v3/peerconnectionstate.go @@ -0,0 +1,94 @@ +package webrtc + +// PeerConnectionState indicates the state of the PeerConnection. +type PeerConnectionState int + +const ( + // PeerConnectionStateNew indicates that any of the ICETransports or + // DTLSTransports are in the "new" state and none of the transports are + // in the "connecting", "checking", "failed" or "disconnected" state, or + // all transports are in the "closed" state, or there are no transports. + PeerConnectionStateNew PeerConnectionState = iota + 1 + + // PeerConnectionStateConnecting indicates that any of the + // ICETransports or DTLSTransports are in the "connecting" or + // "checking" state and none of them is in the "failed" state. + PeerConnectionStateConnecting + + // PeerConnectionStateConnected indicates that all ICETransports and + // DTLSTransports are in the "connected", "completed" or "closed" state + // and at least one of them is in the "connected" or "completed" state. + PeerConnectionStateConnected + + // PeerConnectionStateDisconnected indicates that any of the + // ICETransports or DTLSTransports are in the "disconnected" state + // and none of them are in the "failed" or "connecting" or "checking" state. + PeerConnectionStateDisconnected + + // PeerConnectionStateFailed indicates that any of the ICETransports + // or DTLSTransports are in a "failed" state. + PeerConnectionStateFailed + + // PeerConnectionStateClosed indicates the peer connection is closed + // and the isClosed member variable of PeerConnection is true. + PeerConnectionStateClosed +) + +// This is done this way because of a linter. +const ( + peerConnectionStateNewStr = "new" + peerConnectionStateConnectingStr = "connecting" + peerConnectionStateConnectedStr = "connected" + peerConnectionStateDisconnectedStr = "disconnected" + peerConnectionStateFailedStr = "failed" + peerConnectionStateClosedStr = "closed" +) + +func newPeerConnectionState(raw string) PeerConnectionState { + switch raw { + case peerConnectionStateNewStr: + return PeerConnectionStateNew + case peerConnectionStateConnectingStr: + return PeerConnectionStateConnecting + case peerConnectionStateConnectedStr: + return PeerConnectionStateConnected + case peerConnectionStateDisconnectedStr: + return PeerConnectionStateDisconnected + case peerConnectionStateFailedStr: + return PeerConnectionStateFailed + case peerConnectionStateClosedStr: + return PeerConnectionStateClosed + default: + return PeerConnectionState(Unknown) + } +} + +func (t PeerConnectionState) String() string { + switch t { + case PeerConnectionStateNew: + return peerConnectionStateNewStr + case PeerConnectionStateConnecting: + return peerConnectionStateConnectingStr + case PeerConnectionStateConnected: + return peerConnectionStateConnectedStr + case PeerConnectionStateDisconnected: + return peerConnectionStateDisconnectedStr + case PeerConnectionStateFailed: + return peerConnectionStateFailedStr + case PeerConnectionStateClosed: + return peerConnectionStateClosedStr + default: + return ErrUnknownType.Error() + } +} + +type negotiationNeededState int + +const ( + // NegotiationNeededStateEmpty not running and queue is empty + negotiationNeededStateEmpty = iota + // NegotiationNeededStateEmpty running and queue is empty + negotiationNeededStateRun + // NegotiationNeededStateEmpty running and queue + negotiationNeededStateQueue +) diff --git a/vendor/github.com/pion/webrtc/v3/pkg/media/media.go b/vendor/github.com/pion/webrtc/v3/pkg/media/media.go new file mode 100644 index 0000000..bcd7e33 --- /dev/null +++ b/vendor/github.com/pion/webrtc/v3/pkg/media/media.go @@ -0,0 +1,25 @@ +// Package media provides media writer and filters +package media + +import ( + "time" + + "github.com/pion/rtp" +) + +// A Sample contains encoded media and timing information +type Sample struct { + Data []byte + Timestamp time.Time + Duration time.Duration +} + +// Writer defines an interface to handle +// the creation of media files +type Writer interface { + // Add the content of an RTP packet to the media + WriteRTP(packet *rtp.Packet) error + // Close the media + // Note: Close implementation must be idempotent + Close() error +} diff --git a/vendor/github.com/pion/webrtc/v3/pkg/rtcerr/errors.go b/vendor/github.com/pion/webrtc/v3/pkg/rtcerr/errors.go new file mode 100644 index 0000000..aa94b7b --- /dev/null +++ b/vendor/github.com/pion/webrtc/v3/pkg/rtcerr/errors.go @@ -0,0 +1,160 @@ +// Package rtcerr implements the error wrappers defined throughout the +// WebRTC 1.0 specifications. +package rtcerr + +import ( + "fmt" +) + +// UnknownError indicates the operation failed for an unknown transient reason. +type UnknownError struct { + Err error +} + +func (e *UnknownError) Error() string { + return fmt.Sprintf("UnknownError: %v", e.Err) +} + +// Unwrap returns the result of calling the Unwrap method on err, if err's type contains +// an Unwrap method returning error. Otherwise, Unwrap returns nil. +func (e *UnknownError) Unwrap() error { + return e.Err +} + +// InvalidStateError indicates the object is in an invalid state. +type InvalidStateError struct { + Err error +} + +func (e *InvalidStateError) Error() string { + return fmt.Sprintf("InvalidStateError: %v", e.Err) +} + +// Unwrap returns the result of calling the Unwrap method on err, if err's type contains +// an Unwrap method returning error. Otherwise, Unwrap returns nil. +func (e *InvalidStateError) Unwrap() error { + return e.Err +} + +// InvalidAccessError indicates the object does not support the operation or +// argument. +type InvalidAccessError struct { + Err error +} + +func (e *InvalidAccessError) Error() string { + return fmt.Sprintf("InvalidAccessError: %v", e.Err) +} + +// Unwrap returns the result of calling the Unwrap method on err, if err's type contains +// an Unwrap method returning error. Otherwise, Unwrap returns nil. +func (e *InvalidAccessError) Unwrap() error { + return e.Err +} + +// NotSupportedError indicates the operation is not supported. +type NotSupportedError struct { + Err error +} + +func (e *NotSupportedError) Error() string { + return fmt.Sprintf("NotSupportedError: %v", e.Err) +} + +// Unwrap returns the result of calling the Unwrap method on err, if err's type contains +// an Unwrap method returning error. Otherwise, Unwrap returns nil. +func (e *NotSupportedError) Unwrap() error { + return e.Err +} + +// InvalidModificationError indicates the object cannot be modified in this way. +type InvalidModificationError struct { + Err error +} + +func (e *InvalidModificationError) Error() string { + return fmt.Sprintf("InvalidModificationError: %v", e.Err) +} + +// Unwrap returns the result of calling the Unwrap method on err, if err's type contains +// an Unwrap method returning error. Otherwise, Unwrap returns nil. +func (e *InvalidModificationError) Unwrap() error { + return e.Err +} + +// SyntaxError indicates the string did not match the expected pattern. +type SyntaxError struct { + Err error +} + +func (e *SyntaxError) Error() string { + return fmt.Sprintf("SyntaxError: %v", e.Err) +} + +// Unwrap returns the result of calling the Unwrap method on err, if err's type contains +// an Unwrap method returning error. Otherwise, Unwrap returns nil. +func (e *SyntaxError) Unwrap() error { + return e.Err +} + +// TypeError indicates an error when a value is not of the expected type. +type TypeError struct { + Err error +} + +func (e *TypeError) Error() string { + return fmt.Sprintf("TypeError: %v", e.Err) +} + +// Unwrap returns the result of calling the Unwrap method on err, if err's type contains +// an Unwrap method returning error. Otherwise, Unwrap returns nil. +func (e *TypeError) Unwrap() error { + return e.Err +} + +// OperationError indicates the operation failed for an operation-specific +// reason. +type OperationError struct { + Err error +} + +func (e *OperationError) Error() string { + return fmt.Sprintf("OperationError: %v", e.Err) +} + +// Unwrap returns the result of calling the Unwrap method on err, if err's type contains +// an Unwrap method returning error. Otherwise, Unwrap returns nil. +func (e *OperationError) Unwrap() error { + return e.Err +} + +// NotReadableError indicates the input/output read operation failed. +type NotReadableError struct { + Err error +} + +func (e *NotReadableError) Error() string { + return fmt.Sprintf("NotReadableError: %v", e.Err) +} + +// Unwrap returns the result of calling the Unwrap method on err, if err's type contains +// an Unwrap method returning error. Otherwise, Unwrap returns nil. +func (e *NotReadableError) Unwrap() error { + return e.Err +} + +// RangeError indicates an error when a value is not in the set or range +// of allowed values. +type RangeError struct { + Err error +} + +func (e *RangeError) Error() string { + return fmt.Sprintf("RangeError: %v", e.Err) +} + +// Unwrap returns the result of calling the Unwrap method on err, if err's type contains +// an Unwrap method returning error. Otherwise, Unwrap returns nil. +func (e *RangeError) Unwrap() error { + return e.Err +} diff --git a/vendor/github.com/pion/webrtc/v3/renovate.json b/vendor/github.com/pion/webrtc/v3/renovate.json new file mode 100644 index 0000000..4400fd9 --- /dev/null +++ b/vendor/github.com/pion/webrtc/v3/renovate.json @@ -0,0 +1,15 @@ +{ + "extends": [ + "config:base" + ], + "postUpdateOptions": [ + "gomodTidy" + ], + "commitBody": "Generated by renovateBot", + "packageRules": [ + { + "packagePatterns": ["^golang.org/x/"], + "schedule": ["on the first day of the month"] + } + ] +} diff --git a/vendor/github.com/pion/webrtc/v3/rtcpfeedback.go b/vendor/github.com/pion/webrtc/v3/rtcpfeedback.go new file mode 100644 index 0000000..b377738 --- /dev/null +++ b/vendor/github.com/pion/webrtc/v3/rtcpfeedback.go @@ -0,0 +1,31 @@ +package webrtc + +const ( + // TypeRTCPFBTransportCC .. + TypeRTCPFBTransportCC = "transport-cc" + + // TypeRTCPFBGoogREMB .. + TypeRTCPFBGoogREMB = "goog-remb" + + // TypeRTCPFBACK .. + TypeRTCPFBACK = "ack" + + // TypeRTCPFBCCM .. + TypeRTCPFBCCM = "ccm" + + // TypeRTCPFBNACK .. + TypeRTCPFBNACK = "nack" +) + +// RTCPFeedback signals the connection to use additional RTCP packet types. +// https://draft.ortc.org/#dom-rtcrtcpfeedback +type RTCPFeedback struct { + // Type is the type of feedback. + // see: https://draft.ortc.org/#dom-rtcrtcpfeedback + // valid: ack, ccm, nack, goog-remb, transport-cc + Type string + + // The parameter value depends on the type. + // For example, type="nack" parameter="pli" will send Picture Loss Indicator packets. + Parameter string +} diff --git a/vendor/github.com/pion/webrtc/v3/rtcpmuxpolicy.go b/vendor/github.com/pion/webrtc/v3/rtcpmuxpolicy.go new file mode 100644 index 0000000..f74e440 --- /dev/null +++ b/vendor/github.com/pion/webrtc/v3/rtcpmuxpolicy.go @@ -0,0 +1,66 @@ +package webrtc + +import ( + "encoding/json" +) + +// RTCPMuxPolicy affects what ICE candidates are gathered to support +// non-multiplexed RTCP. +type RTCPMuxPolicy int + +const ( + // RTCPMuxPolicyNegotiate indicates to gather ICE candidates for both + // RTP and RTCP candidates. If the remote-endpoint is capable of + // multiplexing RTCP, multiplex RTCP on the RTP candidates. If it is not, + // use both the RTP and RTCP candidates separately. + RTCPMuxPolicyNegotiate RTCPMuxPolicy = iota + 1 + + // RTCPMuxPolicyRequire indicates to gather ICE candidates only for + // RTP and multiplex RTCP on the RTP candidates. If the remote endpoint is + // not capable of rtcp-mux, session negotiation will fail. + RTCPMuxPolicyRequire +) + +// This is done this way because of a linter. +const ( + rtcpMuxPolicyNegotiateStr = "negotiate" + rtcpMuxPolicyRequireStr = "require" +) + +func newRTCPMuxPolicy(raw string) RTCPMuxPolicy { + switch raw { + case rtcpMuxPolicyNegotiateStr: + return RTCPMuxPolicyNegotiate + case rtcpMuxPolicyRequireStr: + return RTCPMuxPolicyRequire + default: + return RTCPMuxPolicy(Unknown) + } +} + +func (t RTCPMuxPolicy) String() string { + switch t { + case RTCPMuxPolicyNegotiate: + return rtcpMuxPolicyNegotiateStr + case RTCPMuxPolicyRequire: + return rtcpMuxPolicyRequireStr + default: + return ErrUnknownType.Error() + } +} + +// UnmarshalJSON parses the JSON-encoded data and stores the result +func (t *RTCPMuxPolicy) UnmarshalJSON(b []byte) error { + var val string + if err := json.Unmarshal(b, &val); err != nil { + return err + } + + *t = newRTCPMuxPolicy(val) + return nil +} + +// MarshalJSON returns the JSON encoding +func (t RTCPMuxPolicy) MarshalJSON() ([]byte, error) { + return json.Marshal(t.String()) +} diff --git a/vendor/github.com/pion/webrtc/v3/rtpcapabilities.go b/vendor/github.com/pion/webrtc/v3/rtpcapabilities.go new file mode 100644 index 0000000..dc42230 --- /dev/null +++ b/vendor/github.com/pion/webrtc/v3/rtpcapabilities.go @@ -0,0 +1,9 @@ +package webrtc + +// RTPCapabilities represents the capabilities of a transceiver +// +// https://w3c.github.io/webrtc-pc/#rtcrtpcapabilities +type RTPCapabilities struct { + Codecs []RTPCodecCapability + HeaderExtensions []RTPHeaderExtensionCapability +} diff --git a/vendor/github.com/pion/webrtc/v3/rtpcodec.go b/vendor/github.com/pion/webrtc/v3/rtpcodec.go new file mode 100644 index 0000000..f05b98b --- /dev/null +++ b/vendor/github.com/pion/webrtc/v3/rtpcodec.go @@ -0,0 +1,118 @@ +package webrtc + +import ( + "strings" +) + +// RTPCodecType determines the type of a codec +type RTPCodecType int + +const ( + + // RTPCodecTypeAudio indicates this is an audio codec + RTPCodecTypeAudio RTPCodecType = iota + 1 + + // RTPCodecTypeVideo indicates this is a video codec + RTPCodecTypeVideo +) + +func (t RTPCodecType) String() string { + switch t { + case RTPCodecTypeAudio: + return "audio" + case RTPCodecTypeVideo: + return "video" //nolint: goconst + default: + return ErrUnknownType.Error() + } +} + +// NewRTPCodecType creates a RTPCodecType from a string +func NewRTPCodecType(r string) RTPCodecType { + switch { + case strings.EqualFold(r, RTPCodecTypeAudio.String()): + return RTPCodecTypeAudio + case strings.EqualFold(r, RTPCodecTypeVideo.String()): + return RTPCodecTypeVideo + default: + return RTPCodecType(0) + } +} + +// RTPCodecCapability provides information about codec capabilities. +// +// https://w3c.github.io/webrtc-pc/#dictionary-rtcrtpcodeccapability-members +type RTPCodecCapability struct { + MimeType string + ClockRate uint32 + Channels uint16 + SDPFmtpLine string + RTCPFeedback []RTCPFeedback +} + +// RTPHeaderExtensionCapability is used to define a RFC5285 RTP header extension supported by the codec. +// +// https://w3c.github.io/webrtc-pc/#dom-rtcrtpcapabilities-headerextensions +type RTPHeaderExtensionCapability struct { + URI string +} + +// RTPHeaderExtensionParameter represents a negotiated RFC5285 RTP header extension. +// +// https://w3c.github.io/webrtc-pc/#dictionary-rtcrtpheaderextensionparameters-members +type RTPHeaderExtensionParameter struct { + URI string + ID int +} + +// RTPCodecParameters is a sequence containing the media codecs that an RtpSender +// will choose from, as well as entries for RTX, RED and FEC mechanisms. This also +// includes the PayloadType that has been negotiated +// +// https://w3c.github.io/webrtc-pc/#rtcrtpcodecparameters +type RTPCodecParameters struct { + RTPCodecCapability + PayloadType PayloadType + + statsID string +} + +// RTPParameters is a list of negotiated codecs and header extensions +// +// https://w3c.github.io/webrtc-pc/#dictionary-rtcrtpparameters-members +type RTPParameters struct { + HeaderExtensions []RTPHeaderExtensionParameter + Codecs []RTPCodecParameters +} + +type codecMatchType int + +const ( + codecMatchNone codecMatchType = 0 + codecMatchPartial codecMatchType = 1 + codecMatchExact codecMatchType = 2 +) + +// Do a fuzzy find for a codec in the list of codecs +// Used for lookup up a codec in an existing list to find a match +// Returns codecMatchExact, codecMatchPartial, or codecMatchNone +func codecParametersFuzzySearch(needle RTPCodecParameters, haystack []RTPCodecParameters) (RTPCodecParameters, codecMatchType) { + // First attempt to match on MimeType + SDPFmtpLine + // Exact matches means fmtp line cannot be empty + for _, c := range haystack { + if strings.EqualFold(c.RTPCodecCapability.MimeType, needle.RTPCodecCapability.MimeType) && + c.RTPCodecCapability.SDPFmtpLine == needle.RTPCodecCapability.SDPFmtpLine { + return c, codecMatchExact + } + } + + // Fallback to just MimeType if either haystack or needle does not have fmtpline set + for _, c := range haystack { + if strings.EqualFold(c.RTPCodecCapability.MimeType, needle.RTPCodecCapability.MimeType) && + (c.RTPCodecCapability.SDPFmtpLine == "" || needle.RTPCodecCapability.SDPFmtpLine == "") { + return c, codecMatchPartial + } + } + + return RTPCodecParameters{}, codecMatchNone +} diff --git a/vendor/github.com/pion/webrtc/v3/rtpcodingparameters.go b/vendor/github.com/pion/webrtc/v3/rtpcodingparameters.go new file mode 100644 index 0000000..8a08c33 --- /dev/null +++ b/vendor/github.com/pion/webrtc/v3/rtpcodingparameters.go @@ -0,0 +1,10 @@ +package webrtc + +// RTPCodingParameters provides information relating to both encoding and decoding. +// This is a subset of the RFC since Pion WebRTC doesn't implement encoding/decoding itself +// http://draft.ortc.org/#dom-rtcrtpcodingparameters +type RTPCodingParameters struct { + RID string `json:"rid"` + SSRC SSRC `json:"ssrc"` + PayloadType PayloadType `json:"payloadType"` +} diff --git a/vendor/github.com/pion/webrtc/v3/rtpdecodingparameters.go b/vendor/github.com/pion/webrtc/v3/rtpdecodingparameters.go new file mode 100644 index 0000000..77aa1fc --- /dev/null +++ b/vendor/github.com/pion/webrtc/v3/rtpdecodingparameters.go @@ -0,0 +1,8 @@ +package webrtc + +// RTPDecodingParameters provides information relating to both encoding and decoding. +// This is a subset of the RFC since Pion WebRTC doesn't implement decoding itself +// http://draft.ortc.org/#dom-rtcrtpdecodingparameters +type RTPDecodingParameters struct { + RTPCodingParameters +} diff --git a/vendor/github.com/pion/webrtc/v3/rtpencodingparameters.go b/vendor/github.com/pion/webrtc/v3/rtpencodingparameters.go new file mode 100644 index 0000000..09481a5 --- /dev/null +++ b/vendor/github.com/pion/webrtc/v3/rtpencodingparameters.go @@ -0,0 +1,8 @@ +package webrtc + +// RTPEncodingParameters provides information relating to both encoding and decoding. +// This is a subset of the RFC since Pion WebRTC doesn't implement encoding itself +// http://draft.ortc.org/#dom-rtcrtpencodingparameters +type RTPEncodingParameters struct { + RTPCodingParameters +} diff --git a/vendor/github.com/pion/webrtc/v3/rtpreceiveparameters.go b/vendor/github.com/pion/webrtc/v3/rtpreceiveparameters.go new file mode 100644 index 0000000..badf6b7 --- /dev/null +++ b/vendor/github.com/pion/webrtc/v3/rtpreceiveparameters.go @@ -0,0 +1,6 @@ +package webrtc + +// RTPReceiveParameters contains the RTP stack settings used by receivers +type RTPReceiveParameters struct { + Encodings []RTPDecodingParameters +} diff --git a/vendor/github.com/pion/webrtc/v3/rtpreceiver.go b/vendor/github.com/pion/webrtc/v3/rtpreceiver.go new file mode 100644 index 0000000..8d558e0 --- /dev/null +++ b/vendor/github.com/pion/webrtc/v3/rtpreceiver.go @@ -0,0 +1,361 @@ +// +build !js + +package webrtc + +import ( + "fmt" + "io" + "sync" + "time" + + "github.com/pion/interceptor" + "github.com/pion/rtcp" + "github.com/pion/srtp/v2" + "github.com/pion/webrtc/v3/internal/util" +) + +// trackStreams maintains a mapping of RTP/RTCP streams to a specific track +// a RTPReceiver may contain multiple streams if we are dealing with Multicast +type trackStreams struct { + track *TrackRemote + + rtpReadStream *srtp.ReadStreamSRTP + rtpInterceptor interceptor.RTPReader + + rtcpReadStream *srtp.ReadStreamSRTCP + rtcpInterceptor interceptor.RTCPReader +} + +// RTPReceiver allows an application to inspect the receipt of a TrackRemote +type RTPReceiver struct { + kind RTPCodecType + transport *DTLSTransport + + tracks []trackStreams + + closed, received chan interface{} + mu sync.RWMutex + + // A reference to the associated api object + api *API +} + +// NewRTPReceiver constructs a new RTPReceiver +func (api *API) NewRTPReceiver(kind RTPCodecType, transport *DTLSTransport) (*RTPReceiver, error) { + if transport == nil { + return nil, errRTPReceiverDTLSTransportNil + } + + r := &RTPReceiver{ + kind: kind, + transport: transport, + api: api, + closed: make(chan interface{}), + received: make(chan interface{}), + tracks: []trackStreams{}, + } + + return r, nil +} + +// Transport returns the currently-configured *DTLSTransport or nil +// if one has not yet been configured +func (r *RTPReceiver) Transport() *DTLSTransport { + r.mu.RLock() + defer r.mu.RUnlock() + return r.transport +} + +// GetParameters describes the current configuration for the encoding and +// transmission of media on the receiver's track. +func (r *RTPReceiver) GetParameters() RTPParameters { + return r.api.mediaEngine.getRTPParametersByKind(r.kind, []RTPTransceiverDirection{RTPTransceiverDirectionRecvonly}) +} + +// Track returns the RtpTransceiver TrackRemote +func (r *RTPReceiver) Track() *TrackRemote { + r.mu.RLock() + defer r.mu.RUnlock() + + if len(r.tracks) != 1 { + return nil + } + return r.tracks[0].track +} + +// Tracks returns the RtpTransceiver tracks +// A RTPReceiver to support Simulcast may now have multiple tracks +func (r *RTPReceiver) Tracks() []*TrackRemote { + r.mu.RLock() + defer r.mu.RUnlock() + + var tracks []*TrackRemote + for i := range r.tracks { + tracks = append(tracks, r.tracks[i].track) + } + return tracks +} + +// Receive initialize the track and starts all the transports +func (r *RTPReceiver) Receive(parameters RTPReceiveParameters) error { + r.mu.Lock() + defer r.mu.Unlock() + select { + case <-r.received: + return errRTPReceiverReceiveAlreadyCalled + default: + } + defer close(r.received) + + if len(parameters.Encodings) == 1 && parameters.Encodings[0].SSRC != 0 { + t := trackStreams{ + track: newTrackRemote( + r.kind, + parameters.Encodings[0].SSRC, + "", + r, + ), + } + + globalParams := r.GetParameters() + codec := RTPCodecCapability{} + if len(globalParams.Codecs) != 0 { + codec = globalParams.Codecs[0].RTPCodecCapability + } + + streamInfo := createStreamInfo("", parameters.Encodings[0].SSRC, 0, codec, globalParams.HeaderExtensions) + var err error + if t.rtpReadStream, t.rtpInterceptor, t.rtcpReadStream, t.rtcpInterceptor, err = r.streamsForSSRC(parameters.Encodings[0].SSRC, streamInfo); err != nil { + return err + } + + r.tracks = append(r.tracks, t) + } else { + for _, encoding := range parameters.Encodings { + r.tracks = append(r.tracks, trackStreams{ + track: newTrackRemote( + r.kind, + 0, + encoding.RID, + r, + ), + }) + } + } + + return nil +} + +// Read reads incoming RTCP for this RTPReceiver +func (r *RTPReceiver) Read(b []byte) (n int, a interceptor.Attributes, err error) { + select { + case <-r.received: + return r.tracks[0].rtcpInterceptor.Read(b, a) + case <-r.closed: + return 0, nil, io.ErrClosedPipe + } +} + +// ReadSimulcast reads incoming RTCP for this RTPReceiver for given rid +func (r *RTPReceiver) ReadSimulcast(b []byte, rid string) (n int, a interceptor.Attributes, err error) { + select { + case <-r.received: + for _, t := range r.tracks { + if t.track != nil && t.track.rid == rid { + return t.rtcpInterceptor.Read(b, a) + } + } + return 0, nil, fmt.Errorf("%w: %s", errRTPReceiverForRIDTrackStreamNotFound, rid) + case <-r.closed: + return 0, nil, io.ErrClosedPipe + } +} + +// ReadRTCP is a convenience method that wraps Read and unmarshal for you. +// It also runs any configured interceptors. +func (r *RTPReceiver) ReadRTCP() ([]rtcp.Packet, interceptor.Attributes, error) { + b := make([]byte, receiveMTU) + i, attributes, err := r.Read(b) + if err != nil { + return nil, nil, err + } + + pkts, err := rtcp.Unmarshal(b[:i]) + if err != nil { + return nil, nil, err + } + + return pkts, attributes, nil +} + +// ReadSimulcastRTCP is a convenience method that wraps ReadSimulcast and unmarshal for you +func (r *RTPReceiver) ReadSimulcastRTCP(rid string) ([]rtcp.Packet, interceptor.Attributes, error) { + b := make([]byte, receiveMTU) + i, attributes, err := r.ReadSimulcast(b, rid) + if err != nil { + return nil, nil, err + } + + pkts, err := rtcp.Unmarshal(b[:i]) + return pkts, attributes, err +} + +func (r *RTPReceiver) haveReceived() bool { + select { + case <-r.received: + return true + default: + return false + } +} + +// Stop irreversibly stops the RTPReceiver +func (r *RTPReceiver) Stop() error { + r.mu.Lock() + defer r.mu.Unlock() + var err error + + select { + case <-r.closed: + return err + default: + } + + select { + case <-r.received: + for i := range r.tracks { + errs := []error{} + + if r.tracks[i].rtcpReadStream != nil { + errs = append(errs, r.tracks[i].rtcpReadStream.Close()) + } + + if r.tracks[i].rtpReadStream != nil { + errs = append(errs, r.tracks[i].rtpReadStream.Close()) + } + + err = util.FlattenErrs(errs) + } + default: + } + + close(r.closed) + return err +} + +func (r *RTPReceiver) streamsForTrack(t *TrackRemote) *trackStreams { + for i := range r.tracks { + if r.tracks[i].track == t { + return &r.tracks[i] + } + } + return nil +} + +// readRTP should only be called by a track, this only exists so we can keep state in one place +func (r *RTPReceiver) readRTP(b []byte, reader *TrackRemote) (n int, a interceptor.Attributes, err error) { + <-r.received + if t := r.streamsForTrack(reader); t != nil { + return t.rtpInterceptor.Read(b, a) + } + + return 0, nil, fmt.Errorf("%w: %d", errRTPReceiverWithSSRCTrackStreamNotFound, reader.SSRC()) +} + +// receiveForRid is the sibling of Receive expect for RIDs instead of SSRCs +// It populates all the internal state for the given RID +func (r *RTPReceiver) receiveForRid(rid string, params RTPParameters, ssrc SSRC) (*TrackRemote, error) { + r.mu.Lock() + defer r.mu.Unlock() + + for i := range r.tracks { + if r.tracks[i].track.RID() == rid { + r.tracks[i].track.mu.Lock() + r.tracks[i].track.kind = r.kind + r.tracks[i].track.codec = params.Codecs[0] + r.tracks[i].track.params = params + r.tracks[i].track.ssrc = ssrc + streamInfo := createStreamInfo("", ssrc, params.Codecs[0].PayloadType, params.Codecs[0].RTPCodecCapability, params.HeaderExtensions) + r.tracks[i].track.mu.Unlock() + + var err error + if r.tracks[i].rtpReadStream, r.tracks[i].rtpInterceptor, r.tracks[i].rtcpReadStream, r.tracks[i].rtcpInterceptor, err = r.streamsForSSRC(ssrc, streamInfo); err != nil { + return nil, err + } + + return r.tracks[i].track, nil + } + } + + return nil, fmt.Errorf("%w: %d", errRTPReceiverForSSRCTrackStreamNotFound, ssrc) +} + +func (r *RTPReceiver) streamsForSSRC(ssrc SSRC, streamInfo interceptor.StreamInfo) (*srtp.ReadStreamSRTP, interceptor.RTPReader, *srtp.ReadStreamSRTCP, interceptor.RTCPReader, error) { + srtpSession, err := r.transport.getSRTPSession() + if err != nil { + return nil, nil, nil, nil, err + } + + rtpReadStream, err := srtpSession.OpenReadStream(uint32(ssrc)) + if err != nil { + return nil, nil, nil, nil, err + } + + rtpInterceptor := r.api.interceptor.BindRemoteStream(&streamInfo, interceptor.RTPReaderFunc(func(in []byte, a interceptor.Attributes) (n int, attributes interceptor.Attributes, err error) { + n, err = rtpReadStream.Read(in) + return n, a, err + })) + + srtcpSession, err := r.transport.getSRTCPSession() + if err != nil { + return nil, nil, nil, nil, err + } + + rtcpReadStream, err := srtcpSession.OpenReadStream(uint32(ssrc)) + if err != nil { + return nil, nil, nil, nil, err + } + + rtcpInterceptor := r.api.interceptor.BindRTCPReader(interceptor.RTPReaderFunc(func(in []byte, a interceptor.Attributes) (n int, attributes interceptor.Attributes, err error) { + n, err = rtcpReadStream.Read(in) + return n, a, err + })) + + return rtpReadStream, rtpInterceptor, rtcpReadStream, rtcpInterceptor, nil +} + +// SetReadDeadline sets the max amount of time the RTCP stream will block before returning. 0 is forever. +func (r *RTPReceiver) SetReadDeadline(t time.Time) error { + r.mu.RLock() + defer r.mu.RUnlock() + + if err := r.tracks[0].rtcpReadStream.SetReadDeadline(t); err != nil { + return err + } + return nil +} + +// SetReadDeadlineSimulcast sets the max amount of time the RTCP stream for a given rid will block before returning. 0 is forever. +func (r *RTPReceiver) SetReadDeadlineSimulcast(deadline time.Time, rid string) error { + r.mu.RLock() + defer r.mu.RUnlock() + + for _, t := range r.tracks { + if t.track != nil && t.track.rid == rid { + return t.rtcpReadStream.SetReadDeadline(deadline) + } + } + return fmt.Errorf("%w: %s", errRTPReceiverForRIDTrackStreamNotFound, rid) +} + +// setRTPReadDeadline sets the max amount of time the RTP stream will block before returning. 0 is forever. +// This should be fired by calling SetReadDeadline on the TrackRemote +func (r *RTPReceiver) setRTPReadDeadline(deadline time.Time, reader *TrackRemote) error { + r.mu.RLock() + defer r.mu.RUnlock() + + if t := r.streamsForTrack(reader); t != nil { + return t.rtpReadStream.SetReadDeadline(deadline) + } + return fmt.Errorf("%w: %d", errRTPReceiverWithSSRCTrackStreamNotFound, reader.SSRC()) +} diff --git a/vendor/github.com/pion/webrtc/v3/rtpsender.go b/vendor/github.com/pion/webrtc/v3/rtpsender.go new file mode 100644 index 0000000..f3d34c7 --- /dev/null +++ b/vendor/github.com/pion/webrtc/v3/rtpsender.go @@ -0,0 +1,261 @@ +// +build !js + +package webrtc + +import ( + "io" + "sync" + "time" + + "github.com/pion/interceptor" + "github.com/pion/randutil" + "github.com/pion/rtcp" + "github.com/pion/rtp" +) + +// RTPSender allows an application to control how a given Track is encoded and transmitted to a remote peer +type RTPSender struct { + track TrackLocal + + srtpStream *srtpWriterFuture + rtcpInterceptor interceptor.RTCPReader + + context TrackLocalContext + + transport *DTLSTransport + + payloadType PayloadType + ssrc SSRC + + // nolint:godox + // TODO(sgotti) remove this when in future we'll avoid replacing + // a transceiver sender since we can just check the + // transceiver negotiation status + negotiated bool + + // A reference to the associated api object + api *API + id string + + mu sync.RWMutex + sendCalled, stopCalled chan struct{} +} + +// NewRTPSender constructs a new RTPSender +func (api *API) NewRTPSender(track TrackLocal, transport *DTLSTransport) (*RTPSender, error) { + if track == nil { + return nil, errRTPSenderTrackNil + } else if transport == nil { + return nil, errRTPSenderDTLSTransportNil + } + + id, err := randutil.GenerateCryptoRandomString(32, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") + if err != nil { + return nil, err + } + + r := &RTPSender{ + track: track, + transport: transport, + api: api, + sendCalled: make(chan struct{}), + stopCalled: make(chan struct{}), + ssrc: SSRC(randutil.NewMathRandomGenerator().Uint32()), + id: id, + srtpStream: &srtpWriterFuture{}, + } + + r.srtpStream.rtpSender = r + + r.rtcpInterceptor = r.api.interceptor.BindRTCPReader(interceptor.RTPReaderFunc(func(in []byte, a interceptor.Attributes) (n int, attributes interceptor.Attributes, err error) { + n, err = r.srtpStream.Read(in) + return n, a, err + })) + + return r, nil +} + +func (r *RTPSender) isNegotiated() bool { + r.mu.RLock() + defer r.mu.RUnlock() + return r.negotiated +} + +func (r *RTPSender) setNegotiated() { + r.mu.Lock() + defer r.mu.Unlock() + r.negotiated = true +} + +// Transport returns the currently-configured *DTLSTransport or nil +// if one has not yet been configured +func (r *RTPSender) Transport() *DTLSTransport { + r.mu.RLock() + defer r.mu.RUnlock() + return r.transport +} + +// GetParameters describes the current configuration for the encoding and +// transmission of media on the sender's track. +func (r *RTPSender) GetParameters() RTPSendParameters { + return RTPSendParameters{ + RTPParameters: r.api.mediaEngine.getRTPParametersByKind( + r.track.Kind(), + []RTPTransceiverDirection{RTPTransceiverDirectionSendonly}, + ), + Encodings: []RTPEncodingParameters{ + { + RTPCodingParameters: RTPCodingParameters{ + SSRC: r.ssrc, + PayloadType: r.payloadType, + }, + }, + }, + } +} + +// Track returns the RTCRtpTransceiver track, or nil +func (r *RTPSender) Track() TrackLocal { + r.mu.RLock() + defer r.mu.RUnlock() + return r.track +} + +// ReplaceTrack replaces the track currently being used as the sender's source with a new TrackLocal. +// The new track must be of the same media kind (audio, video, etc) and switching the track should not +// require negotiation. +func (r *RTPSender) ReplaceTrack(track TrackLocal) error { + r.mu.Lock() + defer r.mu.Unlock() + + if r.hasSent() && r.track != nil { + if err := r.track.Unbind(r.context); err != nil { + return err + } + } + + if !r.hasSent() || track == nil { + r.track = track + return nil + } + + if _, err := track.Bind(r.context); err != nil { + // Re-bind the original track + if _, reBindErr := r.track.Bind(r.context); reBindErr != nil { + return reBindErr + } + + return err + } + + r.track = track + return nil +} + +// Send Attempts to set the parameters controlling the sending of media. +func (r *RTPSender) Send(parameters RTPSendParameters) error { + r.mu.Lock() + defer r.mu.Unlock() + + if r.hasSent() { + return errRTPSenderSendAlreadyCalled + } + + writeStream := &interceptorToTrackLocalWriter{} + r.context = TrackLocalContext{ + id: r.id, + params: r.api.mediaEngine.getRTPParametersByKind(r.track.Kind(), []RTPTransceiverDirection{RTPTransceiverDirectionSendonly}), + ssrc: parameters.Encodings[0].SSRC, + writeStream: writeStream, + } + + codec, err := r.track.Bind(r.context) + if err != nil { + return err + } + r.context.params.Codecs = []RTPCodecParameters{codec} + + streamInfo := createStreamInfo(r.id, parameters.Encodings[0].SSRC, codec.PayloadType, codec.RTPCodecCapability, parameters.HeaderExtensions) + rtpInterceptor := r.api.interceptor.BindLocalStream(&streamInfo, interceptor.RTPWriterFunc(func(header *rtp.Header, payload []byte, attributes interceptor.Attributes) (int, error) { + return r.srtpStream.WriteRTP(header, payload) + })) + writeStream.interceptor.Store(rtpInterceptor) + + close(r.sendCalled) + return nil +} + +// Stop irreversibly stops the RTPSender +func (r *RTPSender) Stop() error { + r.mu.Lock() + + if stopped := r.hasStopped(); stopped { + r.mu.Unlock() + return nil + } + + close(r.stopCalled) + r.mu.Unlock() + + if !r.hasSent() { + return nil + } + + if err := r.ReplaceTrack(nil); err != nil { + return err + } + + return r.srtpStream.Close() +} + +// Read reads incoming RTCP for this RTPReceiver +func (r *RTPSender) Read(b []byte) (n int, a interceptor.Attributes, err error) { + select { + case <-r.sendCalled: + return r.rtcpInterceptor.Read(b, a) + case <-r.stopCalled: + return 0, nil, io.ErrClosedPipe + } +} + +// ReadRTCP is a convenience method that wraps Read and unmarshals for you. +func (r *RTPSender) ReadRTCP() ([]rtcp.Packet, interceptor.Attributes, error) { + b := make([]byte, receiveMTU) + i, attributes, err := r.Read(b) + if err != nil { + return nil, nil, err + } + + pkts, err := rtcp.Unmarshal(b[:i]) + if err != nil { + return nil, nil, err + } + + return pkts, attributes, nil +} + +// SetReadDeadline sets the deadline for the Read operation. +// Setting to zero means no deadline. +func (r *RTPSender) SetReadDeadline(t time.Time) error { + return r.srtpStream.SetReadDeadline(t) +} + +// hasSent tells if data has been ever sent for this instance +func (r *RTPSender) hasSent() bool { + select { + case <-r.sendCalled: + return true + default: + return false + } +} + +// hasStopped tells if stop has been called +func (r *RTPSender) hasStopped() bool { + select { + case <-r.stopCalled: + return true + default: + return false + } +} diff --git a/vendor/github.com/pion/webrtc/v3/rtpsendparameters.go b/vendor/github.com/pion/webrtc/v3/rtpsendparameters.go new file mode 100644 index 0000000..cc55e5d --- /dev/null +++ b/vendor/github.com/pion/webrtc/v3/rtpsendparameters.go @@ -0,0 +1,7 @@ +package webrtc + +// RTPSendParameters contains the RTP stack settings used by receivers +type RTPSendParameters struct { + RTPParameters + Encodings []RTPEncodingParameters +} diff --git a/vendor/github.com/pion/webrtc/v3/rtptransceiver.go b/vendor/github.com/pion/webrtc/v3/rtptransceiver.go new file mode 100644 index 0000000..ee9de2f --- /dev/null +++ b/vendor/github.com/pion/webrtc/v3/rtptransceiver.go @@ -0,0 +1,187 @@ +// +build !js + +package webrtc + +import ( + "fmt" + "sync/atomic" + + "github.com/pion/rtp" +) + +// RTPTransceiver represents a combination of an RTPSender and an RTPReceiver that share a common mid. +type RTPTransceiver struct { + mid atomic.Value // string + sender atomic.Value // *RTPSender + receiver atomic.Value // *RTPReceiver + direction atomic.Value // RTPTransceiverDirection + + stopped bool + kind RTPCodecType +} + +// Sender returns the RTPTransceiver's RTPSender if it has one +func (t *RTPTransceiver) Sender() *RTPSender { + if v := t.sender.Load(); v != nil { + return v.(*RTPSender) + } + + return nil +} + +// SetSender sets the RTPSender and Track to current transceiver +func (t *RTPTransceiver) SetSender(s *RTPSender, track TrackLocal) error { + t.setSender(s) + return t.setSendingTrack(track) +} + +func (t *RTPTransceiver) setSender(s *RTPSender) { + t.sender.Store(s) +} + +// Receiver returns the RTPTransceiver's RTPReceiver if it has one +func (t *RTPTransceiver) Receiver() *RTPReceiver { + if v := t.receiver.Load(); v != nil { + return v.(*RTPReceiver) + } + + return nil +} + +// setMid sets the RTPTransceiver's mid. If it was already set, will return an error. +func (t *RTPTransceiver) setMid(mid string) error { + if currentMid := t.Mid(); currentMid != "" { + return fmt.Errorf("%w: %s to %s", errRTPTransceiverCannotChangeMid, currentMid, mid) + } + t.mid.Store(mid) + return nil +} + +// Mid gets the Transceiver's mid value. When not already set, this value will be set in CreateOffer or CreateAnswer. +func (t *RTPTransceiver) Mid() string { + if v := t.mid.Load(); v != nil { + return v.(string) + } + return "" +} + +// Kind returns RTPTransceiver's kind. +func (t *RTPTransceiver) Kind() RTPCodecType { + return t.kind +} + +// Direction returns the RTPTransceiver's current direction +func (t *RTPTransceiver) Direction() RTPTransceiverDirection { + return t.direction.Load().(RTPTransceiverDirection) +} + +// Stop irreversibly stops the RTPTransceiver +func (t *RTPTransceiver) Stop() error { + if t.Sender() != nil { + if err := t.Sender().Stop(); err != nil { + return err + } + } + if t.Receiver() != nil { + if err := t.Receiver().Stop(); err != nil { + return err + } + } + + t.setDirection(RTPTransceiverDirectionInactive) + return nil +} + +func (t *RTPTransceiver) setReceiver(r *RTPReceiver) { + t.receiver.Store(r) +} + +func (t *RTPTransceiver) setDirection(d RTPTransceiverDirection) { + t.direction.Store(d) +} + +func (t *RTPTransceiver) setSendingTrack(track TrackLocal) error { + if err := t.Sender().ReplaceTrack(track); err != nil { + return err + } + if track == nil { + t.setSender(nil) + } + + switch { + case track != nil && t.Direction() == RTPTransceiverDirectionRecvonly: + t.setDirection(RTPTransceiverDirectionSendrecv) + case track != nil && t.Direction() == RTPTransceiverDirectionInactive: + t.setDirection(RTPTransceiverDirectionSendonly) + case track == nil && t.Direction() == RTPTransceiverDirectionSendrecv: + t.setDirection(RTPTransceiverDirectionRecvonly) + case track == nil && t.Direction() == RTPTransceiverDirectionSendonly: + t.setDirection(RTPTransceiverDirectionInactive) + default: + return errRTPTransceiverSetSendingInvalidState + } + return nil +} + +func findByMid(mid string, localTransceivers []*RTPTransceiver) (*RTPTransceiver, []*RTPTransceiver) { + for i, t := range localTransceivers { + if t.Mid() == mid { + return t, append(localTransceivers[:i], localTransceivers[i+1:]...) + } + } + + return nil, localTransceivers +} + +// Given a direction+type pluck a transceiver from the passed list +// if no entry satisfies the requested type+direction return a inactive Transceiver +func satisfyTypeAndDirection(remoteKind RTPCodecType, remoteDirection RTPTransceiverDirection, localTransceivers []*RTPTransceiver) (*RTPTransceiver, []*RTPTransceiver) { + // Get direction order from most preferred to least + getPreferredDirections := func() []RTPTransceiverDirection { + switch remoteDirection { + case RTPTransceiverDirectionSendrecv: + return []RTPTransceiverDirection{RTPTransceiverDirectionRecvonly, RTPTransceiverDirectionSendrecv} + case RTPTransceiverDirectionSendonly: + return []RTPTransceiverDirection{RTPTransceiverDirectionRecvonly} + case RTPTransceiverDirectionRecvonly: + return []RTPTransceiverDirection{RTPTransceiverDirectionSendonly, RTPTransceiverDirectionSendrecv} + default: + return []RTPTransceiverDirection{} + } + } + + for _, possibleDirection := range getPreferredDirections() { + for i := range localTransceivers { + t := localTransceivers[i] + if t.Mid() == "" && t.kind == remoteKind && possibleDirection == t.Direction() { + return t, append(localTransceivers[:i], localTransceivers[i+1:]...) + } + } + } + + return nil, localTransceivers +} + +// handleUnknownRTPPacket consumes a single RTP Packet and returns information that is helpful +// for demuxing and handling an unknown SSRC (usually for Simulcast) +func handleUnknownRTPPacket(buf []byte, midExtensionID, streamIDExtensionID uint8) (mid, rid string, payloadType PayloadType, err error) { + rp := &rtp.Packet{} + if err = rp.Unmarshal(buf); err != nil { + return + } + + if !rp.Header.Extension { + return + } + + payloadType = PayloadType(rp.PayloadType) + if payload := rp.GetExtension(midExtensionID); payload != nil { + mid = string(payload) + } + + if payload := rp.GetExtension(streamIDExtensionID); payload != nil { + rid = string(payload) + } + + return +} diff --git a/vendor/github.com/pion/webrtc/v3/rtptransceiverdirection.go b/vendor/github.com/pion/webrtc/v3/rtptransceiverdirection.go new file mode 100644 index 0000000..3073014 --- /dev/null +++ b/vendor/github.com/pion/webrtc/v3/rtptransceiverdirection.go @@ -0,0 +1,85 @@ +package webrtc + +// RTPTransceiverDirection indicates the direction of the RTPTransceiver. +type RTPTransceiverDirection int + +const ( + // RTPTransceiverDirectionSendrecv indicates the RTPSender will offer + // to send RTP and RTPReceiver the will offer to receive RTP. + RTPTransceiverDirectionSendrecv RTPTransceiverDirection = iota + 1 + + // RTPTransceiverDirectionSendonly indicates the RTPSender will offer + // to send RTP. + RTPTransceiverDirectionSendonly + + // RTPTransceiverDirectionRecvonly indicates the RTPReceiver the will + // offer to receive RTP. + RTPTransceiverDirectionRecvonly + + // RTPTransceiverDirectionInactive indicates the RTPSender won't offer + // to send RTP and RTPReceiver the won't offer to receive RTP. + RTPTransceiverDirectionInactive +) + +// This is done this way because of a linter. +const ( + rtpTransceiverDirectionSendrecvStr = "sendrecv" + rtpTransceiverDirectionSendonlyStr = "sendonly" + rtpTransceiverDirectionRecvonlyStr = "recvonly" + rtpTransceiverDirectionInactiveStr = "inactive" +) + +// NewRTPTransceiverDirection defines a procedure for creating a new +// RTPTransceiverDirection from a raw string naming the transceiver direction. +func NewRTPTransceiverDirection(raw string) RTPTransceiverDirection { + switch raw { + case rtpTransceiverDirectionSendrecvStr: + return RTPTransceiverDirectionSendrecv + case rtpTransceiverDirectionSendonlyStr: + return RTPTransceiverDirectionSendonly + case rtpTransceiverDirectionRecvonlyStr: + return RTPTransceiverDirectionRecvonly + case rtpTransceiverDirectionInactiveStr: + return RTPTransceiverDirectionInactive + default: + return RTPTransceiverDirection(Unknown) + } +} + +func (t RTPTransceiverDirection) String() string { + switch t { + case RTPTransceiverDirectionSendrecv: + return rtpTransceiverDirectionSendrecvStr + case RTPTransceiverDirectionSendonly: + return rtpTransceiverDirectionSendonlyStr + case RTPTransceiverDirectionRecvonly: + return rtpTransceiverDirectionRecvonlyStr + case RTPTransceiverDirectionInactive: + return rtpTransceiverDirectionInactiveStr + default: + return ErrUnknownType.Error() + } +} + +// Revers indicate the opposite direction +func (t RTPTransceiverDirection) Revers() RTPTransceiverDirection { + switch t { + case RTPTransceiverDirectionSendonly: + return RTPTransceiverDirectionRecvonly + case RTPTransceiverDirectionRecvonly: + return RTPTransceiverDirectionSendonly + default: + return t + } +} + +func haveRTPTransceiverDirectionIntersection(haystack []RTPTransceiverDirection, needle []RTPTransceiverDirection) bool { + for _, n := range needle { + for _, h := range haystack { + if n == h { + return true + } + } + } + return false +} diff --git a/vendor/github.com/pion/webrtc/v3/rtptransceiverinit.go b/vendor/github.com/pion/webrtc/v3/rtptransceiverinit.go new file mode 100644 index 0000000..3c439b7 --- /dev/null +++ b/vendor/github.com/pion/webrtc/v3/rtptransceiverinit.go @@ -0,0 +1,12 @@ +package webrtc + +// RTPTransceiverInit dictionary is used when calling the WebRTC function addTransceiver() to provide configuration options for the new transceiver. +type RTPTransceiverInit struct { + Direction RTPTransceiverDirection + SendEncodings []RTPEncodingParameters + // Streams []*Track +} + +// RtpTransceiverInit is a temporary mapping while we fix case sensitivity +// Deprecated: Use RTPTransceiverInit instead +type RtpTransceiverInit = RTPTransceiverInit //nolint: stylecheck,golint diff --git a/vendor/github.com/pion/webrtc/v3/sctpcapabilities.go b/vendor/github.com/pion/webrtc/v3/sctpcapabilities.go new file mode 100644 index 0000000..34399d3 --- /dev/null +++ b/vendor/github.com/pion/webrtc/v3/sctpcapabilities.go @@ -0,0 +1,6 @@ +package webrtc + +// SCTPCapabilities indicates the capabilities of the SCTPTransport. +type SCTPCapabilities struct { + MaxMessageSize uint32 `json:"maxMessageSize"` +} diff --git a/vendor/github.com/pion/webrtc/v3/sctptransport.go b/vendor/github.com/pion/webrtc/v3/sctptransport.go new file mode 100644 index 0000000..d7b3764 --- /dev/null +++ b/vendor/github.com/pion/webrtc/v3/sctptransport.go @@ -0,0 +1,379 @@ +// +build !js + +package webrtc + +import ( + "io" + "math" + "sync" + "time" + + "github.com/pion/datachannel" + "github.com/pion/logging" + "github.com/pion/sctp" + "github.com/pion/webrtc/v3/pkg/rtcerr" +) + +const sctpMaxChannels = uint16(65535) + +// SCTPTransport provides details about the SCTP transport. +type SCTPTransport struct { + lock sync.RWMutex + + dtlsTransport *DTLSTransport + + // State represents the current state of the SCTP transport. + state SCTPTransportState + + // SCTPTransportState doesn't have an enum to distinguish between New/Connecting + // so we need a dedicated field + isStarted bool + + // MaxMessageSize represents the maximum size of data that can be passed to + // DataChannel's send() method. + maxMessageSize float64 + + // MaxChannels represents the maximum amount of DataChannel's that can + // be used simultaneously. + maxChannels *uint16 + + // OnStateChange func() + + onErrorHandler func(error) + + association *sctp.Association + onDataChannelHandler func(*DataChannel) + onDataChannelOpenedHandler func(*DataChannel) + + // DataChannels + dataChannels []*DataChannel + dataChannelsOpened uint32 + dataChannelsRequested uint32 + dataChannelsAccepted uint32 + + api *API + log logging.LeveledLogger +} + +// NewSCTPTransport creates a new SCTPTransport. +// This constructor is part of the ORTC API. It is not +// meant to be used together with the basic WebRTC API. +func (api *API) NewSCTPTransport(dtls *DTLSTransport) *SCTPTransport { + res := &SCTPTransport{ + dtlsTransport: dtls, + state: SCTPTransportStateConnecting, + api: api, + log: api.settingEngine.LoggerFactory.NewLogger("ortc"), + } + + res.updateMessageSize() + res.updateMaxChannels() + + return res +} + +// Transport returns the DTLSTransport instance the SCTPTransport is sending over. +func (r *SCTPTransport) Transport() *DTLSTransport { + r.lock.RLock() + defer r.lock.RUnlock() + + return r.dtlsTransport +} + +// GetCapabilities returns the SCTPCapabilities of the SCTPTransport. +func (r *SCTPTransport) GetCapabilities() SCTPCapabilities { + return SCTPCapabilities{ + MaxMessageSize: 0, + } +} + +// Start the SCTPTransport. Since both local and remote parties must mutually +// create an SCTPTransport, SCTP SO (Simultaneous Open) is used to establish +// a connection over SCTP. +func (r *SCTPTransport) Start(remoteCaps SCTPCapabilities) error { + if r.isStarted { + return nil + } + r.isStarted = true + + if err := r.ensureDTLS(); err != nil { + return err + } + + sctpAssociation, err := sctp.Client(sctp.Config{ + NetConn: r.Transport().conn, + LoggerFactory: r.api.settingEngine.LoggerFactory, + }) + if err != nil { + return err + } + + r.lock.Lock() + defer r.lock.Unlock() + + r.association = sctpAssociation + r.state = SCTPTransportStateConnected + + go r.acceptDataChannels(sctpAssociation) + + return nil +} + +// Stop stops the SCTPTransport +func (r *SCTPTransport) Stop() error { + r.lock.Lock() + defer r.lock.Unlock() + if r.association == nil { + return nil + } + err := r.association.Close() + if err != nil { + return err + } + + r.association = nil + r.state = SCTPTransportStateClosed + + return nil +} + +func (r *SCTPTransport) ensureDTLS() error { + dtlsTransport := r.Transport() + if dtlsTransport == nil || dtlsTransport.conn == nil { + return errSCTPTransportDTLS + } + + return nil +} + +func (r *SCTPTransport) acceptDataChannels(a *sctp.Association) { + for { + dc, err := datachannel.Accept(a, &datachannel.Config{ + LoggerFactory: r.api.settingEngine.LoggerFactory, + }) + if err != nil { + if err != io.EOF { + r.log.Errorf("Failed to accept data channel: %v", err) + r.onError(err) + } + return + } + + var ( + maxRetransmits *uint16 + maxPacketLifeTime *uint16 + ) + val := uint16(dc.Config.ReliabilityParameter) + ordered := true + + switch dc.Config.ChannelType { + case datachannel.ChannelTypeReliable: + ordered = true + case datachannel.ChannelTypeReliableUnordered: + ordered = false + case datachannel.ChannelTypePartialReliableRexmit: + ordered = true + maxRetransmits = &val + case datachannel.ChannelTypePartialReliableRexmitUnordered: + ordered = false + maxRetransmits = &val + case datachannel.ChannelTypePartialReliableTimed: + ordered = true + maxPacketLifeTime = &val + case datachannel.ChannelTypePartialReliableTimedUnordered: + ordered = false + maxPacketLifeTime = &val + default: + } + + sid := dc.StreamIdentifier() + rtcDC, err := r.api.newDataChannel(&DataChannelParameters{ + ID: &sid, + Label: dc.Config.Label, + Protocol: dc.Config.Protocol, + Negotiated: dc.Config.Negotiated, + Ordered: ordered, + MaxPacketLifeTime: maxPacketLifeTime, + MaxRetransmits: maxRetransmits, + }, r.api.settingEngine.LoggerFactory.NewLogger("ortc")) + if err != nil { + r.log.Errorf("Failed to accept data channel: %v", err) + r.onError(err) + return + } + + <-r.onDataChannel(rtcDC) + rtcDC.handleOpen(dc) + + r.lock.Lock() + r.dataChannelsOpened++ + handler := r.onDataChannelOpenedHandler + r.lock.Unlock() + + if handler != nil { + handler(rtcDC) + } + } +} + +// OnError sets an event handler which is invoked when +// the SCTP connection error occurs. +func (r *SCTPTransport) OnError(f func(err error)) { + r.lock.Lock() + defer r.lock.Unlock() + r.onErrorHandler = f +} + +func (r *SCTPTransport) onError(err error) { + r.lock.RLock() + handler := r.onErrorHandler + r.lock.RUnlock() + + if handler != nil { + go handler(err) + } +} + +// OnDataChannel sets an event handler which is invoked when a data +// channel message arrives from a remote peer. +func (r *SCTPTransport) OnDataChannel(f func(*DataChannel)) { + r.lock.Lock() + defer r.lock.Unlock() + r.onDataChannelHandler = f +} + +// OnDataChannelOpened sets an event handler which is invoked when a data +// channel is opened +func (r *SCTPTransport) OnDataChannelOpened(f func(*DataChannel)) { + r.lock.Lock() + defer r.lock.Unlock() + r.onDataChannelOpenedHandler = f +} + +func (r *SCTPTransport) onDataChannel(dc *DataChannel) (done chan struct{}) { + r.lock.Lock() + r.dataChannels = append(r.dataChannels, dc) + r.dataChannelsAccepted++ + handler := r.onDataChannelHandler + r.lock.Unlock() + + done = make(chan struct{}) + if handler == nil || dc == nil { + close(done) + return + } + + // Run this synchronously to allow setup done in onDataChannelFn() + // to complete before datachannel event handlers might be called. + go func() { + handler(dc) + close(done) + }() + + return +} + +func (r *SCTPTransport) updateMessageSize() { + r.lock.Lock() + defer r.lock.Unlock() + + var remoteMaxMessageSize float64 = 65536 // pion/webrtc#758 + var canSendSize float64 = 65536 // pion/webrtc#758 + + r.maxMessageSize = r.calcMessageSize(remoteMaxMessageSize, canSendSize) +} + +func (r *SCTPTransport) calcMessageSize(remoteMaxMessageSize, canSendSize float64) float64 { + switch { + case remoteMaxMessageSize == 0 && + canSendSize == 0: + return math.Inf(1) + + case remoteMaxMessageSize == 0: + return canSendSize + + case canSendSize == 0: + return remoteMaxMessageSize + + case canSendSize > remoteMaxMessageSize: + return remoteMaxMessageSize + + default: + return canSendSize + } +} + +func (r *SCTPTransport) updateMaxChannels() { + val := sctpMaxChannels + r.maxChannels = &val +} + +// MaxChannels is the maximum number of RTCDataChannels that can be open simultaneously. +func (r *SCTPTransport) MaxChannels() uint16 { + r.lock.Lock() + defer r.lock.Unlock() + + if r.maxChannels == nil { + return sctpMaxChannels + } + + return *r.maxChannels +} + +// State returns the current state of the SCTPTransport +func (r *SCTPTransport) State() SCTPTransportState { + r.lock.RLock() + defer r.lock.RUnlock() + return r.state +} + +func (r *SCTPTransport) collectStats(collector *statsReportCollector) { + r.lock.Lock() + association := r.association + r.lock.Unlock() + + collector.Collecting() + + stats := TransportStats{ + Timestamp: statsTimestampFrom(time.Now()), + Type: StatsTypeTransport, + ID: "sctpTransport", + } + + if association != nil { + stats.BytesSent = association.BytesSent() + stats.BytesReceived = association.BytesReceived() + } + + collector.Collect(stats.ID, stats) +} + +func (r *SCTPTransport) generateAndSetDataChannelID(dtlsRole DTLSRole, idOut **uint16) error { + isChannelWithID := func(id uint16) bool { + for _, d := range r.dataChannels { + if d.id != nil && *d.id == id { + return true + } + } + return false + } + + var id uint16 + if dtlsRole != DTLSRoleClient { + id++ + } + + max := r.MaxChannels() + + r.lock.Lock() + defer r.lock.Unlock() + for ; id < max-1; id += 2 { + if isChannelWithID(id) { + continue + } + *idOut = &id + return nil + } + + return &rtcerr.OperationError{Err: ErrMaxDataChannelID} +} diff --git a/vendor/github.com/pion/webrtc/v3/sctptransportstate.go b/vendor/github.com/pion/webrtc/v3/sctptransportstate.go new file mode 100644 index 0000000..8dc7941 --- /dev/null +++ b/vendor/github.com/pion/webrtc/v3/sctptransportstate.go @@ -0,0 +1,54 @@ +package webrtc + +// SCTPTransportState indicates the state of the SCTP transport. +type SCTPTransportState int + +const ( + // SCTPTransportStateConnecting indicates the SCTPTransport is in the + // process of negotiating an association. This is the initial state of the + // SCTPTransportState when an SCTPTransport is created. + SCTPTransportStateConnecting SCTPTransportState = iota + 1 + + // SCTPTransportStateConnected indicates the negotiation of an + // association is completed. + SCTPTransportStateConnected + + // SCTPTransportStateClosed indicates a SHUTDOWN or ABORT chunk is + // received or when the SCTP association has been closed intentionally, + // such as by closing the peer connection or applying a remote description + // that rejects data or changes the SCTP port. + SCTPTransportStateClosed +) + +// This is done this way because of a linter. +const ( + sctpTransportStateConnectingStr = "connecting" + sctpTransportStateConnectedStr = "connected" + sctpTransportStateClosedStr = "closed" +) + +func newSCTPTransportState(raw string) SCTPTransportState { + switch raw { + case sctpTransportStateConnectingStr: + return SCTPTransportStateConnecting + case sctpTransportStateConnectedStr: + return SCTPTransportStateConnected + case sctpTransportStateClosedStr: + return SCTPTransportStateClosed + default: + return SCTPTransportState(Unknown) + } +} + +func (s SCTPTransportState) String() string { + switch s { + case SCTPTransportStateConnecting: + return sctpTransportStateConnectingStr + case SCTPTransportStateConnected: + return sctpTransportStateConnectedStr + case SCTPTransportStateClosed: + return sctpTransportStateClosedStr + default: + return ErrUnknownType.Error() + } +} diff --git a/vendor/github.com/pion/webrtc/v3/sdp.go b/vendor/github.com/pion/webrtc/v3/sdp.go new file mode 100644 index 0000000..7454696 --- /dev/null +++ b/vendor/github.com/pion/webrtc/v3/sdp.go @@ -0,0 +1,643 @@ +// +build !js + +package webrtc + +import ( + "fmt" + "net/url" + "regexp" + "strconv" + "strings" + + "github.com/pion/ice/v2" + "github.com/pion/logging" + "github.com/pion/sdp/v3" +) + +// trackDetails represents any media source that can be represented in a SDP +// This isn't keyed by SSRC because it also needs to support rid based sources +type trackDetails struct { + mid string + kind RTPCodecType + streamID string + id string + ssrc SSRC + rids []string +} + +func trackDetailsForSSRC(trackDetails []trackDetails, ssrc SSRC) *trackDetails { + for i := range trackDetails { + if trackDetails[i].ssrc == ssrc { + return &trackDetails[i] + } + } + return nil +} + +func filterTrackWithSSRC(incomingTracks []trackDetails, ssrc SSRC) []trackDetails { + filtered := []trackDetails{} + for i := range incomingTracks { + if incomingTracks[i].ssrc != ssrc { + filtered = append(filtered, incomingTracks[i]) + } + } + return filtered +} + +// extract all trackDetails from an SDP. +func trackDetailsFromSDP(log logging.LeveledLogger, s *sdp.SessionDescription) []trackDetails { // nolint:gocognit + incomingTracks := []trackDetails{} + rtxRepairFlows := map[uint32]bool{} + + for _, media := range s.MediaDescriptions { + // Plan B can have multiple tracks in a signle media section + streamID := "" + trackID := "" + + // If media section is recvonly or inactive skip + if _, ok := media.Attribute(sdp.AttrKeyRecvOnly); ok { + continue + } else if _, ok := media.Attribute(sdp.AttrKeyInactive); ok { + continue + } + + midValue := getMidValue(media) + if midValue == "" { + continue + } + + codecType := NewRTPCodecType(media.MediaName.Media) + if codecType == 0 { + continue + } + + for _, attr := range media.Attributes { + switch attr.Key { + case sdp.AttrKeySSRCGroup: + split := strings.Split(attr.Value, " ") + if split[0] == sdp.SemanticTokenFlowIdentification { + // Add rtx ssrcs to blacklist, to avoid adding them as tracks + // Essentially lines like `a=ssrc-group:FID 2231627014 632943048` are processed by this section + // as this declares that the second SSRC (632943048) is a rtx repair flow (RFC4588) for the first + // (2231627014) as specified in RFC5576 + if len(split) == 3 { + _, err := strconv.ParseUint(split[1], 10, 32) + if err != nil { + log.Warnf("Failed to parse SSRC: %v", err) + continue + } + rtxRepairFlow, err := strconv.ParseUint(split[2], 10, 32) + if err != nil { + log.Warnf("Failed to parse SSRC: %v", err) + continue + } + rtxRepairFlows[uint32(rtxRepairFlow)] = true + incomingTracks = filterTrackWithSSRC(incomingTracks, SSRC(rtxRepairFlow)) // Remove if rtx was added as track before + } + } + + // Handle `a=msid:<stream_id> <track_label>` for Unified plan. The first value is the same as MediaStream.id + // in the browser and can be used to figure out which tracks belong to the same stream. The browser should + // figure this out automatically when an ontrack event is emitted on RTCPeerConnection. + case sdp.AttrKeyMsid: + split := strings.Split(attr.Value, " ") + if len(split) == 2 { + streamID = split[0] + trackID = split[1] + } + + case sdp.AttrKeySSRC: + split := strings.Split(attr.Value, " ") + ssrc, err := strconv.ParseUint(split[0], 10, 32) + if err != nil { + log.Warnf("Failed to parse SSRC: %v", err) + continue + } + + if rtxRepairFlow := rtxRepairFlows[uint32(ssrc)]; rtxRepairFlow { + continue // This ssrc is a RTX repair flow, ignore + } + + if len(split) == 3 && strings.HasPrefix(split[1], "msid:") { + streamID = split[1][len("msid:"):] + trackID = split[2] + } + + isNewTrack := true + trackDetails := &trackDetails{} + for i := range incomingTracks { + if incomingTracks[i].ssrc == SSRC(ssrc) { + trackDetails = &incomingTracks[i] + isNewTrack = false + } + } + + trackDetails.mid = midValue + trackDetails.kind = codecType + trackDetails.streamID = streamID + trackDetails.id = trackID + trackDetails.ssrc = SSRC(ssrc) + + if isNewTrack { + incomingTracks = append(incomingTracks, *trackDetails) + } + } + } + + if rids := getRids(media); len(rids) != 0 && trackID != "" && streamID != "" { + newTrack := trackDetails{ + mid: midValue, + kind: codecType, + streamID: streamID, + id: trackID, + rids: []string{}, + } + for rid := range rids { + newTrack.rids = append(newTrack.rids, rid) + } + + incomingTracks = append(incomingTracks, newTrack) + } + } + return incomingTracks +} + +func getRids(media *sdp.MediaDescription) map[string]string { + rids := map[string]string{} + for _, attr := range media.Attributes { + if attr.Key == "rid" { + split := strings.Split(attr.Value, " ") + rids[split[0]] = attr.Value + } + } + return rids +} + +func addCandidatesToMediaDescriptions(candidates []ICECandidate, m *sdp.MediaDescription, iceGatheringState ICEGatheringState) error { + appendCandidateIfNew := func(c ice.Candidate, attributes []sdp.Attribute) { + marshaled := c.Marshal() + for _, a := range attributes { + if marshaled == a.Value { + return + } + } + + m.WithValueAttribute("candidate", marshaled) + } + + for _, c := range candidates { + candidate, err := c.toICE() + if err != nil { + return err + } + + candidate.SetComponent(1) + appendCandidateIfNew(candidate, m.Attributes) + + candidate.SetComponent(2) + appendCandidateIfNew(candidate, m.Attributes) + } + + if iceGatheringState != ICEGatheringStateComplete { + return nil + } + for _, a := range m.Attributes { + if a.Key == "end-of-candidates" { + return nil + } + } + + m.WithPropertyAttribute("end-of-candidates") + return nil +} + +func addDataMediaSection(d *sdp.SessionDescription, shouldAddCandidates bool, dtlsFingerprints []DTLSFingerprint, midValue string, iceParams ICEParameters, candidates []ICECandidate, dtlsRole sdp.ConnectionRole, iceGatheringState ICEGatheringState) error { + media := (&sdp.MediaDescription{ + MediaName: sdp.MediaName{ + Media: mediaSectionApplication, + Port: sdp.RangedPort{Value: 9}, + Protos: []string{"UDP", "DTLS", "SCTP"}, + Formats: []string{"webrtc-datachannel"}, + }, + ConnectionInformation: &sdp.ConnectionInformation{ + NetworkType: "IN", + AddressType: "IP4", + Address: &sdp.Address{ + Address: "0.0.0.0", + }, + }, + }). + WithValueAttribute(sdp.AttrKeyConnectionSetup, dtlsRole.String()). + WithValueAttribute(sdp.AttrKeyMID, midValue). + WithPropertyAttribute(RTPTransceiverDirectionSendrecv.String()). + WithPropertyAttribute("sctp-port:5000"). + WithICECredentials(iceParams.UsernameFragment, iceParams.Password) + + for _, f := range dtlsFingerprints { + media = media.WithFingerprint(f.Algorithm, strings.ToUpper(f.Value)) + } + + if shouldAddCandidates { + if err := addCandidatesToMediaDescriptions(candidates, media, iceGatheringState); err != nil { + return err + } + } + + d.WithMedia(media) + return nil +} + +func populateLocalCandidates(sessionDescription *SessionDescription, i *ICEGatherer, iceGatheringState ICEGatheringState) *SessionDescription { + if sessionDescription == nil || i == nil { + return sessionDescription + } + + candidates, err := i.GetLocalCandidates() + if err != nil { + return sessionDescription + } + + parsed := sessionDescription.parsed + if len(parsed.MediaDescriptions) > 0 { + m := parsed.MediaDescriptions[0] + if err = addCandidatesToMediaDescriptions(candidates, m, iceGatheringState); err != nil { + return sessionDescription + } + } + + sdp, err := parsed.Marshal() + if err != nil { + return sessionDescription + } + + return &SessionDescription{ + SDP: string(sdp), + Type: sessionDescription.Type, + } +} + +func addTransceiverSDP(d *sdp.SessionDescription, isPlanB, shouldAddCandidates bool, dtlsFingerprints []DTLSFingerprint, mediaEngine *MediaEngine, midValue string, iceParams ICEParameters, candidates []ICECandidate, dtlsRole sdp.ConnectionRole, iceGatheringState ICEGatheringState, mediaSection mediaSection) (bool, error) { + transceivers := mediaSection.transceivers + if len(transceivers) < 1 { + return false, errSDPZeroTransceivers + } + // Use the first transceiver to generate the section attributes + t := transceivers[0] + media := sdp.NewJSEPMediaDescription(t.kind.String(), []string{}). + WithValueAttribute(sdp.AttrKeyConnectionSetup, dtlsRole.String()). + WithValueAttribute(sdp.AttrKeyMID, midValue). + WithICECredentials(iceParams.UsernameFragment, iceParams.Password). + WithPropertyAttribute(sdp.AttrKeyRTCPMux). + WithPropertyAttribute(sdp.AttrKeyRTCPRsize) + + codecs := mediaEngine.getCodecsByKind(t.kind) + for _, codec := range codecs { + name := strings.TrimPrefix(codec.MimeType, "audio/") + name = strings.TrimPrefix(name, "video/") + media.WithCodec(uint8(codec.PayloadType), name, codec.ClockRate, codec.Channels, codec.SDPFmtpLine) + + for _, feedback := range codec.RTPCodecCapability.RTCPFeedback { + media.WithValueAttribute("rtcp-fb", fmt.Sprintf("%d %s %s", codec.PayloadType, feedback.Type, feedback.Parameter)) + } + } + if len(codecs) == 0 { + // Explicitly reject track if we don't have the codec + d.WithMedia(&sdp.MediaDescription{ + MediaName: sdp.MediaName{ + Media: t.kind.String(), + Port: sdp.RangedPort{Value: 0}, + Protos: []string{"UDP", "TLS", "RTP", "SAVPF"}, + Formats: []string{"0"}, + }, + }) + return false, nil + } + + directions := []RTPTransceiverDirection{} + if t.Sender() != nil { + directions = append(directions, RTPTransceiverDirectionSendonly) + } + if t.Receiver() != nil { + directions = append(directions, RTPTransceiverDirectionRecvonly) + } + + parameters := mediaEngine.getRTPParametersByKind(t.kind, directions) + for _, rtpExtension := range parameters.HeaderExtensions { + extURL, err := url.Parse(rtpExtension.URI) + if err != nil { + return false, err + } + media.WithExtMap(sdp.ExtMap{Value: rtpExtension.ID, URI: extURL}) + } + + if len(mediaSection.ridMap) > 0 { + recvRids := make([]string, 0, len(mediaSection.ridMap)) + + for rid := range mediaSection.ridMap { + media.WithValueAttribute("rid", rid+" recv") + recvRids = append(recvRids, rid) + } + // Simulcast + media.WithValueAttribute("simulcast", "recv "+strings.Join(recvRids, ";")) + } + + for _, mt := range transceivers { + if mt.Sender() != nil && mt.Sender().Track() != nil { + track := mt.Sender().Track() + media = media.WithMediaSource(uint32(mt.Sender().ssrc), track.StreamID() /* cname */, track.StreamID() /* streamLabel */, track.ID()) + if !isPlanB { + media = media.WithPropertyAttribute("msid:" + track.StreamID() + " " + track.ID()) + break + } + } + } + + media = media.WithPropertyAttribute(t.Direction().String()) + + for _, fingerprint := range dtlsFingerprints { + media = media.WithFingerprint(fingerprint.Algorithm, strings.ToUpper(fingerprint.Value)) + } + + if shouldAddCandidates { + if err := addCandidatesToMediaDescriptions(candidates, media, iceGatheringState); err != nil { + return false, err + } + } + + d.WithMedia(media) + + return true, nil +} + +type mediaSection struct { + id string + transceivers []*RTPTransceiver + data bool + ridMap map[string]string +} + +// populateSDP serializes a PeerConnections state into an SDP +func populateSDP(d *sdp.SessionDescription, isPlanB bool, dtlsFingerprints []DTLSFingerprint, mediaDescriptionFingerprint bool, isICELite bool, mediaEngine *MediaEngine, connectionRole sdp.ConnectionRole, candidates []ICECandidate, iceParams ICEParameters, mediaSections []mediaSection, iceGatheringState ICEGatheringState) (*sdp.SessionDescription, error) { + var err error + mediaDtlsFingerprints := []DTLSFingerprint{} + + if mediaDescriptionFingerprint { + mediaDtlsFingerprints = dtlsFingerprints + } + + bundleValue := "BUNDLE" + bundleCount := 0 + appendBundle := func(midValue string) { + bundleValue += " " + midValue + bundleCount++ + } + + for i, m := range mediaSections { + if m.data && len(m.transceivers) != 0 { + return nil, errSDPMediaSectionMediaDataChanInvalid + } else if !isPlanB && len(m.transceivers) > 1 { + return nil, errSDPMediaSectionMultipleTrackInvalid + } + + shouldAddID := true + shouldAddCanidates := i == 0 + if m.data { + if err = addDataMediaSection(d, shouldAddCanidates, mediaDtlsFingerprints, m.id, iceParams, candidates, connectionRole, iceGatheringState); err != nil { + return nil, err + } + } else { + shouldAddID, err = addTransceiverSDP(d, isPlanB, shouldAddCanidates, mediaDtlsFingerprints, mediaEngine, m.id, iceParams, candidates, connectionRole, iceGatheringState, m) + if err != nil { + return nil, err + } + } + + if shouldAddID { + appendBundle(m.id) + } + } + + if !mediaDescriptionFingerprint { + for _, fingerprint := range dtlsFingerprints { + d.WithFingerprint(fingerprint.Algorithm, strings.ToUpper(fingerprint.Value)) + } + } + + if isICELite { + // RFC 5245 S15.3 + d = d.WithValueAttribute(sdp.AttrKeyICELite, sdp.AttrKeyICELite) + } + + return d.WithValueAttribute(sdp.AttrKeyGroup, bundleValue), nil +} + +func getMidValue(media *sdp.MediaDescription) string { + for _, attr := range media.Attributes { + if attr.Key == "mid" { + return attr.Value + } + } + return "" +} + +func descriptionIsPlanB(desc *SessionDescription) bool { + if desc == nil || desc.parsed == nil { + return false + } + + detectionRegex := regexp.MustCompile(`(?i)^(audio|video|data)$`) + for _, media := range desc.parsed.MediaDescriptions { + if len(detectionRegex.FindStringSubmatch(getMidValue(media))) == 2 { + return true + } + } + return false +} + +func getPeerDirection(media *sdp.MediaDescription) RTPTransceiverDirection { + for _, a := range media.Attributes { + if direction := NewRTPTransceiverDirection(a.Key); direction != RTPTransceiverDirection(Unknown) { + return direction + } + } + return RTPTransceiverDirection(Unknown) +} + +func extractFingerprint(desc *sdp.SessionDescription) (string, string, error) { + fingerprints := []string{} + + if fingerprint, haveFingerprint := desc.Attribute("fingerprint"); haveFingerprint { + fingerprints = append(fingerprints, fingerprint) + } + + for _, m := range desc.MediaDescriptions { + if fingerprint, haveFingerprint := m.Attribute("fingerprint"); haveFingerprint { + fingerprints = append(fingerprints, fingerprint) + } + } + + if len(fingerprints) < 1 { + return "", "", ErrSessionDescriptionNoFingerprint + } + + for _, m := range fingerprints { + if m != fingerprints[0] { + return "", "", ErrSessionDescriptionConflictingFingerprints + } + } + + parts := strings.Split(fingerprints[0], " ") + if len(parts) != 2 { + return "", "", ErrSessionDescriptionInvalidFingerprint + } + return parts[1], parts[0], nil +} + +func extractICEDetails(desc *sdp.SessionDescription) (string, string, []ICECandidate, error) { + candidates := []ICECandidate{} + remotePwds := []string{} + remoteUfrags := []string{} + + if ufrag, haveUfrag := desc.Attribute("ice-ufrag"); haveUfrag { + remoteUfrags = append(remoteUfrags, ufrag) + } + if pwd, havePwd := desc.Attribute("ice-pwd"); havePwd { + remotePwds = append(remotePwds, pwd) + } + + for _, m := range desc.MediaDescriptions { + if ufrag, haveUfrag := m.Attribute("ice-ufrag"); haveUfrag { + remoteUfrags = append(remoteUfrags, ufrag) + } + if pwd, havePwd := m.Attribute("ice-pwd"); havePwd { + remotePwds = append(remotePwds, pwd) + } + + for _, a := range m.Attributes { + if a.IsICECandidate() { + c, err := ice.UnmarshalCandidate(a.Value) + if err != nil { + return "", "", nil, err + } + + candidate, err := newICECandidateFromICE(c) + if err != nil { + return "", "", nil, err + } + + candidates = append(candidates, candidate) + } + } + } + + if len(remoteUfrags) == 0 { + return "", "", nil, ErrSessionDescriptionMissingIceUfrag + } else if len(remotePwds) == 0 { + return "", "", nil, ErrSessionDescriptionMissingIcePwd + } + + for _, m := range remoteUfrags { + if m != remoteUfrags[0] { + return "", "", nil, ErrSessionDescriptionConflictingIceUfrag + } + } + + for _, m := range remotePwds { + if m != remotePwds[0] { + return "", "", nil, ErrSessionDescriptionConflictingIcePwd + } + } + + return remoteUfrags[0], remotePwds[0], candidates, nil +} + +func haveApplicationMediaSection(desc *sdp.SessionDescription) bool { + for _, m := range desc.MediaDescriptions { + if m.MediaName.Media == mediaSectionApplication { + return true + } + } + + return false +} + +func getByMid(searchMid string, desc *SessionDescription) *sdp.MediaDescription { + for _, m := range desc.parsed.MediaDescriptions { + if mid, ok := m.Attribute(sdp.AttrKeyMID); ok && mid == searchMid { + return m + } + } + return nil +} + +// haveDataChannel return MediaDescription with MediaName equal application +func haveDataChannel(desc *SessionDescription) *sdp.MediaDescription { + for _, d := range desc.parsed.MediaDescriptions { + if d.MediaName.Media == mediaSectionApplication { + return d + } + } + return nil +} + +func codecsFromMediaDescription(m *sdp.MediaDescription) (out []RTPCodecParameters, err error) { + s := &sdp.SessionDescription{ + MediaDescriptions: []*sdp.MediaDescription{m}, + } + + for _, payloadStr := range m.MediaName.Formats { + payloadType, err := strconv.Atoi(payloadStr) + if err != nil { + return nil, err + } + + codec, err := s.GetCodecForPayloadType(uint8(payloadType)) + if err != nil { + if payloadType == 0 { + continue + } + return nil, err + } + + channels := uint16(0) + val, err := strconv.Atoi(codec.EncodingParameters) + if err == nil { + channels = uint16(val) + } + + feedback := []RTCPFeedback{} + for _, raw := range codec.RTCPFeedback { + split := strings.Split(raw, " ") + entry := RTCPFeedback{Type: split[0]} + if len(split) == 2 { + entry.Parameter = split[1] + } + + feedback = append(feedback, entry) + } + + out = append(out, RTPCodecParameters{ + RTPCodecCapability: RTPCodecCapability{m.MediaName.Media + "/" + codec.Name, codec.ClockRate, channels, codec.Fmtp, feedback}, + PayloadType: PayloadType(payloadType), + }) + } + + return out, nil +} + +func rtpExtensionsFromMediaDescription(m *sdp.MediaDescription) (map[string]int, error) { + out := map[string]int{} + + for _, a := range m.Attributes { + if a.Key == sdp.AttrKeyExtMap { + e := sdp.ExtMap{} + if err := e.Unmarshal(a.String()); err != nil { + return nil, err + } + + out[e.URI.String()] = e.Value + } + } + + return out, nil +} diff --git a/vendor/github.com/pion/webrtc/v3/sdpsemantics.go b/vendor/github.com/pion/webrtc/v3/sdpsemantics.go new file mode 100644 index 0000000..b8d396c --- /dev/null +++ b/vendor/github.com/pion/webrtc/v3/sdpsemantics.go @@ -0,0 +1,74 @@ +package webrtc + +import ( + "encoding/json" +) + +// SDPSemantics determines which style of SDP offers and answers +// can be used +type SDPSemantics int + +const ( + // SDPSemanticsUnifiedPlan uses unified-plan offers and answers + // (the default in Chrome since M72) + // https://tools.ietf.org/html/draft-roach-mmusic-unified-plan-00 + SDPSemanticsUnifiedPlan SDPSemantics = iota + + // SDPSemanticsPlanB uses plan-b offers and answers + // NB: This format should be considered deprecated + // https://tools.ietf.org/html/draft-uberti-rtcweb-plan-00 + SDPSemanticsPlanB + + // SDPSemanticsUnifiedPlanWithFallback prefers unified-plan + // offers and answers, but will respond to a plan-b offer + // with a plan-b answer + SDPSemanticsUnifiedPlanWithFallback +) + +const ( + sdpSemanticsUnifiedPlanWithFallback = "unified-plan-with-fallback" + sdpSemanticsUnifiedPlan = "unified-plan" + sdpSemanticsPlanB = "plan-b" +) + +func newSDPSemantics(raw string) SDPSemantics { + switch raw { + case sdpSemanticsUnifiedPlan: + return SDPSemanticsUnifiedPlan + case sdpSemanticsPlanB: + return SDPSemanticsPlanB + case sdpSemanticsUnifiedPlanWithFallback: + return SDPSemanticsUnifiedPlanWithFallback + default: + return SDPSemantics(Unknown) + } +} + +func (s SDPSemantics) String() string { + switch s { + case SDPSemanticsUnifiedPlanWithFallback: + return sdpSemanticsUnifiedPlanWithFallback + case SDPSemanticsUnifiedPlan: + return sdpSemanticsUnifiedPlan + case SDPSemanticsPlanB: + return sdpSemanticsPlanB + default: + return ErrUnknownType.Error() + } +} + +// UnmarshalJSON parses the JSON-encoded data and stores the result +func (s *SDPSemantics) UnmarshalJSON(b []byte) error { + var val string + if err := json.Unmarshal(b, &val); err != nil { + return err + } + + *s = newSDPSemantics(val) + return nil +} + +// MarshalJSON returns the JSON encoding +func (s SDPSemantics) MarshalJSON() ([]byte, error) { + return json.Marshal(s.String()) +} diff --git a/vendor/github.com/pion/webrtc/v3/sdptype.go b/vendor/github.com/pion/webrtc/v3/sdptype.go new file mode 100644 index 0000000..bd49b6d --- /dev/null +++ b/vendor/github.com/pion/webrtc/v3/sdptype.go @@ -0,0 +1,100 @@ +package webrtc + +import ( + "encoding/json" + "strings" +) + +// SDPType describes the type of an SessionDescription. +type SDPType int + +const ( + // SDPTypeOffer indicates that a description MUST be treated as an SDP + // offer. + SDPTypeOffer SDPType = iota + 1 + + // SDPTypePranswer indicates that a description MUST be treated as an + // SDP answer, but not a final answer. A description used as an SDP + // pranswer may be applied as a response to an SDP offer, or an update to + // a previously sent SDP pranswer. + SDPTypePranswer + + // SDPTypeAnswer indicates that a description MUST be treated as an SDP + // final answer, and the offer-answer exchange MUST be considered complete. + // A description used as an SDP answer may be applied as a response to an + // SDP offer or as an update to a previously sent SDP pranswer. + SDPTypeAnswer + + // SDPTypeRollback indicates that a description MUST be treated as + // canceling the current SDP negotiation and moving the SDP offer and + // answer back to what it was in the previous stable state. Note the + // local or remote SDP descriptions in the previous stable state could be + // null if there has not yet been a successful offer-answer negotiation. + SDPTypeRollback +) + +// This is done this way because of a linter. +const ( + sdpTypeOfferStr = "offer" + sdpTypePranswerStr = "pranswer" + sdpTypeAnswerStr = "answer" + sdpTypeRollbackStr = "rollback" +) + +// NewSDPType creates an SDPType from a string +func NewSDPType(raw string) SDPType { + switch raw { + case sdpTypeOfferStr: + return SDPTypeOffer + case sdpTypePranswerStr: + return SDPTypePranswer + case sdpTypeAnswerStr: + return SDPTypeAnswer + case sdpTypeRollbackStr: + return SDPTypeRollback + default: + return SDPType(Unknown) + } +} + +func (t SDPType) String() string { + switch t { + case SDPTypeOffer: + return sdpTypeOfferStr + case SDPTypePranswer: + return sdpTypePranswerStr + case SDPTypeAnswer: + return sdpTypeAnswerStr + case SDPTypeRollback: + return sdpTypeRollbackStr + default: + return ErrUnknownType.Error() + } +} + +// MarshalJSON enables JSON marshaling of a SDPType +func (t SDPType) MarshalJSON() ([]byte, error) { + return json.Marshal(t.String()) +} + +// UnmarshalJSON enables JSON unmarshaling of a SDPType +func (t *SDPType) UnmarshalJSON(b []byte) error { + var s string + if err := json.Unmarshal(b, &s); err != nil { + return err + } + switch strings.ToLower(s) { + default: + return ErrUnknownType + case "offer": + *t = SDPTypeOffer + case "pranswer": + *t = SDPTypePranswer + case "answer": + *t = SDPTypeAnswer + case "rollback": + *t = SDPTypeRollback + } + + return nil +} diff --git a/vendor/github.com/pion/webrtc/v3/sessiondescription.go b/vendor/github.com/pion/webrtc/v3/sessiondescription.go new file mode 100644 index 0000000..5b93391 --- /dev/null +++ b/vendor/github.com/pion/webrtc/v3/sessiondescription.go @@ -0,0 +1,21 @@ +package webrtc + +import ( + "github.com/pion/sdp/v3" +) + +// SessionDescription is used to expose local and remote session descriptions. +type SessionDescription struct { + Type SDPType `json:"type"` + SDP string `json:"sdp"` + + // This will never be initialized by callers, internal use only + parsed *sdp.SessionDescription +} + +// Unmarshal is a helper to deserialize the sdp +func (sd *SessionDescription) Unmarshal() (*sdp.SessionDescription, error) { + sd.parsed = &sdp.SessionDescription{} + err := sd.parsed.Unmarshal([]byte(sd.SDP)) + return sd.parsed, err +} diff --git a/vendor/github.com/pion/webrtc/v3/settingengine.go b/vendor/github.com/pion/webrtc/v3/settingengine.go new file mode 100644 index 0000000..8091e6a --- /dev/null +++ b/vendor/github.com/pion/webrtc/v3/settingengine.go @@ -0,0 +1,265 @@ +// +build !js + +package webrtc + +import ( + "io" + "time" + + "github.com/pion/ice/v2" + "github.com/pion/logging" + "github.com/pion/transport/packetio" + "github.com/pion/transport/vnet" + "golang.org/x/net/proxy" +) + +// SettingEngine allows influencing behavior in ways that are not +// supported by the WebRTC API. This allows us to support additional +// use-cases without deviating from the WebRTC API elsewhere. +type SettingEngine struct { + ephemeralUDP struct { + PortMin uint16 + PortMax uint16 + } + detach struct { + DataChannels bool + } + timeout struct { + ICEDisconnectedTimeout *time.Duration + ICEFailedTimeout *time.Duration + ICEKeepaliveInterval *time.Duration + ICEHostAcceptanceMinWait *time.Duration + ICESrflxAcceptanceMinWait *time.Duration + ICEPrflxAcceptanceMinWait *time.Duration + ICERelayAcceptanceMinWait *time.Duration + } + candidates struct { + ICELite bool + ICENetworkTypes []NetworkType + InterfaceFilter func(string) bool + NAT1To1IPs []string + NAT1To1IPCandidateType ICECandidateType + MulticastDNSMode ice.MulticastDNSMode + MulticastDNSHostName string + UsernameFragment string + Password string + } + replayProtection struct { + DTLS *uint + SRTP *uint + SRTCP *uint + } + sdpMediaLevelFingerprints bool + answeringDTLSRole DTLSRole + disableCertificateFingerprintVerification bool + disableSRTPReplayProtection bool + disableSRTCPReplayProtection bool + vnet *vnet.Net + BufferFactory func(packetType packetio.BufferPacketType, ssrc uint32) io.ReadWriteCloser + LoggerFactory logging.LoggerFactory + iceTCPMux ice.TCPMux + iceProxyDialer proxy.Dialer + disableMediaEngineCopy bool +} + +// DetachDataChannels enables detaching data channels. When enabled +// data channels have to be detached in the OnOpen callback using the +// DataChannel.Detach method. +func (e *SettingEngine) DetachDataChannels() { + e.detach.DataChannels = true +} + +// SetICETimeouts sets the behavior around ICE Timeouts +// * disconnectedTimeout is the duration without network activity before a Agent is considered disconnected. Default is 5 Seconds +// * failedTimeout is the duration without network activity before a Agent is considered failed after disconnected. Default is 25 Seconds +// * keepAliveInterval is how often the ICE Agent sends extra traffic if there is no activity, if media is flowing no traffic will be sent. Default is 2 seconds +func (e *SettingEngine) SetICETimeouts(disconnectedTimeout, failedTimeout, keepAliveInterval time.Duration) { + e.timeout.ICEDisconnectedTimeout = &disconnectedTimeout + e.timeout.ICEFailedTimeout = &failedTimeout + e.timeout.ICEKeepaliveInterval = &keepAliveInterval +} + +// SetHostAcceptanceMinWait sets the ICEHostAcceptanceMinWait +func (e *SettingEngine) SetHostAcceptanceMinWait(t time.Duration) { + e.timeout.ICEHostAcceptanceMinWait = &t +} + +// SetSrflxAcceptanceMinWait sets the ICESrflxAcceptanceMinWait +func (e *SettingEngine) SetSrflxAcceptanceMinWait(t time.Duration) { + e.timeout.ICESrflxAcceptanceMinWait = &t +} + +// SetPrflxAcceptanceMinWait sets the ICEPrflxAcceptanceMinWait +func (e *SettingEngine) SetPrflxAcceptanceMinWait(t time.Duration) { + e.timeout.ICEPrflxAcceptanceMinWait = &t +} + +// SetRelayAcceptanceMinWait sets the ICERelayAcceptanceMinWait +func (e *SettingEngine) SetRelayAcceptanceMinWait(t time.Duration) { + e.timeout.ICERelayAcceptanceMinWait = &t +} + +// SetEphemeralUDPPortRange limits the pool of ephemeral ports that +// ICE UDP connections can allocate from. This affects both host candidates, +// and the local address of server reflexive candidates. +func (e *SettingEngine) SetEphemeralUDPPortRange(portMin, portMax uint16) error { + if portMax < portMin { + return ice.ErrPort + } + + e.ephemeralUDP.PortMin = portMin + e.ephemeralUDP.PortMax = portMax + return nil +} + +// SetLite configures whether or not the ice agent should be a lite agent +func (e *SettingEngine) SetLite(lite bool) { + e.candidates.ICELite = lite +} + +// SetNetworkTypes configures what types of candidate networks are supported +// during local and server reflexive gathering. +func (e *SettingEngine) SetNetworkTypes(candidateTypes []NetworkType) { + e.candidates.ICENetworkTypes = candidateTypes +} + +// SetInterfaceFilter sets the filtering functions when gathering ICE candidates +// This can be used to exclude certain network interfaces from ICE. Which may be +// useful if you know a certain interface will never succeed, or if you wish to reduce +// the amount of information you wish to expose to the remote peer +func (e *SettingEngine) SetInterfaceFilter(filter func(string) bool) { + e.candidates.InterfaceFilter = filter +} + +// SetNAT1To1IPs sets a list of external IP addresses of 1:1 (D)NAT +// and a candidate type for which the external IP address is used. +// This is useful when you are host a server using Pion on an AWS EC2 instance +// which has a private address, behind a 1:1 DNAT with a public IP (e.g. +// Elastic IP). In this case, you can give the public IP address so that +// Pion will use the public IP address in its candidate instead of the private +// IP address. The second argument, candidateType, is used to tell Pion which +// type of candidate should use the given public IP address. +// Two types of candidates are supported: +// +// ICECandidateTypeHost: +// The public IP address will be used for the host candidate in the SDP. +// ICECandidateTypeSrflx: +// A server reflexive candidate with the given public IP address will be added +// to the SDP. +// +// Please note that if you choose ICECandidateTypeHost, then the private IP address +// won't be advertised with the peer. Also, this option cannot be used along with mDNS. +// +// If you choose ICECandidateTypeSrflx, it simply adds a server reflexive candidate +// with the public IP. The host candidate is still available along with mDNS +// capabilities unaffected. Also, you cannot give STUN server URL at the same time. +// It will result in an error otherwise. +func (e *SettingEngine) SetNAT1To1IPs(ips []string, candidateType ICECandidateType) { + e.candidates.NAT1To1IPs = ips + e.candidates.NAT1To1IPCandidateType = candidateType +} + +// SetAnsweringDTLSRole sets the DTLS role that is selected when offering +// The DTLS role controls if the WebRTC Client as a client or server. This +// may be useful when interacting with non-compliant clients or debugging issues. +// +// DTLSRoleActive: +// Act as DTLS Client, send the ClientHello and starts the handshake +// DTLSRolePassive: +// Act as DTLS Server, wait for ClientHello +func (e *SettingEngine) SetAnsweringDTLSRole(role DTLSRole) error { + if role != DTLSRoleClient && role != DTLSRoleServer { + return errSettingEngineSetAnsweringDTLSRole + } + + e.answeringDTLSRole = role + return nil +} + +// SetVNet sets the VNet instance that is passed to pion/ice +// +// VNet is a virtual network layer for Pion, allowing users to simulate +// different topologies, latency, loss and jitter. This can be useful for +// learning WebRTC concepts or testing your application in a lab environment +func (e *SettingEngine) SetVNet(vnet *vnet.Net) { + e.vnet = vnet +} + +// SetICEMulticastDNSMode controls if pion/ice queries and generates mDNS ICE Candidates +func (e *SettingEngine) SetICEMulticastDNSMode(multicastDNSMode ice.MulticastDNSMode) { + e.candidates.MulticastDNSMode = multicastDNSMode +} + +// SetMulticastDNSHostName sets a static HostName to be used by pion/ice instead of generating one on startup +// +// This should only be used for a single PeerConnection. Having multiple PeerConnections with the same HostName will cause +// undefined behavior +func (e *SettingEngine) SetMulticastDNSHostName(hostName string) { + e.candidates.MulticastDNSHostName = hostName +} + +// SetICECredentials sets a staic uFrag/uPwd to be used by pion/ice +// +// This is useful if you want to do signalless WebRTC session, or having a reproducible environment with static credentials +func (e *SettingEngine) SetICECredentials(usernameFragment, password string) { + e.candidates.UsernameFragment = usernameFragment + e.candidates.Password = password +} + +// DisableCertificateFingerprintVerification disables fingerprint verification after DTLS Handshake has finished +func (e *SettingEngine) DisableCertificateFingerprintVerification(isDisabled bool) { + e.disableCertificateFingerprintVerification = isDisabled +} + +// SetDTLSReplayProtectionWindow sets a replay attack protection window size of DTLS connection. +func (e *SettingEngine) SetDTLSReplayProtectionWindow(n uint) { + e.replayProtection.DTLS = &n +} + +// SetSRTPReplayProtectionWindow sets a replay attack protection window size of SRTP session. +func (e *SettingEngine) SetSRTPReplayProtectionWindow(n uint) { + e.disableSRTPReplayProtection = false + e.replayProtection.SRTP = &n +} + +// SetSRTCPReplayProtectionWindow sets a replay attack protection window size of SRTCP session. +func (e *SettingEngine) SetSRTCPReplayProtectionWindow(n uint) { + e.disableSRTCPReplayProtection = false + e.replayProtection.SRTCP = &n +} + +// DisableSRTPReplayProtection disables SRTP replay protection. +func (e *SettingEngine) DisableSRTPReplayProtection(isDisabled bool) { + e.disableSRTPReplayProtection = isDisabled +} + +// DisableSRTCPReplayProtection disables SRTCP replay protection. +func (e *SettingEngine) DisableSRTCPReplayProtection(isDisabled bool) { + e.disableSRTCPReplayProtection = isDisabled +} + +// SetSDPMediaLevelFingerprints configures the logic for DTLS Fingerprint insertion +// If true, fingerprints will be inserted in the sdp at the fingerprint +// level, instead of the session level. This helps with compatibility with +// some webrtc implementations. +func (e *SettingEngine) SetSDPMediaLevelFingerprints(sdpMediaLevelFingerprints bool) { + e.sdpMediaLevelFingerprints = sdpMediaLevelFingerprints +} + +// SetICETCPMux enables ICE-TCP when set to a non-nil value. Make sure that +// NetworkTypeTCP4 or NetworkTypeTCP6 is enabled as well. +func (e *SettingEngine) SetICETCPMux(tcpMux ice.TCPMux) { + e.iceTCPMux = tcpMux +} + +// SetICEProxyDialer sets the proxy dialer interface based on golang.org/x/net/proxy. +func (e *SettingEngine) SetICEProxyDialer(d proxy.Dialer) { + e.iceProxyDialer = d +} + +// DisableMediaEngineCopy stops the MediaEngine from being copied. This allows a user to modify +// the MediaEngine after the PeerConnection has been constructed. This is useful if you wish to +// modify codecs after signaling. Make sure not to share MediaEngines between PeerConnections. +func (e *SettingEngine) DisableMediaEngineCopy(isDisabled bool) { + e.disableMediaEngineCopy = isDisabled +} diff --git a/vendor/github.com/pion/webrtc/v3/settingengine_js.go b/vendor/github.com/pion/webrtc/v3/settingengine_js.go new file mode 100644 index 0000000..5b77d66 --- /dev/null +++ b/vendor/github.com/pion/webrtc/v3/settingengine_js.go @@ -0,0 +1,19 @@ +// +build js,wasm + +package webrtc + +// SettingEngine allows influencing behavior in ways that are not +// supported by the WebRTC API. This allows us to support additional +// use-cases without deviating from the WebRTC API elsewhere. +type SettingEngine struct { + detach struct { + DataChannels bool + } +} + +// DetachDataChannels enables detaching data channels. When enabled +// data channels have to be detached in the OnOpen callback using the +// DataChannel.Detach method. +func (e *SettingEngine) DetachDataChannels() { + e.detach.DataChannels = true +} diff --git a/vendor/github.com/pion/webrtc/v3/signalingstate.go b/vendor/github.com/pion/webrtc/v3/signalingstate.go new file mode 100644 index 0000000..b64dffc --- /dev/null +++ b/vendor/github.com/pion/webrtc/v3/signalingstate.go @@ -0,0 +1,188 @@ +package webrtc + +import ( + "fmt" + "sync/atomic" + + "github.com/pion/webrtc/v3/pkg/rtcerr" +) + +type stateChangeOp int + +const ( + stateChangeOpSetLocal stateChangeOp = iota + 1 + stateChangeOpSetRemote +) + +func (op stateChangeOp) String() string { + switch op { + case stateChangeOpSetLocal: + return "SetLocal" + case stateChangeOpSetRemote: + return "SetRemote" + default: + return "Unknown State Change Operation" + } +} + +// SignalingState indicates the signaling state of the offer/answer process. +type SignalingState int32 + +const ( + // SignalingStateStable indicates there is no offer/answer exchange in + // progress. This is also the initial state, in which case the local and + // remote descriptions are nil. + SignalingStateStable SignalingState = iota + 1 + + // SignalingStateHaveLocalOffer indicates that a local description, of + // type "offer", has been successfully applied. + SignalingStateHaveLocalOffer + + // SignalingStateHaveRemoteOffer indicates that a remote description, of + // type "offer", has been successfully applied. + SignalingStateHaveRemoteOffer + + // SignalingStateHaveLocalPranswer indicates that a remote description + // of type "offer" has been successfully applied and a local description + // of type "pranswer" has been successfully applied. + SignalingStateHaveLocalPranswer + + // SignalingStateHaveRemotePranswer indicates that a local description + // of type "offer" has been successfully applied and a remote description + // of type "pranswer" has been successfully applied. + SignalingStateHaveRemotePranswer + + // SignalingStateClosed indicates The PeerConnection has been closed. + SignalingStateClosed +) + +// This is done this way because of a linter. +const ( + signalingStateStableStr = "stable" + signalingStateHaveLocalOfferStr = "have-local-offer" + signalingStateHaveRemoteOfferStr = "have-remote-offer" + signalingStateHaveLocalPranswerStr = "have-local-pranswer" + signalingStateHaveRemotePranswerStr = "have-remote-pranswer" + signalingStateClosedStr = "closed" +) + +func newSignalingState(raw string) SignalingState { + switch raw { + case signalingStateStableStr: + return SignalingStateStable + case signalingStateHaveLocalOfferStr: + return SignalingStateHaveLocalOffer + case signalingStateHaveRemoteOfferStr: + return SignalingStateHaveRemoteOffer + case signalingStateHaveLocalPranswerStr: + return SignalingStateHaveLocalPranswer + case signalingStateHaveRemotePranswerStr: + return SignalingStateHaveRemotePranswer + case signalingStateClosedStr: + return SignalingStateClosed + default: + return SignalingState(Unknown) + } +} + +func (t SignalingState) String() string { + switch t { + case SignalingStateStable: + return signalingStateStableStr + case SignalingStateHaveLocalOffer: + return signalingStateHaveLocalOfferStr + case SignalingStateHaveRemoteOffer: + return signalingStateHaveRemoteOfferStr + case SignalingStateHaveLocalPranswer: + return signalingStateHaveLocalPranswerStr + case SignalingStateHaveRemotePranswer: + return signalingStateHaveRemotePranswerStr + case SignalingStateClosed: + return signalingStateClosedStr + default: + return ErrUnknownType.Error() + } +} + +// Get thread safe read value +func (t *SignalingState) Get() SignalingState { + return SignalingState(atomic.LoadInt32((*int32)(t))) +} + +// Set thread safe write value +func (t *SignalingState) Set(state SignalingState) { + atomic.StoreInt32((*int32)(t), int32(state)) +} + +func checkNextSignalingState(cur, next SignalingState, op stateChangeOp, sdpType SDPType) (SignalingState, error) { // nolint:gocognit + // Special case for rollbacks + if sdpType == SDPTypeRollback && cur == SignalingStateStable { + return cur, &rtcerr.InvalidModificationError{ + Err: errSignalingStateCannotRollback, + } + } + + // 4.3.1 valid state transitions + switch cur { // nolint:exhaustive + case SignalingStateStable: + switch op { + case stateChangeOpSetLocal: + // stable->SetLocal(offer)->have-local-offer + if sdpType == SDPTypeOffer && next == SignalingStateHaveLocalOffer { + return next, nil + } + case stateChangeOpSetRemote: + // stable->SetRemote(offer)->have-remote-offer + if sdpType == SDPTypeOffer && next == SignalingStateHaveRemoteOffer { + return next, nil + } + } + case SignalingStateHaveLocalOffer: + if op == stateChangeOpSetRemote { + switch sdpType { // nolint:exhaustive + // have-local-offer->SetRemote(answer)->stable + case SDPTypeAnswer: + if next == SignalingStateStable { + return next, nil + } + // have-local-offer->SetRemote(pranswer)->have-remote-pranswer + case SDPTypePranswer: + if next == SignalingStateHaveRemotePranswer { + return next, nil + } + } + } + case SignalingStateHaveRemotePranswer: + if op == stateChangeOpSetRemote && sdpType == SDPTypeAnswer { + // have-remote-pranswer->SetRemote(answer)->stable + if next == SignalingStateStable { + return next, nil + } + } + case SignalingStateHaveRemoteOffer: + if op == stateChangeOpSetLocal { + switch sdpType { // nolint:exhaustive + // have-remote-offer->SetLocal(answer)->stable + case SDPTypeAnswer: + if next == SignalingStateStable { + return next, nil + } + // have-remote-offer->SetLocal(pranswer)->have-local-pranswer + case SDPTypePranswer: + if next == SignalingStateHaveLocalPranswer { + return next, nil + } + } + } + case SignalingStateHaveLocalPranswer: + if op == stateChangeOpSetLocal && sdpType == SDPTypeAnswer { + // have-local-pranswer->SetLocal(answer)->stable + if next == SignalingStateStable { + return next, nil + } + } + } + return cur, &rtcerr.InvalidModificationError{ + Err: fmt.Errorf("%w: %s->%s(%s)->%s", errSignalingStateProposedTransitionInvalid, cur, op, sdpType, next), + } +} diff --git a/vendor/github.com/pion/webrtc/v3/srtp_writer_future.go b/vendor/github.com/pion/webrtc/v3/srtp_writer_future.go new file mode 100644 index 0000000..4d8bafe --- /dev/null +++ b/vendor/github.com/pion/webrtc/v3/srtp_writer_future.go @@ -0,0 +1,118 @@ +// +build !js + +package webrtc + +import ( + "io" + "sync/atomic" + "time" + + "github.com/pion/rtp" + "github.com/pion/srtp/v2" +) + +// srtpWriterFuture blocks Read/Write calls until +// the SRTP Session is available +type srtpWriterFuture struct { + rtpSender *RTPSender + rtcpReadStream atomic.Value // *srtp.ReadStreamSRTCP + rtpWriteStream atomic.Value // *srtp.WriteStreamSRTP +} + +func (s *srtpWriterFuture) init(returnWhenNoSRTP bool) error { + if returnWhenNoSRTP { + select { + case <-s.rtpSender.stopCalled: + return io.ErrClosedPipe + case <-s.rtpSender.transport.srtpReady: + default: + return nil + } + } else { + select { + case <-s.rtpSender.stopCalled: + return io.ErrClosedPipe + case <-s.rtpSender.transport.srtpReady: + } + } + + srtcpSession, err := s.rtpSender.transport.getSRTCPSession() + if err != nil { + return err + } + + rtcpReadStream, err := srtcpSession.OpenReadStream(uint32(s.rtpSender.ssrc)) + if err != nil { + return err + } + + srtpSession, err := s.rtpSender.transport.getSRTPSession() + if err != nil { + return err + } + + rtpWriteStream, err := srtpSession.OpenWriteStream() + if err != nil { + return err + } + + s.rtcpReadStream.Store(rtcpReadStream) + s.rtpWriteStream.Store(rtpWriteStream) + return nil +} + +func (s *srtpWriterFuture) Close() error { + if value := s.rtcpReadStream.Load(); value != nil { + return value.(*srtp.ReadStreamSRTCP).Close() + } + + return nil +} + +func (s *srtpWriterFuture) Read(b []byte) (n int, err error) { + if value := s.rtcpReadStream.Load(); value != nil { + return value.(*srtp.ReadStreamSRTCP).Read(b) + } + + if err := s.init(false); err != nil || s.rtcpReadStream.Load() == nil { + return 0, err + } + + return s.Read(b) +} + +func (s *srtpWriterFuture) SetReadDeadline(t time.Time) error { + if value := s.rtcpReadStream.Load(); value != nil { + return value.(*srtp.ReadStreamSRTCP).SetReadDeadline(t) + } + + if err := s.init(false); err != nil || s.rtcpReadStream.Load() == nil { + return err + } + + return s.SetReadDeadline(t) +} + +func (s *srtpWriterFuture) WriteRTP(header *rtp.Header, payload []byte) (int, error) { + if value := s.rtpWriteStream.Load(); value != nil { + return value.(*srtp.WriteStreamSRTP).WriteRTP(header, payload) + } + + if err := s.init(true); err != nil || s.rtpWriteStream.Load() == nil { + return 0, err + } + + return s.WriteRTP(header, payload) +} + +func (s *srtpWriterFuture) Write(b []byte) (int, error) { + if value := s.rtpWriteStream.Load(); value != nil { + return value.(*srtp.WriteStreamSRTP).Write(b) + } + + if err := s.init(true); err != nil || s.rtpWriteStream.Load() == nil { + return 0, err + } + + return s.Write(b) +} diff --git a/vendor/github.com/pion/webrtc/v3/stats.go b/vendor/github.com/pion/webrtc/v3/stats.go new file mode 100644 index 0000000..a218d9e --- /dev/null +++ b/vendor/github.com/pion/webrtc/v3/stats.go @@ -0,0 +1,1452 @@ +package webrtc + +import ( + "fmt" + "sync" + "time" + + "github.com/pion/ice/v2" +) + +// A Stats object contains a set of statistics copies out of a monitored component +// of the WebRTC stack at a specific time. +type Stats interface{} + +// StatsType indicates the type of the object that a Stats object represents. +type StatsType string + +const ( + // StatsTypeCodec is used by CodecStats. + StatsTypeCodec StatsType = "codec" + + // StatsTypeInboundRTP is used by InboundRTPStreamStats. + StatsTypeInboundRTP StatsType = "inbound-rtp" + + // StatsTypeOutboundRTP is used by OutboundRTPStreamStats. + StatsTypeOutboundRTP StatsType = "outbound-rtp" + + // StatsTypeRemoteInboundRTP is used by RemoteInboundRTPStreamStats. + StatsTypeRemoteInboundRTP StatsType = "remote-inbound-rtp" + + // StatsTypeRemoteOutboundRTP is used by RemoteOutboundRTPStreamStats. + StatsTypeRemoteOutboundRTP StatsType = "remote-outbound-rtp" + + // StatsTypeCSRC is used by RTPContributingSourceStats. + StatsTypeCSRC StatsType = "csrc" + + // StatsTypePeerConnection used by PeerConnectionStats. + StatsTypePeerConnection StatsType = "peer-connection" + + // StatsTypeDataChannel is used by DataChannelStats. + StatsTypeDataChannel StatsType = "data-channel" + + // StatsTypeStream is used by MediaStreamStats. + StatsTypeStream StatsType = "stream" + + // StatsTypeTrack is used by SenderVideoTrackAttachmentStats and SenderAudioTrackAttachmentStats. + StatsTypeTrack StatsType = "track" + + // StatsTypeSender is used by by the AudioSenderStats or VideoSenderStats depending on kind. + StatsTypeSender StatsType = "sender" + + // StatsTypeReceiver is used by the AudioReceiverStats or VideoReceiverStats depending on kind. + StatsTypeReceiver StatsType = "receiver" + + // StatsTypeTransport is used by TransportStats. + StatsTypeTransport StatsType = "transport" + + // StatsTypeCandidatePair is used by ICECandidatePairStats. + StatsTypeCandidatePair StatsType = "candidate-pair" + + // StatsTypeLocalCandidate is used by ICECandidateStats for the local candidate. + StatsTypeLocalCandidate StatsType = "local-candidate" + + // StatsTypeRemoteCandidate is used by ICECandidateStats for the remote candidate. + StatsTypeRemoteCandidate StatsType = "remote-candidate" + + // StatsTypeCertificate is used by CertificateStats. + StatsTypeCertificate StatsType = "certificate" +) + +// StatsTimestamp is a timestamp represented by the floating point number of +// milliseconds since the epoch. +type StatsTimestamp float64 + +// Time returns the time.Time represented by this timestamp. +func (s StatsTimestamp) Time() time.Time { + millis := float64(s) + nanos := int64(millis * float64(time.Millisecond)) + + return time.Unix(0, nanos).UTC() +} + +func statsTimestampFrom(t time.Time) StatsTimestamp { + return StatsTimestamp(t.UnixNano() / int64(time.Millisecond)) +} + +func statsTimestampNow() StatsTimestamp { + return statsTimestampFrom(time.Now()) +} + +// StatsReport collects Stats objects indexed by their ID. +type StatsReport map[string]Stats + +type statsReportCollector struct { + collectingGroup sync.WaitGroup + report StatsReport + mux sync.Mutex +} + +func newStatsReportCollector() *statsReportCollector { + return &statsReportCollector{report: make(StatsReport)} +} + +func (src *statsReportCollector) Collecting() { + src.collectingGroup.Add(1) +} + +func (src *statsReportCollector) Collect(id string, stats Stats) { + src.mux.Lock() + defer src.mux.Unlock() + + src.report[id] = stats + src.collectingGroup.Done() +} + +func (src *statsReportCollector) Done() { + src.collectingGroup.Done() +} + +func (src *statsReportCollector) Ready() StatsReport { + src.collectingGroup.Wait() + src.mux.Lock() + defer src.mux.Unlock() + return src.report +} + +// CodecType specifies whether a CodecStats objects represents a media format +// that is being encoded or decoded +type CodecType string + +const ( + // CodecTypeEncode means the attached CodecStats represents a media format that + // is being encoded, or that the implementation is prepared to encode. + CodecTypeEncode CodecType = "encode" + + // CodecTypeDecode means the attached CodecStats represents a media format + // that the implementation is prepared to decode. + CodecTypeDecode CodecType = "decode" +) + +// CodecStats contains statistics for a codec that is currently being used by RTP streams +// being sent or received by this PeerConnection object. +type CodecStats struct { + // Timestamp is the timestamp associated with this object. + Timestamp StatsTimestamp `json:"timestamp"` + + // Type is the object's StatsType + Type StatsType `json:"type"` + + // ID is a unique id that is associated with the component inspected to produce + // this Stats object. Two Stats objects will have the same ID if they were produced + // by inspecting the same underlying object. + ID string `json:"id"` + + // PayloadType as used in RTP encoding or decoding + PayloadType PayloadType `json:"payloadType"` + + // CodecType of this CodecStats + CodecType CodecType `json:"codecType"` + + // TransportID is the unique identifier of the transport on which this codec is + // being used, which can be used to look up the corresponding TransportStats object. + TransportID string `json:"transportId"` + + // MimeType is the codec MIME media type/subtype. e.g., video/vp8 or equivalent. + MimeType string `json:"mimeType"` + + // ClockRate represents the media sampling rate. + ClockRate uint32 `json:"clockRate"` + + // Channels is 2 for stereo, missing for most other cases. + Channels uint8 `json:"channels"` + + // SDPFmtpLine is the a=fmtp line in the SDP corresponding to the codec, + // i.e., after the colon following the PT. + SDPFmtpLine string `json:"sdpFmtpLine"` + + // Implementation identifies the implementation used. This is useful for diagnosing + // interoperability issues. + Implementation string `json:"implementation"` +} + +// InboundRTPStreamStats contains statistics for an inbound RTP stream that is +// currently received with this PeerConnection object. +type InboundRTPStreamStats struct { + // Timestamp is the timestamp associated with this object. + Timestamp StatsTimestamp `json:"timestamp"` + + // Type is the object's StatsType + Type StatsType `json:"type"` + + // ID is a unique id that is associated with the component inspected to produce + // this Stats object. Two Stats objects will have the same ID if they were produced + // by inspecting the same underlying object. + ID string `json:"id"` + + // SSRC is the 32-bit unsigned integer value used to identify the source of the + // stream of RTP packets that this stats object concerns. + SSRC SSRC `json:"ssrc"` + + // Kind is either "audio" or "video" + Kind string `json:"kind"` + + // It is a unique identifier that is associated to the object that was inspected + // to produce the TransportStats associated with this RTP stream. + TransportID string `json:"transportId"` + + // CodecID is a unique identifier that is associated to the object that was inspected + // to produce the CodecStats associated with this RTP stream. + CodecID string `json:"codecId"` + + // FIRCount counts the total number of Full Intra Request (FIR) packets received + // by the sender. This metric is only valid for video and is sent by receiver. + FIRCount uint32 `json:"firCount"` + + // PLICount counts the total number of Picture Loss Indication (PLI) packets + // received by the sender. This metric is only valid for video and is sent by receiver. + PLICount uint32 `json:"pliCount"` + + // NACKCount counts the total number of Negative ACKnowledgement (NACK) packets + // received by the sender and is sent by receiver. + NACKCount uint32 `json:"nackCount"` + + // SLICount counts the total number of Slice Loss Indication (SLI) packets received + // by the sender. This metric is only valid for video and is sent by receiver. + SLICount uint32 `json:"sliCount"` + + // QPSum is the sum of the QP values of frames passed. The count of frames is + // in FramesDecoded for inbound stream stats, and in FramesEncoded for outbound stream stats. + QPSum uint64 `json:"qpSum"` + + // PacketsReceived is the total number of RTP packets received for this SSRC. + PacketsReceived uint32 `json:"packetsReceived"` + + // PacketsLost is the total number of RTP packets lost for this SSRC. Note that + // because of how this is estimated, it can be negative if more packets are received than sent. + PacketsLost int32 `json:"packetsLost"` + + // Jitter is the packet jitter measured in seconds for this SSRC + Jitter float64 `json:"jitter"` + + // PacketsDiscarded is the cumulative number of RTP packets discarded by the jitter + // buffer due to late or early-arrival, i.e., these packets are not played out. + // RTP packets discarded due to packet duplication are not reported in this metric. + PacketsDiscarded uint32 `json:"packetsDiscarded"` + + // PacketsRepaired is the cumulative number of lost RTP packets repaired after applying + // an error-resilience mechanism. It is measured for the primary source RTP packets + // and only counted for RTP packets that have no further chance of repair. + PacketsRepaired uint32 `json:"packetsRepaired"` + + // BurstPacketsLost is the cumulative number of RTP packets lost during loss bursts. + BurstPacketsLost uint32 `json:"burstPacketsLost"` + + // BurstPacketsDiscarded is the cumulative number of RTP packets discarded during discard bursts. + BurstPacketsDiscarded uint32 `json:"burstPacketsDiscarded"` + + // BurstLossCount is the cumulative number of bursts of lost RTP packets. + BurstLossCount uint32 `json:"burstLossCount"` + + // BurstDiscardCount is the cumulative number of bursts of discarded RTP packets. + BurstDiscardCount uint32 `json:"burstDiscardCount"` + + // BurstLossRate is the fraction of RTP packets lost during bursts to the + // total number of RTP packets expected in the bursts. + BurstLossRate float64 `json:"burstLossRate"` + + // BurstDiscardRate is the fraction of RTP packets discarded during bursts to + // the total number of RTP packets expected in bursts. + BurstDiscardRate float64 `json:"burstDiscardRate"` + + // GapLossRate is the fraction of RTP packets lost during the gap periods. + GapLossRate float64 `json:"gapLossRate"` + + // GapDiscardRate is the fraction of RTP packets discarded during the gap periods. + GapDiscardRate float64 `json:"gapDiscardRate"` + + // TrackID is the identifier of the stats object representing the receiving track, + // a ReceiverAudioTrackAttachmentStats or ReceiverVideoTrackAttachmentStats. + TrackID string `json:"trackId"` + + // ReceiverID is the stats ID used to look up the AudioReceiverStats or VideoReceiverStats + // object receiving this stream. + ReceiverID string `json:"receiverId"` + + // RemoteID is used for looking up the remote RemoteOutboundRTPStreamStats object + // for the same SSRC. + RemoteID string `json:"remoteId"` + + // FramesDecoded represents the total number of frames correctly decoded for this SSRC, + // i.e., frames that would be displayed if no frames are dropped. Only valid for video. + FramesDecoded uint32 `json:"framesDecoded"` + + // LastPacketReceivedTimestamp represents the timestamp at which the last packet was + // received for this SSRC. This differs from Timestamp, which represents the time + // at which the statistics were generated by the local endpoint. + LastPacketReceivedTimestamp StatsTimestamp `json:"lastPacketReceivedTimestamp"` + + // AverageRTCPInterval is the average RTCP interval between two consecutive compound RTCP packets. + // This is calculated by the sending endpoint when sending compound RTCP reports. + // Compound packets must contain at least a RTCP RR or SR packet and an SDES packet + // with the CNAME item. + AverageRTCPInterval float64 `json:"averageRtcpInterval"` + + // FECPacketsReceived is the total number of RTP FEC packets received for this SSRC. + // This counter can also be incremented when receiving FEC packets in-band with media packets (e.g., with Opus). + FECPacketsReceived uint32 `json:"fecPacketsReceived"` + + // BytesReceived is the total number of bytes received for this SSRC. + BytesReceived uint64 `json:"bytesReceived"` + + // PacketsFailedDecryption is the cumulative number of RTP packets that failed + // to be decrypted. These packets are not counted by PacketsDiscarded. + PacketsFailedDecryption uint32 `json:"packetsFailedDecryption"` + + // PacketsDuplicated is the cumulative number of packets discarded because they + // are duplicated. Duplicate packets are not counted in PacketsDiscarded. + // + // Duplicated packets have the same RTP sequence number and content as a previously + // received packet. If multiple duplicates of a packet are received, all of them are counted. + // An improved estimate of lost packets can be calculated by adding PacketsDuplicated to PacketsLost. + PacketsDuplicated uint32 `json:"packetsDuplicated"` + + // PerDSCPPacketsReceived is the total number of packets received for this SSRC, + // per Differentiated Services code point (DSCP) [RFC2474]. DSCPs are identified + // as decimal integers in string form. Note that due to network remapping and bleaching, + // these numbers are not expected to match the numbers seen on sending. Not all + // OSes make this information available. + PerDSCPPacketsReceived map[string]uint32 `json:"perDscpPacketsReceived"` +} + +// QualityLimitationReason lists the reason for limiting the resolution and/or framerate. +// Only valid for video. +type QualityLimitationReason string + +const ( + // QualityLimitationReasonNone means the resolution and/or framerate is not limited. + QualityLimitationReasonNone QualityLimitationReason = "none" + + // QualityLimitationReasonCPU means the resolution and/or framerate is primarily limited due to CPU load. + QualityLimitationReasonCPU QualityLimitationReason = "cpu" + + // QualityLimitationReasonBandwidth means the resolution and/or framerate is primarily limited due to congestion cues during bandwidth estimation. Typical, congestion control algorithms use inter-arrival time, round-trip time, packet or other congestion cues to perform bandwidth estimation. + QualityLimitationReasonBandwidth QualityLimitationReason = "bandwidth" + + // QualityLimitationReasonOther means the resolution and/or framerate is primarily limited for a reason other than the above. + QualityLimitationReasonOther QualityLimitationReason = "other" +) + +// OutboundRTPStreamStats contains statistics for an outbound RTP stream that is +// currently sent with this PeerConnection object. +type OutboundRTPStreamStats struct { + // Timestamp is the timestamp associated with this object. + Timestamp StatsTimestamp `json:"timestamp"` + + // Type is the object's StatsType + Type StatsType `json:"type"` + + // ID is a unique id that is associated with the component inspected to produce + // this Stats object. Two Stats objects will have the same ID if they were produced + // by inspecting the same underlying object. + ID string `json:"id"` + + // SSRC is the 32-bit unsigned integer value used to identify the source of the + // stream of RTP packets that this stats object concerns. + SSRC SSRC `json:"ssrc"` + + // Kind is either "audio" or "video" + Kind string `json:"kind"` + + // It is a unique identifier that is associated to the object that was inspected + // to produce the TransportStats associated with this RTP stream. + TransportID string `json:"transportId"` + + // CodecID is a unique identifier that is associated to the object that was inspected + // to produce the CodecStats associated with this RTP stream. + CodecID string `json:"codecId"` + + // FIRCount counts the total number of Full Intra Request (FIR) packets received + // by the sender. This metric is only valid for video and is sent by receiver. + FIRCount uint32 `json:"firCount"` + + // PLICount counts the total number of Picture Loss Indication (PLI) packets + // received by the sender. This metric is only valid for video and is sent by receiver. + PLICount uint32 `json:"pliCount"` + + // NACKCount counts the total number of Negative ACKnowledgement (NACK) packets + // received by the sender and is sent by receiver. + NACKCount uint32 `json:"nackCount"` + + // SLICount counts the total number of Slice Loss Indication (SLI) packets received + // by the sender. This metric is only valid for video and is sent by receiver. + SLICount uint32 `json:"sliCount"` + + // QPSum is the sum of the QP values of frames passed. The count of frames is + // in FramesDecoded for inbound stream stats, and in FramesEncoded for outbound stream stats. + QPSum uint64 `json:"qpSum"` + + // PacketsSent is the total number of RTP packets sent for this SSRC. + PacketsSent uint32 `json:"packetsSent"` + + // PacketsDiscardedOnSend is the total number of RTP packets for this SSRC that + // have been discarded due to socket errors, i.e. a socket error occurred when handing + // the packets to the socket. This might happen due to various reasons, including + // full buffer or no available memory. + PacketsDiscardedOnSend uint32 `json:"packetsDiscardedOnSend"` + + // FECPacketsSent is the total number of RTP FEC packets sent for this SSRC. + // This counter can also be incremented when sending FEC packets in-band with + // media packets (e.g., with Opus). + FECPacketsSent uint32 `json:"fecPacketsSent"` + + // BytesSent is the total number of bytes sent for this SSRC. + BytesSent uint64 `json:"bytesSent"` + + // BytesDiscardedOnSend is the total number of bytes for this SSRC that have + // been discarded due to socket errors, i.e. a socket error occurred when handing + // the packets containing the bytes to the socket. This might happen due to various + // reasons, including full buffer or no available memory. + BytesDiscardedOnSend uint64 `json:"bytesDiscardedOnSend"` + + // TrackID is the identifier of the stats object representing the current track + // attachment to the sender of this stream, a SenderAudioTrackAttachmentStats + // or SenderVideoTrackAttachmentStats. + TrackID string `json:"trackId"` + + // SenderID is the stats ID used to look up the AudioSenderStats or VideoSenderStats + // object sending this stream. + SenderID string `json:"senderId"` + + // RemoteID is used for looking up the remote RemoteInboundRTPStreamStats object + // for the same SSRC. + RemoteID string `json:"remoteId"` + + // LastPacketSentTimestamp represents the timestamp at which the last packet was + // sent for this SSRC. This differs from timestamp, which represents the time at + // which the statistics were generated by the local endpoint. + LastPacketSentTimestamp StatsTimestamp `json:"lastPacketSentTimestamp"` + + // TargetBitrate is the current target bitrate configured for this particular SSRC + // and is the Transport Independent Application Specific (TIAS) bitrate [RFC3890]. + // Typically, the target bitrate is a configuration parameter provided to the codec's + // encoder and does not count the size of the IP or other transport layers like TCP or UDP. + // It is measured in bits per second and the bitrate is calculated over a 1 second window. + TargetBitrate float64 `json:"targetBitrate"` + + // FramesEncoded represents the total number of frames successfully encoded for this RTP media stream. + // Only valid for video. + FramesEncoded uint32 `json:"framesEncoded"` + + // TotalEncodeTime is the total number of seconds that has been spent encoding the + // framesEncoded frames of this stream. The average encode time can be calculated by + // dividing this value with FramesEncoded. The time it takes to encode one frame is the + // time passed between feeding the encoder a frame and the encoder returning encoded data + // for that frame. This does not include any additional time it may take to packetize the resulting data. + TotalEncodeTime float64 `json:"totalEncodeTime"` + + // AverageRTCPInterval is the average RTCP interval between two consecutive compound RTCP + // packets. This is calculated by the sending endpoint when sending compound RTCP reports. + // Compound packets must contain at least a RTCP RR or SR packet and an SDES packet with the CNAME item. + AverageRTCPInterval float64 `json:"averageRtcpInterval"` + + // QualityLimitationReason is the current reason for limiting the resolution and/or framerate, + // or "none" if not limited. Only valid for video. + QualityLimitationReason QualityLimitationReason `json:"qualityLimitationReason"` + + // QualityLimitationDurations is record of the total time, in seconds, that this + // stream has spent in each quality limitation state. The record includes a mapping + // for all QualityLimitationReason types, including "none". Only valid for video. + QualityLimitationDurations map[string]float64 `json:"qualityLimitationDurations"` + + // PerDSCPPacketsSent is the total number of packets sent for this SSRC, per DSCP. + // DSCPs are identified as decimal integers in string form. + PerDSCPPacketsSent map[string]uint32 `json:"perDscpPacketsSent"` +} + +// RemoteInboundRTPStreamStats contains statistics for the remote endpoint's inbound +// RTP stream corresponding to an outbound stream that is currently sent with this +// PeerConnection object. It is measured at the remote endpoint and reported in an RTCP +// Receiver Report (RR) or RTCP Extended Report (XR). +type RemoteInboundRTPStreamStats struct { + // Timestamp is the timestamp associated with this object. + Timestamp StatsTimestamp `json:"timestamp"` + + // Type is the object's StatsType + Type StatsType `json:"type"` + + // ID is a unique id that is associated with the component inspected to produce + // this Stats object. Two Stats objects will have the same ID if they were produced + // by inspecting the same underlying object. + ID string `json:"id"` + + // SSRC is the 32-bit unsigned integer value used to identify the source of the + // stream of RTP packets that this stats object concerns. + SSRC SSRC `json:"ssrc"` + + // Kind is either "audio" or "video" + Kind string `json:"kind"` + + // It is a unique identifier that is associated to the object that was inspected + // to produce the TransportStats associated with this RTP stream. + TransportID string `json:"transportId"` + + // CodecID is a unique identifier that is associated to the object that was inspected + // to produce the CodecStats associated with this RTP stream. + CodecID string `json:"codecId"` + + // FIRCount counts the total number of Full Intra Request (FIR) packets received + // by the sender. This metric is only valid for video and is sent by receiver. + FIRCount uint32 `json:"firCount"` + + // PLICount counts the total number of Picture Loss Indication (PLI) packets + // received by the sender. This metric is only valid for video and is sent by receiver. + PLICount uint32 `json:"pliCount"` + + // NACKCount counts the total number of Negative ACKnowledgement (NACK) packets + // received by the sender and is sent by receiver. + NACKCount uint32 `json:"nackCount"` + + // SLICount counts the total number of Slice Loss Indication (SLI) packets received + // by the sender. This metric is only valid for video and is sent by receiver. + SLICount uint32 `json:"sliCount"` + + // QPSum is the sum of the QP values of frames passed. The count of frames is + // in FramesDecoded for inbound stream stats, and in FramesEncoded for outbound stream stats. + QPSum uint64 `json:"qpSum"` + + // PacketsReceived is the total number of RTP packets received for this SSRC. + PacketsReceived uint32 `json:"packetsReceived"` + + // PacketsLost is the total number of RTP packets lost for this SSRC. Note that + // because of how this is estimated, it can be negative if more packets are received than sent. + PacketsLost int32 `json:"packetsLost"` + + // Jitter is the packet jitter measured in seconds for this SSRC + Jitter float64 `json:"jitter"` + + // PacketsDiscarded is the cumulative number of RTP packets discarded by the jitter + // buffer due to late or early-arrival, i.e., these packets are not played out. + // RTP packets discarded due to packet duplication are not reported in this metric. + PacketsDiscarded uint32 `json:"packetsDiscarded"` + + // PacketsRepaired is the cumulative number of lost RTP packets repaired after applying + // an error-resilience mechanism. It is measured for the primary source RTP packets + // and only counted for RTP packets that have no further chance of repair. + PacketsRepaired uint32 `json:"packetsRepaired"` + + // BurstPacketsLost is the cumulative number of RTP packets lost during loss bursts. + BurstPacketsLost uint32 `json:"burstPacketsLost"` + + // BurstPacketsDiscarded is the cumulative number of RTP packets discarded during discard bursts. + BurstPacketsDiscarded uint32 `json:"burstPacketsDiscarded"` + + // BurstLossCount is the cumulative number of bursts of lost RTP packets. + BurstLossCount uint32 `json:"burstLossCount"` + + // BurstDiscardCount is the cumulative number of bursts of discarded RTP packets. + BurstDiscardCount uint32 `json:"burstDiscardCount"` + + // BurstLossRate is the fraction of RTP packets lost during bursts to the + // total number of RTP packets expected in the bursts. + BurstLossRate float64 `json:"burstLossRate"` + + // BurstDiscardRate is the fraction of RTP packets discarded during bursts to + // the total number of RTP packets expected in bursts. + BurstDiscardRate float64 `json:"burstDiscardRate"` + + // GapLossRate is the fraction of RTP packets lost during the gap periods. + GapLossRate float64 `json:"gapLossRate"` + + // GapDiscardRate is the fraction of RTP packets discarded during the gap periods. + GapDiscardRate float64 `json:"gapDiscardRate"` + + // LocalID is used for looking up the local OutboundRTPStreamStats object for the same SSRC. + LocalID string `json:"localId"` + + // RoundTripTime is the estimated round trip time for this SSRC based on the + // RTCP timestamps in the RTCP Receiver Report (RR) and measured in seconds. + RoundTripTime float64 `json:"roundTripTime"` + + // FractionLost is the the fraction packet loss reported for this SSRC. + FractionLost float64 `json:"fractionLost"` +} + +// RemoteOutboundRTPStreamStats contains statistics for the remote endpoint's outbound +// RTP stream corresponding to an inbound stream that is currently received with this +// PeerConnection object. It is measured at the remote endpoint and reported in an +// RTCP Sender Report (SR). +type RemoteOutboundRTPStreamStats struct { + // Timestamp is the timestamp associated with this object. + Timestamp StatsTimestamp `json:"timestamp"` + + // Type is the object's StatsType + Type StatsType `json:"type"` + + // ID is a unique id that is associated with the component inspected to produce + // this Stats object. Two Stats objects will have the same ID if they were produced + // by inspecting the same underlying object. + ID string `json:"id"` + + // SSRC is the 32-bit unsigned integer value used to identify the source of the + // stream of RTP packets that this stats object concerns. + SSRC SSRC `json:"ssrc"` + + // Kind is either "audio" or "video" + Kind string `json:"kind"` + + // It is a unique identifier that is associated to the object that was inspected + // to produce the TransportStats associated with this RTP stream. + TransportID string `json:"transportId"` + + // CodecID is a unique identifier that is associated to the object that was inspected + // to produce the CodecStats associated with this RTP stream. + CodecID string `json:"codecId"` + + // FIRCount counts the total number of Full Intra Request (FIR) packets received + // by the sender. This metric is only valid for video and is sent by receiver. + FIRCount uint32 `json:"firCount"` + + // PLICount counts the total number of Picture Loss Indication (PLI) packets + // received by the sender. This metric is only valid for video and is sent by receiver. + PLICount uint32 `json:"pliCount"` + + // NACKCount counts the total number of Negative ACKnowledgement (NACK) packets + // received by the sender and is sent by receiver. + NACKCount uint32 `json:"nackCount"` + + // SLICount counts the total number of Slice Loss Indication (SLI) packets received + // by the sender. This metric is only valid for video and is sent by receiver. + SLICount uint32 `json:"sliCount"` + + // QPSum is the sum of the QP values of frames passed. The count of frames is + // in FramesDecoded for inbound stream stats, and in FramesEncoded for outbound stream stats. + QPSum uint64 `json:"qpSum"` + + // PacketsSent is the total number of RTP packets sent for this SSRC. + PacketsSent uint32 `json:"packetsSent"` + + // PacketsDiscardedOnSend is the total number of RTP packets for this SSRC that + // have been discarded due to socket errors, i.e. a socket error occurred when handing + // the packets to the socket. This might happen due to various reasons, including + // full buffer or no available memory. + PacketsDiscardedOnSend uint32 `json:"packetsDiscardedOnSend"` + + // FECPacketsSent is the total number of RTP FEC packets sent for this SSRC. + // This counter can also be incremented when sending FEC packets in-band with + // media packets (e.g., with Opus). + FECPacketsSent uint32 `json:"fecPacketsSent"` + + // BytesSent is the total number of bytes sent for this SSRC. + BytesSent uint64 `json:"bytesSent"` + + // BytesDiscardedOnSend is the total number of bytes for this SSRC that have + // been discarded due to socket errors, i.e. a socket error occurred when handing + // the packets containing the bytes to the socket. This might happen due to various + // reasons, including full buffer or no available memory. + BytesDiscardedOnSend uint64 `json:"bytesDiscardedOnSend"` + + // LocalID is used for looking up the local InboundRTPStreamStats object for the same SSRC. + LocalID string `json:"localId"` + + // RemoteTimestamp represents the remote timestamp at which these statistics were + // sent by the remote endpoint. This differs from timestamp, which represents the + // time at which the statistics were generated or received by the local endpoint. + // The RemoteTimestamp, if present, is derived from the NTP timestamp in an RTCP + // Sender Report (SR) packet, which reflects the remote endpoint's clock. + // That clock may not be synchronized with the local clock. + RemoteTimestamp StatsTimestamp `json:"remoteTimestamp"` +} + +// RTPContributingSourceStats contains statistics for a contributing source (CSRC) that contributed +// to an inbound RTP stream. +type RTPContributingSourceStats struct { + // Timestamp is the timestamp associated with this object. + Timestamp StatsTimestamp `json:"timestamp"` + + // Type is the object's StatsType + Type StatsType `json:"type"` + + // ID is a unique id that is associated with the component inspected to produce + // this Stats object. Two Stats objects will have the same ID if they were produced + // by inspecting the same underlying object. + ID string `json:"id"` + + // ContributorSSRC is the SSRC identifier of the contributing source represented + // by this stats object. It is a 32-bit unsigned integer that appears in the CSRC + // list of any packets the relevant source contributed to. + ContributorSSRC SSRC `json:"contributorSsrc"` + + // InboundRTPStreamID is the ID of the InboundRTPStreamStats object representing + // the inbound RTP stream that this contributing source is contributing to. + InboundRTPStreamID string `json:"inboundRtpStreamId"` + + // PacketsContributedTo is the total number of RTP packets that this contributing + // source contributed to. This value is incremented each time a packet is counted + // by InboundRTPStreamStats.packetsReceived, and the packet's CSRC list contains + // the SSRC identifier of this contributing source, ContributorSSRC. + PacketsContributedTo uint32 `json:"packetsContributedTo"` + + // AudioLevel is present if the last received RTP packet that this source contributed + // to contained an [RFC6465] mixer-to-client audio level header extension. The value + // of audioLevel is between 0..1 (linear), where 1.0 represents 0 dBov, 0 represents + // silence, and 0.5 represents approximately 6 dBSPL change in the sound pressure level from 0 dBov. + AudioLevel float64 `json:"audioLevel"` +} + +// PeerConnectionStats contains statistics related to the PeerConnection object. +type PeerConnectionStats struct { + // Timestamp is the timestamp associated with this object. + Timestamp StatsTimestamp `json:"timestamp"` + + // Type is the object's StatsType + Type StatsType `json:"type"` + + // ID is a unique id that is associated with the component inspected to produce + // this Stats object. Two Stats objects will have the same ID if they were produced + // by inspecting the same underlying object. + ID string `json:"id"` + + // DataChannelsOpened represents the number of unique DataChannels that have + // entered the "open" state during their lifetime. + DataChannelsOpened uint32 `json:"dataChannelsOpened"` + + // DataChannelsClosed represents the number of unique DataChannels that have + // left the "open" state during their lifetime (due to being closed by either + // end or the underlying transport being closed). DataChannels that transition + // from "connecting" to "closing" or "closed" without ever being "open" + // are not counted in this number. + DataChannelsClosed uint32 `json:"dataChannelsClosed"` + + // DataChannelsRequested Represents the number of unique DataChannels returned + // from a successful createDataChannel() call on the PeerConnection. If the + // underlying data transport is not established, these may be in the "connecting" state. + DataChannelsRequested uint32 `json:"dataChannelsRequested"` + + // DataChannelsAccepted represents the number of unique DataChannels signaled + // in a "datachannel" event on the PeerConnection. + DataChannelsAccepted uint32 `json:"dataChannelsAccepted"` +} + +// DataChannelStats contains statistics related to each DataChannel ID. +type DataChannelStats struct { + // Timestamp is the timestamp associated with this object. + Timestamp StatsTimestamp `json:"timestamp"` + + // Type is the object's StatsType + Type StatsType `json:"type"` + + // ID is a unique id that is associated with the component inspected to produce + // this Stats object. Two Stats objects will have the same ID if they were produced + // by inspecting the same underlying object. + ID string `json:"id"` + + // Label is the "label" value of the DataChannel object. + Label string `json:"label"` + + // Protocol is the "protocol" value of the DataChannel object. + Protocol string `json:"protocol"` + + // DataChannelIdentifier is the "id" attribute of the DataChannel object. + DataChannelIdentifier int32 `json:"dataChannelIdentifier"` + + // TransportID the ID of the TransportStats object for transport used to carry this datachannel. + TransportID string `json:"transportId"` + + // State is the "readyState" value of the DataChannel object. + State DataChannelState `json:"state"` + + // MessagesSent represents the total number of API "message" events sent. + MessagesSent uint32 `json:"messagesSent"` + + // BytesSent represents the total number of payload bytes sent on this + // datachannel not including headers or padding. + BytesSent uint64 `json:"bytesSent"` + + // MessagesReceived represents the total number of API "message" events received. + MessagesReceived uint32 `json:"messagesReceived"` + + // BytesReceived represents the total number of bytes received on this + // datachannel not including headers or padding. + BytesReceived uint64 `json:"bytesReceived"` +} + +// MediaStreamStats contains statistics related to a specific MediaStream. +type MediaStreamStats struct { + // Timestamp is the timestamp associated with this object. + Timestamp StatsTimestamp `json:"timestamp"` + + // Type is the object's StatsType + Type StatsType `json:"type"` + + // ID is a unique id that is associated with the component inspected to produce + // this Stats object. Two Stats objects will have the same ID if they were produced + // by inspecting the same underlying object. + ID string `json:"id"` + + // StreamIdentifier is the "id" property of the MediaStream + StreamIdentifier string `json:"streamIdentifier"` + + // TrackIDs is a list of the identifiers of the stats object representing the + // stream's tracks, either ReceiverAudioTrackAttachmentStats or ReceiverVideoTrackAttachmentStats. + TrackIDs []string `json:"trackIds"` +} + +// AudioSenderStats represents the stats about one audio sender of a PeerConnection +// object for which one calls GetStats. +// +// It appears in the stats as soon as the RTPSender is added by either AddTrack +// or AddTransceiver, or by media negotiation. +type AudioSenderStats struct { + // Timestamp is the timestamp associated with this object. + Timestamp StatsTimestamp `json:"timestamp"` + + // Type is the object's StatsType + Type StatsType `json:"type"` + + // ID is a unique id that is associated with the component inspected to produce + // this Stats object. Two Stats objects will have the same ID if they were produced + // by inspecting the same underlying object. + ID string `json:"id"` + + // TrackIdentifier represents the id property of the track. + TrackIdentifier string `json:"trackIdentifier"` + + // RemoteSource is true if the source is remote, for instance if it is sourced + // from another host via a PeerConnection. False otherwise. Only applicable for 'track' stats. + RemoteSource bool `json:"remoteSource"` + + // Ended reflects the "ended" state of the track. + Ended bool `json:"ended"` + + // Kind is either "audio" or "video". This reflects the "kind" attribute of the MediaStreamTrack. + Kind string `json:"kind"` + + // AudioLevel represents the output audio level of the track. + // + // The value is a value between 0..1 (linear), where 1.0 represents 0 dBov, + // 0 represents silence, and 0.5 represents approximately 6 dBSPL change in + // the sound pressure level from 0 dBov. + // + // If the track is sourced from an Receiver, does no audio processing, has a + // constant level, and has a volume setting of 1.0, the audio level is expected + // to be the same as the audio level of the source SSRC, while if the volume setting + // is 0.5, the AudioLevel is expected to be half that value. + // + // For outgoing audio tracks, the AudioLevel is the level of the audio being sent. + AudioLevel float64 `json:"audioLevel"` + + // TotalAudioEnergy is the total energy of all the audio samples sent/received + // for this object, calculated by duration * Math.pow(energy/maxEnergy, 2) for + // each audio sample seen. + TotalAudioEnergy float64 `json:"totalAudioEnergy"` + + // VoiceActivityFlag represents whether the last RTP packet sent or played out + // by this track contained voice activity or not based on the presence of the + // V bit in the extension header, as defined in [RFC6464]. + // + // This value indicates the voice activity in the latest RTP packet played out + // from a given SSRC, and is defined in RTPSynchronizationSource.voiceActivityFlag. + VoiceActivityFlag bool `json:"voiceActivityFlag"` + + // TotalSamplesDuration represents the total duration in seconds of all samples + // that have sent or received (and thus counted by TotalSamplesSent or TotalSamplesReceived). + // Can be used with TotalAudioEnergy to compute an average audio level over different intervals. + TotalSamplesDuration float64 `json:"totalSamplesDuration"` + + // EchoReturnLoss is only present while the sender is sending a track sourced from + // a microphone where echo cancellation is applied. Calculated in decibels. + EchoReturnLoss float64 `json:"echoReturnLoss"` + + // EchoReturnLossEnhancement is only present while the sender is sending a track + // sourced from a microphone where echo cancellation is applied. Calculated in decibels. + EchoReturnLossEnhancement float64 `json:"echoReturnLossEnhancement"` + + // TotalSamplesSent is the total number of samples that have been sent by this sender. + TotalSamplesSent uint64 `json:"totalSamplesSent"` +} + +// SenderAudioTrackAttachmentStats object represents the stats about one attachment +// of an audio MediaStreamTrack to the PeerConnection object for which one calls GetStats. +// +// It appears in the stats as soon as it is attached (via AddTrack, via AddTransceiver, +// via ReplaceTrack on an RTPSender object). +// +// If an audio track is attached twice (via AddTransceiver or ReplaceTrack), there +// will be two SenderAudioTrackAttachmentStats objects, one for each attachment. +// They will have the same "TrackIdentifier" attribute, but different "ID" attributes. +// +// If the track is detached from the PeerConnection (via removeTrack or via replaceTrack), +// it continues to appear, but with the "ObjectDeleted" member set to true. +type SenderAudioTrackAttachmentStats AudioSenderStats + +// VideoSenderStats represents the stats about one video sender of a PeerConnection +// object for which one calls GetStats. +// +// It appears in the stats as soon as the sender is added by either AddTrack or +// AddTransceiver, or by media negotiation. +type VideoSenderStats struct { + // Timestamp is the timestamp associated with this object. + Timestamp StatsTimestamp `json:"timestamp"` + + // Type is the object's StatsType + Type StatsType `json:"type"` + + // ID is a unique id that is associated with the component inspected to produce + // this Stats object. Two Stats objects will have the same ID if they were produced + // by inspecting the same underlying object. + ID string `json:"id"` + + // FramesCaptured represents the total number of frames captured, before encoding, + // for this RTPSender (or for this MediaStreamTrack, if type is "track"). For example, + // if type is "sender" and this sender's track represents a camera, then this is the + // number of frames produced by the camera for this track while being sent by this sender, + // combined with the number of frames produced by all tracks previously attached to this + // sender while being sent by this sender. Framerates can vary due to hardware limitations + // or environmental factors such as lighting conditions. + FramesCaptured uint32 `json:"framesCaptured"` + + // FramesSent represents the total number of frames sent by this RTPSender + // (or for this MediaStreamTrack, if type is "track"). + FramesSent uint32 `json:"framesSent"` + + // HugeFramesSent represents the total number of huge frames sent by this RTPSender + // (or for this MediaStreamTrack, if type is "track"). Huge frames, by definition, + // are frames that have an encoded size at least 2.5 times the average size of the frames. + // The average size of the frames is defined as the target bitrate per second divided + // by the target fps at the time the frame was encoded. These are usually complex + // to encode frames with a lot of changes in the picture. This can be used to estimate, + // e.g slide changes in the streamed presentation. If a huge frame is also a key frame, + // then both counters HugeFramesSent and KeyFramesSent are incremented. + HugeFramesSent uint32 `json:"hugeFramesSent"` + + // KeyFramesSent represents the total number of key frames sent by this RTPSender + // (or for this MediaStreamTrack, if type is "track"), such as Infra-frames in + // VP8 [RFC6386] or I-frames in H.264 [RFC6184]. This is a subset of FramesSent. + // FramesSent - KeyFramesSent gives you the number of delta frames sent. + KeyFramesSent uint32 `json:"keyFramesSent"` +} + +// SenderVideoTrackAttachmentStats represents the stats about one attachment of a +// video MediaStreamTrack to the PeerConnection object for which one calls GetStats. +// +// It appears in the stats as soon as it is attached (via AddTrack, via AddTransceiver, +// via ReplaceTrack on an RTPSender object). +// +// If a video track is attached twice (via AddTransceiver or ReplaceTrack), there +// will be two SenderVideoTrackAttachmentStats objects, one for each attachment. +// They will have the same "TrackIdentifier" attribute, but different "ID" attributes. +// +// If the track is detached from the PeerConnection (via RemoveTrack or via ReplaceTrack), +// it continues to appear, but with the "ObjectDeleted" member set to true. +type SenderVideoTrackAttachmentStats VideoSenderStats + +// AudioReceiverStats contains audio metrics related to a specific receiver. +type AudioReceiverStats struct { + // Timestamp is the timestamp associated with this object. + Timestamp StatsTimestamp `json:"timestamp"` + + // Type is the object's StatsType + Type StatsType `json:"type"` + + // ID is a unique id that is associated with the component inspected to produce + // this Stats object. Two Stats objects will have the same ID if they were produced + // by inspecting the same underlying object. + ID string `json:"id"` + + // AudioLevel represents the output audio level of the track. + // + // The value is a value between 0..1 (linear), where 1.0 represents 0 dBov, + // 0 represents silence, and 0.5 represents approximately 6 dBSPL change in + // the sound pressure level from 0 dBov. + // + // If the track is sourced from an Receiver, does no audio processing, has a + // constant level, and has a volume setting of 1.0, the audio level is expected + // to be the same as the audio level of the source SSRC, while if the volume setting + // is 0.5, the AudioLevel is expected to be half that value. + // + // For outgoing audio tracks, the AudioLevel is the level of the audio being sent. + AudioLevel float64 `json:"audioLevel"` + + // TotalAudioEnergy is the total energy of all the audio samples sent/received + // for this object, calculated by duration * Math.pow(energy/maxEnergy, 2) for + // each audio sample seen. + TotalAudioEnergy float64 `json:"totalAudioEnergy"` + + // VoiceActivityFlag represents whether the last RTP packet sent or played out + // by this track contained voice activity or not based on the presence of the + // V bit in the extension header, as defined in [RFC6464]. + // + // This value indicates the voice activity in the latest RTP packet played out + // from a given SSRC, and is defined in RTPSynchronizationSource.voiceActivityFlag. + VoiceActivityFlag bool `json:"voiceActivityFlag"` + + // TotalSamplesDuration represents the total duration in seconds of all samples + // that have sent or received (and thus counted by TotalSamplesSent or TotalSamplesReceived). + // Can be used with TotalAudioEnergy to compute an average audio level over different intervals. + TotalSamplesDuration float64 `json:"totalSamplesDuration"` + + // EstimatedPlayoutTimestamp is the estimated playout time of this receiver's + // track. The playout time is the NTP timestamp of the last playable sample that + // has a known timestamp (from an RTCP SR packet mapping RTP timestamps to NTP + // timestamps), extrapolated with the time elapsed since it was ready to be played out. + // This is the "current time" of the track in NTP clock time of the sender and + // can be present even if there is no audio currently playing. + // + // This can be useful for estimating how much audio and video is out of + // sync for two tracks from the same source: + // AudioTrackStats.EstimatedPlayoutTimestamp - VideoTrackStats.EstimatedPlayoutTimestamp + EstimatedPlayoutTimestamp StatsTimestamp `json:"estimatedPlayoutTimestamp"` + + // JitterBufferDelay is the sum of the time, in seconds, each sample takes from + // the time it is received and to the time it exits the jitter buffer. + // This increases upon samples exiting, having completed their time in the buffer + // (incrementing JitterBufferEmittedCount). The average jitter buffer delay can + // be calculated by dividing the JitterBufferDelay with the JitterBufferEmittedCount. + JitterBufferDelay float64 `json:"jitterBufferDelay"` + + // JitterBufferEmittedCount is the total number of samples that have come out + // of the jitter buffer (increasing JitterBufferDelay). + JitterBufferEmittedCount uint64 `json:"jitterBufferEmittedCount"` + + // TotalSamplesReceived is the total number of samples that have been received + // by this receiver. This includes ConcealedSamples. + TotalSamplesReceived uint64 `json:"totalSamplesReceived"` + + // ConcealedSamples is the total number of samples that are concealed samples. + // A concealed sample is a sample that is based on data that was synthesized + // to conceal packet loss and does not represent incoming data. + ConcealedSamples uint64 `json:"concealedSamples"` + + // ConcealmentEvents is the number of concealment events. This counter increases + // every time a concealed sample is synthesized after a non-concealed sample. + // That is, multiple consecutive concealed samples will increase the concealedSamples + // count multiple times but is a single concealment event. + ConcealmentEvents uint64 `json:"concealmentEvents"` +} + +// VideoReceiverStats contains video metrics related to a specific receiver. +type VideoReceiverStats struct { + // Timestamp is the timestamp associated with this object. + Timestamp StatsTimestamp `json:"timestamp"` + + // Type is the object's StatsType + Type StatsType `json:"type"` + + // ID is a unique id that is associated with the component inspected to produce + // this Stats object. Two Stats objects will have the same ID if they were produced + // by inspecting the same underlying object. + ID string `json:"id"` + + // FrameWidth represents the width of the last processed frame for this track. + // Before the first frame is processed this attribute is missing. + FrameWidth uint32 `json:"frameWidth"` + + // FrameHeight represents the height of the last processed frame for this track. + // Before the first frame is processed this attribute is missing. + FrameHeight uint32 `json:"frameHeight"` + + // FramesPerSecond represents the nominal FPS value before the degradation preference + // is applied. It is the number of complete frames in the last second. For sending + // tracks it is the current captured FPS and for the receiving tracks it is the + // current decoding framerate. + FramesPerSecond float64 `json:"framesPerSecond"` + + // EstimatedPlayoutTimestamp is the estimated playout time of this receiver's + // track. The playout time is the NTP timestamp of the last playable sample that + // has a known timestamp (from an RTCP SR packet mapping RTP timestamps to NTP + // timestamps), extrapolated with the time elapsed since it was ready to be played out. + // This is the "current time" of the track in NTP clock time of the sender and + // can be present even if there is no audio currently playing. + // + // This can be useful for estimating how much audio and video is out of + // sync for two tracks from the same source: + // AudioTrackStats.EstimatedPlayoutTimestamp - VideoTrackStats.EstimatedPlayoutTimestamp + EstimatedPlayoutTimestamp StatsTimestamp `json:"estimatedPlayoutTimestamp"` + + // JitterBufferDelay is the sum of the time, in seconds, each sample takes from + // the time it is received and to the time it exits the jitter buffer. + // This increases upon samples exiting, having completed their time in the buffer + // (incrementing JitterBufferEmittedCount). The average jitter buffer delay can + // be calculated by dividing the JitterBufferDelay with the JitterBufferEmittedCount. + JitterBufferDelay float64 `json:"jitterBufferDelay"` + + // JitterBufferEmittedCount is the total number of samples that have come out + // of the jitter buffer (increasing JitterBufferDelay). + JitterBufferEmittedCount uint64 `json:"jitterBufferEmittedCount"` + + // FramesReceived Represents the total number of complete frames received for + // this receiver. This metric is incremented when the complete frame is received. + FramesReceived uint32 `json:"framesReceived"` + + // KeyFramesReceived represents the total number of complete key frames received + // for this MediaStreamTrack, such as Infra-frames in VP8 [RFC6386] or I-frames + // in H.264 [RFC6184]. This is a subset of framesReceived. `framesReceived - keyFramesReceived` + // gives you the number of delta frames received. This metric is incremented when + // the complete key frame is received. It is not incremented if a partial key + // frames is received and sent for decoding, i.e., the frame could not be recovered + // via retransmission or FEC. + KeyFramesReceived uint32 `json:"keyFramesReceived"` + + // FramesDecoded represents the total number of frames correctly decoded for this + // SSRC, i.e., frames that would be displayed if no frames are dropped. + FramesDecoded uint32 `json:"framesDecoded"` + + // FramesDropped is the total number of frames dropped predecode or dropped + // because the frame missed its display deadline for this receiver's track. + FramesDropped uint32 `json:"framesDropped"` + + // The cumulative number of partial frames lost. This metric is incremented when + // the frame is sent to the decoder. If the partial frame is received and recovered + // via retransmission or FEC before decoding, the FramesReceived counter is incremented. + PartialFramesLost uint32 `json:"partialFramesLost"` + + // FullFramesLost is the cumulative number of full frames lost. + FullFramesLost uint32 `json:"fullFramesLost"` +} + +// TransportStats contains transport statistics related to the PeerConnection object. +type TransportStats struct { + // Timestamp is the timestamp associated with this object. + Timestamp StatsTimestamp `json:"timestamp"` + + // Type is the object's StatsType + Type StatsType `json:"type"` + + // ID is a unique id that is associated with the component inspected to produce + // this Stats object. Two Stats objects will have the same ID if they were produced + // by inspecting the same underlying object. + ID string `json:"id"` + + // PacketsSent represents the total number of packets sent over this transport. + PacketsSent uint32 `json:"packetsSent"` + + // PacketsReceived represents the total number of packets received on this transport. + PacketsReceived uint32 `json:"packetsReceived"` + + // BytesSent represents the total number of payload bytes sent on this PeerConnection + // not including headers or padding. + BytesSent uint64 `json:"bytesSent"` + + // BytesReceived represents the total number of bytes received on this PeerConnection + // not including headers or padding. + BytesReceived uint64 `json:"bytesReceived"` + + // RTCPTransportStatsID is the ID of the transport that gives stats for the RTCP + // component If RTP and RTCP are not multiplexed and this record has only + // the RTP component stats. + RTCPTransportStatsID string `json:"rtcpTransportStatsId"` + + // ICERole is set to the current value of the "role" attribute of the underlying + // DTLSTransport's "transport". + ICERole ICERole `json:"iceRole"` + + // DTLSState is set to the current value of the "state" attribute of the underlying DTLSTransport. + DTLSState DTLSTransportState `json:"dtlsState"` + + // SelectedCandidatePairID is a unique identifier that is associated to the object + // that was inspected to produce the ICECandidatePairStats associated with this transport. + SelectedCandidatePairID string `json:"selectedCandidatePairId"` + + // LocalCertificateID is the ID of the CertificateStats for the local certificate. + // Present only if DTLS is negotiated. + LocalCertificateID string `json:"localCertificateId"` + + // LocalCertificateID is the ID of the CertificateStats for the remote certificate. + // Present only if DTLS is negotiated. + RemoteCertificateID string `json:"remoteCertificateId"` + + // DTLSCipher is the descriptive name of the cipher suite used for the DTLS transport, + // as defined in the "Description" column of the IANA cipher suite registry. + DTLSCipher string `json:"dtlsCipher"` + + // SRTPCipher is the descriptive name of the protection profile used for the SRTP + // transport, as defined in the "Profile" column of the IANA DTLS-SRTP protection + // profile registry. + SRTPCipher string `json:"srtpCipher"` +} + +// StatsICECandidatePairState is the state of an ICE candidate pair used in the +// ICECandidatePairStats object. +type StatsICECandidatePairState string + +func toStatsICECandidatePairState(state ice.CandidatePairState) (StatsICECandidatePairState, error) { + switch state { + case ice.CandidatePairStateWaiting: + return StatsICECandidatePairStateWaiting, nil + case ice.CandidatePairStateInProgress: + return StatsICECandidatePairStateInProgress, nil + case ice.CandidatePairStateFailed: + return StatsICECandidatePairStateFailed, nil + case ice.CandidatePairStateSucceeded: + return StatsICECandidatePairStateSucceeded, nil + default: + // NOTE: this should never happen[tm] + err := fmt.Errorf("%w: %s", errStatsICECandidateStateInvalid, state.String()) + return StatsICECandidatePairState("Unknown"), err + } +} + +const ( + // StatsICECandidatePairStateFrozen means a check for this pair hasn't been + // performed, and it can't yet be performed until some other check succeeds, + // allowing this pair to unfreeze and move into the Waiting state. + StatsICECandidatePairStateFrozen StatsICECandidatePairState = "frozen" + + // StatsICECandidatePairStateWaiting means a check has not been performed for + // this pair, and can be performed as soon as it is the highest-priority Waiting + // pair on the check list. + StatsICECandidatePairStateWaiting StatsICECandidatePairState = "waiting" + + // StatsICECandidatePairStateInProgress means a check has been sent for this pair, + // but the transaction is in progress. + StatsICECandidatePairStateInProgress StatsICECandidatePairState = "in-progress" + + // StatsICECandidatePairStateFailed means a check for this pair was already done + // and failed, either never producing any response or producing an unrecoverable + // failure response. + StatsICECandidatePairStateFailed StatsICECandidatePairState = "failed" + + // StatsICECandidatePairStateSucceeded means a check for this pair was already + // done and produced a successful result. + StatsICECandidatePairStateSucceeded StatsICECandidatePairState = "succeeded" +) + +// ICECandidatePairStats contains ICE candidate pair statistics related +// to the ICETransport objects. +type ICECandidatePairStats struct { + // Timestamp is the timestamp associated with this object. + Timestamp StatsTimestamp `json:"timestamp"` + + // Type is the object's StatsType + Type StatsType `json:"type"` + + // ID is a unique id that is associated with the component inspected to produce + // this Stats object. Two Stats objects will have the same ID if they were produced + // by inspecting the same underlying object. + ID string `json:"id"` + + // TransportID is a unique identifier that is associated to the object that + // was inspected to produce the TransportStats associated with this candidate pair. + TransportID string `json:"transportId"` + + // LocalCandidateID is a unique identifier that is associated to the object + // that was inspected to produce the ICECandidateStats for the local candidate + // associated with this candidate pair. + LocalCandidateID string `json:"localCandidateId"` + + // RemoteCandidateID is a unique identifier that is associated to the object + // that was inspected to produce the ICECandidateStats for the remote candidate + // associated with this candidate pair. + RemoteCandidateID string `json:"remoteCandidateId"` + + // State represents the state of the checklist for the local and remote + // candidates in a pair. + State StatsICECandidatePairState `json:"state"` + + // Nominated is true when this valid pair that should be used for media + // if it is the highest-priority one amongst those whose nominated flag is set + Nominated bool `json:"nominated"` + + // PacketsSent represents the total number of packets sent on this candidate pair. + PacketsSent uint32 `json:"packetsSent"` + + // PacketsReceived represents the total number of packets received on this candidate pair. + PacketsReceived uint32 `json:"packetsReceived"` + + // BytesSent represents the total number of payload bytes sent on this candidate pair + // not including headers or padding. + BytesSent uint64 `json:"bytesSent"` + + // BytesReceived represents the total number of payload bytes received on this candidate pair + // not including headers or padding. + BytesReceived uint64 `json:"bytesReceived"` + + // LastPacketSentTimestamp represents the timestamp at which the last packet was + // sent on this particular candidate pair, excluding STUN packets. + LastPacketSentTimestamp StatsTimestamp `json:"lastPacketSentTimestamp"` + + // LastPacketReceivedTimestamp represents the timestamp at which the last packet + // was received on this particular candidate pair, excluding STUN packets. + LastPacketReceivedTimestamp StatsTimestamp `json:"lastPacketReceivedTimestamp"` + + // FirstRequestTimestamp represents the timestamp at which the first STUN request + // was sent on this particular candidate pair. + FirstRequestTimestamp StatsTimestamp `json:"firstRequestTimestamp"` + + // LastRequestTimestamp represents the timestamp at which the last STUN request + // was sent on this particular candidate pair. The average interval between two + // consecutive connectivity checks sent can be calculated with + // (LastRequestTimestamp - FirstRequestTimestamp) / RequestsSent. + LastRequestTimestamp StatsTimestamp `json:"lastRequestTimestamp"` + + // LastResponseTimestamp represents the timestamp at which the last STUN response + // was received on this particular candidate pair. + LastResponseTimestamp StatsTimestamp `json:"lastResponseTimestamp"` + + // TotalRoundTripTime represents the sum of all round trip time measurements + // in seconds since the beginning of the session, based on STUN connectivity + // check responses (ResponsesReceived), including those that reply to requests + // that are sent in order to verify consent. The average round trip time can + // be computed from TotalRoundTripTime by dividing it by ResponsesReceived. + TotalRoundTripTime float64 `json:"totalRoundTripTime"` + + // CurrentRoundTripTime represents the latest round trip time measured in seconds, + // computed from both STUN connectivity checks, including those that are sent + // for consent verification. + CurrentRoundTripTime float64 `json:"currentRoundTripTime"` + + // AvailableOutgoingBitrate is calculated by the underlying congestion control + // by combining the available bitrate for all the outgoing RTP streams using + // this candidate pair. The bitrate measurement does not count the size of the + // IP or other transport layers like TCP or UDP. It is similar to the TIAS defined + // in RFC 3890, i.e., it is measured in bits per second and the bitrate is calculated + // over a 1 second window. + AvailableOutgoingBitrate float64 `json:"availableOutgoingBitrate"` + + // AvailableIncomingBitrate is calculated by the underlying congestion control + // by combining the available bitrate for all the incoming RTP streams using + // this candidate pair. The bitrate measurement does not count the size of the + // IP or other transport layers like TCP or UDP. It is similar to the TIAS defined + // in RFC 3890, i.e., it is measured in bits per second and the bitrate is + // calculated over a 1 second window. + AvailableIncomingBitrate float64 `json:"availableIncomingBitrate"` + + // CircuitBreakerTriggerCount represents the number of times the circuit breaker + // is triggered for this particular 5-tuple, ceasing transmission. + CircuitBreakerTriggerCount uint32 `json:"circuitBreakerTriggerCount"` + + // RequestsReceived represents the total number of connectivity check requests + // received (including retransmissions). It is impossible for the receiver to + // tell whether the request was sent in order to check connectivity or check + // consent, so all connectivity checks requests are counted here. + RequestsReceived uint64 `json:"requestsReceived"` + + // RequestsSent represents the total number of connectivity check requests + // sent (not including retransmissions). + RequestsSent uint64 `json:"requestsSent"` + + // ResponsesReceived represents the total number of connectivity check responses received. + ResponsesReceived uint64 `json:"responsesReceived"` + + // ResponsesSent represents the total number of connectivity check responses sent. + // Since we cannot distinguish connectivity check requests and consent requests, + // all responses are counted. + ResponsesSent uint64 `json:"responsesSent"` + + // RetransmissionsReceived represents the total number of connectivity check + // request retransmissions received. + RetransmissionsReceived uint64 `json:"retransmissionsReceived"` + + // RetransmissionsSent represents the total number of connectivity check + // request retransmissions sent. + RetransmissionsSent uint64 `json:"retransmissionsSent"` + + // ConsentRequestsSent represents the total number of consent requests sent. + ConsentRequestsSent uint64 `json:"consentRequestsSent"` + + // ConsentExpiredTimestamp represents the timestamp at which the latest valid + // STUN binding response expired. + ConsentExpiredTimestamp StatsTimestamp `json:"consentExpiredTimestamp"` +} + +// ICECandidateStats contains ICE candidate statistics related to the ICETransport objects. +type ICECandidateStats struct { + // Timestamp is the timestamp associated with this object. + Timestamp StatsTimestamp `json:"timestamp"` + + // Type is the object's StatsType + Type StatsType `json:"type"` + + // ID is a unique id that is associated with the component inspected to produce + // this Stats object. Two Stats objects will have the same ID if they were produced + // by inspecting the same underlying object. + ID string `json:"id"` + + // TransportID is a unique identifier that is associated to the object that + // was inspected to produce the TransportStats associated with this candidate. + TransportID string `json:"transportId"` + + // NetworkType represents the type of network interface used by the base of a + // local candidate (the address the ICE agent sends from). Only present for + // local candidates; it's not possible to know what type of network interface + // a remote candidate is using. + // + // Note: + // This stat only tells you about the network interface used by the first "hop"; + // it's possible that a connection will be bottlenecked by another type of network. + // For example, when using Wi-Fi tethering, the networkType of the relevant candidate + // would be "wifi", even when the next hop is over a cellular connection. + NetworkType NetworkType `json:"networkType"` + + // IP is the IP address of the candidate, allowing for IPv4 addresses and + // IPv6 addresses, but fully qualified domain names (FQDNs) are not allowed. + IP string `json:"ip"` + + // Port is the port number of the candidate. + Port int32 `json:"port"` + + // Protocol is one of udp and tcp. + Protocol string `json:"protocol"` + + // CandidateType is the "Type" field of the ICECandidate. + CandidateType ICECandidateType `json:"candidateType"` + + // Priority is the "Priority" field of the ICECandidate. + Priority int32 `json:"priority"` + + // URL is the URL of the TURN or STUN server indicated in the that translated + // this IP address. It is the URL address surfaced in an PeerConnectionICEEvent. + URL string `json:"url"` + + // RelayProtocol is the protocol used by the endpoint to communicate with the + // TURN server. This is only present for local candidates. Valid values for + // the TURN URL protocol is one of udp, tcp, or tls. + RelayProtocol string `json:"relayProtocol"` + + // Deleted is true if the candidate has been deleted/freed. For host candidates, + // this means that any network resources (typically a socket) associated with the + // candidate have been released. For TURN candidates, this means the TURN allocation + // is no longer active. + // + // Only defined for local candidates. For remote candidates, this property is not applicable. + Deleted bool `json:"deleted"` +} + +// CertificateStats contains information about a certificate used by an ICETransport. +type CertificateStats struct { + // Timestamp is the timestamp associated with this object. + Timestamp StatsTimestamp `json:"timestamp"` + + // Type is the object's StatsType + Type StatsType `json:"type"` + + // ID is a unique id that is associated with the component inspected to produce + // this Stats object. Two Stats objects will have the same ID if they were produced + // by inspecting the same underlying object. + ID string `json:"id"` + + // Fingerprint is the fingerprint of the certificate. + Fingerprint string `json:"fingerprint"` + + // FingerprintAlgorithm is the hash function used to compute the certificate fingerprint. For instance, "sha-256". + FingerprintAlgorithm string `json:"fingerprintAlgorithm"` + + // Base64Certificate is the DER-encoded base-64 representation of the certificate. + Base64Certificate string `json:"base64Certificate"` + + // IssuerCertificateID refers to the stats object that contains the next certificate + // in the certificate chain. If the current certificate is at the end of the chain + // (i.e. a self-signed certificate), this will not be set. + IssuerCertificateID string `json:"issuerCertificateId"` +} diff --git a/vendor/github.com/pion/webrtc/v3/stats_go.go b/vendor/github.com/pion/webrtc/v3/stats_go.go new file mode 100644 index 0000000..e1f622e --- /dev/null +++ b/vendor/github.com/pion/webrtc/v3/stats_go.go @@ -0,0 +1,93 @@ +// +build !js + +package webrtc + +// GetConnectionStats is a helper method to return the associated stats for a given PeerConnection +func (r StatsReport) GetConnectionStats(conn *PeerConnection) (PeerConnectionStats, bool) { + statsID := conn.getStatsID() + stats, ok := r[statsID] + if !ok { + return PeerConnectionStats{}, false + } + + pcStats, ok := stats.(PeerConnectionStats) + if !ok { + return PeerConnectionStats{}, false + } + return pcStats, true +} + +// GetDataChannelStats is a helper method to return the associated stats for a given DataChannel +func (r StatsReport) GetDataChannelStats(dc *DataChannel) (DataChannelStats, bool) { + statsID := dc.getStatsID() + stats, ok := r[statsID] + if !ok { + return DataChannelStats{}, false + } + + dcStats, ok := stats.(DataChannelStats) + if !ok { + return DataChannelStats{}, false + } + return dcStats, true +} + +// GetICECandidateStats is a helper method to return the associated stats for a given ICECandidate +func (r StatsReport) GetICECandidateStats(c *ICECandidate) (ICECandidateStats, bool) { + statsID := c.statsID + stats, ok := r[statsID] + if !ok { + return ICECandidateStats{}, false + } + + candidateStats, ok := stats.(ICECandidateStats) + if !ok { + return ICECandidateStats{}, false + } + return candidateStats, true +} + +// GetICECandidatePairStats is a helper method to return the associated stats for a given ICECandidatePair +func (r StatsReport) GetICECandidatePairStats(c *ICECandidatePair) (ICECandidatePairStats, bool) { + statsID := c.statsID + stats, ok := r[statsID] + if !ok { + return ICECandidatePairStats{}, false + } + + candidateStats, ok := stats.(ICECandidatePairStats) + if !ok { + return ICECandidatePairStats{}, false + } + return candidateStats, true +} + +// GetCertificateStats is a helper method to return the associated stats for a given Certificate +func (r StatsReport) GetCertificateStats(c *Certificate) (CertificateStats, bool) { + statsID := c.statsID + stats, ok := r[statsID] + if !ok { + return CertificateStats{}, false + } + + certificateStats, ok := stats.(CertificateStats) + if !ok { + return CertificateStats{}, false + } + return certificateStats, true +} + +// GetCodecStats is a helper method to return the associated stats for a given Codec +func (r StatsReport) GetCodecStats(c *RTPCodecParameters) (CodecStats, bool) { + statsID := c.statsID + stats, ok := r[statsID] + if !ok { + return CodecStats{}, false + } + + codecStats, ok := stats.(CodecStats) + if !ok { + return CodecStats{}, false + } + return codecStats, true +} diff --git a/vendor/github.com/pion/webrtc/v3/track_local.go b/vendor/github.com/pion/webrtc/v3/track_local.go new file mode 100644 index 0000000..e6e1da1 --- /dev/null +++ b/vendor/github.com/pion/webrtc/v3/track_local.go @@ -0,0 +1,75 @@ +package webrtc + +import "github.com/pion/rtp" + +// TrackLocalWriter is the Writer for outbound RTP Packets +type TrackLocalWriter interface { + // WriteRTP encrypts a RTP packet and writes to the connection + WriteRTP(header *rtp.Header, payload []byte) (int, error) + + // Write encrypts and writes a full RTP packet + Write(b []byte) (int, error) +} + +// TrackLocalContext is the Context passed when a TrackLocal has been Binded/Unbinded from a PeerConnection, and used +// in Interceptors. +type TrackLocalContext struct { + id string + params RTPParameters + ssrc SSRC + writeStream TrackLocalWriter +} + +// CodecParameters returns the negotiated RTPCodecParameters. These are the codecs supported by both +// PeerConnections and the SSRC/PayloadTypes +func (t *TrackLocalContext) CodecParameters() []RTPCodecParameters { + return t.params.Codecs +} + +// HeaderExtensions returns the negotiated RTPHeaderExtensionParameters. These are the header extensions supported by +// both PeerConnections and the SSRC/PayloadTypes +func (t *TrackLocalContext) HeaderExtensions() []RTPHeaderExtensionParameter { + return t.params.HeaderExtensions +} + +// SSRC requires the negotiated SSRC of this track +// This track may have multiple if RTX is enabled +func (t *TrackLocalContext) SSRC() SSRC { + return t.ssrc +} + +// WriteStream returns the WriteStream for this TrackLocal. The implementer writes the outbound +// media packets to it +func (t *TrackLocalContext) WriteStream() TrackLocalWriter { + return t.writeStream +} + +// ID is a unique identifier that is used for both Bind/Unbind +func (t *TrackLocalContext) ID() string { + return t.id +} + +// TrackLocal is an interface that controls how the user can send media +// The user can provide their own TrackLocal implementatiosn, or use +// the implementations in pkg/media +type TrackLocal interface { + // Bind should implement the way how the media data flows from the Track to the PeerConnection + // This will be called internally after signaling is complete and the list of available + // codecs has been determined + Bind(TrackLocalContext) (RTPCodecParameters, error) + + // Unbind should implement the teardown logic when the track is no longer needed. This happens + // because a track has been stopped. + Unbind(TrackLocalContext) error + + // ID is the unique identifier for this Track. This should be unique for the + // stream, but doesn't have to globally unique. A common example would be 'audio' or 'video' + // and StreamID would be 'desktop' or 'webcam' + ID() string + + // StreamID is the group this track belongs too. This must be unique + StreamID() string + + // Kind controls if this TrackLocal is audio or video + Kind() RTPCodecType +} diff --git a/vendor/github.com/pion/webrtc/v3/track_local_static.go b/vendor/github.com/pion/webrtc/v3/track_local_static.go new file mode 100644 index 0000000..cf5aa22 --- /dev/null +++ b/vendor/github.com/pion/webrtc/v3/track_local_static.go @@ -0,0 +1,267 @@ +// +build !js + +package webrtc + +import ( + "strings" + "sync" + + "github.com/pion/rtp" + "github.com/pion/webrtc/v3/internal/util" + "github.com/pion/webrtc/v3/pkg/media" +) + +// trackBinding is a single bind for a Track +// Bind can be called multiple times, this stores the +// result for a single bind call so that it can be used when writing +type trackBinding struct { + id string + ssrc SSRC + payloadType PayloadType + writeStream TrackLocalWriter +} + +// TrackLocalStaticRTP is a TrackLocal that has a pre-set codec and accepts RTP Packets. +// If you wish to send a media.Sample use TrackLocalStaticSample +type TrackLocalStaticRTP struct { + mu sync.RWMutex + bindings []trackBinding + codec RTPCodecCapability + id, streamID string +} + +// NewTrackLocalStaticRTP returns a TrackLocalStaticRTP. +func NewTrackLocalStaticRTP(c RTPCodecCapability, id, streamID string) (*TrackLocalStaticRTP, error) { + return &TrackLocalStaticRTP{ + codec: c, + bindings: []trackBinding{}, + id: id, + streamID: streamID, + }, nil +} + +// Bind is called by the PeerConnection after negotiation is complete +// This asserts that the code requested is supported by the remote peer. +// If so it setups all the state (SSRC and PayloadType) to have a call +func (s *TrackLocalStaticRTP) Bind(t TrackLocalContext) (RTPCodecParameters, error) { + s.mu.Lock() + defer s.mu.Unlock() + + parameters := RTPCodecParameters{RTPCodecCapability: s.codec} + if codec, matchType := codecParametersFuzzySearch(parameters, t.CodecParameters()); matchType != codecMatchNone { + s.bindings = append(s.bindings, trackBinding{ + ssrc: t.SSRC(), + payloadType: codec.PayloadType, + writeStream: t.WriteStream(), + id: t.ID(), + }) + return codec, nil + } + + return RTPCodecParameters{}, ErrUnsupportedCodec +} + +// Unbind implements the teardown logic when the track is no longer needed. This happens +// because a track has been stopped. +func (s *TrackLocalStaticRTP) Unbind(t TrackLocalContext) error { + s.mu.Lock() + defer s.mu.Unlock() + + for i := range s.bindings { + if s.bindings[i].id == t.ID() { + s.bindings[i] = s.bindings[len(s.bindings)-1] + s.bindings = s.bindings[:len(s.bindings)-1] + return nil + } + } + + return ErrUnbindFailed +} + +// ID is the unique identifier for this Track. This should be unique for the +// stream, but doesn't have to globally unique. A common example would be 'audio' or 'video' +// and StreamID would be 'desktop' or 'webcam' +func (s *TrackLocalStaticRTP) ID() string { return s.id } + +// StreamID is the group this track belongs too. This must be unique +func (s *TrackLocalStaticRTP) StreamID() string { return s.streamID } + +// Kind controls if this TrackLocal is audio or video +func (s *TrackLocalStaticRTP) Kind() RTPCodecType { + switch { + case strings.HasPrefix(s.codec.MimeType, "audio/"): + return RTPCodecTypeAudio + case strings.HasPrefix(s.codec.MimeType, "video/"): + return RTPCodecTypeVideo + default: + return RTPCodecType(0) + } +} + +// Codec gets the Codec of the track +func (s *TrackLocalStaticRTP) Codec() RTPCodecCapability { + return s.codec +} + +// packetPool is a pool of packets used by WriteRTP and Write below +// nolint:gochecknoglobals +var rtpPacketPool = sync.Pool{ + New: func() interface{} { + return &rtp.Packet{} + }, +} + +// WriteRTP writes a RTP Packet to the TrackLocalStaticRTP +// If one PeerConnection fails the packets will still be sent to +// all PeerConnections. The error message will contain the ID of the failed +// PeerConnections so you can remove them +func (s *TrackLocalStaticRTP) WriteRTP(p *rtp.Packet) error { + ipacket := rtpPacketPool.Get() + packet := ipacket.(*rtp.Packet) + defer func() { + *packet = rtp.Packet{} + rtpPacketPool.Put(ipacket) + }() + *packet = *p + return s.writeRTP(packet) +} + +// writeRTP is like WriteRTP, except that it may modify the packet p +func (s *TrackLocalStaticRTP) writeRTP(p *rtp.Packet) error { + s.mu.RLock() + defer s.mu.RUnlock() + + writeErrs := []error{} + + for _, b := range s.bindings { + p.Header.SSRC = uint32(b.ssrc) + p.Header.PayloadType = uint8(b.payloadType) + if _, err := b.writeStream.WriteRTP(&p.Header, p.Payload); err != nil { + writeErrs = append(writeErrs, err) + } + } + + return util.FlattenErrs(writeErrs) +} + +// Write writes a RTP Packet as a buffer to the TrackLocalStaticRTP +// If one PeerConnection fails the packets will still be sent to +// all PeerConnections. The error message will contain the ID of the failed +// PeerConnections so you can remove them +func (s *TrackLocalStaticRTP) Write(b []byte) (n int, err error) { + ipacket := rtpPacketPool.Get() + packet := ipacket.(*rtp.Packet) + defer func() { + *packet = rtp.Packet{} + rtpPacketPool.Put(ipacket) + }() + + if err = packet.Unmarshal(b); err != nil { + return 0, err + } + + return len(b), s.writeRTP(packet) +} + +// TrackLocalStaticSample is a TrackLocal that has a pre-set codec and accepts Samples. +// If you wish to send a RTP Packet use TrackLocalStaticRTP +type TrackLocalStaticSample struct { + packetizer rtp.Packetizer + rtpTrack *TrackLocalStaticRTP + clockRate float64 +} + +// NewTrackLocalStaticSample returns a TrackLocalStaticSample +func NewTrackLocalStaticSample(c RTPCodecCapability, id, streamID string) (*TrackLocalStaticSample, error) { + rtpTrack, err := NewTrackLocalStaticRTP(c, id, streamID) + if err != nil { + return nil, err + } + + return &TrackLocalStaticSample{ + rtpTrack: rtpTrack, + }, nil +} + +// ID is the unique identifier for this Track. This should be unique for the +// stream, but doesn't have to globally unique. A common example would be 'audio' or 'video' +// and StreamID would be 'desktop' or 'webcam' +func (s *TrackLocalStaticSample) ID() string { return s.rtpTrack.ID() } + +// StreamID is the group this track belongs too. This must be unique +func (s *TrackLocalStaticSample) StreamID() string { return s.rtpTrack.StreamID() } + +// Kind controls if this TrackLocal is audio or video +func (s *TrackLocalStaticSample) Kind() RTPCodecType { return s.rtpTrack.Kind() } + +// Codec gets the Codec of the track +func (s *TrackLocalStaticSample) Codec() RTPCodecCapability { + return s.rtpTrack.Codec() +} + +// Bind is called by the PeerConnection after negotiation is complete +// This asserts that the code requested is supported by the remote peer. +// If so it setups all the state (SSRC and PayloadType) to have a call +func (s *TrackLocalStaticSample) Bind(t TrackLocalContext) (RTPCodecParameters, error) { + codec, err := s.rtpTrack.Bind(t) + if err != nil { + return codec, err + } + + s.rtpTrack.mu.Lock() + defer s.rtpTrack.mu.Unlock() + + // We only need one packetizer + if s.packetizer != nil { + return codec, nil + } + + payloader, err := payloaderForCodec(codec.RTPCodecCapability) + if err != nil { + return codec, err + } + + s.packetizer = rtp.NewPacketizer( + rtpOutboundMTU, + 0, // Value is handled when writing + 0, // Value is handled when writing + payloader, + rtp.NewRandomSequencer(), + codec.ClockRate, + ) + s.clockRate = float64(codec.RTPCodecCapability.ClockRate) + return codec, nil +} + +// Unbind implements the teardown logic when the track is no longer needed. This happens +// because a track has been stopped. +func (s *TrackLocalStaticSample) Unbind(t TrackLocalContext) error { + return s.rtpTrack.Unbind(t) +} + +// WriteSample writes a Sample to the TrackLocalStaticSample +// If one PeerConnection fails the packets will still be sent to +// all PeerConnections. The error message will contain the ID of the failed +// PeerConnections so you can remove them +func (s *TrackLocalStaticSample) WriteSample(sample media.Sample) error { + s.rtpTrack.mu.RLock() + p := s.packetizer + clockRate := s.clockRate + s.rtpTrack.mu.RUnlock() + + if p == nil { + return nil + } + + samples := sample.Duration.Seconds() * clockRate + packets := p.(rtp.Packetizer).Packetize(sample.Data, uint32(samples)) + + writeErrs := []error{} + for _, p := range packets { + if err := s.rtpTrack.WriteRTP(p); err != nil { + writeErrs = append(writeErrs, err) + } + } + + return util.FlattenErrs(writeErrs) +} diff --git a/vendor/github.com/pion/webrtc/v3/track_remote.go b/vendor/github.com/pion/webrtc/v3/track_remote.go new file mode 100644 index 0000000..6733b7c --- /dev/null +++ b/vendor/github.com/pion/webrtc/v3/track_remote.go @@ -0,0 +1,183 @@ +// +build !js + +package webrtc + +import ( + "sync" + "time" + + "github.com/pion/interceptor" + "github.com/pion/rtp" +) + +// TrackRemote represents a single inbound source of media +type TrackRemote struct { + mu sync.RWMutex + + id string + streamID string + + payloadType PayloadType + kind RTPCodecType + ssrc SSRC + codec RTPCodecParameters + params RTPParameters + rid string + + receiver *RTPReceiver + peeked []byte + peekedAttributes interceptor.Attributes +} + +func newTrackRemote(kind RTPCodecType, ssrc SSRC, rid string, receiver *RTPReceiver) *TrackRemote { + return &TrackRemote{ + kind: kind, + ssrc: ssrc, + rid: rid, + receiver: receiver, + } +} + +// ID is the unique identifier for this Track. This should be unique for the +// stream, but doesn't have to globally unique. A common example would be 'audio' or 'video' +// and StreamID would be 'desktop' or 'webcam' +func (t *TrackRemote) ID() string { + t.mu.RLock() + defer t.mu.RUnlock() + return t.id +} + +// RID gets the RTP Stream ID of this Track +// With Simulcast you will have multiple tracks with the same ID, but different RID values. +// In many cases a TrackRemote will not have an RID, so it is important to assert it is non-zero +func (t *TrackRemote) RID() string { + t.mu.RLock() + defer t.mu.RUnlock() + + return t.rid +} + +// PayloadType gets the PayloadType of the track +func (t *TrackRemote) PayloadType() PayloadType { + t.mu.RLock() + defer t.mu.RUnlock() + return t.payloadType +} + +// Kind gets the Kind of the track +func (t *TrackRemote) Kind() RTPCodecType { + t.mu.RLock() + defer t.mu.RUnlock() + return t.kind +} + +// StreamID is the group this track belongs too. This must be unique +func (t *TrackRemote) StreamID() string { + t.mu.RLock() + defer t.mu.RUnlock() + return t.streamID +} + +// SSRC gets the SSRC of the track +func (t *TrackRemote) SSRC() SSRC { + t.mu.RLock() + defer t.mu.RUnlock() + return t.ssrc +} + +// Msid gets the Msid of the track +func (t *TrackRemote) Msid() string { + return t.StreamID() + " " + t.ID() +} + +// Codec gets the Codec of the track +func (t *TrackRemote) Codec() RTPCodecParameters { + t.mu.RLock() + defer t.mu.RUnlock() + return t.codec +} + +// Read reads data from the track. +func (t *TrackRemote) Read(b []byte) (n int, attributes interceptor.Attributes, err error) { + t.mu.RLock() + r := t.receiver + peeked := t.peeked != nil + t.mu.RUnlock() + + if peeked { + t.mu.Lock() + data := t.peeked + attributes = t.peekedAttributes + + t.peeked = nil + t.peekedAttributes = nil + t.mu.Unlock() + // someone else may have stolen our packet when we + // released the lock. Deal with it. + if data != nil { + n = copy(b, data) + return + } + } + + return r.readRTP(b, t) +} + +// ReadRTP is a convenience method that wraps Read and unmarshals for you. +func (t *TrackRemote) ReadRTP() (*rtp.Packet, interceptor.Attributes, error) { + b := make([]byte, receiveMTU) + i, attributes, err := t.Read(b) + if err != nil { + return nil, nil, err + } + + r := &rtp.Packet{} + if err := r.Unmarshal(b[:i]); err != nil { + return nil, nil, err + } + return r, attributes, nil +} + +// determinePayloadType blocks and reads a single packet to determine the PayloadType for this Track +// this is useful because we can't announce it to the user until we know the payloadType +func (t *TrackRemote) determinePayloadType() error { + b := make([]byte, receiveMTU) + n, _, err := t.peek(b) + if err != nil { + return err + } + r := rtp.Packet{} + if err := r.Unmarshal(b[:n]); err != nil { + return err + } + + t.mu.Lock() + t.payloadType = PayloadType(r.PayloadType) + defer t.mu.Unlock() + + return nil +} + +// peek is like Read, but it doesn't discard the packet read +func (t *TrackRemote) peek(b []byte) (n int, a interceptor.Attributes, err error) { + n, a, err = t.Read(b) + if err != nil { + return + } + + t.mu.Lock() + // this might overwrite data if somebody peeked between the Read + // and us getting the lock. Oh well, we'll just drop a packet in + // that case. + data := make([]byte, n) + n = copy(data, b[:n]) + t.peeked = data + t.peekedAttributes = a + t.mu.Unlock() + return +} + +// SetReadDeadline sets the max amount of time the RTP stream will block before returning. 0 is forever. +func (t *TrackRemote) SetReadDeadline(deadline time.Time) error { + return t.receiver.setRTPReadDeadline(deadline, t) +} diff --git a/vendor/github.com/pion/webrtc/v3/webrtc.go b/vendor/github.com/pion/webrtc/v3/webrtc.go new file mode 100644 index 0000000..ff32a55 --- /dev/null +++ b/vendor/github.com/pion/webrtc/v3/webrtc.go @@ -0,0 +1,17 @@ +// Package webrtc implements the WebRTC 1.0 as defined in W3C WebRTC specification document. +package webrtc + +// SSRC represents a synchronization source +// A synchronization source is a randomly chosen +// value meant to be globally unique within a particular +// RTP session. Used to identify a single stream of media. +// +// https://tools.ietf.org/html/rfc3550#section-3 +type SSRC uint32 + +// PayloadType identifies the format of the RTP payload and determines +// its interpretation by the application. Each codec in a RTP Session +// will have a different PayloadType +// +// https://tools.ietf.org/html/rfc3550#section-3 +type PayloadType uint8 diff --git a/vendor/github.com/pion/webrtc/v3/yarn.lock b/vendor/github.com/pion/webrtc/v3/yarn.lock new file mode 100644 index 0000000..90f73c2 --- /dev/null +++ b/vendor/github.com/pion/webrtc/v3/yarn.lock @@ -0,0 +1,795 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +abbrev@1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" + integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== + +ajv@^6.5.5: + version "6.12.2" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.2.tgz#c629c5eced17baf314437918d2da88c99d5958cd" + integrity sha512-k+V+hzjm5q/Mr8ef/1Y9goCmlsK4I6Sm74teeyGvFk1XrOsbsKLjEdrvny42CZ+a8sXbk8KWpY/bDwS+FLL2UQ== + dependencies: + fast-deep-equal "^3.1.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + +ansi-regex@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" + integrity sha1-w7M6te42DYbg5ijwRorn7yfWVN8= + +ansi-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" + integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg= + +aproba@^1.0.3: + version "1.2.0" + resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" + integrity sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw== + +are-we-there-yet@~1.1.2: + version "1.1.5" + resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz#4b35c2944f062a8bfcda66410760350fe9ddfc21" + integrity sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w== + dependencies: + delegates "^1.0.0" + readable-stream "^2.0.6" + +asn1@~0.2.3: + version "0.2.4" + resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136" + integrity sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg== + dependencies: + safer-buffer "~2.1.0" + +assert-plus@1.0.0, assert-plus@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" + integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU= + +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + integrity sha1-x57Zf380y48robyXkLzDZkdLS3k= + +aws-sign2@~0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" + integrity sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg= + +aws4@^1.8.0: + version "1.10.0" + resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.10.0.tgz#a17b3a8ea811060e74d47d306122400ad4497ae2" + integrity sha512-3YDiu347mtVtjpyV3u5kVqQLP242c06zwDOgpeRnybmXlYYsLbtTrUBUm8i8srONt+FWobl5aibnU1030PeeuA== + +balanced-match@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" + integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= + +bcrypt-pbkdf@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" + integrity sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4= + dependencies: + tweetnacl "^0.14.3" + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +caseless@~0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" + integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw= + +chownr@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.1.tgz#54726b8b8fff4df053c42187e801fb4412df1494" + integrity sha512-j38EvO5+LHX84jlo6h4UzmOwi0UgW61WRyPtJz4qaadK5eY3BTS5TY/S1Stc3Uk2lIM6TPevAlULiEJwie860g== + +code-point-at@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" + integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c= + +combined-stream@^1.0.6, combined-stream@~1.0.6: + version "1.0.8" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" + integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== + dependencies: + delayed-stream "~1.0.0" + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= + +console-control-strings@^1.0.0, console-control-strings@~1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" + integrity sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4= + +core-util-is@1.0.2, core-util-is@~1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" + integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= + +dashdash@^1.12.0: + version "1.14.1" + resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" + integrity sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA= + dependencies: + assert-plus "^1.0.0" + +debug@^2.1.2: + version "2.6.9" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" + integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== + dependencies: + ms "2.0.0" + +deep-extend@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" + integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== + +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= + +delegates@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" + integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o= + +detect-libc@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" + integrity sha1-+hN8S9aY7fVc1c0CrFWfkaTEups= + +domexception@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/domexception/-/domexception-1.0.1.tgz#937442644ca6a31261ef36e3ec677fe805582c90" + integrity sha512-raigMkn7CJNNo6Ihro1fzG7wr3fHuYVytzquZKX5n0yizGsTcYgzdIUwj1X9pK0VvjeihV+XiclP+DjwbsSKug== + dependencies: + webidl-conversions "^4.0.2" + +ecc-jsbn@~0.1.1: + version "0.1.2" + resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9" + integrity sha1-OoOpBOVDUyh4dMVkt1SThoSamMk= + dependencies: + jsbn "~0.1.0" + safer-buffer "^2.1.0" + +extend@~3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" + integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== + +extsprintf@1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" + integrity sha1-lpGEQOMEGnpBT4xS48V06zw+HgU= + +extsprintf@^1.2.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f" + integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8= + +fast-deep-equal@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz#545145077c501491e33b15ec408c294376e94ae4" + integrity sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA== + +fast-json-stable-stringify@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== + +forever-agent@~0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" + integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE= + +form-data@~2.3.2: + version "2.3.3" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6" + integrity sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.6" + mime-types "^2.1.12" + +fs-minipass@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.5.tgz#06c277218454ec288df77ada54a03b8702aacb9d" + integrity sha512-JhBl0skXjUPCFH7x6x61gQxrKyXsxB5gcgePLZCwfyCGGsTISMoIeObbrvVeP6Xmyaudw4TT43qV2Gz+iyd2oQ== + dependencies: + minipass "^2.2.1" + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= + +gauge@~2.7.3: + version "2.7.4" + resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7" + integrity sha1-LANAXHU4w51+s3sxcCLjJfsBi/c= + dependencies: + aproba "^1.0.3" + console-control-strings "^1.0.0" + has-unicode "^2.0.0" + object-assign "^4.1.0" + signal-exit "^3.0.0" + string-width "^1.0.1" + strip-ansi "^3.0.1" + wide-align "^1.1.0" + +getpass@^0.1.1: + version "0.1.7" + resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" + integrity sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo= + dependencies: + assert-plus "^1.0.0" + +glob@^7.1.3: + version "7.1.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.3.tgz#3960832d3f1574108342dafd3a67b332c0969df1" + integrity sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + +har-schema@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" + integrity sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI= + +har-validator@~5.1.3: + version "5.1.3" + resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.3.tgz#1ef89ebd3e4996557675eed9893110dc350fa080" + integrity sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g== + dependencies: + ajv "^6.5.5" + har-schema "^2.0.0" + +has-unicode@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" + integrity sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk= + +http-signature@~1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1" + integrity sha1-muzZJRFHcvPZW2WmCruPfBj7rOE= + dependencies: + assert-plus "^1.0.0" + jsprim "^1.2.2" + sshpk "^1.7.0" + +iconv-lite@^0.4.4: + version "0.4.24" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" + integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== + dependencies: + safer-buffer ">= 2.1.2 < 3" + +ignore-walk@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-3.0.1.tgz#a83e62e7d272ac0e3b551aaa82831a19b69f82f8" + integrity sha512-DTVlMx3IYPe0/JJcYP7Gxg7ttZZu3IInhuEhbchuqneY9wWe5Ojy2mXLBaQFUQmo0AW2r3qG7m1mg86js+gnlQ== + dependencies: + minimatch "^3.0.4" + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2, inherits@~2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" + integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= + +ini@~1.3.0: + version "1.3.5" + resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" + integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw== + +is-fullwidth-code-point@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb" + integrity sha1-754xOG8DGn8NZDr4L95QxFfvAMs= + dependencies: + number-is-nan "^1.0.0" + +is-fullwidth-code-point@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" + integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8= + +is-typedarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" + integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= + +isarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" + integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= + +isstream@~0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" + integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo= + +jsbn@~0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" + integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM= + +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + +json-schema@0.2.3: + version "0.2.3" + resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" + integrity sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM= + +json-stringify-safe@~5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" + integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus= + +jsprim@^1.2.2: + version "1.4.1" + resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" + integrity sha1-MT5mvB5cwG5Di8G3SZwuXFastqI= + dependencies: + assert-plus "1.0.0" + extsprintf "1.3.0" + json-schema "0.2.3" + verror "1.10.0" + +mime-db@1.44.0: + version "1.44.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.44.0.tgz#fa11c5eb0aca1334b4233cb4d52f10c5a6272f92" + integrity sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg== + +mime-types@^2.1.12, mime-types@~2.1.19: + version "2.1.27" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.27.tgz#47949f98e279ea53119f5722e0f34e529bec009f" + integrity sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w== + dependencies: + mime-db "1.44.0" + +minimatch@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" + integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== + dependencies: + brace-expansion "^1.1.7" + +minimist@0.0.8: + version "0.0.8" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" + integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0= + +minimist@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" + integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ= + +minipass@^2.2.1, minipass@^2.3.4: + version "2.3.5" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.3.5.tgz#cacebe492022497f656b0f0f51e2682a9ed2d848" + integrity sha512-Gi1W4k059gyRbyVUZQ4mEqLm0YIUiGYfvxhF6SIlk3ui1WVxMTGfGdQ2SInh3PDrRTVvPKgULkpJtT4RH10+VA== + dependencies: + safe-buffer "^5.1.2" + yallist "^3.0.0" + +minizlib@^1.1.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.2.1.tgz#dd27ea6136243c7c880684e8672bb3a45fd9b614" + integrity sha512-7+4oTUOWKg7AuL3vloEWekXY2/D20cevzsrNT2kGWm+39J9hGTCBv8VI5Pm5lXZ/o3/mdR4f8rflAPhnQb8mPA== + dependencies: + minipass "^2.2.1" + +mkdirp@^0.5.0, mkdirp@^0.5.1: + version "0.5.1" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" + integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM= + dependencies: + minimist "0.0.8" + +ms@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" + integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= + +needle@^2.2.1: + version "2.2.4" + resolved "https://registry.yarnpkg.com/needle/-/needle-2.2.4.tgz#51931bff82533b1928b7d1d69e01f1b00ffd2a4e" + integrity sha512-HyoqEb4wr/rsoaIDfTH2aVL9nWtQqba2/HvMv+++m8u0dz808MaagKILxtfeSN7QU7nvbQ79zk3vYOJp9zsNEA== + dependencies: + debug "^2.1.2" + iconv-lite "^0.4.4" + sax "^1.2.4" + +node-pre-gyp@^0.13.0: + version "0.13.0" + resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.13.0.tgz#df9ab7b68dd6498137717838e4f92a33fc9daa42" + integrity sha512-Md1D3xnEne8b/HGVQkZZwV27WUi1ZRuZBij24TNaZwUPU3ZAFtvT6xxJGaUVillfmMKnn5oD1HoGsp2Ftik7SQ== + dependencies: + detect-libc "^1.0.2" + mkdirp "^0.5.1" + needle "^2.2.1" + nopt "^4.0.1" + npm-packlist "^1.1.6" + npmlog "^4.0.2" + rc "^1.2.7" + rimraf "^2.6.1" + semver "^5.3.0" + tar "^4" + +nopt@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.1.tgz#d0d4685afd5415193c8c7505602d0d17cd64474d" + integrity sha1-0NRoWv1UFRk8jHUFYC0NF81kR00= + dependencies: + abbrev "1" + osenv "^0.1.4" + +npm-bundled@^1.0.1: + version "1.0.6" + resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-1.0.6.tgz#e7ba9aadcef962bb61248f91721cd932b3fe6bdd" + integrity sha512-8/JCaftHwbd//k6y2rEWp6k1wxVfpFzB6t1p825+cUb7Ym2XQfhwIC5KwhrvzZRJu+LtDE585zVaS32+CGtf0g== + +npm-packlist@^1.1.6: + version "1.4.1" + resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-1.4.1.tgz#19064cdf988da80ea3cee45533879d90192bbfbc" + integrity sha512-+TcdO7HJJ8peiiYhvPxsEDhF3PJFGUGRcFsGve3vxvxdcpO2Z4Z7rkosRM0kWj6LfbK/P0gu3dzk5RU1ffvFcw== + dependencies: + ignore-walk "^3.0.1" + npm-bundled "^1.0.1" + +npmlog@^4.0.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b" + integrity sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg== + dependencies: + are-we-there-yet "~1.1.2" + console-control-strings "~1.1.0" + gauge "~2.7.3" + set-blocking "~2.0.0" + +number-is-nan@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" + integrity sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0= + +oauth-sign@~0.9.0: + version "0.9.0" + resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" + integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ== + +object-assign@^4.1.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= + +once@^1.3.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= + dependencies: + wrappy "1" + +os-homedir@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" + integrity sha1-/7xJiDNuDoM94MFox+8VISGqf7M= + +os-tmpdir@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" + integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= + +osenv@^0.1.4: + version "0.1.5" + resolved "https://registry.yarnpkg.com/osenv/-/osenv-0.1.5.tgz#85cdfafaeb28e8677f416e287592b5f3f49ea410" + integrity sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g== + dependencies: + os-homedir "^1.0.0" + os-tmpdir "^1.0.0" + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= + +performance-now@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" + integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns= + +process-nextick-args@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.0.tgz#a37d732f4271b4ab1ad070d35508e8290788ffaa" + integrity sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw== + +psl@^1.1.28: + version "1.8.0" + resolved "https://registry.yarnpkg.com/psl/-/psl-1.8.0.tgz#9326f8bcfb013adcc005fdff056acce020e51c24" + integrity sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ== + +punycode@^2.1.0, punycode@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" + integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== + +qs@~6.5.2: + version "6.5.2" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" + integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA== + +rc@^1.2.7: + version "1.2.8" + resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" + integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== + dependencies: + deep-extend "^0.6.0" + ini "~1.3.0" + minimist "^1.2.0" + strip-json-comments "~2.0.1" + +readable-stream@^2.0.6: + version "2.3.6" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf" + integrity sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw== + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.3" + isarray "~1.0.0" + process-nextick-args "~2.0.0" + safe-buffer "~5.1.1" + string_decoder "~1.1.1" + util-deprecate "~1.0.1" + +request@2.88.2: + version "2.88.2" + resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3" + integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw== + dependencies: + aws-sign2 "~0.7.0" + aws4 "^1.8.0" + caseless "~0.12.0" + combined-stream "~1.0.6" + extend "~3.0.2" + forever-agent "~0.6.1" + form-data "~2.3.2" + har-validator "~5.1.3" + http-signature "~1.2.0" + is-typedarray "~1.0.0" + isstream "~0.1.2" + json-stringify-safe "~5.0.1" + mime-types "~2.1.19" + oauth-sign "~0.9.0" + performance-now "^2.1.0" + qs "~6.5.2" + safe-buffer "^5.1.2" + tough-cookie "~2.5.0" + tunnel-agent "^0.6.0" + uuid "^3.3.2" + +rimraf@^2.6.1: + version "2.6.3" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab" + integrity sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA== + dependencies: + glob "^7.1.3" + +safe-buffer@^5.0.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + +safe-buffer@^5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: + version "5.1.2" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" + integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== + +"safer-buffer@>= 2.1.2 < 3", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + +sax@^1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" + integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== + +semver@^5.3.0: + version "5.6.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.6.0.tgz#7e74256fbaa49c75aa7c7a205cc22799cac80004" + integrity sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg== + +set-blocking@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" + integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= + +signal-exit@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" + integrity sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0= + +sshpk@^1.7.0: + version "1.16.1" + resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877" + integrity sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg== + dependencies: + asn1 "~0.2.3" + assert-plus "^1.0.0" + bcrypt-pbkdf "^1.0.0" + dashdash "^1.12.0" + ecc-jsbn "~0.1.1" + getpass "^0.1.1" + jsbn "~0.1.0" + safer-buffer "^2.0.2" + tweetnacl "~0.14.0" + +string-width@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" + integrity sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M= + dependencies: + code-point-at "^1.0.0" + is-fullwidth-code-point "^1.0.0" + strip-ansi "^3.0.0" + +"string-width@^1.0.2 || 2": + version "2.1.1" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" + integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw== + dependencies: + is-fullwidth-code-point "^2.0.0" + strip-ansi "^4.0.0" + +string_decoder@~1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" + integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== + dependencies: + safe-buffer "~5.1.0" + +strip-ansi@^3.0.0, strip-ansi@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" + integrity sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8= + dependencies: + ansi-regex "^2.0.0" + +strip-ansi@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" + integrity sha1-qEeQIusaw2iocTibY1JixQXuNo8= + dependencies: + ansi-regex "^3.0.0" + +strip-json-comments@~2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" + integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= + +tar@^4: + version "4.4.8" + resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.8.tgz#b19eec3fde2a96e64666df9fdb40c5ca1bc3747d" + integrity sha512-LzHF64s5chPQQS0IYBn9IN5h3i98c12bo4NCO7e0sGM2llXQ3p2FGC5sdENN4cTW48O915Sh+x+EXx7XW96xYQ== + dependencies: + chownr "^1.1.1" + fs-minipass "^1.2.5" + minipass "^2.3.4" + minizlib "^1.1.1" + mkdirp "^0.5.0" + safe-buffer "^5.1.2" + yallist "^3.0.2" + +tough-cookie@~2.5.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2" + integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g== + dependencies: + psl "^1.1.28" + punycode "^2.1.1" + +tunnel-agent@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" + integrity sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0= + dependencies: + safe-buffer "^5.0.1" + +tweetnacl@^0.14.3, tweetnacl@~0.14.0: + version "0.14.5" + resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" + integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q= + +uri-js@^4.2.2: + version "4.2.2" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.2.2.tgz#94c540e1ff772956e2299507c010aea6c8838eb0" + integrity sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ== + dependencies: + punycode "^2.1.0" + +util-deprecate@~1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= + +uuid@^3.3.2: + version "3.4.0" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" + integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== + +verror@1.10.0: + version "1.10.0" + resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400" + integrity sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA= + dependencies: + assert-plus "^1.0.0" + core-util-is "1.0.2" + extsprintf "^1.2.0" + +webidl-conversions@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad" + integrity sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg== + +wide-align@^1.1.0: + version "1.1.3" + resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457" + integrity sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA== + dependencies: + string-width "^1.0.2 || 2" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= + +wrtc@0.4.7: + version "0.4.7" + resolved "https://registry.yarnpkg.com/wrtc/-/wrtc-0.4.7.tgz#c61530cd662713e50bffe64b7a78673ce070426c" + integrity sha512-P6Hn7VT4lfSH49HxLHcHhDq+aFf/jd9dPY7lDHeFhZ22N3858EKuwm2jmnlPzpsRGEPaoF6XwkcxY5SYnt4f/g== + dependencies: + node-pre-gyp "^0.13.0" + optionalDependencies: + domexception "^1.0.1" + +yallist@^3.0.0, yallist@^3.0.2: + version "3.0.3" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.0.3.tgz#b4b049e314be545e3ce802236d6cd22cd91c3de9" + integrity sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A== |