1
0
mirror of https://github.com/veggiedefender/torrent-client.git synced 2025-11-06 09:29:16 +02:00

Break p2p.Peer and parsePeers into its own package

This commit is contained in:
Jesse Li
2020-01-02 10:39:35 -05:00
parent 25810161eb
commit 73a9a5216b
6 changed files with 79 additions and 66 deletions

View File

@@ -8,6 +8,7 @@ import (
"time" "time"
"github.com/veggiedefender/torrent-client/bitfield" "github.com/veggiedefender/torrent-client/bitfield"
"github.com/veggiedefender/torrent-client/peers"
"github.com/veggiedefender/torrent-client/message" "github.com/veggiedefender/torrent-client/message"
@@ -15,7 +16,7 @@ import (
) )
type client struct { type client struct {
peer Peer peer peers.Peer
infoHash [20]byte infoHash [20]byte
peerID [20]byte peerID [20]byte
conn net.Conn conn net.Conn
@@ -57,7 +58,7 @@ func recvBitfield(conn net.Conn, r *bufio.Reader) (bitfield.Bitfield, error) {
return msg.Payload, nil return msg.Payload, nil
} }
func newClient(peer Peer, peerID, infoHash [20]byte) (*client, error) { func newClient(peer peers.Peer, peerID, infoHash [20]byte) (*client, error) {
hostPort := net.JoinHostPort(peer.IP.String(), strconv.Itoa(int(peer.Port))) hostPort := net.JoinHostPort(peer.IP.String(), strconv.Itoa(int(peer.Port)))
conn, err := net.DialTimeout("tcp", hostPort, 3*time.Second) conn, err := net.DialTimeout("tcp", hostPort, 3*time.Second)
if err != nil { if err != nil {

View File

@@ -5,11 +5,11 @@ import (
"crypto/sha1" "crypto/sha1"
"fmt" "fmt"
"log" "log"
"net"
"runtime" "runtime"
"time" "time"
"github.com/veggiedefender/torrent-client/message" "github.com/veggiedefender/torrent-client/message"
"github.com/veggiedefender/torrent-client/peers"
) )
// MaxBlockSize is the largest number of bytes a request can ask for // MaxBlockSize is the largest number of bytes a request can ask for
@@ -18,15 +18,9 @@ const MaxBlockSize = 16384
// MaxBacklog is the number of unfulfilled requests a client can have in its pipeline // MaxBacklog is the number of unfulfilled requests a client can have in its pipeline
const MaxBacklog = 5 const MaxBacklog = 5
// Peer encodes connection information for a peer
type Peer struct {
IP net.IP
Port uint16
}
// Torrent holds data required to download a torrent from a list of peers // Torrent holds data required to download a torrent from a list of peers
type Torrent struct { type Torrent struct {
Peers []Peer Peers []peers.Peer
PeerID [20]byte PeerID [20]byte
InfoHash [20]byte InfoHash [20]byte
PieceHashes [][20]byte PieceHashes [][20]byte
@@ -146,7 +140,7 @@ func checkIntegrity(pw *pieceWork, buf []byte) error {
return nil return nil
} }
func (t *Torrent) startDownloadWorker(peer Peer, workQueue chan *pieceWork, results chan *pieceResult) { func (t *Torrent) startDownloadWorker(peer peers.Peer, workQueue chan *pieceWork, results chan *pieceResult) {
c, err := newClient(peer, t.PeerID, t.InfoHash) c, err := newClient(peer, t.PeerID, t.InfoHash)
if err != nil { if err != nil {
log.Printf("Could not handshake with %s. Disconnecting\n", peer.IP) log.Printf("Could not handshake with %s. Disconnecting\n", peer.IP)

30
peers/peers.go Normal file
View File

@@ -0,0 +1,30 @@
package peers
import (
"encoding/binary"
"fmt"
"net"
)
// Peer encodes connection information for a peer
type Peer struct {
IP net.IP
Port uint16
}
// Unmarshal parses peer IP addresses and ports from a buffer
func Unmarshal(peersBin []byte) ([]Peer, error) {
const peerSize = 6 // 4 for IP, 2 for port
numPeers := len(peersBin) / peerSize
if len(peersBin)%peerSize != 0 {
err := fmt.Errorf("Received malformed peers")
return nil, err
}
peers := make([]Peer, numPeers)
for i := 0; i < numPeers; i++ {
offset := i * peerSize
peers[i].IP = net.IP(peersBin[offset : offset+4])
peers[i].Port = binary.BigEndian.Uint16([]byte(peersBin[offset+4 : offset+6]))
}
return peers, nil
}

39
peers/peers_test.go Normal file
View File

@@ -0,0 +1,39 @@
package peers
import (
"net"
"testing"
"github.com/stretchr/testify/assert"
)
func TestUnmarshal(t *testing.T) {
tests := map[string]struct {
input string
output []Peer
fails bool
}{
"correctly parses peers": {
input: string([]byte{127, 0, 0, 1, 0x00, 0x50, 1, 1, 1, 1, 0x01, 0xbb}),
output: []Peer{
{IP: net.IP{127, 0, 0, 1}, Port: 80},
{IP: net.IP{1, 1, 1, 1}, Port: 443},
},
},
"not enough bytes in peers": {
input: string([]byte{127, 0, 0, 1, 0x00}),
output: nil,
fails: true,
},
}
for _, test := range tests {
peers, err := Unmarshal([]byte(test.input))
if test.fails {
assert.NotNil(t, err)
} else {
assert.Nil(t, err)
}
assert.Equal(t, test.output, peers)
}
}

View File

@@ -1,16 +1,14 @@
package torrentfile package torrentfile
import ( import (
"encoding/binary"
"fmt"
"net"
"net/http" "net/http"
"net/url" "net/url"
"strconv" "strconv"
"time" "time"
"github.com/veggiedefender/torrent-client/peers"
"github.com/jackpal/bencode-go" "github.com/jackpal/bencode-go"
"github.com/veggiedefender/torrent-client/p2p"
) )
type bencodeTrackerResp struct { type bencodeTrackerResp struct {
@@ -18,22 +16,6 @@ type bencodeTrackerResp struct {
Peers string `bencode:"port"` Peers string `bencode:"port"`
} }
func parsePeers(peersBin string) ([]p2p.Peer, error) {
const peerSize = 6 // 4 for IP, 2 for port
numPeers := len(peersBin) / peerSize
if len(peersBin)%peerSize != 0 {
err := fmt.Errorf("Received malformed peers")
return nil, err
}
peers := make([]p2p.Peer, numPeers)
for i := 0; i < numPeers; i++ {
offset := i * peerSize
peers[i].IP = net.IP(peersBin[offset : offset+4])
peers[i].Port = binary.BigEndian.Uint16([]byte(peersBin[offset+4 : offset+6]))
}
return peers, nil
}
func (t *TorrentFile) buildTrackerURL(peerID [20]byte, port uint16) (string, error) { func (t *TorrentFile) buildTrackerURL(peerID [20]byte, port uint16) (string, error) {
base, err := url.Parse(t.Announce) base, err := url.Parse(t.Announce)
if err != nil { if err != nil {
@@ -52,7 +34,7 @@ func (t *TorrentFile) buildTrackerURL(peerID [20]byte, port uint16) (string, err
return base.String(), nil return base.String(), nil
} }
func (t *TorrentFile) requestPeers(peerID [20]byte, port uint16) ([]p2p.Peer, error) { func (t *TorrentFile) requestPeers(peerID [20]byte, port uint16) ([]peers.Peer, error) {
url, err := t.buildTrackerURL(peerID, port) url, err := t.buildTrackerURL(peerID, port)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -71,7 +53,7 @@ func (t *TorrentFile) requestPeers(peerID [20]byte, port uint16) ([]p2p.Peer, er
return nil, err return nil, err
} }
peers, err := parsePeers(trackerResp.Peers) peers, err := peers.Unmarshal([]byte(trackerResp.Peers))
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@@ -1,11 +1,9 @@
package torrentfile package torrentfile
import ( import (
"net"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/veggiedefender/torrent-client/p2p"
) )
func TestBuildTrackerURL(t *testing.T) { func TestBuildTrackerURL(t *testing.T) {
@@ -27,34 +25,3 @@ func TestBuildTrackerURL(t *testing.T) {
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, url, expected) assert.Equal(t, url, expected)
} }
func TestParsePeers(t *testing.T) {
tests := map[string]struct {
input string
output []p2p.Peer
fails bool
}{
"correctly parses peers": {
input: string([]byte{127, 0, 0, 1, 0x00, 0x50, 1, 1, 1, 1, 0x01, 0xbb}),
output: []p2p.Peer{
{IP: net.IP{127, 0, 0, 1}, Port: 80},
{IP: net.IP{1, 1, 1, 1}, Port: 443},
},
},
"not enough bytes in peers": {
input: string([]byte{127, 0, 0, 1, 0x00}),
output: nil,
fails: true,
},
}
for _, test := range tests {
peers, err := parsePeers(test.input)
if test.fails {
assert.NotNil(t, err)
} else {
assert.Nil(t, err)
}
assert.Equal(t, test.output, peers)
}
}