Files
bt/tracker/tracker.go

291 lines
7.9 KiB
Go
Raw Normal View History

2023-03-01 22:50:46 +08:00
// Copyright 2020~2023 xgfone
2020-06-07 13:43:15 +08:00
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package tracker supplies some common type interfaces of the BT tracker
// protocol.
package tracker
import (
"bytes"
2020-06-12 21:03:02 +08:00
"context"
2020-06-07 13:43:15 +08:00
"encoding/binary"
"errors"
"fmt"
"net"
"net/http"
2020-06-07 13:43:15 +08:00
"net/url"
2023-04-01 21:07:18 +08:00
"github.com/xgfone/go-bt/metainfo"
"github.com/xgfone/go-bt/tracker/httptracker"
"github.com/xgfone/go-bt/tracker/udptracker"
2020-06-07 13:43:15 +08:00
)
// Predefine some announce events.
//
// BEP 3
const (
None uint32 = iota
Completed // The local peer just completed the torrent.
Started // The local peer has just resumed this torrent.
Stopped // The local peer is leaving the swarm.
)
// AnnounceRequest is the common Announce request.
//
// BEP 3, 15
type AnnounceRequest struct {
2020-06-12 21:03:02 +08:00
InfoHash metainfo.Hash // Required
PeerID metainfo.Hash // Required
2020-06-07 13:43:15 +08:00
2020-06-12 21:03:02 +08:00
Uploaded int64 // Required, but default: 0, which should be only used for test or first.
Downloaded int64 // Required, but default: 0, which should be only used for test or first.
Left int64 // Required, but default: 0, which should be only used for test or last.
Event uint32 // Required, but default: 0
2020-06-07 13:43:15 +08:00
2020-06-12 21:03:02 +08:00
IP net.IP // Optional
Key int32 // Optional
2023-03-01 22:50:46 +08:00
NumWant int32 // Optional
2020-06-12 21:03:02 +08:00
Port uint16 // Optional
2020-06-07 13:43:15 +08:00
}
// ToHTTPAnnounceRequest creates a new httptracker.AnnounceRequest from itself.
func (ar AnnounceRequest) ToHTTPAnnounceRequest() httptracker.AnnounceRequest {
var ip string
if len(ar.IP) != 0 {
ip = ar.IP.String()
}
return httptracker.AnnounceRequest{
InfoHash: ar.InfoHash,
PeerID: ar.PeerID,
Uploaded: ar.Uploaded,
Downloaded: ar.Downloaded,
Left: ar.Left,
Port: ar.Port,
IP: ip,
Event: ar.Event,
NumWant: ar.NumWant,
Key: ar.Key,
2023-03-03 23:50:54 +08:00
Compact: true,
2020-06-07 13:43:15 +08:00
}
}
// ToUDPAnnounceRequest creates a new udptracker.AnnounceRequest from itself.
func (ar AnnounceRequest) ToUDPAnnounceRequest() udptracker.AnnounceRequest {
return udptracker.AnnounceRequest{
InfoHash: ar.InfoHash,
PeerID: ar.PeerID,
Downloaded: ar.Downloaded,
Left: ar.Left,
Uploaded: ar.Uploaded,
Event: ar.Event,
Key: ar.Key,
NumWant: ar.NumWant,
Port: ar.Port,
}
}
// AnnounceResponse is a common Announce response.
//
// BEP 3, 15
type AnnounceResponse struct {
2023-03-03 23:50:54 +08:00
Interval uint32 // Reflush Interval
Leechers uint32 // Incomplete
Seeders uint32 // Complete
2023-03-01 22:50:46 +08:00
Addresses []metainfo.HostAddr
2020-06-07 13:43:15 +08:00
}
// FromHTTPAnnounceResponse sets itself from r.
func (ar *AnnounceResponse) FromHTTPAnnounceResponse(r httptracker.AnnounceResponse) {
ar.Interval = r.Interval
ar.Leechers = r.Incomplete
ar.Seeders = r.Complete
2023-03-01 22:50:46 +08:00
ar.Addresses = make([]metainfo.HostAddr, 0, len(r.Peers)+len(r.Peers6))
for _, p := range r.Peers {
ar.Addresses = append(ar.Addresses, metainfo.NewHostAddr(p.IP, p.Port))
2020-06-07 13:43:15 +08:00
}
2023-03-01 22:50:46 +08:00
for _, p := range r.Peers6 {
ar.Addresses = append(ar.Addresses, metainfo.NewHostAddr(p.IP, p.Port))
2020-06-07 13:43:15 +08:00
}
}
// FromUDPAnnounceResponse sets itself from r.
func (ar *AnnounceResponse) FromUDPAnnounceResponse(r udptracker.AnnounceResponse) {
ar.Interval = r.Interval
ar.Leechers = r.Leechers
ar.Seeders = r.Seeders
2023-03-01 22:50:46 +08:00
ar.Addresses = make([]metainfo.HostAddr, len(r.Addresses))
for i, a := range r.Addresses {
ar.Addresses[i] = metainfo.NewHostAddr(a.IP.String(), a.Port)
}
2020-06-07 13:43:15 +08:00
}
// ScrapeResponseResult is a commont Scrape response result.
type ScrapeResponseResult struct {
// Seeders is the number of active peers that have completed downloading.
Seeders uint32 `bencode:"complete"` // BEP 15, 48
// Leechers is the number of active peers that have not completed downloading.
Leechers uint32 `bencode:"incomplete"` // BEP 15, 48
// Completed is the total number of peers that have ever completed downloading.
Completed uint32 `bencode:"downloaded"` // BEP 15, 48
}
// EncodeTo encodes the response to buf.
func (r ScrapeResponseResult) EncodeTo(buf *bytes.Buffer) {
binary.Write(buf, binary.BigEndian, r.Seeders)
binary.Write(buf, binary.BigEndian, r.Completed)
binary.Write(buf, binary.BigEndian, r.Leechers)
}
// DecodeFrom decodes the response from b.
func (r *ScrapeResponseResult) DecodeFrom(b []byte) {
r.Seeders = binary.BigEndian.Uint32(b[:4])
r.Completed = binary.BigEndian.Uint32(b[4:8])
r.Leechers = binary.BigEndian.Uint32(b[8:12])
}
// ScrapeResponse is a commont Scrape response.
type ScrapeResponse map[metainfo.Hash]ScrapeResponseResult
// FromHTTPScrapeResponse sets itself from r.
func (sr ScrapeResponse) FromHTTPScrapeResponse(r httptracker.ScrapeResponse) {
for k, v := range r.Files {
sr[k] = ScrapeResponseResult{
Seeders: v.Complete,
Leechers: v.Incomplete,
Completed: v.Downloaded,
}
}
}
// FromUDPScrapeResponse sets itself from hs and r.
2023-03-01 22:50:46 +08:00
func (sr ScrapeResponse) FromUDPScrapeResponse(hs []metainfo.Hash, r []udptracker.ScrapeResponse) {
2020-06-07 13:43:15 +08:00
klen := len(hs)
if _len := len(r); _len < klen {
klen = _len
}
for i := 0; i < klen; i++ {
sr[hs[i]] = ScrapeResponseResult{
Seeders: r[i].Seeders,
Leechers: r[i].Leechers,
Completed: r[i].Completed,
}
}
}
// Client is the interface of BT tracker client.
type Client interface {
2020-06-12 21:03:02 +08:00
Announce(context.Context, AnnounceRequest) (AnnounceResponse, error)
Scrape(context.Context, []metainfo.Hash) (ScrapeResponse, error)
String() string
Close() error
2020-06-07 13:43:15 +08:00
}
// NewClient returns a new Client.
2023-03-01 22:50:46 +08:00
//
// If id is ZERO, use a random hash instead.
// If client is nil, use http.DefaultClient instead for the http tracker.
func NewClient(connURL string, id metainfo.Hash, client *http.Client) (c Client, err error) {
u, err := url.Parse(connURL)
if err != nil {
return
2020-06-18 22:39:22 +08:00
}
2023-03-01 22:50:46 +08:00
tclient := &tclient{url: connURL}
switch u.Scheme {
case "http", "https":
tclient.http = httptracker.NewClient(id, connURL, "")
tclient.http.Client = client
case "udp", "udp4", "udp6":
tclient.udp, err = udptracker.NewClientByDial(u.Scheme, u.Host, id)
if err != nil {
return
}
if p := u.RequestURI(); p != "" {
tclient.exts = []udptracker.Extension{udptracker.NewURLData([]byte(p))}
2020-06-07 13:43:15 +08:00
}
2023-03-01 22:50:46 +08:00
default:
err = fmt.Errorf("unknown url scheme '%s'", u.Scheme)
2020-06-07 13:43:15 +08:00
}
2023-03-01 22:50:46 +08:00
return tclient, nil
2020-06-07 13:43:15 +08:00
}
type tclient struct {
2020-06-12 21:03:02 +08:00
url string
http *httptracker.Client // BEP 3
udp *udptracker.Client // BEP 15
exts []udptracker.Extension // BEP 41
2020-06-07 13:43:15 +08:00
}
2020-06-12 21:03:02 +08:00
func (c *tclient) String() string { return c.url }
func (c *tclient) Close() error {
if c.http != nil {
return c.http.Close()
}
return c.udp.Close()
}
func (c *tclient) Announce(ctx context.Context, req AnnounceRequest) (resp AnnounceResponse, err error) {
2020-06-07 13:43:15 +08:00
if c.http != nil {
var r httptracker.AnnounceResponse
2020-06-12 21:03:02 +08:00
if r, err = c.http.Announce(ctx, req.ToHTTPAnnounceRequest()); err != nil {
2020-06-07 13:43:15 +08:00
return
} else if r.FailureReason != "" {
err = errors.New(r.FailureReason)
return
}
resp.FromHTTPAnnounceResponse(r)
return
}
r := req.ToUDPAnnounceRequest()
r.Exts = c.exts
2020-06-12 21:03:02 +08:00
rs, err := c.udp.Announce(ctx, r)
2020-06-07 13:43:15 +08:00
if err == nil {
resp.FromUDPAnnounceResponse(rs)
}
return
}
2020-06-12 21:03:02 +08:00
func (c *tclient) Scrape(ctx context.Context, hs []metainfo.Hash) (resp ScrapeResponse, err error) {
2020-06-07 13:43:15 +08:00
if c.http != nil {
var r httptracker.ScrapeResponse
2020-06-12 21:03:02 +08:00
if r, err = c.http.Scrape(ctx, hs); err != nil {
2020-06-07 13:43:15 +08:00
return
} else if r.FailureReason != "" {
err = errors.New(r.FailureReason)
return
}
resp = make(ScrapeResponse, len(r.Files))
resp.FromHTTPScrapeResponse(r)
return
}
2020-06-12 21:03:02 +08:00
r, err := c.udp.Scrape(ctx, hs)
2020-06-07 13:43:15 +08:00
if err == nil {
resp = make(ScrapeResponse, len(r))
resp.FromUDPScrapeResponse(hs, r)
}
return
}