9 "github.com/anacrolix/log"
11 "github.com/anacrolix/torrent/tracker"
12 "github.com/gorilla/websocket"
13 "github.com/pion/datachannel"
14 "github.com/pion/webrtc/v2"
17 // Client represents the webtorrent client
18 type TrackerClient struct {
22 outboundOffers map[string]outboundOffer // OfferID to outboundOffer
23 tracker *websocket.Conn
24 onConn onDataChannelOpen
28 // outboundOffer represents an outstanding offer.
29 type outboundOffer struct {
30 originalOffer webrtc.SessionDescription
34 type DataChannelContext struct {
35 Local, Remote webrtc.SessionDescription
40 type onDataChannelOpen func(_ datachannel.ReadWriteCloser, dcc DataChannelContext)
42 func NewClient(peerId, infoHash [20]byte, onConn onDataChannelOpen, logger log.Logger) *TrackerClient {
43 return &TrackerClient{
44 outboundOffers: make(map[string]outboundOffer),
45 peerIDBinary: binaryToJsonString(peerId[:]),
46 infoHashBinary: binaryToJsonString(infoHash[:]),
52 func (c *TrackerClient) Run(ar tracker.AnnounceRequest, url string) error {
53 t, _, err := websocket.DefaultDialer.Dial(url, nil)
55 return fmt.Errorf("failed to dial tracker: %w", err)
58 c.logger.WithValues(log.Info).Printf("dialed tracker %q", url)
64 c.logger.WithValues(log.Error).Printf("error announcing: %v", err)
67 return c.trackerReadLoop()
70 func (c *TrackerClient) announce(request tracker.AnnounceRequest) error {
71 transport, offer, err := newTransport()
73 return fmt.Errorf("failed to create transport: %w", err)
76 var randOfferId [20]byte
77 _, err = rand.Read(randOfferId[:])
79 return fmt.Errorf("failed to generate bytes: %w", err)
81 offerIDBinary := binaryToJsonString(randOfferId[:])
84 c.outboundOffers[offerIDBinary] = outboundOffer{
90 req := AnnounceRequest{
91 Numwant: 1, // If higher we need to create equal amount of offers
97 InfoHash: c.infoHashBinary,
98 PeerID: c.peerIDBinary,
100 OfferID: offerIDBinary,
105 data, err := json.Marshal(req)
107 return fmt.Errorf("failed to marshal request: %w", err)
111 err = tracker.WriteMessage(websocket.TextMessage, data)
113 return fmt.Errorf("write AnnounceRequest: %w", err)
120 func (c *TrackerClient) trackerReadLoop() error {
126 _, message, err := tracker.ReadMessage()
128 return fmt.Errorf("read error: %w", err)
130 c.logger.WithValues(log.Debug).Printf("received message from tracker: %q", message)
132 var ar AnnounceResponse
133 if err := json.Unmarshal(message, &ar); err != nil {
134 log.Printf("error unmarshaling announce response: %v", err)
137 if ar.InfoHash != c.infoHashBinary {
138 log.Printf("announce response for different hash: expected %q got %q", c.infoHashBinary, ar.InfoHash)
142 case ar.Offer != nil:
143 _, answer, err := newTransportFromOffer(*ar.Offer, c.onConn, ar.OfferID)
145 return fmt.Errorf("write AnnounceResponse: %w", err)
148 req := AnnounceResponse{
150 InfoHash: c.infoHashBinary,
151 PeerID: c.peerIDBinary,
156 data, err := json.Marshal(req)
158 return fmt.Errorf("failed to marshal request: %w", err)
162 err = tracker.WriteMessage(websocket.TextMessage, data)
164 return fmt.Errorf("write AnnounceResponse: %w", err)
168 case ar.Answer != nil:
170 offer, ok := c.outboundOffers[ar.OfferID]
173 c.logger.WithValues(log.Warning).Printf("could not find offer for id %q", ar.OfferID)
176 c.logger.Printf("offer %q got answer %v", ar.OfferID, *ar.Answer)
177 err = offer.transport.SetAnswer(*ar.Answer, func(dc datachannel.ReadWriteCloser) {
178 c.onConn(dc, DataChannelContext{
179 Local: offer.originalOffer,
186 return fmt.Errorf("failed to sent answer: %w", err)