10 "github.com/anacrolix/missinggo/v2/pproffd"
11 "github.com/pion/datachannel"
13 "github.com/pion/webrtc/v2"
17 metrics = expvar.NewMap("webtorrent")
18 api = func() *webrtc.API {
19 // Enable the detach API (since it's non-standard but more idiomatic).
20 s := webrtc.SettingEngine{}
21 s.DetachDataChannels()
22 return webrtc.NewAPI(webrtc.WithSettingEngine(s))
24 config = webrtc.Configuration{ICEServers: []webrtc.ICEServer{{URLs: []string{"stun:stun.l.google.com:19302"}}}}
25 newPeerConnectionMu sync.Mutex
28 type wrappedPeerConnection struct {
29 *webrtc.PeerConnection
33 func (me wrappedPeerConnection) Close() error {
34 return me.CloseWrapper.Close()
37 func newPeerConnection() (wrappedPeerConnection, error) {
38 newPeerConnectionMu.Lock()
39 defer newPeerConnectionMu.Unlock()
40 pc, err := api.NewPeerConnection(config)
42 return wrappedPeerConnection{}, err
44 return wrappedPeerConnection{
46 pproffd.NewCloseWrapper(pc),
50 // newOffer creates a transport and returns a WebRTC offer to be announced
52 peerConnection wrappedPeerConnection,
53 dataChannel *webrtc.DataChannel,
54 offer webrtc.SessionDescription,
57 peerConnection, err = newPeerConnection()
61 dataChannel, err = peerConnection.CreateDataChannel("webrtc-datachannel", nil)
63 peerConnection.Close()
66 offer, err = peerConnection.CreateOffer(nil)
68 peerConnection.Close()
71 err = peerConnection.SetLocalDescription(offer)
73 peerConnection.Close()
79 func initAnsweringPeerConnection(
80 peerConnection wrappedPeerConnection,
82 offer webrtc.SessionDescription,
83 onOpen onDataChannelOpen,
84 ) (answer webrtc.SessionDescription, err error) {
85 err = peerConnection.SetRemoteDescription(offer)
89 answer, err = peerConnection.CreateAnswer(nil)
93 err = peerConnection.SetLocalDescription(answer)
97 timer := time.AfterFunc(30*time.Second, func() {
98 metrics.Add("answering peer connections timed out", 1)
99 peerConnection.Close()
101 peerConnection.OnDataChannel(func(d *webrtc.DataChannel) {
102 setDataChannelOnOpen(d, peerConnection, func(dc datachannel.ReadWriteCloser) {
104 metrics.Add("answering peer connection conversions", 1)
105 onOpen(dc, DataChannelContext{answer, offer, offerId, false})
111 // getAnswerForOffer creates a transport from a WebRTC offer and and returns a WebRTC answer to be
113 func getAnswerForOffer(
114 offer webrtc.SessionDescription, onOpen onDataChannelOpen, offerId string,
116 answer webrtc.SessionDescription, err error,
118 peerConnection, err := newPeerConnection()
120 err = fmt.Errorf("failed to peer connection: %w", err)
123 answer, err = initAnsweringPeerConnection(peerConnection, offerId, offer, onOpen)
125 peerConnection.Close()
130 func (t *outboundOffer) setAnswer(answer webrtc.SessionDescription, onOpen func(datachannel.ReadWriteCloser)) error {
131 setDataChannelOnOpen(t.dataChannel, t.peerConnection, onOpen)
132 err := t.peerConnection.SetRemoteDescription(answer)
134 // TODO: Maybe grab this inside the onOpen callback and mark the offer used there.
140 type datachannelReadWriter interface {
147 type ioCloserFunc func() error
149 func (me ioCloserFunc) Close() error {
153 func setDataChannelOnOpen(
154 dc *webrtc.DataChannel,
155 pc wrappedPeerConnection,
156 onOpen func(closer datachannel.ReadWriteCloser),
159 raw, err := dc.Detach()
161 // This shouldn't happen if the API is configured correctly, and we call from OnOpen.
164 onOpen(hookDataChannelCloser(raw, pc))
168 func hookDataChannelCloser(dcrwc datachannel.ReadWriteCloser, pc wrappedPeerConnection) datachannel.ReadWriteCloser {
170 datachannelReadWriter
174 ioCloserFunc(pc.Close),