mirror of
https://github.com/pion/webrtc.git
synced 2025-11-02 03:32:48 +08:00
@@ -69,7 +69,8 @@ func main() {
|
||||
fmt.Println(util.Encode(offer))
|
||||
|
||||
// Wait for the answer to be pasted
|
||||
answer := util.Decode(util.MustReadStdin())
|
||||
answer := webrtc.RTCSessionDescription{}
|
||||
util.Decode(util.MustReadStdin(), &answer)
|
||||
|
||||
// Apply the answer as the remote description
|
||||
err = peerConnection.SetRemoteDescription(answer)
|
||||
|
||||
@@ -66,7 +66,8 @@ func main() {
|
||||
fmt.Println(util.Encode(offer))
|
||||
|
||||
// Wait for the answer to be pasted
|
||||
answer := util.Decode(util.MustReadStdin())
|
||||
answer := webrtc.RTCSessionDescription{}
|
||||
util.Decode(util.MustReadStdin(), answer)
|
||||
|
||||
// Apply the answer as the remote description
|
||||
err = peerConnection.SetRemoteDescription(answer)
|
||||
|
||||
@@ -60,7 +60,8 @@ func main() {
|
||||
})
|
||||
|
||||
// Wait for the offer to be pasted
|
||||
offer := util.Decode(util.MustReadStdin())
|
||||
offer := webrtc.RTCSessionDescription{}
|
||||
util.Decode(util.MustReadStdin(), offer)
|
||||
|
||||
// Set the remote SessionDescription
|
||||
err = peerConnection.SetRemoteDescription(offer)
|
||||
|
||||
@@ -63,7 +63,8 @@ func main() {
|
||||
})
|
||||
|
||||
// Wait for the offer to be pasted
|
||||
offer := util.Decode(util.MustReadStdin())
|
||||
offer := webrtc.RTCSessionDescription{}
|
||||
util.Decode(util.MustReadStdin(), &offer)
|
||||
|
||||
// Set the remote SessionDescription
|
||||
err = peerConnection.SetRemoteDescription(offer)
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
|
||||
"github.com/pions/webrtc"
|
||||
"github.com/pions/webrtc/examples/util"
|
||||
"github.com/pions/webrtc/examples/util/gstreamer-sink"
|
||||
gst "github.com/pions/webrtc/examples/util/gstreamer-sink"
|
||||
"github.com/pions/webrtc/pkg/ice"
|
||||
"github.com/pions/webrtc/pkg/rtcp"
|
||||
)
|
||||
@@ -63,7 +63,8 @@ func main() {
|
||||
})
|
||||
|
||||
// Wait for the offer to be pasted
|
||||
offer := util.Decode(util.MustReadStdin())
|
||||
offer := webrtc.RTCSessionDescription{}
|
||||
util.Decode(util.MustReadStdin(), &offer)
|
||||
|
||||
// Set the remote SessionDescription
|
||||
err = peerConnection.SetRemoteDescription(offer)
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
|
||||
"github.com/pions/webrtc"
|
||||
"github.com/pions/webrtc/examples/util"
|
||||
"github.com/pions/webrtc/examples/util/gstreamer-src"
|
||||
gst "github.com/pions/webrtc/examples/util/gstreamer-src"
|
||||
"github.com/pions/webrtc/pkg/ice"
|
||||
)
|
||||
|
||||
@@ -55,7 +55,8 @@ func main() {
|
||||
fmt.Println(util.Encode(offer))
|
||||
|
||||
// Wait for the answer to be pasted
|
||||
answer := util.Decode(util.MustReadStdin())
|
||||
answer := webrtc.RTCSessionDescription{}
|
||||
util.Decode(util.MustReadStdin(), &answer)
|
||||
|
||||
// Set the remote SessionDescription
|
||||
err = peerConnection.SetRemoteDescription(answer)
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
|
||||
"github.com/pions/webrtc"
|
||||
"github.com/pions/webrtc/examples/util"
|
||||
"github.com/pions/webrtc/examples/util/gstreamer-src"
|
||||
gst "github.com/pions/webrtc/examples/util/gstreamer-src"
|
||||
"github.com/pions/webrtc/pkg/ice"
|
||||
)
|
||||
|
||||
@@ -53,7 +53,8 @@ func main() {
|
||||
util.Check(err)
|
||||
|
||||
// Wait for the offer to be pasted
|
||||
offer := util.Decode(util.MustReadStdin())
|
||||
offer := webrtc.RTCSessionDescription{}
|
||||
util.Decode(util.MustReadStdin(), &offer)
|
||||
|
||||
// Set the remote SessionDescription
|
||||
err = peerConnection.SetRemoteDescription(offer)
|
||||
|
||||
149
examples/ortc/main.go
Normal file
149
examples/ortc/main.go
Normal file
@@ -0,0 +1,149 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/pions/webrtc"
|
||||
"github.com/pions/webrtc/examples/util"
|
||||
"github.com/pions/webrtc/pkg/datachannel"
|
||||
)
|
||||
|
||||
func main() {
|
||||
isOffer := flag.Bool("offer", false, "Act as the offerer if set")
|
||||
flag.Parse()
|
||||
|
||||
// Everything below is the pion-WebRTC (ORTC) API! Thanks for using it ❤️.
|
||||
|
||||
// Prepare ICE gathering options
|
||||
iceOptions := webrtc.RTCIceGatherOptions{
|
||||
ICEServers: []webrtc.RTCIceServer{
|
||||
{URLs: []string{"stun:stun.l.google.com:19302"}},
|
||||
},
|
||||
}
|
||||
|
||||
// Create the ICE gatherer
|
||||
gatherer, err := webrtc.NewRTCIceGatherer(iceOptions)
|
||||
util.Check(err)
|
||||
|
||||
// Construct the ICE transport
|
||||
ice := webrtc.NewRTCIceTransport(gatherer)
|
||||
|
||||
// Construct the DTLS transport
|
||||
dtls, err := webrtc.NewRTCDtlsTransport(ice, nil)
|
||||
util.Check(err)
|
||||
|
||||
// Construct the SCTP transport
|
||||
sctp := webrtc.NewRTCSctpTransport(dtls)
|
||||
|
||||
// Handle incoming data channels
|
||||
sctp.OnDataChannel(func(channel *webrtc.RTCDataChannel) {
|
||||
fmt.Printf("New DataChannel %s %d\n", channel.Label, channel.ID)
|
||||
|
||||
// Register the handlers
|
||||
channel.OnOpen(handleOnOpen(channel))
|
||||
channel.OnMessage(handleMessage(channel))
|
||||
})
|
||||
|
||||
// Gather candidates
|
||||
err = gatherer.Gather()
|
||||
util.Check(err)
|
||||
|
||||
iceCandidates, err := gatherer.GetLocalCandidates()
|
||||
util.Check(err)
|
||||
|
||||
iceParams, err := gatherer.GetLocalParameters()
|
||||
util.Check(err)
|
||||
|
||||
dtlsParams := dtls.GetLocalParameters()
|
||||
|
||||
sctpCapabilities := sctp.GetCapabilities()
|
||||
|
||||
signal := Signal{
|
||||
ICECandidates: iceCandidates,
|
||||
ICEParameters: iceParams,
|
||||
DtlsParameters: dtlsParams,
|
||||
SCTPCapabilities: sctpCapabilities,
|
||||
}
|
||||
|
||||
// Exchange the information
|
||||
fmt.Println(util.Encode(signal))
|
||||
remoteSignal := Signal{}
|
||||
util.Decode(util.MustReadStdin(), &remoteSignal)
|
||||
|
||||
iceRole := webrtc.RTCIceRoleControlled
|
||||
if *isOffer {
|
||||
iceRole = webrtc.RTCIceRoleControlling
|
||||
}
|
||||
|
||||
err = ice.SetRemoteCandidates(remoteSignal.ICECandidates)
|
||||
util.Check(err)
|
||||
|
||||
// Start the ICE transport
|
||||
err = ice.Start(nil, remoteSignal.ICEParameters, &iceRole)
|
||||
util.Check(err)
|
||||
|
||||
// Start the DTLS transport
|
||||
err = dtls.Start(remoteSignal.DtlsParameters)
|
||||
util.Check(err)
|
||||
|
||||
// Start the SCTP transport
|
||||
err = sctp.Start(remoteSignal.SCTPCapabilities)
|
||||
util.Check(err)
|
||||
|
||||
// Construct the data channel as the offerer
|
||||
if *isOffer {
|
||||
dcParams := &webrtc.RTCDataChannelParameters{
|
||||
Label: "Foo",
|
||||
ID: 1,
|
||||
}
|
||||
var channel *webrtc.RTCDataChannel
|
||||
channel, err = webrtc.NewRTCDataChannel(sctp, dcParams)
|
||||
util.Check(err)
|
||||
|
||||
// Register the handlers
|
||||
// channel.OnOpen(handleOnOpen(channel)) // TODO: OnOpen on handle ChannelAck
|
||||
go handleOnOpen(channel)() // Temporary alternative
|
||||
channel.OnMessage(handleMessage(channel))
|
||||
}
|
||||
|
||||
select {}
|
||||
}
|
||||
|
||||
// Signal is used to exchange signaling info.
|
||||
// This is not part of the ORTC spec. You are free
|
||||
// to exchange this information any way you want.
|
||||
type Signal struct {
|
||||
ICECandidates []webrtc.RTCIceCandidate `json:"iceCandidates"`
|
||||
ICEParameters webrtc.RTCIceParameters `json:"iceParameters"`
|
||||
DtlsParameters webrtc.RTCDtlsParameters `json:"dtlsParameters"`
|
||||
SCTPCapabilities webrtc.RTCSctpCapabilities `json:"sctpCapabilities"`
|
||||
}
|
||||
|
||||
func handleOnOpen(channel *webrtc.RTCDataChannel) func() {
|
||||
return func() {
|
||||
fmt.Printf("Data channel '%s'-'%d' open. Random messages will now be sent to any connected DataChannels every 5 seconds\n", channel.Label, channel.ID)
|
||||
|
||||
for range time.NewTicker(5 * time.Second).C {
|
||||
message := util.RandSeq(15)
|
||||
fmt.Printf("Sending %s \n", message)
|
||||
|
||||
err := channel.Send(datachannel.PayloadString{Data: []byte(message)})
|
||||
util.Check(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func handleMessage(channel *webrtc.RTCDataChannel) func(datachannel.Payload) {
|
||||
return func(payload datachannel.Payload) {
|
||||
switch p := payload.(type) {
|
||||
case *datachannel.PayloadString:
|
||||
fmt.Printf("Message '%s' from DataChannel '%s' payload '%s'\n", p.PayloadType().String(), channel.Label, string(p.Data))
|
||||
case *datachannel.PayloadBinary:
|
||||
fmt.Printf("Message '%s' from DataChannel '%s' payload '% 02x'\n", p.PayloadType().String(), channel.Label, p.Data)
|
||||
default:
|
||||
fmt.Printf("Message '%s' from DataChannel '%s' no payload \n", p.PayloadType().String(), channel.Label)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -66,7 +66,8 @@ func main() {
|
||||
})
|
||||
|
||||
// Wait for the offer to be pasted
|
||||
offer := util.Decode(util.MustReadStdin())
|
||||
offer := webrtc.RTCSessionDescription{}
|
||||
util.Decode(util.MustReadStdin(), &offer)
|
||||
|
||||
// Set the remote SessionDescription
|
||||
err = peerConnection.SetRemoteDescription(offer)
|
||||
|
||||
@@ -48,7 +48,9 @@ const (
|
||||
|
||||
func main() {
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
offer := util.Decode(mustReadStdin(reader))
|
||||
|
||||
offer := webrtc.RTCSessionDescription{}
|
||||
util.Decode(mustReadStdin(reader), &offer)
|
||||
fmt.Println("")
|
||||
|
||||
/* Everything below is the pion-WebRTC API, thanks for using it! */
|
||||
@@ -103,7 +105,9 @@ func main() {
|
||||
for {
|
||||
fmt.Println("")
|
||||
fmt.Println("Paste an SDP to start sendonly peer connection")
|
||||
recvOnlyOffer := util.Decode(mustReadStdin(reader))
|
||||
|
||||
recvOnlyOffer := webrtc.RTCSessionDescription{}
|
||||
util.Decode(mustReadStdin(reader), &recvOnlyOffer)
|
||||
|
||||
// Create a new RTCPeerConnection
|
||||
peerConnection, err := webrtc.New(peerConnectionConfig)
|
||||
|
||||
@@ -13,8 +13,6 @@ import (
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/pions/webrtc"
|
||||
)
|
||||
|
||||
// Allows compressing offer/answer to bypass terminal input limits.
|
||||
@@ -52,8 +50,8 @@ func MustReadStdin() string {
|
||||
|
||||
// Encode encodes the input in base64
|
||||
// It can optionally zip the input before encoding
|
||||
func Encode(sdp webrtc.RTCSessionDescription) string {
|
||||
b, err := json.Marshal(sdp)
|
||||
func Encode(obj interface{}) string {
|
||||
b, err := json.Marshal(obj)
|
||||
Check(err)
|
||||
|
||||
if compress {
|
||||
@@ -65,7 +63,7 @@ func Encode(sdp webrtc.RTCSessionDescription) string {
|
||||
|
||||
// Decode decodes the input from base64
|
||||
// It can optionally unzip the input after decoding
|
||||
func Decode(in string) webrtc.RTCSessionDescription {
|
||||
func Decode(in string, obj interface{}) {
|
||||
b, err := base64.StdEncoding.DecodeString(in)
|
||||
Check(err)
|
||||
|
||||
@@ -73,11 +71,8 @@ func Decode(in string) webrtc.RTCSessionDescription {
|
||||
b = unzip(b)
|
||||
}
|
||||
|
||||
var sdp webrtc.RTCSessionDescription
|
||||
err = json.Unmarshal(b, &sdp)
|
||||
err = json.Unmarshal(b, obj)
|
||||
Check(err)
|
||||
|
||||
return sdp
|
||||
}
|
||||
|
||||
// RandSeq generates a random string to serve as dummy data
|
||||
|
||||
@@ -102,6 +102,45 @@ type RTCDataChannel struct {
|
||||
dataChannel *datachannel.DataChannel
|
||||
}
|
||||
|
||||
// NewRTCDataChannel creates a new RTCDataChannel.
|
||||
// This constructor is part of the ORTC API. It is not
|
||||
// meant to be used together with the basic WebRTC API.
|
||||
func NewRTCDataChannel(transport *RTCSctpTransport, params *RTCDataChannelParameters) (*RTCDataChannel, error) {
|
||||
c := &RTCDataChannel{
|
||||
Transport: transport,
|
||||
Label: params.Label,
|
||||
ID: ¶ms.ID,
|
||||
}
|
||||
|
||||
if err := c.ensureSCTP(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cfg := &datachannel.Config{
|
||||
ChannelType: datachannel.ChannelTypeReliable, // TODO: Wiring
|
||||
Priority: datachannel.ChannelPriorityNormal, // TODO: Wiring
|
||||
ReliabilityParameter: 0, // TODO: Wiring
|
||||
Label: c.Label,
|
||||
}
|
||||
|
||||
dc, err := datachannel.Dial(c.Transport.association, *c.ID, cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c.handleOpen(dc)
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (d *RTCDataChannel) ensureSCTP() error {
|
||||
if d.Transport == nil ||
|
||||
d.Transport.association == nil {
|
||||
return errors.New("SCTP not establisched")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// OnOpen sets an event handler which is invoked when
|
||||
// the underlying data transport has been established (or re-established).
|
||||
func (d *RTCDataChannel) OnOpen(f func()) {
|
||||
|
||||
@@ -17,16 +17,16 @@ func TestGenerateDataChannelID(t *testing.T) {
|
||||
c *RTCPeerConnection
|
||||
result uint16
|
||||
}{
|
||||
{true, &RTCPeerConnection{sctpTransport: newRTCSctpTransport(), dataChannels: map[uint16]*RTCDataChannel{}}, 0},
|
||||
{true, &RTCPeerConnection{sctpTransport: newRTCSctpTransport(), dataChannels: map[uint16]*RTCDataChannel{1: nil}}, 0},
|
||||
{true, &RTCPeerConnection{sctpTransport: newRTCSctpTransport(), dataChannels: map[uint16]*RTCDataChannel{0: nil}}, 2},
|
||||
{true, &RTCPeerConnection{sctpTransport: newRTCSctpTransport(), dataChannels: map[uint16]*RTCDataChannel{0: nil, 2: nil}}, 4},
|
||||
{true, &RTCPeerConnection{sctpTransport: newRTCSctpTransport(), dataChannels: map[uint16]*RTCDataChannel{0: nil, 4: nil}}, 2},
|
||||
{false, &RTCPeerConnection{sctpTransport: newRTCSctpTransport(), dataChannels: map[uint16]*RTCDataChannel{}}, 1},
|
||||
{false, &RTCPeerConnection{sctpTransport: newRTCSctpTransport(), dataChannels: map[uint16]*RTCDataChannel{0: nil}}, 1},
|
||||
{false, &RTCPeerConnection{sctpTransport: newRTCSctpTransport(), dataChannels: map[uint16]*RTCDataChannel{1: nil}}, 3},
|
||||
{false, &RTCPeerConnection{sctpTransport: newRTCSctpTransport(), dataChannels: map[uint16]*RTCDataChannel{1: nil, 3: nil}}, 5},
|
||||
{false, &RTCPeerConnection{sctpTransport: newRTCSctpTransport(), dataChannels: map[uint16]*RTCDataChannel{1: nil, 5: nil}}, 3},
|
||||
{true, &RTCPeerConnection{sctpTransport: NewRTCSctpTransport(nil), dataChannels: map[uint16]*RTCDataChannel{}}, 0},
|
||||
{true, &RTCPeerConnection{sctpTransport: NewRTCSctpTransport(nil), dataChannels: map[uint16]*RTCDataChannel{1: nil}}, 0},
|
||||
{true, &RTCPeerConnection{sctpTransport: NewRTCSctpTransport(nil), dataChannels: map[uint16]*RTCDataChannel{0: nil}}, 2},
|
||||
{true, &RTCPeerConnection{sctpTransport: NewRTCSctpTransport(nil), dataChannels: map[uint16]*RTCDataChannel{0: nil, 2: nil}}, 4},
|
||||
{true, &RTCPeerConnection{sctpTransport: NewRTCSctpTransport(nil), dataChannels: map[uint16]*RTCDataChannel{0: nil, 4: nil}}, 2},
|
||||
{false, &RTCPeerConnection{sctpTransport: NewRTCSctpTransport(nil), dataChannels: map[uint16]*RTCDataChannel{}}, 1},
|
||||
{false, &RTCPeerConnection{sctpTransport: NewRTCSctpTransport(nil), dataChannels: map[uint16]*RTCDataChannel{0: nil}}, 1},
|
||||
{false, &RTCPeerConnection{sctpTransport: NewRTCSctpTransport(nil), dataChannels: map[uint16]*RTCDataChannel{1: nil}}, 3},
|
||||
{false, &RTCPeerConnection{sctpTransport: NewRTCSctpTransport(nil), dataChannels: map[uint16]*RTCDataChannel{1: nil, 3: nil}}, 5},
|
||||
{false, &RTCPeerConnection{sctpTransport: NewRTCSctpTransport(nil), dataChannels: map[uint16]*RTCDataChannel{1: nil, 5: nil}}, 3},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
|
||||
7
rtcdatachannelparameters.go
Normal file
7
rtcdatachannelparameters.go
Normal file
@@ -0,0 +1,7 @@
|
||||
package webrtc
|
||||
|
||||
// RTCDataChannelParameters describes the configuration of the RTCDataChannel.
|
||||
type RTCDataChannelParameters struct {
|
||||
Label string `json:"label"`
|
||||
ID uint16 `json:"id"`
|
||||
}
|
||||
@@ -5,10 +5,10 @@ package webrtc
|
||||
type RTCDtlsFingerprint struct {
|
||||
// Algorithm specifies one of the the hash function algorithms defined in
|
||||
// the 'Hash function Textual Names' registry.
|
||||
Algorithm string
|
||||
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
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
7
rtcdtlsparameters.go
Normal file
7
rtcdtlsparameters.go
Normal file
@@ -0,0 +1,7 @@
|
||||
package webrtc
|
||||
|
||||
// RTCDtlsParameters holds information relating to DTLS configuration.
|
||||
type RTCDtlsParameters struct {
|
||||
Role RTCDtlsRole `json:"role"`
|
||||
Fingerprints []RTCDtlsFingerprint `json:"fingerprints"`
|
||||
}
|
||||
30
rtcdtlsrole.go
Normal file
30
rtcdtlsrole.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package webrtc
|
||||
|
||||
// RTCDtlsRole indicates the role of the DTLS transport.
|
||||
type RTCDtlsRole byte
|
||||
|
||||
const (
|
||||
// RTCDtlsRoleAuto defines the DLTS 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.
|
||||
RTCDtlsRoleAuto RTCDtlsRole = iota + 1
|
||||
|
||||
// RTCDtlsRoleClient defines the DTLS client role.
|
||||
RTCDtlsRoleClient
|
||||
|
||||
// RTCDtlsRoleServer defines the DTLS server role.
|
||||
RTCDtlsRoleServer
|
||||
)
|
||||
|
||||
func (r RTCDtlsRole) String() string {
|
||||
switch r {
|
||||
case RTCDtlsRoleAuto:
|
||||
return "auto"
|
||||
case RTCDtlsRoleClient:
|
||||
return "client"
|
||||
case RTCDtlsRoleServer:
|
||||
return "server"
|
||||
default:
|
||||
return "Unknown DTLS role"
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,164 @@
|
||||
package webrtc
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/x509"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/pions/dtls/pkg/dtls"
|
||||
"github.com/pions/webrtc/pkg/rtcerr"
|
||||
)
|
||||
|
||||
// RTCDtlsTransport allows an application access to information about the DTLS
|
||||
// transport over which RTP and RTCP packets are sent and received by
|
||||
// RTCRtpSender and RTCRtpReceiver, as well other data such as SCTP packets sent
|
||||
// and received by data channels.
|
||||
type RTCDtlsTransport struct {
|
||||
// Transport RTCIceTransport
|
||||
lock sync.RWMutex
|
||||
|
||||
iceTransport *RTCIceTransport
|
||||
certificates []RTCCertificate
|
||||
// State RTCDtlsTransportState
|
||||
|
||||
// OnStateChange func()
|
||||
// OnError func()
|
||||
|
||||
conn *dtls.Conn
|
||||
}
|
||||
|
||||
// NewRTCDtlsTransport creates a new RTCDtlsTransport.
|
||||
// This constructor is part of the ORTC API. It is not
|
||||
// meant to be used together with the basic WebRTC API.
|
||||
func NewRTCDtlsTransport(transport *RTCIceTransport, certificates []RTCCertificate) (*RTCDtlsTransport, error) {
|
||||
t := &RTCDtlsTransport{iceTransport: transport}
|
||||
|
||||
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 = []RTCCertificate{*certificate}
|
||||
}
|
||||
|
||||
return t, nil
|
||||
}
|
||||
|
||||
// GetLocalParameters returns the DTLS parameters of the local RTCDtlsTransport upon construction.
|
||||
func (t *RTCDtlsTransport) GetLocalParameters() RTCDtlsParameters {
|
||||
fingerprints := []RTCDtlsFingerprint{}
|
||||
|
||||
for _, c := range t.certificates {
|
||||
prints := c.GetFingerprints() // TODO: Should be only one?
|
||||
fingerprints = append(fingerprints, prints...)
|
||||
}
|
||||
|
||||
return RTCDtlsParameters{
|
||||
Role: RTCDtlsRoleAuto, // always returns the default role
|
||||
Fingerprints: fingerprints,
|
||||
}
|
||||
}
|
||||
|
||||
// Start DTLS transport negotiation with the parameters of the remote DTLS transport
|
||||
func (t *RTCDtlsTransport) Start(remoteParameters RTCDtlsParameters) error {
|
||||
t.lock.Lock()
|
||||
defer t.lock.Unlock()
|
||||
|
||||
if err := t.ensureICEConn(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO: Mux
|
||||
dtlsEndpoint := t.iceTransport.conn
|
||||
|
||||
// TODO: handle multiple certs
|
||||
cert := t.certificates[0]
|
||||
|
||||
isClient := true
|
||||
switch remoteParameters.Role {
|
||||
case RTCDtlsRoleClient:
|
||||
isClient = true
|
||||
case RTCDtlsRoleServer:
|
||||
isClient = false
|
||||
default:
|
||||
if t.iceTransport.Role() == RTCIceRoleControlling {
|
||||
isClient = false
|
||||
}
|
||||
}
|
||||
|
||||
dtlsCofig := &dtls.Config{Certificate: cert.x509Cert, PrivateKey: cert.privateKey}
|
||||
if isClient {
|
||||
// Assumes the peer offered to be passive and we accepted.
|
||||
dtlsConn, err := dtls.Client(dtlsEndpoint, dtlsCofig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
t.conn = dtlsConn
|
||||
} else {
|
||||
// Assumes we offer to be passive and this is accepted.
|
||||
dtlsConn, err := dtls.Server(dtlsEndpoint, dtlsCofig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
t.conn = dtlsConn
|
||||
}
|
||||
|
||||
// Check the fingerprint if a certificate was exchanged
|
||||
remoteCert := t.conn.RemoteCertificate()
|
||||
if remoteCert != nil {
|
||||
err := t.validateFingerPrint(remoteParameters, remoteCert)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
fmt.Println("Warning: Certificate not checked")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *RTCDtlsTransport) validateFingerPrint(remoteParameters RTCDtlsParameters, remoteCert *x509.Certificate) error {
|
||||
for _, fp := range remoteParameters.Fingerprints {
|
||||
hashAlgo, err := dtls.HashAlgorithmString(fp.Algorithm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
remoteValue, err := dtls.Fingerprint(remoteCert, hashAlgo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if strings.ToLower(remoteValue) == strings.ToLower(fp.Value) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return errors.New("No matching fingerprint")
|
||||
}
|
||||
|
||||
func (t *RTCDtlsTransport) ensureICEConn() error {
|
||||
if t.iceTransport == nil ||
|
||||
t.iceTransport.conn == nil {
|
||||
return errors.New("ICE connection not started")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
86
rtcicecandidate.go
Normal file
86
rtcicecandidate.go
Normal file
@@ -0,0 +1,86 @@
|
||||
package webrtc
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"github.com/pions/webrtc/pkg/ice"
|
||||
)
|
||||
|
||||
// RTCIceCandidate represents a ice candidate
|
||||
type RTCIceCandidate struct {
|
||||
Foundation string `json:"foundation"`
|
||||
Priority uint32 `json:"priority"`
|
||||
IP string `json:"ip"`
|
||||
Protocol RTCIceProtocol `json:"protocol"`
|
||||
Port uint16 `json:"port"`
|
||||
Typ RTCIceCandidateType `json:"type"`
|
||||
RelatedAddress string `json:"relatedAddress"`
|
||||
RelatedPort uint16 `json:"relatedPort"`
|
||||
}
|
||||
|
||||
func newRTCIceCandidatesFromICE(iceCandidates []*ice.Candidate) ([]RTCIceCandidate, error) {
|
||||
candidates := []RTCIceCandidate{}
|
||||
|
||||
for _, i := range iceCandidates {
|
||||
c, err := newRTCIceCandidateFromICE(i)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
candidates = append(candidates, c)
|
||||
}
|
||||
|
||||
return candidates, nil
|
||||
}
|
||||
|
||||
func newRTCIceCandidateFromICE(i *ice.Candidate) (RTCIceCandidate, error) {
|
||||
typ, err := convertTypeFromICE(i.Type)
|
||||
if err != nil {
|
||||
return RTCIceCandidate{}, err
|
||||
}
|
||||
return RTCIceCandidate{
|
||||
Foundation: "foundation",
|
||||
Priority: uint32(i.Priority(i.Type.Preference(), uint16(1))), // TODO: component support
|
||||
IP: i.IP.String(),
|
||||
Protocol: newRTCIceProtocol(i.NetworkType.NetworkShort()),
|
||||
Port: uint16(i.Port), // TODO store differently in ICE package
|
||||
Typ: typ,
|
||||
RelatedAddress: "", // TODO
|
||||
RelatedPort: 0, // TODO (parse & store port correctly in ICE package)
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c RTCIceCandidate) toICE() (*ice.Candidate, error) {
|
||||
ip := net.ParseIP(c.IP)
|
||||
if ip == nil {
|
||||
return nil, errors.New("Failed to parse IP address")
|
||||
}
|
||||
|
||||
switch c.Typ {
|
||||
case RTCIceCandidateTypeHost:
|
||||
return ice.NewCandidateHost(c.Protocol.String(), ip, int(c.Port))
|
||||
|
||||
case RTCIceCandidateTypeSrflx:
|
||||
return ice.NewCandidateServerReflexive(c.Protocol.String(), ip, int(c.Port),
|
||||
c.RelatedAddress, int(c.RelatedPort))
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("Unknown candidate type: %s", c.Typ)
|
||||
}
|
||||
}
|
||||
|
||||
func convertTypeFromICE(t ice.CandidateType) (RTCIceCandidateType, error) {
|
||||
switch t {
|
||||
case ice.CandidateTypeHost:
|
||||
return RTCIceCandidateTypeHost, nil
|
||||
case ice.CandidateTypeServerReflexive:
|
||||
return RTCIceCandidateTypeSrflx, nil
|
||||
// case ice.CandidateTypePeerReflexive:
|
||||
// return RTCIceCandidateTypePrflx, nil
|
||||
// case ice.CandidateTypeRelay:
|
||||
// return RTCIceCandidateTypeRelay, nil
|
||||
default:
|
||||
return RTCIceCandidateType(t), fmt.Errorf("Unknown ICE candidate type: %s", t)
|
||||
}
|
||||
}
|
||||
105
rtcicegatherer.go
Normal file
105
rtcicegatherer.go
Normal file
@@ -0,0 +1,105 @@
|
||||
package webrtc
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sync"
|
||||
|
||||
"github.com/pions/webrtc/pkg/ice"
|
||||
)
|
||||
|
||||
// The RTCIceGatherer 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 RTCIceGatherer struct {
|
||||
lock sync.RWMutex
|
||||
state RTCIceGathererState
|
||||
|
||||
validatedServers []*ice.URL
|
||||
|
||||
agent *ice.Agent
|
||||
}
|
||||
|
||||
// NewRTCIceGatherer creates a new NewRTCIceGatherer.
|
||||
// This constructor is part of the ORTC API. It is not
|
||||
// meant to be used together with the basic WebRTC API.
|
||||
func NewRTCIceGatherer(opts RTCIceGatherOptions) (*RTCIceGatherer, error) {
|
||||
validatedServers := []*ice.URL{}
|
||||
if len(opts.ICEServers) > 0 {
|
||||
for _, server := range opts.ICEServers {
|
||||
url, err := server.validate()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
validatedServers = append(validatedServers, url...)
|
||||
}
|
||||
}
|
||||
|
||||
return &RTCIceGatherer{
|
||||
state: RTCIceGathererStateNew,
|
||||
validatedServers: validatedServers,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// State indicates the current state of the ICE gatherer.
|
||||
func (g *RTCIceGatherer) State() RTCIceGathererState {
|
||||
g.lock.RLock()
|
||||
defer g.lock.RUnlock()
|
||||
return g.state
|
||||
}
|
||||
|
||||
// Gather ICE candidates.
|
||||
func (g *RTCIceGatherer) Gather() error {
|
||||
g.lock.Lock()
|
||||
defer g.lock.Unlock()
|
||||
|
||||
config := &ice.AgentConfig{
|
||||
Urls: g.validatedServers,
|
||||
Notifier: nil, // TODO
|
||||
PortMin: defaultSettingEngine.EphemeralUDP.PortMin,
|
||||
PortMax: defaultSettingEngine.EphemeralUDP.PortMax,
|
||||
}
|
||||
agent, err := ice.NewAgent(config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
g.agent = agent
|
||||
g.state = RTCIceGathererStateComplete
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetLocalParameters returns the ICE parameters of the RTCIceGatherer.
|
||||
func (g *RTCIceGatherer) GetLocalParameters() (RTCIceParameters, error) {
|
||||
g.lock.RLock()
|
||||
defer g.lock.RUnlock()
|
||||
if g.agent == nil {
|
||||
return RTCIceParameters{}, errors.New("Gatherer not started")
|
||||
}
|
||||
|
||||
frag, pwd := g.agent.GetLocalUserCredentials()
|
||||
|
||||
return RTCIceParameters{
|
||||
UsernameFragment: frag,
|
||||
Password: pwd,
|
||||
IceLite: false,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetLocalCandidates returns the sequence of valid local candidates associated with the RTCIceGatherer.
|
||||
func (g *RTCIceGatherer) GetLocalCandidates() ([]RTCIceCandidate, error) {
|
||||
g.lock.RLock()
|
||||
defer g.lock.RUnlock()
|
||||
|
||||
if g.agent == nil {
|
||||
return nil, errors.New("Gatherer not started")
|
||||
}
|
||||
|
||||
iceCandidates, err := g.agent.GetLocalCandidates()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return newRTCIceCandidatesFromICE(iceCandidates)
|
||||
}
|
||||
36
rtcicegathererstate.go
Normal file
36
rtcicegathererstate.go
Normal file
@@ -0,0 +1,36 @@
|
||||
package webrtc
|
||||
|
||||
// RTCIceGathererState represents the current state of the ICE gatherer.
|
||||
type RTCIceGathererState byte
|
||||
|
||||
const (
|
||||
// RTCIceGathererStateNew indicates object has been created but
|
||||
// gather() has not been called.
|
||||
RTCIceGathererStateNew RTCIceGathererState = iota + 1
|
||||
|
||||
// RTCIceGathererStateGathering indicates gather() has been called,
|
||||
// and the RTCIceGatherer is in the process of gathering candidates.
|
||||
RTCIceGathererStateGathering
|
||||
|
||||
// RTCIceGathererStateComplete indicates the RTCIceGatherer has completed gathering.
|
||||
RTCIceGathererStateComplete
|
||||
|
||||
// RTCIceGathererStateClosed indicates the closed state can only be entered
|
||||
// when the RTCIceGatherer has been closed intentionally by calling close().
|
||||
RTCIceGathererStateClosed
|
||||
)
|
||||
|
||||
func (s RTCIceGathererState) String() string {
|
||||
switch s {
|
||||
case RTCIceGathererStateNew:
|
||||
return "new"
|
||||
case RTCIceGathererStateGathering:
|
||||
return "gathering"
|
||||
case RTCIceGathererStateComplete:
|
||||
return "complete"
|
||||
case RTCIceGathererStateClosed:
|
||||
return "closed"
|
||||
default:
|
||||
return "Unknown state"
|
||||
}
|
||||
}
|
||||
6
rtcicegatheroptions.go
Normal file
6
rtcicegatheroptions.go
Normal file
@@ -0,0 +1,6 @@
|
||||
package webrtc
|
||||
|
||||
// RTCIceGatherOptions provides options relating to the gathering of ICE candidates.
|
||||
type RTCIceGatherOptions struct {
|
||||
ICEServers []RTCIceServer
|
||||
}
|
||||
9
rtciceparameters.go
Normal file
9
rtciceparameters.go
Normal file
@@ -0,0 +1,9 @@
|
||||
package webrtc
|
||||
|
||||
// RTCIceParameters includes the ICE username fragment
|
||||
// and password and other ICE-related parameters.
|
||||
type RTCIceParameters struct {
|
||||
UsernameFragment string `json:"usernameFragment"`
|
||||
Password string `json:"password"`
|
||||
IceLite bool `json:"iceLite"`
|
||||
}
|
||||
@@ -18,35 +18,41 @@ func (s RTCIceServer) parseURL(i int) (*ice.URL, error) {
|
||||
return ice.ParseURL(s.URLs[i])
|
||||
}
|
||||
|
||||
func (s RTCIceServer) validate() error {
|
||||
func (s RTCIceServer) validate() ([]*ice.URL, error) {
|
||||
urls := []*ice.URL{}
|
||||
|
||||
for i := range s.URLs {
|
||||
url, err := s.parseURL(i)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, 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 &rtcerr.InvalidAccessError{Err: ErrNoTurnCredencials}
|
||||
return nil, &rtcerr.InvalidAccessError{Err: ErrNoTurnCredencials}
|
||||
}
|
||||
|
||||
switch s.CredentialType {
|
||||
case RTCIceCredentialTypePassword:
|
||||
// https://www.w3.org/TR/webrtc/#set-the-configuration (step #11.3.3)
|
||||
if _, ok := s.Credential.(string); !ok {
|
||||
return &rtcerr.InvalidAccessError{Err: ErrTurnCredencials}
|
||||
return nil, &rtcerr.InvalidAccessError{Err: ErrTurnCredencials}
|
||||
}
|
||||
|
||||
case RTCIceCredentialTypeOauth:
|
||||
// https://www.w3.org/TR/webrtc/#set-the-configuration (step #11.3.4)
|
||||
if _, ok := s.Credential.(RTCOAuthCredential); !ok {
|
||||
return &rtcerr.InvalidAccessError{Err: ErrTurnCredencials}
|
||||
return nil, &rtcerr.InvalidAccessError{Err: ErrTurnCredencials}
|
||||
}
|
||||
|
||||
default:
|
||||
return &rtcerr.InvalidAccessError{Err: ErrTurnCredencials}
|
||||
return nil, &rtcerr.InvalidAccessError{Err: ErrTurnCredencials}
|
||||
}
|
||||
}
|
||||
|
||||
urls = append(urls, url)
|
||||
}
|
||||
return nil
|
||||
|
||||
return urls, nil
|
||||
}
|
||||
|
||||
@@ -32,7 +32,8 @@ func TestRTCIceServer_validate(t *testing.T) {
|
||||
}
|
||||
|
||||
for i, testCase := range testCases {
|
||||
assert.Nil(t, testCase.iceServer.validate(), "testCase: %d %v", i, testCase)
|
||||
_, err := testCase.iceServer.validate()
|
||||
assert.Nil(t, err, "testCase: %d %v", i, testCase)
|
||||
}
|
||||
})
|
||||
t.Run("Failure", func(t *testing.T) {
|
||||
@@ -70,8 +71,9 @@ func TestRTCIceServer_validate(t *testing.T) {
|
||||
}
|
||||
|
||||
for i, testCase := range testCases {
|
||||
_, err := testCase.iceServer.validate()
|
||||
assert.EqualError(t,
|
||||
testCase.iceServer.validate(),
|
||||
err,
|
||||
testCase.expectedErr.Error(),
|
||||
"testCase: %d %v", i, testCase,
|
||||
)
|
||||
|
||||
@@ -1,12 +1,25 @@
|
||||
package webrtc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"sync"
|
||||
|
||||
"github.com/pions/webrtc/pkg/ice"
|
||||
)
|
||||
|
||||
// RTCIceTransport allows an application access to information about the ICE
|
||||
// transport over which packets are sent and received.
|
||||
type RTCIceTransport struct {
|
||||
// Role RTCIceRole
|
||||
lock sync.RWMutex
|
||||
|
||||
role RTCIceRole
|
||||
// Component RTCIceComponent
|
||||
// State RTCIceTransportState
|
||||
// gatheringState RTCIceGathererState
|
||||
|
||||
gatherer *RTCIceGatherer
|
||||
conn *ice.Conn
|
||||
}
|
||||
|
||||
// func (t *RTCIceTransport) GetLocalCandidates() []RTCIceCandidate {
|
||||
@@ -28,3 +41,95 @@ type RTCIceTransport struct {
|
||||
// func (t *RTCIceTransport) GetRemoteParameters() RTCIceParameters {
|
||||
//
|
||||
// }
|
||||
|
||||
// NewRTCIceTransport creates a new NewRTCIceTransport.
|
||||
// This constructor is part of the ORTC API. It is not
|
||||
// meant to be used together with the basic WebRTC API.
|
||||
func NewRTCIceTransport(gatherer *RTCIceGatherer) *RTCIceTransport {
|
||||
return &RTCIceTransport{gatherer: gatherer}
|
||||
}
|
||||
|
||||
// Start incoming connectivity checks based on its configured role.
|
||||
func (t *RTCIceTransport) Start(gatherer *RTCIceGatherer, params RTCIceParameters, role *RTCIceRole) error {
|
||||
t.lock.Lock()
|
||||
defer t.lock.Unlock()
|
||||
|
||||
if gatherer != nil {
|
||||
t.gatherer = gatherer
|
||||
}
|
||||
|
||||
if err := t.ensureGatherer(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if role == nil {
|
||||
controlled := RTCIceRoleControlled
|
||||
role = &controlled
|
||||
}
|
||||
t.role = *role
|
||||
|
||||
switch t.role {
|
||||
case RTCIceRoleControlling:
|
||||
iceConn, err := t.gatherer.agent.Dial(context.TODO(),
|
||||
params.UsernameFragment,
|
||||
params.Password)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
t.conn = iceConn
|
||||
|
||||
case RTCIceRoleControlled:
|
||||
iceConn, err := t.gatherer.agent.Accept(context.TODO(),
|
||||
params.UsernameFragment,
|
||||
params.Password)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
t.conn = iceConn
|
||||
|
||||
default:
|
||||
return errors.New("Unknown ICE Role")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Role indicates the current role of the ICE transport.
|
||||
func (t *RTCIceTransport) Role() RTCIceRole {
|
||||
t.lock.RLock()
|
||||
defer t.lock.RUnlock()
|
||||
|
||||
return t.role
|
||||
}
|
||||
|
||||
// SetRemoteCandidates sets the sequence of candidates associated with the remote RTCIceTransport.
|
||||
func (t *RTCIceTransport) SetRemoteCandidates(remoteCandidates []RTCIceCandidate) error {
|
||||
t.lock.RLock()
|
||||
defer t.lock.RUnlock()
|
||||
|
||||
if err := t.ensureGatherer(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, c := range remoteCandidates {
|
||||
i, err := c.toICE()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = t.gatherer.agent.AddRemoteCandidate(i)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *RTCIceTransport) ensureGatherer() error {
|
||||
if t.gatherer == nil ||
|
||||
t.gatherer.agent == nil {
|
||||
return errors.New("Gatherer not started")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -138,7 +138,7 @@ func New(configuration RTCConfiguration) (*RTCPeerConnection, error) {
|
||||
IceGatheringState: RTCIceGatheringStateNew,
|
||||
ConnectionState: RTCPeerConnectionStateNew,
|
||||
mediaEngine: DefaultMediaEngine,
|
||||
sctpTransport: newRTCSctpTransport(),
|
||||
sctpTransport: NewRTCSctpTransport(nil),
|
||||
dataChannels: make(map[uint16]*RTCDataChannel),
|
||||
}
|
||||
|
||||
@@ -222,7 +222,7 @@ func (pc *RTCPeerConnection) initConfiguration(configuration RTCConfiguration) e
|
||||
|
||||
if len(configuration.IceServers) > 0 {
|
||||
for _, server := range configuration.IceServers {
|
||||
if err := server.validate(); err != nil {
|
||||
if _, err := server.validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -408,7 +408,7 @@ func (pc *RTCPeerConnection) SetConfiguration(configuration RTCConfiguration) er
|
||||
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 {
|
||||
if _, err := server.validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
6
rtcsctpcapabilities.go
Normal file
6
rtcsctpcapabilities.go
Normal file
@@ -0,0 +1,6 @@
|
||||
package webrtc
|
||||
|
||||
// RTCSctpCapabilities indicates the capabilities of the RTCSctpTransport.
|
||||
type RTCSctpCapabilities struct {
|
||||
MaxMessageSize uint32 `json:"maxMessageSize"`
|
||||
}
|
||||
@@ -1,11 +1,19 @@
|
||||
package webrtc
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"sync"
|
||||
|
||||
"github.com/pions/datachannel"
|
||||
"github.com/pions/sctp"
|
||||
)
|
||||
|
||||
// RTCSctpTransport provides details about the SCTP transport.
|
||||
type RTCSctpTransport struct {
|
||||
lock sync.RWMutex
|
||||
|
||||
// Transport represents the transport over which all SCTP packets for data
|
||||
// channels will be sent and received.
|
||||
Transport *RTCDtlsTransport
|
||||
@@ -13,6 +21,8 @@ type RTCSctpTransport struct {
|
||||
// State represents the current state of the SCTP transport.
|
||||
State RTCSctpTransportState
|
||||
|
||||
port uint16
|
||||
|
||||
// MaxMessageSize represents the maximum size of data that can be passed to
|
||||
// RTCDataChannel's send() method.
|
||||
MaxMessageSize float64
|
||||
@@ -25,11 +35,19 @@ type RTCSctpTransport struct {
|
||||
|
||||
// dataChannels
|
||||
// dataChannels map[uint16]*RTCDataChannel
|
||||
|
||||
association *sctp.Association
|
||||
onDataChannelHandler func(*RTCDataChannel)
|
||||
}
|
||||
|
||||
func newRTCSctpTransport() *RTCSctpTransport {
|
||||
// NewRTCSctpTransport creates a new RTCSctpTransport.
|
||||
// This constructor is part of the ORTC API. It is not
|
||||
// meant to be used together with the basic WebRTC API.
|
||||
func NewRTCSctpTransport(transport *RTCDtlsTransport) *RTCSctpTransport {
|
||||
res := &RTCSctpTransport{
|
||||
Transport: transport,
|
||||
State: RTCSctpTransportStateConnecting,
|
||||
port: 5000, // TODO
|
||||
}
|
||||
|
||||
res.updateMessageSize()
|
||||
@@ -38,6 +56,97 @@ func newRTCSctpTransport() *RTCSctpTransport {
|
||||
return res
|
||||
}
|
||||
|
||||
// GetCapabilities returns the RTCSctpCapabilities of the RTCSctpTransport.
|
||||
func (r *RTCSctpTransport) GetCapabilities() RTCSctpCapabilities {
|
||||
return RTCSctpCapabilities{
|
||||
MaxMessageSize: 0,
|
||||
}
|
||||
}
|
||||
|
||||
// Start the RTCSctpTransport. Since both local and remote parties must mutually
|
||||
// create an RTCSctpTransport, SCTP SO (Simultaneous Open) is used to establish
|
||||
// a connection over SCTP.
|
||||
func (r *RTCSctpTransport) Start(remoteCaps RTCSctpCapabilities) error {
|
||||
r.lock.Lock()
|
||||
defer r.lock.Unlock()
|
||||
|
||||
// TODO: port
|
||||
_ = r.MaxMessageSize // TODO
|
||||
|
||||
if err := r.ensureDTLS(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sctpAssociation, err := sctp.Client(r.Transport.conn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
r.association = sctpAssociation
|
||||
|
||||
go r.acceptDataChannels()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *RTCSctpTransport) ensureDTLS() error {
|
||||
if r.Transport == nil ||
|
||||
r.Transport.conn == nil {
|
||||
return errors.New("DTLS not establisched")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *RTCSctpTransport) acceptDataChannels() {
|
||||
for {
|
||||
dc, err := datachannel.Accept(r.association)
|
||||
if err != nil {
|
||||
fmt.Println("Failed to accept data channel:", err)
|
||||
// TODO: Kill DataChannel/PeerConnection?
|
||||
return
|
||||
}
|
||||
|
||||
sid := dc.StreamIdentifier()
|
||||
rtcDC := &RTCDataChannel{
|
||||
ID: &sid,
|
||||
Label: dc.Config.Label,
|
||||
ReadyState: RTCDataChannelStateOpen,
|
||||
}
|
||||
|
||||
<-r.onDataChannel(rtcDC)
|
||||
rtcDC.handleOpen(dc)
|
||||
}
|
||||
}
|
||||
|
||||
// OnDataChannel sets an event handler which is invoked when a data
|
||||
// channel message arrives from a remote peer.
|
||||
func (r *RTCSctpTransport) OnDataChannel(f func(*RTCDataChannel)) {
|
||||
r.lock.Lock()
|
||||
defer r.lock.Unlock()
|
||||
r.onDataChannelHandler = f
|
||||
}
|
||||
|
||||
func (r *RTCSctpTransport) onDataChannel(dc *RTCDataChannel) (done chan struct{}) {
|
||||
r.lock.Lock()
|
||||
hdlr := r.onDataChannelHandler
|
||||
r.lock.Unlock()
|
||||
|
||||
done = make(chan struct{})
|
||||
if hdlr == 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() {
|
||||
hdlr(dc)
|
||||
close(done)
|
||||
}()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (r *RTCSctpTransport) updateMessageSize() {
|
||||
var remoteMaxMessageSize float64 = 65536 // TODO: get from SDP
|
||||
var canSendSize float64 = 65536 // TODO: Get from SCTP implementation
|
||||
|
||||
Reference in New Issue
Block a user