9 "github.com/anacrolix/missinggo/v2/pproffd"
10 "github.com/pion/datachannel"
12 "github.com/pion/webrtc/v3"
16 metrics = expvar.NewMap("webtorrent")
17 api = func() *webrtc.API {
18 // Enable the detach API (since it's non-standard but more idiomatic).
19 s := webrtc.SettingEngine{}
20 s.DetachDataChannels()
21 return webrtc.NewAPI(webrtc.WithSettingEngine(s))
23 config = webrtc.Configuration{ICEServers: []webrtc.ICEServer{{URLs: []string{"stun:stun.l.google.com:19302"}}}}
24 newPeerConnectionMu sync.Mutex
27 type wrappedPeerConnection struct {
28 *webrtc.PeerConnection
33 func (me *wrappedPeerConnection) Close() error {
35 defer me.closeMu.Unlock()
36 return me.CloseWrapper.Close()
39 func newPeerConnection() (*wrappedPeerConnection, error) {
40 newPeerConnectionMu.Lock()
41 defer newPeerConnectionMu.Unlock()
42 pc, err := api.NewPeerConnection(config)
46 return &wrappedPeerConnection{
48 CloseWrapper: pproffd.NewCloseWrapper(pc),
52 // newOffer creates a transport and returns a WebRTC offer to be announced
54 peerConnection *wrappedPeerConnection,
55 dataChannel *webrtc.DataChannel,
56 offer webrtc.SessionDescription,
59 peerConnection, err = newPeerConnection()
63 dataChannel, err = peerConnection.CreateDataChannel("webrtc-datachannel", nil)
65 peerConnection.Close()
68 offer, err = peerConnection.CreateOffer(nil)
70 peerConnection.Close()
74 gatherComplete := webrtc.GatheringCompletePromise(peerConnection.PeerConnection)
75 err = peerConnection.SetLocalDescription(offer)
77 peerConnection.Close()
82 offer = *peerConnection.LocalDescription()
86 func initAnsweringPeerConnection(
87 peerConnection *wrappedPeerConnection,
88 offer webrtc.SessionDescription,
89 ) (answer webrtc.SessionDescription, err error) {
90 err = peerConnection.SetRemoteDescription(offer)
94 answer, err = peerConnection.CreateAnswer(nil)
99 gatherComplete := webrtc.GatheringCompletePromise(peerConnection.PeerConnection)
100 err = peerConnection.SetLocalDescription(answer)
106 answer = *peerConnection.LocalDescription()
110 // newAnsweringPeerConnection creates a transport from a WebRTC offer and and returns a WebRTC answer to be
112 func newAnsweringPeerConnection(offer webrtc.SessionDescription) (
113 peerConn *wrappedPeerConnection, answer webrtc.SessionDescription, err error,
115 peerConn, err = newPeerConnection()
117 err = fmt.Errorf("failed to create new connection: %w", err)
120 answer, err = initAnsweringPeerConnection(peerConn, offer)
127 func (t *outboundOffer) setAnswer(answer webrtc.SessionDescription, onOpen func(datachannel.ReadWriteCloser)) error {
128 setDataChannelOnOpen(t.dataChannel, t.peerConnection, onOpen)
129 err := t.peerConnection.SetRemoteDescription(answer)
133 type datachannelReadWriter interface {
140 type ioCloserFunc func() error
142 func (me ioCloserFunc) Close() error {
146 func setDataChannelOnOpen(
147 dc *webrtc.DataChannel,
148 pc *wrappedPeerConnection,
149 onOpen func(closer datachannel.ReadWriteCloser),
152 raw, err := dc.Detach()
154 // This shouldn't happen if the API is configured correctly, and we call from OnOpen.
157 onOpen(hookDataChannelCloser(raw, pc))
161 // Hooks the datachannel's Close to Close the owning PeerConnection. The datachannel takes ownership
162 // and responsibility for the PeerConnection.
163 func hookDataChannelCloser(dcrwc datachannel.ReadWriteCloser, pc *wrappedPeerConnection) datachannel.ReadWriteCloser {
165 datachannelReadWriter
169 ioCloserFunc(pc.Close),