2020-04-06 16:45:47 +10:00
|
|
|
package webtorrent
|
2020-04-05 13:55:14 +10:00
|
|
|
|
|
|
|
import (
|
2020-04-13 14:31:39 +10:00
|
|
|
"crypto/rand"
|
2020-04-05 13:55:14 +10:00
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
|
|
|
"sync"
|
|
|
|
|
2020-04-06 16:45:47 +10:00
|
|
|
"github.com/anacrolix/log"
|
|
|
|
|
|
|
|
"github.com/anacrolix/torrent/tracker"
|
2020-04-05 13:55:14 +10:00
|
|
|
"github.com/gorilla/websocket"
|
|
|
|
"github.com/pion/datachannel"
|
|
|
|
"github.com/pion/webrtc/v2"
|
|
|
|
)
|
|
|
|
|
|
|
|
// Client represents the webtorrent client
|
2020-04-13 14:31:39 +10:00
|
|
|
type TrackerClient struct {
|
2020-04-06 16:45:47 +10:00
|
|
|
lock sync.Mutex
|
|
|
|
peerIDBinary string
|
2020-04-05 13:55:14 +10:00
|
|
|
infoHashBinary string
|
2020-04-07 14:30:27 +10:00
|
|
|
outboundOffers map[string]outboundOffer // OfferID to outboundOffer
|
2020-04-06 16:45:47 +10:00
|
|
|
tracker *websocket.Conn
|
2020-04-07 14:30:27 +10:00
|
|
|
onConn onDataChannelOpen
|
|
|
|
logger log.Logger
|
2020-04-05 13:55:14 +10:00
|
|
|
}
|
|
|
|
|
2020-04-07 14:30:27 +10:00
|
|
|
// outboundOffer represents an outstanding offer.
|
|
|
|
type outboundOffer struct {
|
|
|
|
originalOffer webrtc.SessionDescription
|
2020-04-13 14:31:39 +10:00
|
|
|
transport *transport
|
2020-04-05 13:55:14 +10:00
|
|
|
}
|
|
|
|
|
2020-04-07 14:30:27 +10:00
|
|
|
type DataChannelContext struct {
|
|
|
|
Local, Remote webrtc.SessionDescription
|
2020-04-13 14:04:34 +10:00
|
|
|
OfferId string
|
2020-04-07 14:30:27 +10:00
|
|
|
LocalOffered bool
|
|
|
|
}
|
|
|
|
|
|
|
|
type onDataChannelOpen func(_ datachannel.ReadWriteCloser, dcc DataChannelContext)
|
2020-04-07 10:59:10 +10:00
|
|
|
|
2020-04-13 19:12:54 +10:00
|
|
|
func NewTrackerClient(peerId, infoHash [20]byte, onConn onDataChannelOpen, logger log.Logger) *TrackerClient {
|
2020-04-13 14:31:39 +10:00
|
|
|
return &TrackerClient{
|
2020-04-07 14:30:27 +10:00
|
|
|
outboundOffers: make(map[string]outboundOffer),
|
2020-04-06 16:45:47 +10:00
|
|
|
peerIDBinary: binaryToJsonString(peerId[:]),
|
|
|
|
infoHashBinary: binaryToJsonString(infoHash[:]),
|
2020-04-07 10:59:10 +10:00
|
|
|
onConn: onConn,
|
2020-04-07 14:30:27 +10:00
|
|
|
logger: logger,
|
2020-04-05 13:55:14 +10:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-13 14:31:39 +10:00
|
|
|
func (c *TrackerClient) Run(ar tracker.AnnounceRequest, url string) error {
|
2020-04-07 14:30:27 +10:00
|
|
|
t, _, err := websocket.DefaultDialer.Dial(url, nil)
|
2020-04-05 13:55:14 +10:00
|
|
|
if err != nil {
|
2020-04-07 14:30:27 +10:00
|
|
|
return fmt.Errorf("failed to dial tracker: %w", err)
|
2020-04-05 13:55:14 +10:00
|
|
|
}
|
|
|
|
defer t.Close()
|
2020-04-16 17:20:58 +10:00
|
|
|
c.logger.WithDefaultLevel(log.Debug).Printf("dialed tracker %q", url)
|
2020-04-05 13:55:14 +10:00
|
|
|
c.tracker = t
|
|
|
|
|
2020-04-07 14:30:27 +10:00
|
|
|
go func() {
|
|
|
|
err := c.announce(ar)
|
|
|
|
if err != nil {
|
2020-04-16 17:20:58 +10:00
|
|
|
c.logger.WithDefaultLevel(log.Error).Printf("error announcing: %v", err)
|
2020-04-07 14:30:27 +10:00
|
|
|
}
|
|
|
|
}()
|
|
|
|
return c.trackerReadLoop()
|
2020-04-05 13:55:14 +10:00
|
|
|
}
|
|
|
|
|
2020-04-13 14:31:39 +10:00
|
|
|
func (c *TrackerClient) announce(request tracker.AnnounceRequest) error {
|
|
|
|
transport, offer, err := newTransport()
|
2020-04-05 13:55:14 +10:00
|
|
|
if err != nil {
|
2020-04-06 16:45:47 +10:00
|
|
|
return fmt.Errorf("failed to create transport: %w", err)
|
2020-04-05 13:55:14 +10:00
|
|
|
}
|
|
|
|
|
2020-04-13 14:31:39 +10:00
|
|
|
var randOfferId [20]byte
|
|
|
|
_, err = rand.Read(randOfferId[:])
|
2020-04-05 13:55:14 +10:00
|
|
|
if err != nil {
|
2020-04-06 16:45:47 +10:00
|
|
|
return fmt.Errorf("failed to generate bytes: %w", err)
|
2020-04-05 13:55:14 +10:00
|
|
|
}
|
2020-04-13 14:31:39 +10:00
|
|
|
offerIDBinary := binaryToJsonString(randOfferId[:])
|
2020-04-05 13:55:14 +10:00
|
|
|
|
|
|
|
c.lock.Lock()
|
2020-04-07 14:30:27 +10:00
|
|
|
c.outboundOffers[offerIDBinary] = outboundOffer{
|
|
|
|
transport: transport,
|
|
|
|
originalOffer: offer,
|
|
|
|
}
|
2020-04-05 13:55:14 +10:00
|
|
|
c.lock.Unlock()
|
|
|
|
|
|
|
|
req := AnnounceRequest{
|
|
|
|
Numwant: 1, // If higher we need to create equal amount of offers
|
|
|
|
Uploaded: 0,
|
|
|
|
Downloaded: 0,
|
2020-04-06 16:45:47 +10:00
|
|
|
Left: request.Left,
|
2020-04-05 13:55:14 +10:00
|
|
|
Event: "started",
|
|
|
|
Action: "announce",
|
|
|
|
InfoHash: c.infoHashBinary,
|
|
|
|
PeerID: c.peerIDBinary,
|
2020-04-06 16:45:47 +10:00
|
|
|
Offers: []Offer{{
|
|
|
|
OfferID: offerIDBinary,
|
|
|
|
Offer: offer,
|
|
|
|
}},
|
2020-04-05 13:55:14 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
data, err := json.Marshal(req)
|
|
|
|
if err != nil {
|
2020-04-06 16:45:47 +10:00
|
|
|
return fmt.Errorf("failed to marshal request: %w", err)
|
2020-04-05 13:55:14 +10:00
|
|
|
}
|
|
|
|
c.lock.Lock()
|
|
|
|
tracker := c.tracker
|
|
|
|
err = tracker.WriteMessage(websocket.TextMessage, data)
|
|
|
|
if err != nil {
|
2020-04-06 16:45:47 +10:00
|
|
|
return fmt.Errorf("write AnnounceRequest: %w", err)
|
2020-04-05 13:55:14 +10:00
|
|
|
c.lock.Unlock()
|
|
|
|
}
|
|
|
|
c.lock.Unlock()
|
2020-04-06 16:45:47 +10:00
|
|
|
return nil
|
2020-04-05 13:55:14 +10:00
|
|
|
}
|
|
|
|
|
2020-04-13 14:31:39 +10:00
|
|
|
func (c *TrackerClient) trackerReadLoop() error {
|
2020-04-05 13:55:14 +10:00
|
|
|
c.lock.Lock()
|
|
|
|
tracker := c.tracker
|
|
|
|
c.lock.Unlock()
|
|
|
|
for {
|
|
|
|
_, message, err := tracker.ReadMessage()
|
|
|
|
if err != nil {
|
2020-04-06 16:45:47 +10:00
|
|
|
return fmt.Errorf("read error: %w", err)
|
2020-04-05 13:55:14 +10:00
|
|
|
}
|
2020-04-16 17:20:58 +10:00
|
|
|
c.logger.WithDefaultLevel(log.Debug).Printf("received message from tracker: %q", message)
|
2020-04-05 13:55:14 +10:00
|
|
|
|
|
|
|
var ar AnnounceResponse
|
|
|
|
if err := json.Unmarshal(message, &ar); err != nil {
|
2020-04-13 19:13:23 +10:00
|
|
|
c.logger.Printf("error unmarshaling announce response: %v", err)
|
2020-04-05 13:55:14 +10:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
if ar.InfoHash != c.infoHashBinary {
|
2020-04-13 19:13:23 +10:00
|
|
|
c.logger.Printf("announce response for different hash: expected %q got %q", c.infoHashBinary, ar.InfoHash)
|
2020-04-05 13:55:14 +10:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
switch {
|
|
|
|
case ar.Offer != nil:
|
2020-04-13 14:31:39 +10:00
|
|
|
_, answer, err := newTransportFromOffer(*ar.Offer, c.onConn, ar.OfferID)
|
2020-04-05 13:55:14 +10:00
|
|
|
if err != nil {
|
2020-04-06 16:45:47 +10:00
|
|
|
return fmt.Errorf("write AnnounceResponse: %w", err)
|
2020-04-05 13:55:14 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
req := AnnounceResponse{
|
|
|
|
Action: "announce",
|
|
|
|
InfoHash: c.infoHashBinary,
|
|
|
|
PeerID: c.peerIDBinary,
|
|
|
|
ToPeerID: ar.PeerID,
|
|
|
|
Answer: &answer,
|
|
|
|
OfferID: ar.OfferID,
|
|
|
|
}
|
|
|
|
data, err := json.Marshal(req)
|
|
|
|
if err != nil {
|
2020-04-06 16:45:47 +10:00
|
|
|
return fmt.Errorf("failed to marshal request: %w", err)
|
2020-04-05 13:55:14 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
c.lock.Lock()
|
|
|
|
err = tracker.WriteMessage(websocket.TextMessage, data)
|
|
|
|
if err != nil {
|
2020-04-06 16:45:47 +10:00
|
|
|
return fmt.Errorf("write AnnounceResponse: %w", err)
|
2020-04-05 13:55:14 +10:00
|
|
|
c.lock.Unlock()
|
|
|
|
}
|
|
|
|
c.lock.Unlock()
|
|
|
|
case ar.Answer != nil:
|
|
|
|
c.lock.Lock()
|
2020-04-07 14:30:27 +10:00
|
|
|
offer, ok := c.outboundOffers[ar.OfferID]
|
2020-04-05 13:55:14 +10:00
|
|
|
c.lock.Unlock()
|
|
|
|
if !ok {
|
2020-04-16 17:20:58 +10:00
|
|
|
c.logger.WithDefaultLevel(log.Warning).Printf("could not find offer for id %q", ar.OfferID)
|
2020-04-05 13:55:14 +10:00
|
|
|
continue
|
|
|
|
}
|
2020-04-13 14:17:46 +10:00
|
|
|
c.logger.Printf("offer %q got answer %v", ar.OfferID, *ar.Answer)
|
2020-04-07 14:30:27 +10:00
|
|
|
err = offer.transport.SetAnswer(*ar.Answer, func(dc datachannel.ReadWriteCloser) {
|
|
|
|
c.onConn(dc, DataChannelContext{
|
|
|
|
Local: offer.originalOffer,
|
|
|
|
Remote: *ar.Answer,
|
2020-04-13 14:04:34 +10:00
|
|
|
OfferId: ar.OfferID,
|
2020-04-07 14:30:27 +10:00
|
|
|
LocalOffered: true,
|
|
|
|
})
|
2020-04-07 10:59:10 +10:00
|
|
|
})
|
2020-04-05 13:55:14 +10:00
|
|
|
if err != nil {
|
2020-04-13 14:17:46 +10:00
|
|
|
return fmt.Errorf("failed to sent answer: %w", err)
|
2020-04-05 13:55:14 +10:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|