291 lines
7.7 KiB
Go
291 lines
7.7 KiB
Go
package tracker
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/binary"
|
|
"io"
|
|
"net/http"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/veggiedefender/torrent-client/internal/torrentfile"
|
|
)
|
|
|
|
func TestParsePeers(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
raw := []byte{127, 0, 0, 1, 0x1A, 0xE1}
|
|
peers, err := parsePeers(raw)
|
|
if err != nil {
|
|
t.Fatalf("parsePeers returned error: %v", err)
|
|
}
|
|
if len(peers) != 1 {
|
|
t.Fatalf("unexpected peer count: got %d", len(peers))
|
|
}
|
|
if got, want := peers[0].IP.String(), "127.0.0.1"; got != want {
|
|
t.Fatalf("unexpected peer IP: got %s want %s", got, want)
|
|
}
|
|
if got, want := peers[0].Port, uint16(6881); got != want {
|
|
t.Fatalf("unexpected peer port: got %d want %d", got, want)
|
|
}
|
|
}
|
|
|
|
func TestParsePeersRejectsInvalidLength(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
_, err := parsePeers([]byte{1, 2, 3, 4, 5})
|
|
if err == nil {
|
|
t.Fatal("expected parsePeers to fail for invalid compact peer data")
|
|
}
|
|
}
|
|
|
|
func TestGetPeersBuildsAnnounceAndParsesResponse(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
var infoHash [20]byte
|
|
copy(infoHash[:], []byte("abcdefghijklmnopqrst"))
|
|
|
|
var peerID [20]byte
|
|
copy(peerID[:], []byte("-ZT0001-123456789012"))
|
|
|
|
tf := &torrentfile.TorrentFile{
|
|
Announce: "http://tracker.test/announce",
|
|
InfoHash: infoHash,
|
|
Length: 1000,
|
|
}
|
|
|
|
var seenRequest *http.Request
|
|
client := fakeClient{
|
|
do: func(req *http.Request) (*http.Response, error) {
|
|
seenRequest = req
|
|
payload := []byte("d8:intervali1800e5:peers6:\x7f\x00\x00\x01\x1a\xe1e")
|
|
return &http.Response{
|
|
StatusCode: http.StatusOK,
|
|
Body: io.NopCloser(bytes.NewReader(payload)),
|
|
}, nil
|
|
},
|
|
}
|
|
|
|
peers, err := getPeersWithClient(context.Background(), client, tf, AnnounceOptions{
|
|
PeerID: peerID,
|
|
Port: 6881,
|
|
Downloaded: 100,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("GetPeers returned error: %v", err)
|
|
}
|
|
|
|
if seenRequest == nil {
|
|
t.Fatal("expected tracker request to be sent")
|
|
}
|
|
|
|
q := seenRequest.URL.Query()
|
|
if got := q.Get("compact"); got != "1" {
|
|
t.Fatalf("unexpected compact value: got %q", got)
|
|
}
|
|
if got := q.Get("port"); got != "6881" {
|
|
t.Fatalf("unexpected port value: got %q", got)
|
|
}
|
|
if got := q.Get("left"); got != "900" {
|
|
t.Fatalf("unexpected left value: got %q", got)
|
|
}
|
|
if got := q.Get("numwant"); got != "50" {
|
|
t.Fatalf("unexpected numwant value: got %q", got)
|
|
}
|
|
if got := []byte(q.Get("info_hash")); !equalBytes(got, infoHash[:]) {
|
|
t.Fatalf("unexpected info_hash value: got %x want %x", got, infoHash)
|
|
}
|
|
if got := []byte(q.Get("peer_id")); !equalBytes(got, peerID[:]) {
|
|
t.Fatalf("unexpected peer_id value: got %x want %x", got, peerID)
|
|
}
|
|
|
|
if len(peers) != 1 {
|
|
t.Fatalf("unexpected peer count: got %d", len(peers))
|
|
}
|
|
if got, want := peers[0].Port, uint16(6881); got != want {
|
|
t.Fatalf("unexpected peer port: got %d want %d", got, want)
|
|
}
|
|
}
|
|
|
|
func TestBuildAnnounceURLEncodesBinaryValues(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
var infoHash [20]byte
|
|
copy(infoHash[:], []byte{0x00, 0xFF, 0x2F, 'A', 0x20})
|
|
|
|
var peerID [20]byte
|
|
copy(peerID[:], []byte("-ZT0001-\x00\xFFabc"))
|
|
|
|
tf := &torrentfile.TorrentFile{
|
|
Announce: "http://tracker.test/announce?token=abc",
|
|
InfoHash: infoHash,
|
|
Length: 10,
|
|
}
|
|
|
|
announceURL, err := buildAnnounceURL(tf, AnnounceOptions{
|
|
PeerID: peerID,
|
|
Port: 6881,
|
|
NumWant: 42,
|
|
Uploaded: -5,
|
|
Downloaded: -7,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("buildAnnounceURL returned error: %v", err)
|
|
}
|
|
|
|
if !strings.Contains(announceURL, "token=abc") {
|
|
t.Fatalf("announce URL did not preserve existing query params: %s", announceURL)
|
|
}
|
|
if !strings.Contains(announceURL, "info_hash=%00%FF%2FA%20") {
|
|
t.Fatalf("announce URL did not encode binary info hash correctly: %s", announceURL)
|
|
}
|
|
if !strings.Contains(announceURL, "peer_id=-ZT0001-%00%FFabc") {
|
|
t.Fatalf("announce URL did not encode binary peer ID correctly: %s", announceURL)
|
|
}
|
|
if !strings.Contains(announceURL, "uploaded=0") {
|
|
t.Fatalf("announce URL did not clamp uploaded value: %s", announceURL)
|
|
}
|
|
if !strings.Contains(announceURL, "downloaded=0") {
|
|
t.Fatalf("announce URL did not clamp downloaded value: %s", announceURL)
|
|
}
|
|
if !strings.Contains(announceURL, "left=10") {
|
|
t.Fatalf("announce URL did not compute left bytes correctly: %s", announceURL)
|
|
}
|
|
}
|
|
|
|
func TestBuildAnnounceURLRejectsUnsupportedScheme(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
_, err := buildAnnounceURL(&torrentfile.TorrentFile{
|
|
Announce: "udp://tracker.test:6969/announce",
|
|
Length: 10,
|
|
}, AnnounceOptions{})
|
|
if err == nil {
|
|
t.Fatal("expected buildAnnounceURL to reject unsupported tracker scheme")
|
|
}
|
|
}
|
|
|
|
func TestGetPeersReturnsTrackerFailure(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tf := &torrentfile.TorrentFile{
|
|
Announce: "http://tracker.test/announce",
|
|
Length: 10,
|
|
}
|
|
|
|
client := fakeClient{
|
|
do: func(req *http.Request) (*http.Response, error) {
|
|
payload := []byte("d14:failure reason11:bad requeste")
|
|
return &http.Response{
|
|
StatusCode: http.StatusOK,
|
|
Body: io.NopCloser(bytes.NewReader(payload)),
|
|
}, nil
|
|
},
|
|
}
|
|
|
|
_, err := getPeersWithClient(context.Background(), client, tf, AnnounceOptions{})
|
|
if err == nil {
|
|
t.Fatal("expected getPeersWithClient to return tracker failure error")
|
|
}
|
|
}
|
|
|
|
func TestParseUDPConnectResponse(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
transactionID := uint32(42)
|
|
connectionID := uint64(0x0102030405060708)
|
|
|
|
payload := make([]byte, 16)
|
|
binary.BigEndian.PutUint32(payload[0:4], 0)
|
|
binary.BigEndian.PutUint32(payload[4:8], transactionID)
|
|
binary.BigEndian.PutUint64(payload[8:16], connectionID)
|
|
|
|
got, err := parseUDPConnectResponse(payload, transactionID)
|
|
if err != nil {
|
|
t.Fatalf("parseUDPConnectResponse returned error: %v", err)
|
|
}
|
|
if got != connectionID {
|
|
t.Fatalf("unexpected connection ID: got %x want %x", got, connectionID)
|
|
}
|
|
}
|
|
|
|
func TestParseUDPAnnounceResponse(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
transactionID := uint32(99)
|
|
payload := make([]byte, 20+6)
|
|
binary.BigEndian.PutUint32(payload[0:4], 1)
|
|
binary.BigEndian.PutUint32(payload[4:8], transactionID)
|
|
binary.BigEndian.PutUint32(payload[8:12], 1800)
|
|
binary.BigEndian.PutUint32(payload[12:16], 10)
|
|
binary.BigEndian.PutUint32(payload[16:20], 20)
|
|
copy(payload[20:], []byte{127, 0, 0, 1, 0x1A, 0xE1})
|
|
|
|
peers, err := parseUDPAnnounceResponse(payload, transactionID)
|
|
if err != nil {
|
|
t.Fatalf("parseUDPAnnounceResponse returned error: %v", err)
|
|
}
|
|
if len(peers) != 1 {
|
|
t.Fatalf("unexpected peer count: got %d", len(peers))
|
|
}
|
|
if got, want := peers[0].Port, uint16(6881); got != want {
|
|
t.Fatalf("unexpected peer port: got %d want %d", got, want)
|
|
}
|
|
}
|
|
|
|
func TestBuildUDPAnnounceRequest(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tf := &torrentfile.TorrentFile{
|
|
Length: 100,
|
|
}
|
|
copy(tf.InfoHash[:], []byte("abcdefghijklmnopqrst"))
|
|
|
|
var peerID [20]byte
|
|
copy(peerID[:], []byte("-ZT0001-123456789012"))
|
|
|
|
req, err := buildUDPAnnounceRequest(0x0102030405060708, 42, tf, AnnounceOptions{
|
|
PeerID: peerID,
|
|
Port: 6881,
|
|
Uploaded: -1,
|
|
Downloaded: 20,
|
|
NumWant: 50,
|
|
}, 0xAABBCCDD)
|
|
if err != nil {
|
|
t.Fatalf("buildUDPAnnounceRequest returned error: %v", err)
|
|
}
|
|
|
|
if len(req) != 98 {
|
|
t.Fatalf("unexpected announce request length: got %d", len(req))
|
|
}
|
|
if got, want := binary.BigEndian.Uint32(req[8:12]), uint32(1); got != want {
|
|
t.Fatalf("unexpected action: got %d want %d", got, want)
|
|
}
|
|
if got, want := binary.BigEndian.Uint64(req[64:72]), uint64(80); got != want {
|
|
t.Fatalf("unexpected left bytes: got %d want %d", got, want)
|
|
}
|
|
if got, want := binary.BigEndian.Uint64(req[72:80]), uint64(0); got != want {
|
|
t.Fatalf("unexpected uploaded bytes: got %d want %d", got, want)
|
|
}
|
|
}
|
|
|
|
type fakeClient struct {
|
|
do func(req *http.Request) (*http.Response, error)
|
|
}
|
|
|
|
func (c fakeClient) Do(req *http.Request) (*http.Response, error) {
|
|
return c.do(req)
|
|
}
|
|
|
|
func equalBytes(a, b []byte) bool {
|
|
if len(a) != len(b) {
|
|
return false
|
|
}
|
|
for i := range a {
|
|
if a[i] != b[i] {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|