8 "github.com/anacrolix/log"
10 "github.com/anacrolix/torrent/tracker"
11 "github.com/anacrolix/torrent/webtorrent/buffer"
12 "github.com/gorilla/websocket"
13 "github.com/pion/datachannel"
14 "github.com/pion/webrtc/v2"
18 trackerURL = `wss://tracker.openwebtorrent.com/` // For simplicity
21 // Client represents the webtorrent client
26 offeredPeers map[string]Peer // OfferID to Peer
27 tracker *websocket.Conn
28 onConn func(_ datachannel.ReadWriteCloser, initiatedLocally bool)
31 // Peer represents a remote peer
37 func binaryToJsonString(b []byte) string {
40 seq = append(seq, rune(v))
45 type onDataChannelOpen func(_ datachannel.ReadWriteCloser, initiatedLocally bool)
47 func NewClient(peerId, infoHash [20]byte, onConn onDataChannelOpen) *Client {
49 offeredPeers: make(map[string]Peer),
50 peerIDBinary: binaryToJsonString(peerId[:]),
51 infoHashBinary: binaryToJsonString(infoHash[:]),
56 func (c *Client) Run(ar tracker.AnnounceRequest) error {
57 t, _, err := websocket.DefaultDialer.Dial(trackerURL, nil)
59 return fmt.Errorf("failed to dial tracker: %v", err)
70 func (c *Client) announce(request tracker.AnnounceRequest) error {
71 transpot, offer, err := NewTransport()
73 return fmt.Errorf("failed to create transport: %w", err)
76 randOfferID, err := buffer.RandomBytes(20)
78 return fmt.Errorf("failed to generate bytes: %w", err)
80 // OfferID := randOfferID.ToStringHex()
81 offerIDBinary := randOfferID.ToStringLatin1()
84 c.offeredPeers[offerIDBinary] = Peer{transport: transpot}
87 req := AnnounceRequest{
88 Numwant: 1, // If higher we need to create equal amount of offers
94 InfoHash: c.infoHashBinary,
95 PeerID: c.peerIDBinary,
97 OfferID: offerIDBinary,
102 data, err := json.Marshal(req)
104 return fmt.Errorf("failed to marshal request: %w", err)
108 err = tracker.WriteMessage(websocket.TextMessage, data)
110 return fmt.Errorf("write AnnounceRequest: %w", err)
117 func (c *Client) trackerReadLoop() error {
123 _, message, err := tracker.ReadMessage()
125 return fmt.Errorf("read error: %w", err)
127 log.Printf("recv: %q", message)
129 var ar AnnounceResponse
130 if err := json.Unmarshal(message, &ar); err != nil {
131 log.Printf("error unmarshaling announce response: %v", err)
134 if ar.InfoHash != c.infoHashBinary {
135 log.Printf("announce response for different hash: expected %q got %q", c.infoHashBinary, ar.InfoHash)
139 case ar.Offer != nil:
140 t, answer, err := NewTransportFromOffer(*ar.Offer, func(dc datachannel.ReadWriteCloser) {
144 return fmt.Errorf("write AnnounceResponse: %w", err)
147 req := AnnounceResponse{
149 InfoHash: c.infoHashBinary,
150 PeerID: c.peerIDBinary,
155 data, err := json.Marshal(req)
157 return fmt.Errorf("failed to marshal request: %w", err)
161 err = tracker.WriteMessage(websocket.TextMessage, data)
163 return fmt.Errorf("write AnnounceResponse: %w", err)
168 // Do something with the peer
169 _ = Peer{peerID: ar.PeerID, transport: t}
170 case ar.Answer != nil:
172 peer, ok := c.offeredPeers[ar.OfferID]
175 log.Printf("could not find peer for offer %q", ar.OfferID)
178 log.Printf("offer %q got answer %v", ar.OfferID, *ar.Answer)
179 err = peer.transport.SetAnswer(*ar.Answer, func(dc datachannel.ReadWriteCloser) {
183 return fmt.Errorf("failed to sent answer: %v", err)
189 type AnnounceRequest struct {
190 Numwant int `json:"numwant"`
191 Uploaded int `json:"uploaded"`
192 Downloaded int `json:"downloaded"`
193 Left int64 `json:"left"`
194 Event string `json:"event"`
195 Action string `json:"action"`
196 InfoHash string `json:"info_hash"`
197 PeerID string `json:"peer_id"`
198 Offers []Offer `json:"offers"`
202 OfferID string `json:"offer_id"`
203 Offer webrtc.SessionDescription `json:"offer"`
206 type AnnounceResponse struct {
207 InfoHash string `json:"info_hash"`
208 Action string `json:"action"`
209 Interval *int `json:"interval,omitempty"`
210 Complete *int `json:"complete,omitempty"`
211 Incomplete *int `json:"incomplete,omitempty"`
212 PeerID string `json:"peer_id,omitempty"`
213 ToPeerID string `json:"to_peer_id,omitempty"`
214 Answer *webrtc.SessionDescription `json:"answer,omitempty"`
215 Offer *webrtc.SessionDescription `json:"offer,omitempty"`
216 OfferID string `json:"offer_id,omitempty"`