// Package webrtc implements the WebRTC 1.0 as defined in W3C WebRTC specification document. package webrtc import ( "crypto/ecdsa" "crypto/elliptic" "crypto/rand" "fmt" "net" "strings" "sync" "time" "encoding/binary" "github.com/pions/webrtc/internal/datachannel" "github.com/pions/webrtc/internal/network" "github.com/pions/webrtc/internal/sdp" "github.com/pions/webrtc/pkg/ice" "github.com/pions/webrtc/pkg/media" "github.com/pions/webrtc/pkg/rtcerr" "github.com/pions/webrtc/pkg/rtcp" "github.com/pions/webrtc/pkg/rtp" "github.com/pkg/errors" ) // Unknown defines default public constant to use for "enum" like struct // comparisons when no value was defined. const Unknown = iota // RTCPeerConnection represents a WebRTC connection that establishes a // peer-to-peer communications with another RTCPeerConnection instance in a // browser, or to another endpoint implementing the required protocols. type RTCPeerConnection struct { sync.RWMutex configuration RTCConfiguration // CurrentLocalDescription represents the local description that was // successfully negotiated the last time the RTCPeerConnection transitioned // into the stable state plus any local candidates that have been generated // by the IceAgent since the offer or answer was created. CurrentLocalDescription *RTCSessionDescription // 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 // RTCPeerConnection is in the stable state, the value is null. PendingLocalDescription *RTCSessionDescription // CurrentRemoteDescription represents the last remote description that was // successfully negotiated the last time the RTCPeerConnection transitioned // into the stable state plus any remote candidates that have been supplied // via AddIceCandidate() since the offer or answer was created. CurrentRemoteDescription *RTCSessionDescription // 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 RTCPeerConnection is in the stable state, the value is // null. PendingRemoteDescription *RTCSessionDescription // SignalingState attribute returns the signaling state of the // RTCPeerConnection instance. SignalingState RTCSignalingState // IceGatheringState attribute returns the ICE gathering state of the // RTCPeerConnection instance. IceGatheringState RTCIceGatheringState // FIXME NOT-USED // IceConnectionState attribute returns the ICE connection state of the // RTCPeerConnection instance. // IceConnectionState RTCIceConnectionState // FIXME SWAP-FOR-THIS IceConnectionState ice.ConnectionState // FIXME REMOVE // ConnectionState attribute returns the connection state of the // RTCPeerConnection instance. ConnectionState RTCPeerConnectionState idpLoginURL *string isClosed bool negotiationNeeded bool lastOffer string lastAnswer string // Media mediaEngine *MediaEngine rtpTransceivers []*RTCRtpTransceiver // sctpTransport sctpTransport *RTCSctpTransport // DataChannels dataChannels map[uint16]*RTCDataChannel // OnNegotiationNeeded func() // FIXME NOT-USED // OnIceCandidate func() // FIXME NOT-USED // OnIceCandidateError func() // FIXME NOT-USED // OnIceGatheringStateChange func() // FIXME NOT-USED // OnConnectionStateChange func() // FIXME NOT-USED onSignalingStateChangeHandler func(RTCSignalingState) onICEConnectionStateChangeHandler func(ice.ConnectionState) onTrackHandler func(*RTCTrack) onDataChannelHandler func(*RTCDataChannel) // Deprecated: Internal mechanism which will be removed. networkManager *network.Manager } // New creates a new RTCPeerConfiguration with the provided configuration func New(configuration RTCConfiguration) (*RTCPeerConnection, 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 := RTCPeerConnection{ configuration: RTCConfiguration{ IceServers: []RTCIceServer{}, IceTransportPolicy: RTCIceTransportPolicyAll, BundlePolicy: RTCBundlePolicyBalanced, RtcpMuxPolicy: RTCRtcpMuxPolicyRequire, Certificates: []RTCCertificate{}, IceCandidatePoolSize: 0, }, isClosed: false, negotiationNeeded: false, lastOffer: "", lastAnswer: "", SignalingState: RTCSignalingStateStable, // IceConnectionState: RTCIceConnectionStateNew, // FIXME SWAP-FOR-THIS IceConnectionState: ice.ConnectionStateNew, // FIXME REMOVE IceGatheringState: RTCIceGatheringStateNew, ConnectionState: RTCPeerConnectionStateNew, mediaEngine: DefaultMediaEngine, sctpTransport: newRTCSctpTransport(), dataChannels: make(map[uint16]*RTCDataChannel), } var err error if err = pc.initConfiguration(configuration); err != nil { return nil, err } var urls []*ice.URL for _, server := range pc.configuration.IceServers { for _, rawURL := range server.URLs { var url *ice.URL url, err = ice.ParseURL(rawURL) if err != nil { return nil, err } urls = append(urls, url) } } pc.networkManager = network.NewManager(urls, pc.generateChannel, pc.iceStateChange) return &pc, nil } // initConfiguration defines validation of the specified RTCConfiguration 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 *RTCPeerConnection) initConfiguration(configuration RTCConfiguration) 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 = []RTCCertificate{*certificate} } if configuration.BundlePolicy != RTCBundlePolicy(Unknown) { pc.configuration.BundlePolicy = configuration.BundlePolicy } if configuration.RtcpMuxPolicy != RTCRtcpMuxPolicy(Unknown) { pc.configuration.RtcpMuxPolicy = configuration.RtcpMuxPolicy } if configuration.IceCandidatePoolSize != 0 { pc.configuration.IceCandidatePoolSize = configuration.IceCandidatePoolSize } if configuration.IceTransportPolicy != RTCIceTransportPolicy(Unknown) { pc.configuration.IceTransportPolicy = configuration.IceTransportPolicy } if len(configuration.IceServers) > 0 { for _, server := range configuration.IceServers { if err := server.validate(); err != nil { return err } } pc.configuration.IceServers = configuration.IceServers } return nil } // OnSignalingStateChange sets an event handler which is invoked when the // peer connection's signaling state changes func (pc *RTCPeerConnection) OnSignalingStateChange(f func(RTCSignalingState)) { pc.Lock() defer pc.Unlock() pc.onSignalingStateChangeHandler = f } func (pc *RTCPeerConnection) onSignalingStateChange(newState RTCSignalingState) (done chan struct{}) { pc.RLock() hdlr := pc.onSignalingStateChangeHandler pc.RUnlock() done = make(chan struct{}) if hdlr == nil { close(done) return } go func() { hdlr(newState) close(done) }() return } // OnDataChannel sets an event handler which is invoked when a data // channel message arrives from a remote peer. func (pc *RTCPeerConnection) OnDataChannel(f func(*RTCDataChannel)) { pc.Lock() defer pc.Unlock() pc.onDataChannelHandler = f } func (pc *RTCPeerConnection) onDataChannel(dc *RTCDataChannel) (done chan struct{}) { pc.RLock() hdlr := pc.onDataChannelHandler pc.RUnlock() 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 } // OnTrack sets an event handler which is called when remote track // arrives from a remote peer. func (pc *RTCPeerConnection) OnTrack(f func(*RTCTrack)) { pc.Lock() defer pc.Unlock() pc.onTrackHandler = f } func (pc *RTCPeerConnection) onTrack(t *RTCTrack) (done chan struct{}) { pc.RLock() hdlr := pc.onTrackHandler pc.RUnlock() done = make(chan struct{}) if hdlr == nil || t == nil { close(done) return } go func() { hdlr(t) close(done) }() return } // OnICEConnectionStateChange sets an event handler which is called // when an ICE connection state is changed. func (pc *RTCPeerConnection) OnICEConnectionStateChange(f func(ice.ConnectionState)) { pc.Lock() defer pc.Unlock() pc.onICEConnectionStateChangeHandler = f } func (pc *RTCPeerConnection) onICEConnectionStateChange(cs ice.ConnectionState) (done chan struct{}) { pc.RLock() hdlr := pc.onICEConnectionStateChangeHandler pc.RUnlock() done = make(chan struct{}) if hdlr == nil { close(done) return } go func() { hdlr(cs) close(done) }() return } // SetConfiguration updates the configuration of this RTCPeerConnection object. func (pc *RTCPeerConnection) SetConfiguration(configuration RTCConfiguration) error { // https://www.w3.org/TR/webrtc/#dom-rtcpeerconnection-setconfiguration (step #2) if pc.isClosed { 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 != RTCBundlePolicy(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 != RTCRtcpMuxPolicy(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 != RTCIceTransportPolicy(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 an RTCConfiguration object representing the current // configuration of this RTCPeerConnection object. The returned object is a // copy and direct mutation on it will not take affect until SetConfiguration // has been called with RTCConfiguration passed as its only argument. // https://www.w3.org/TR/webrtc/#dom-rtcpeerconnection-getconfiguration func (pc *RTCPeerConnection) GetConfiguration() RTCConfiguration { return pc.configuration } // ------------------------------------------------------------------------ // --- FIXME - BELOW CODE NEEDS REVIEW/CLEANUP // ------------------------------------------------------------------------ // CreateOffer starts the RTCPeerConnection and generates the localDescription func (pc *RTCPeerConnection) CreateOffer(options *RTCOfferOptions) (RTCSessionDescription, error) { useIdentity := pc.idpLoginURL != nil if options != nil { return RTCSessionDescription{}, errors.Errorf("TODO handle options") } else if useIdentity { return RTCSessionDescription{}, errors.Errorf("TODO handle identity provider") } else if pc.isClosed { return RTCSessionDescription{}, &rtcerr.InvalidStateError{Err: ErrConnectionClosed} } d := sdp.NewJSEPSessionDescription(useIdentity) pc.addFingerprint(d) candidates, err := pc.generateLocalCandidates() if err != nil { return RTCSessionDescription{}, err } bundleValue := "BUNDLE" if pc.addRTPMediaSection(d, RTCRtpCodecTypeAudio, "audio", RTCRtpTransceiverDirectionSendrecv, candidates, sdp.ConnectionRoleActpass) { bundleValue += " audio" } if pc.addRTPMediaSection(d, RTCRtpCodecTypeVideo, "video", RTCRtpTransceiverDirectionSendrecv, candidates, sdp.ConnectionRoleActpass) { bundleValue += " video" } pc.addDataMediaSection(d, "data", candidates, sdp.ConnectionRoleActpass) d = d.WithValueAttribute(sdp.AttrKeyGroup, bundleValue+" data") for _, m := range d.MediaDescriptions { m.WithPropertyAttribute("setup:actpass") } desc := RTCSessionDescription{ Type: RTCSdpTypeOffer, Sdp: d.Marshal(), parsed: d, } pc.lastOffer = desc.Sdp // FIXME: This doesn't follow the JS API spec, but removing it // would mean our examples and existing code have to change if err := pc.SetLocalDescription(desc); err != nil { return RTCSessionDescription{}, err } return desc, nil } // CreateAnswer starts the RTCPeerConnection and generates the localDescription func (pc *RTCPeerConnection) CreateAnswer(options *RTCAnswerOptions) (RTCSessionDescription, error) { useIdentity := pc.idpLoginURL != nil if options != nil { return RTCSessionDescription{}, errors.Errorf("TODO handle options") } else if useIdentity { return RTCSessionDescription{}, errors.Errorf("TODO handle identity provider") } else if pc.isClosed { return RTCSessionDescription{}, &rtcerr.InvalidStateError{Err: ErrConnectionClosed} } candidates, err := pc.generateLocalCandidates() if err != nil { return RTCSessionDescription{}, err } d := sdp.NewJSEPSessionDescription(useIdentity) pc.addFingerprint(d) bundleValue := "BUNDLE" for _, remoteMedia := range pc.RemoteDescription().parsed.MediaDescriptions { // TODO @trivigy better SDP parser var peerDirection RTCRtpTransceiverDirection midValue := "" for _, a := range remoteMedia.Attributes { if strings.HasPrefix(*a.String(), "mid") { midValue = (*a.String())[len("mid:"):] } else if strings.HasPrefix(*a.String(), "sendrecv") { peerDirection = RTCRtpTransceiverDirectionSendrecv } else if strings.HasPrefix(*a.String(), "sendonly") { peerDirection = RTCRtpTransceiverDirectionSendonly } else if strings.HasPrefix(*a.String(), "recvonly") { peerDirection = RTCRtpTransceiverDirectionRecvonly } } appendBundle := func() { bundleValue += " " + midValue } if strings.HasPrefix(*remoteMedia.MediaName.String(), "audio") { if pc.addRTPMediaSection(d, RTCRtpCodecTypeAudio, midValue, peerDirection, candidates, sdp.ConnectionRoleActive) { appendBundle() } } else if strings.HasPrefix(*remoteMedia.MediaName.String(), "video") { if pc.addRTPMediaSection(d, RTCRtpCodecTypeVideo, midValue, peerDirection, candidates, sdp.ConnectionRoleActive) { appendBundle() } } else if strings.HasPrefix(*remoteMedia.MediaName.String(), "application") { pc.addDataMediaSection(d, midValue, candidates, sdp.ConnectionRoleActive) appendBundle() } } d = d.WithValueAttribute(sdp.AttrKeyGroup, bundleValue) desc := RTCSessionDescription{ Type: RTCSdpTypeAnswer, Sdp: d.Marshal(), parsed: d, } pc.lastAnswer = desc.Sdp // FIXME: This doesn't follow the JS API spec, but removing it // would mean our examples and existing code have to change if err := pc.SetLocalDescription(desc); err != nil { return RTCSessionDescription{}, err } return desc, nil } // 4.4.1.6 Set the RTCSessionDescription func (pc *RTCPeerConnection) setDescription(sd *RTCSessionDescription, op rtcStateChangeOp) error { if pc.isClosed { return &rtcerr.InvalidStateError{Err: ErrConnectionClosed} } cur := pc.SignalingState setLocal := rtcStateChangeOpSetLocal setRemote := rtcStateChangeOpSetRemote newSdpDoesNotMatchOffer := &rtcerr.InvalidModificationError{Err: errors.New("New sdp does not match previous offer")} newSdpDoesNotMatchAnswer := &rtcerr.InvalidModificationError{Err: errors.New("New sdp does not match previous answer")} var nextState RTCSignalingState var err error switch op { case setLocal: switch sd.Type { // stable->SetLocal(offer)->have-local-offer case RTCSdpTypeOffer: if sd.Sdp != pc.lastOffer { return newSdpDoesNotMatchOffer } nextState, err = checkNextSignalingState(cur, RTCSignalingStateHaveLocalOffer, setLocal, sd.Type) if err == nil { pc.PendingLocalDescription = sd } // have-remote-offer->SetLocal(answer)->stable // have-local-pranswer->SetLocal(answer)->stable case RTCSdpTypeAnswer: if sd.Sdp != pc.lastAnswer { return newSdpDoesNotMatchAnswer } nextState, err = checkNextSignalingState(cur, RTCSignalingStateStable, setLocal, sd.Type) if err == nil { pc.CurrentLocalDescription = sd pc.CurrentRemoteDescription = pc.PendingRemoteDescription pc.PendingRemoteDescription = nil pc.PendingLocalDescription = nil } case RTCSdpTypeRollback: nextState, err = checkNextSignalingState(cur, RTCSignalingStateStable, setLocal, sd.Type) if err == nil { pc.PendingLocalDescription = nil } // have-remote-offer->SetLocal(pranswer)->have-local-pranswer case RTCSdpTypePranswer: if sd.Sdp != pc.lastAnswer { return newSdpDoesNotMatchAnswer } nextState, err = checkNextSignalingState(cur, RTCSignalingStateHaveLocalPranswer, setLocal, sd.Type) if err == nil { pc.PendingLocalDescription = sd } default: return &rtcerr.OperationError{Err: fmt.Errorf("Invalid state change op: %s(%s)", op, sd.Type)} } case setRemote: switch sd.Type { // stable->SetRemote(offer)->have-remote-offer case RTCSdpTypeOffer: nextState, err = checkNextSignalingState(cur, RTCSignalingStateHaveRemoteOffer, setRemote, sd.Type) if err == nil { pc.PendingRemoteDescription = sd } // have-local-offer->SetRemote(answer)->stable // have-remote-pranswer->SetRemote(answer)->stable case RTCSdpTypeAnswer: nextState, err = checkNextSignalingState(cur, RTCSignalingStateStable, setRemote, sd.Type) if err == nil { pc.CurrentRemoteDescription = sd pc.CurrentLocalDescription = pc.PendingLocalDescription pc.PendingRemoteDescription = nil pc.PendingLocalDescription = nil } case RTCSdpTypeRollback: nextState, err = checkNextSignalingState(cur, RTCSignalingStateStable, setRemote, sd.Type) if err == nil { pc.PendingRemoteDescription = nil } // have-local-offer->SetRemote(pranswer)->have-remote-pranswer case RTCSdpTypePranswer: nextState, err = checkNextSignalingState(cur, RTCSignalingStateHaveRemotePranswer, setRemote, sd.Type) if err == nil { pc.PendingRemoteDescription = sd } default: return &rtcerr.OperationError{Err: fmt.Errorf("Invalid state change op: %s(%s)", op, sd.Type)} } default: return &rtcerr.OperationError{Err: fmt.Errorf("Unhandled state change op: %q", op)} } if err == nil { pc.SignalingState = nextState pc.onSignalingStateChange(nextState) } return err } // SetLocalDescription sets the SessionDescription of the local peer func (pc *RTCPeerConnection) SetLocalDescription(desc RTCSessionDescription) error { if pc.isClosed { return &rtcerr.InvalidStateError{Err: ErrConnectionClosed} } // JSEP 5.4 if desc.Sdp == "" { switch desc.Type { case RTCSdpTypeAnswer, RTCSdpTypePranswer: desc.Sdp = pc.lastAnswer case RTCSdpTypeOffer: desc.Sdp = pc.lastOffer default: return &rtcerr.InvalidModificationError{ Err: fmt.Errorf("Invalid SDP type supplied to SetLocalDescription(): %s", desc.Type), } } } // TODO: Initiate ICE candidate gathering? desc.parsed = &sdp.SessionDescription{} if err := desc.parsed.Unmarshal(desc.Sdp); err != nil { return err } return pc.setDescription(&desc, rtcStateChangeOpSetLocal) } // 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 *RTCPeerConnection) LocalDescription() *RTCSessionDescription { if pc.PendingLocalDescription != nil { return pc.PendingLocalDescription } return pc.CurrentLocalDescription } // SetRemoteDescription sets the SessionDescription of the remote peer func (pc *RTCPeerConnection) SetRemoteDescription(desc RTCSessionDescription) error { // FIXME: Remove this when renegotiation is supported if pc.CurrentRemoteDescription != nil { return errors.Errorf("remoteDescription is already defined, SetRemoteDescription can only be called once") } if pc.isClosed { return &rtcerr.InvalidStateError{Err: ErrConnectionClosed} } desc.parsed = &sdp.SessionDescription{} if err := desc.parsed.Unmarshal(desc.Sdp); err != nil { return err } if err := pc.setDescription(&desc, rtcStateChangeOpSetRemote); err != nil { return err } weOffer := true remoteUfrag := "" remotePwd := "" if desc.Type == RTCSdpTypeOffer { weOffer = false } for _, m := range pc.RemoteDescription().parsed.MediaDescriptions { for _, a := range m.Attributes { if strings.HasPrefix(*a.String(), "candidate") { c, err := sdp.ICECandidateUnmarshal(*a.String()) if err != nil { return err } if err := pc.networkManager.IceAgent.AddRemoteCandidate(c); err != nil { return err } } else if strings.HasPrefix(*a.String(), "ice-ufrag") { remoteUfrag = (*a.String())[len("ice-ufrag:"):] } else if strings.HasPrefix(*a.String(), "ice-pwd") { remotePwd = (*a.String())[len("ice-pwd:"):] } } } fingerprint, ok := desc.parsed.Attribute("fingerprint") if !ok { fingerprint, ok = desc.parsed.MediaDescriptions[0].Attribute("fingerprint") if !ok { return errors.New("could not find fingerprint") } } var fingerprintHash string parts := strings.Split(fingerprint, " ") if len(parts) != 2 { return errors.New("invalid fingerprint") } fingerprint = parts[1] fingerprintHash = parts[0] go func() { cert := pc.configuration.Certificates[0] // TODO: handle multiple certs err := pc.networkManager.Start(weOffer, remoteUfrag, remotePwd, cert.x509Cert, cert.privateKey, fingerprint, fingerprintHash) if err != nil { fmt.Println("Failed to start manager", err) } // Temporary data channel glue pc.openDataChannels() pc.acceptDataChannels() }() return nil } // openDataChannels opens the existing data channels // TODO: Move to RTCDataChannel func (pc *RTCPeerConnection) openDataChannels() { pc.RLock() defer pc.RUnlock() for _, rtcDC := range pc.dataChannels { dc, err := pc.networkManager.OpenDataChannel( *rtcDC.ID, &datachannel.Config{ ChannelType: datachannel.ChannelTypeReliable, // TODO: Wiring Priority: datachannel.ChannelPriorityNormal, // TODO: Wiring ReliabilityParameter: 0, // TODO: Wiring Label: rtcDC.Label, }) if err != nil { fmt.Println("failed to open data channel", err) continue } rtcDC.ReadyState = RTCDataChannelStateOpen rtcDC.handleOpen(dc) } } // acceptDataChannels accepts data channels // TODO: Move to RTCSctpTransport func (pc *RTCPeerConnection) acceptDataChannels() { for { dc, err := pc.networkManager.AcceptDataChannel() 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, rtcPeerConnection: pc, ReadyState: RTCDataChannelStateOpen, } pc.Lock() pc.dataChannels[sid] = rtcDC pc.Unlock() <-pc.onDataChannel(rtcDC) rtcDC.handleOpen(dc) } } // 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 *RTCPeerConnection) RemoteDescription() *RTCSessionDescription { 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 *RTCPeerConnection) AddIceCandidate(s string) error { c, err := sdp.ICECandidateUnmarshal(s) if err != nil { return err } return pc.networkManager.IceAgent.AddRemoteCandidate(c) } // ------------------------------------------------------------------------ // --- FIXME - BELOW CODE NEEDS RE-ORGANIZATION - https://w3c.github.io/webrtc-pc/#rtp-media-api // ------------------------------------------------------------------------ // GetSenders returns the RTCRtpSender that are currently attached to this RTCPeerConnection func (pc *RTCPeerConnection) GetSenders() []RTCRtpSender { result := make([]RTCRtpSender, len(pc.rtpTransceivers)) for i, tranceiver := range pc.rtpTransceivers { result[i] = *tranceiver.Sender } return result } // GetReceivers returns the RTCRtpReceivers that are currently attached to this RTCPeerConnection func (pc *RTCPeerConnection) GetReceivers() []RTCRtpReceiver { result := make([]RTCRtpReceiver, len(pc.rtpTransceivers)) for i, tranceiver := range pc.rtpTransceivers { result[i] = *tranceiver.Receiver } return result } // GetTransceivers returns the RTCRtpTransceiver that are currently attached to this RTCPeerConnection func (pc *RTCPeerConnection) GetTransceivers() []RTCRtpTransceiver { result := make([]RTCRtpTransceiver, len(pc.rtpTransceivers)) for i, tranceiver := range pc.rtpTransceivers { result[i] = *tranceiver } return result } // AddTrack adds a RTCTrack to the RTCPeerConnection func (pc *RTCPeerConnection) AddTrack(track *RTCTrack) (*RTCRtpSender, error) { if pc.isClosed { return nil, &rtcerr.InvalidStateError{Err: ErrConnectionClosed} } for _, transceiver := range pc.rtpTransceivers { if transceiver.Sender.Track == nil { continue } if track.ID == transceiver.Sender.Track.ID { return nil, &rtcerr.InvalidAccessError{Err: ErrExistingTrack} } } var transceiver *RTCRtpTransceiver for _, t := range pc.rtpTransceivers { if !t.stopped && // t.Sender == nil && // TODO: check that the sender has never sent t.Sender.Track == nil && t.Receiver.Track != nil && t.Receiver.Track.Kind == track.Kind { transceiver = t break } } if transceiver != nil { if err := transceiver.setSendingTrack(track); err != nil { return nil, err } } else { var receiver *RTCRtpReceiver sender := newRTCRtpSender(track) transceiver = pc.newRTCRtpTransceiver( receiver, sender, RTCRtpTransceiverDirectionSendonly, ) } transceiver.Mid = track.Kind.String() // TODO: Mid generation return transceiver.Sender, nil } // func (pc *RTCPeerConnection) RemoveTrack() { // panic("not implemented yet") // FIXME NOT-IMPLEMENTED nolint // } // func (pc *RTCPeerConnection) AddTransceiver() RTCRtpTransceiver { // panic("not implemented yet") // FIXME NOT-IMPLEMENTED nolint // } // ------------------------------------------------------------------------ // --- FIXME - BELOW CODE NEEDS RE-ORGANIZATION - https://w3c.github.io/webrtc-pc/#peer-to-peer-data-api // ------------------------------------------------------------------------ // CreateDataChannel creates a new RTCDataChannel object with the given label // and optitional RTCDataChannelInit used to configure properties of the // underlying channel such as data reliability. func (pc *RTCPeerConnection) CreateDataChannel(label string, options *RTCDataChannelInit) (*RTCDataChannel, error) { // https://w3c.github.io/webrtc-pc/#peer-to-peer-data-api (Step #2) if pc.isClosed { return nil, &rtcerr.InvalidStateError{Err: ErrConnectionClosed} } // https://w3c.github.io/webrtc-pc/#peer-to-peer-data-api (Step #5) if len(label) > 65535 { return nil, &rtcerr.TypeError{Err: ErrStringSizeLimit} } // https://w3c.github.io/webrtc-pc/#peer-to-peer-data-api (Step #3) // Some variables defined explicitly despite their implicit zero values to // allow better readability to understand what is happening. Additionally, // some members are set to a non zero value default due to the default // definitions in https://w3c.github.io/webrtc-pc/#dom-rtcdatachannelinit // which are later overwriten by the options if any were specified. channel := RTCDataChannel{ rtcPeerConnection: pc, // https://w3c.github.io/webrtc-pc/#peer-to-peer-data-api (Step #4) Label: label, Ordered: true, MaxPacketLifeTime: nil, MaxRetransmits: nil, Protocol: "", Negotiated: false, ID: nil, Priority: RTCPriorityTypeLow, // https://w3c.github.io/webrtc-pc/#dfn-create-an-rtcdatachannel (Step #2) ReadyState: RTCDataChannelStateConnecting, // https://w3c.github.io/webrtc-pc/#dfn-create-an-rtcdatachannel (Step #3) BufferedAmount: 0, } if options != nil { // https://w3c.github.io/webrtc-pc/#peer-to-peer-data-api (Step #7) if options.MaxPacketLifeTime != nil { channel.MaxPacketLifeTime = options.MaxPacketLifeTime } // https://w3c.github.io/webrtc-pc/#peer-to-peer-data-api (Step #8) if options.MaxRetransmits != nil { channel.MaxRetransmits = options.MaxRetransmits } // https://w3c.github.io/webrtc-pc/#peer-to-peer-data-api (Step #9) if options.Ordered != nil { channel.Ordered = *options.Ordered } // https://w3c.github.io/webrtc-pc/#peer-to-peer-data-api (Step #10) if options.Protocol != nil { channel.Protocol = *options.Protocol } // https://w3c.github.io/webrtc-pc/#peer-to-peer-da ta-api (Step #12) if options.Negotiated != nil { channel.Negotiated = *options.Negotiated } // https://w3c.github.io/webrtc-pc/#peer-to-peer-data-api (Step #13) if options.ID != nil && channel.Negotiated { channel.ID = options.ID } // https://w3c.github.io/webrtc-pc/#peer-to-peer-data-api (Step #15) if options.Priority != nil { channel.Priority = *options.Priority } } // https://w3c.github.io/webrtc-pc/#peer-to-peer-data-api (Step #11) if len(channel.Protocol) > 65535 { return nil, &rtcerr.TypeError{Err: ErrStringSizeLimit} } // https://w3c.github.io/webrtc-pc/#peer-to-peer-data-api (Step #14) if channel.Negotiated && channel.ID == nil { return nil, &rtcerr.TypeError{Err: ErrNegotiatedWithoutID} } // https://w3c.github.io/webrtc-pc/#peer-to-peer-data-api (Step #16) if channel.MaxPacketLifeTime != nil && channel.MaxRetransmits != nil { return nil, &rtcerr.TypeError{Err: ErrRetransmitsOrPacketLifeTime} } // FIXME https://w3c.github.io/webrtc-pc/#dom-rtcpeerconnection-createdatachannel (Step #17) // https://w3c.github.io/webrtc-pc/#peer-to-peer-data-api (Step #20) channel.Transport = pc.sctpTransport // https://w3c.github.io/webrtc-pc/#peer-to-peer-data-api (Step #19) if channel.ID == nil { var err error if channel.ID, err = pc.generateDataChannelID(true); err != nil { return nil, err } // if err := channel.generateID(); err != nil { // return nil, err // } } // // https://w3c.github.io/webrtc-pc/#peer-to-peer-data-api (Step #18) if *channel.ID > 65534 { return nil, &rtcerr.TypeError{Err: ErrMaxDataChannelID} } if pc.sctpTransport.State == RTCSctpTransportStateConnected && *channel.ID >= *pc.sctpTransport.MaxChannels { return nil, &rtcerr.OperationError{Err: ErrMaxDataChannelID} } // Remember datachannel pc.dataChannels[*channel.ID] = &channel // Send opening message // pc.networkManager.SendOpenChannelMessage(id, label) return &channel, nil } func (pc *RTCPeerConnection) generateDataChannelID(client bool) (*uint16, error) { var id uint16 if !client { id++ } for ; id < *pc.sctpTransport.MaxChannels-1; id += 2 { _, ok := pc.dataChannels[id] if !ok { return &id, nil } } return nil, &rtcerr.OperationError{Err: ErrMaxDataChannelID} } // SetMediaEngine allows overwriting the default media engine used by the RTCPeerConnection // This enables RTCPeerConnection with support for different codecs func (pc *RTCPeerConnection) SetMediaEngine(m *MediaEngine) { pc.mediaEngine = m } // SetIdentityProvider is used to configure an identity provider to generate identity assertions func (pc *RTCPeerConnection) SetIdentityProvider(provider string) error { return errors.Errorf("TODO SetIdentityProvider") } // SendRTCP sends a user provided RTCP packet to the connected peer // If no peer is connected the packet is discarded func (pc *RTCPeerConnection) SendRTCP(pkt rtcp.Packet) error { raw, err := pkt.Marshal() if err != nil { return err } pc.networkManager.SendRTCP(raw) return nil } // Close ends the RTCPeerConnection func (pc *RTCPeerConnection) Close() error { // https://www.w3.org/TR/webrtc/#dom-rtcpeerconnection-close (step #2) if pc.isClosed { return nil } err := pc.networkManager.Close() // https://www.w3.org/TR/webrtc/#dom-rtcpeerconnection-close (step #3) pc.isClosed = true // https://www.w3.org/TR/webrtc/#dom-rtcpeerconnection-close (step #4) pc.SignalingState = RTCSignalingStateClosed // https://www.w3.org/TR/webrtc/#dom-rtcpeerconnection-close (step #11) // pc.IceConnectionState = RTCIceConnectionStateClosed pc.IceConnectionState = ice.ConnectionStateClosed // FIXME REMOVE // https://www.w3.org/TR/webrtc/#dom-rtcpeerconnection-close (step #12) pc.ConnectionState = RTCPeerConnectionStateClosed return err } /* Everything below is private */ func (pc *RTCPeerConnection) generateChannel(ssrc uint32, payloadType uint8) (tpair *network.TransportPair) { pc.RLock() if pc.onTrackHandler == nil { pc.RUnlock() return nil } pc.RUnlock() sdpCodec, err := pc.CurrentLocalDescription.parsed.GetCodecForPayloadType(payloadType) if err != nil { fmt.Printf("No codec could be found in RemoteDescription for payloadType %d \n", payloadType) return nil } codec, err := pc.mediaEngine.getCodecSDP(sdpCodec) if err != nil { fmt.Printf("Codec %s in not registered\n", sdpCodec) return nil } rtpTransport := make(chan *rtp.Packet, 15) rtcpTransport := make(chan rtcp.Packet, 15) track := &RTCTrack{ PayloadType: payloadType, Kind: codec.Type, ID: "0", // TODO extract from remoteDescription Label: "", // TODO extract from remoteDescription Ssrc: ssrc, Codec: codec, Packets: rtpTransport, RTCPPackets: rtcpTransport, } // TODO: Register the receiving Track pc.onTrack(track) return &network.TransportPair{RTP: rtpTransport, RTCP: rtcpTransport} } func (pc *RTCPeerConnection) iceStateChange(newState ice.ConnectionState) { pc.Lock() pc.IceConnectionState = newState pc.Unlock() pc.onICEConnectionStateChange(newState) } func (pc *RTCPeerConnection) generateLocalCandidates() ([]string, error) { candidates, err := pc.networkManager.IceAgent.GetLocalCandidates() if err != nil { return nil, err } res := make([]string, 0) for _, c := range candidates { res = append(res, sdp.ICECandidateMarshal(c)...) } return res, nil } func localDirection(weSend bool, peerDirection RTCRtpTransceiverDirection) RTCRtpTransceiverDirection { theySend := (peerDirection == RTCRtpTransceiverDirectionSendrecv || peerDirection == RTCRtpTransceiverDirectionSendonly) if weSend && theySend { return RTCRtpTransceiverDirectionSendrecv } else if weSend && !theySend { return RTCRtpTransceiverDirectionSendonly } else if !weSend && theySend { return RTCRtpTransceiverDirectionRecvonly } return RTCRtpTransceiverDirectionInactive } func (pc *RTCPeerConnection) addFingerprint(d *sdp.SessionDescription) { // TODO: Handle multiple certificates for _, fingerprint := range pc.configuration.Certificates[0].GetFingerprints() { d.WithFingerprint(fingerprint.Algorithm, strings.ToUpper(fingerprint.Value)) } } func (pc *RTCPeerConnection) addRTPMediaSection(d *sdp.SessionDescription, codecType RTCRtpCodecType, midValue string, peerDirection RTCRtpTransceiverDirection, candidates []string, dtlsRole sdp.ConnectionRole) bool { if codecs := pc.mediaEngine.getCodecsByKind(codecType); len(codecs) == 0 { return false } media := sdp.NewJSEPMediaDescription(codecType.String(), []string{}). WithValueAttribute(sdp.AttrKeyConnectionSetup, dtlsRole.String()). // TODO: Support other connection types WithValueAttribute(sdp.AttrKeyMID, midValue). WithICECredentials(pc.networkManager.IceAgent.GetLocalUserCredentials()). WithPropertyAttribute(sdp.AttrKeyRtcpMux). // TODO: support RTCP fallback WithPropertyAttribute(sdp.AttrKeyRtcpRsize) // TODO: Support Reduced-Size RTCP? for _, codec := range pc.mediaEngine.getCodecsByKind(codecType) { media.WithCodec(codec.PayloadType, codec.Name, codec.ClockRate, codec.Channels, codec.SdpFmtpLine) } weSend := false for _, transceiver := range pc.rtpTransceivers { if transceiver.Sender == nil || transceiver.Sender.Track == nil || transceiver.Sender.Track.Kind != codecType { continue } weSend = true track := transceiver.Sender.Track media = media.WithMediaSource(track.Ssrc, track.Label /* cname */, track.Label /* streamLabel */, track.Label) } media = media.WithPropertyAttribute(localDirection(weSend, peerDirection).String()) for _, c := range candidates { media.WithCandidate(c) } media.WithPropertyAttribute("end-of-candidates") d.WithMedia(media) return true } func (pc *RTCPeerConnection) addDataMediaSection(d *sdp.SessionDescription, midValue string, candidates []string, dtlsRole sdp.ConnectionRole) { media := (&sdp.MediaDescription{ MediaName: sdp.MediaName{ Media: "application", Port: sdp.RangedPort{Value: 9}, Protos: []string{"DTLS", "SCTP"}, Formats: []string{"5000"}, }, ConnectionInformation: &sdp.ConnectionInformation{ NetworkType: "IN", AddressType: "IP4", Address: &sdp.Address{ IP: net.ParseIP("0.0.0.0"), }, }, }). WithValueAttribute(sdp.AttrKeyConnectionSetup, dtlsRole.String()). // TODO: Support other connection types WithValueAttribute(sdp.AttrKeyMID, midValue). WithPropertyAttribute(RTCRtpTransceiverDirectionSendrecv.String()). WithPropertyAttribute("sctpmap:5000 webrtc-datachannel 1024"). WithICECredentials(pc.networkManager.IceAgent.GetLocalUserCredentials()) for _, c := range candidates { media.WithCandidate(c) } media.WithPropertyAttribute("end-of-candidates") d.WithMedia(media) } func (pc *RTCPeerConnection) newRTCTrack(payloadType uint8, ssrc uint32, id, label string) (*RTCTrack, error) { codec, err := pc.mediaEngine.getCodec(payloadType) if err != nil { return nil, err } if codec.Payloader == nil { return nil, errors.New("codec payloader not set") } trackInput := make(chan media.RTCSample, 15) // Is the buffering needed? rawPackets := make(chan *rtp.Packet) rtcpPackets := make(chan rtcp.Packet) if ssrc == 0 { buf := make([]byte, 4) _, err = rand.Read(buf) if err != nil { return nil, errors.New("failed to generate random value") } ssrc = binary.LittleEndian.Uint32(buf) go func() { packetizer := rtp.NewPacketizer( 1400, payloadType, ssrc, codec.Payloader, rtp.NewRandomSequencer(), codec.ClockRate, ) for { in := <-trackInput packets := packetizer.Packetize(in.Data, in.Samples) for _, p := range packets { pc.networkManager.SendRTP(p) } } }() close(rawPackets) } else { // If SSRC is not 0, then we are working with an established RTP stream // and need to accept raw RTP packets for forwarding. go func() { for { p := <-rawPackets pc.networkManager.SendRTP(p) } }() close(trackInput) } t := &RTCTrack{ PayloadType: payloadType, Kind: codec.Type, ID: id, Label: label, Ssrc: ssrc, Codec: codec, RTCPPackets: rtcpPackets, Samples: trackInput, RawRTP: rawPackets, } pc.networkManager.AddTransportPair(ssrc, nil, rtcpPackets) return t, nil } // NewRawRTPTrack initializes a new *RTCTrack configured to accept raw *rtp.Packet // // NB: If the source RTP stream is being broadcast to multiple tracks, each track // must receive its own copies of the source packets in order to avoid packet corruption. func (pc *RTCPeerConnection) NewRawRTPTrack(payloadType uint8, ssrc uint32, id, label string) (*RTCTrack, error) { if ssrc == 0 { return nil, errors.New("SSRC supplied to NewRawRTPTrack() must be non-zero") } return pc.newRTCTrack(payloadType, ssrc, id, label) } // NewRTCSampleTrack initializes a new *RTCTrack configured to accept media.RTCSample func (pc *RTCPeerConnection) NewRTCSampleTrack(payloadType uint8, id, label string) (*RTCTrack, error) { return pc.newRTCTrack(payloadType, 0, id, label) } // NewRTCTrack is used to create a new RTCTrack // // Deprecated: Use NewRTCSampleTrack() instead func (pc *RTCPeerConnection) NewRTCTrack(payloadType uint8, id, label string) (*RTCTrack, error) { return pc.NewRTCSampleTrack(payloadType, id, label) } func (pc *RTCPeerConnection) newRTCRtpTransceiver( receiver *RTCRtpReceiver, sender *RTCRtpSender, direction RTCRtpTransceiverDirection, ) *RTCRtpTransceiver { t := &RTCRtpTransceiver{ Receiver: receiver, Sender: sender, Direction: direction, } pc.rtpTransceivers = append(pc.rtpTransceivers, t) return t }