commit 941fba7a64a92c4259dc555121e84e076ed2d7f2 Author: Jesse Li Date: Sat Dec 21 23:14:33 2019 -0500 Parse torrent and fetch peers diff --git a/main.go b/main.go new file mode 100644 index 0000000..350d0e6 --- /dev/null +++ b/main.go @@ -0,0 +1,22 @@ +package main + +import ( + "log" + "os" + + "github.com/veggiedefender/torrent-client/torrent" +) + +func main() { + file, err := os.Open(os.Args[1]) + if err != nil { + log.Fatal(err) + } + defer file.Close() + + to, err := torrent.Open(file) + if err != nil { + log.Fatal(err) + } + to.Download() +} diff --git a/torrent/torrent.go b/torrent/torrent.go new file mode 100644 index 0000000..3c4546a --- /dev/null +++ b/torrent/torrent.go @@ -0,0 +1,65 @@ +package torrent + +import ( + "bytes" + "crypto/rand" + "crypto/sha1" + "fmt" + "io" + + "github.com/jackpal/bencode-go" +) + +const port = 6881 + +// Info info +type Info struct { + Pieces string `bencode:"pieces"` + PieceLength int `bencode:"piece length"` + Length int `bencode:"length"` + Name string `bencode:"name"` +} + +// Torrent torrent +type Torrent struct { + Announce string `bencode:"announce"` + Info Info `bencode:"info"` +} + +// Download downloads a torrent +func (to *Torrent) Download() error { + peerID := make([]byte, 20) + _, err := rand.Read(peerID) + if err != nil { + return err + } + + tracker := Tracker{ + PeerID: peerID, + Torrent: to, + Port: port, + } + peers, err := tracker.getPeers() + fmt.Println(peers) + return nil +} + +// Open parses a torrent file +func Open(r io.Reader) (*Torrent, error) { + to := Torrent{} + err := bencode.Unmarshal(r, &to) + if err != nil { + return nil, err + } + return &to, nil +} + +func (i *Info) hash() ([]byte, error) { + var buf bytes.Buffer + err := bencode.Marshal(&buf, *i) + if err != nil { + return nil, err + } + h := sha1.Sum(buf.Bytes()) + return h[:], nil +} diff --git a/torrent/tracker.go b/torrent/tracker.go new file mode 100644 index 0000000..1159f19 --- /dev/null +++ b/torrent/tracker.go @@ -0,0 +1,95 @@ +package torrent + +import ( + "encoding/binary" + "errors" + "net" + "net/http" + "net/url" + "strconv" + + "github.com/jackpal/bencode-go" +) + +// Tracker tracker +type Tracker struct { + PeerID []byte + Torrent *Torrent + Port uint16 +} + +// TrackerResponse t +type TrackerResponse struct { + Interval int `bencode:"interval"` + Peers string `bencode:"port"` +} + +// Peer p +type Peer struct { + IP net.IP + Port uint16 +} + +func parsePeers(peersBin string) ([]Peer, error) { + peerSize := 6 // 4 for IP, 2 for port + numPeers := len(peersBin) / peerSize + if len(peersBin)%peerSize != 0 { + err := errors.New("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 +} + +func (tr *Tracker) buildTrackerURL() (string, error) { + base, err := url.Parse(tr.Torrent.Announce) + if err != nil { + return "", err + } + infoHash, err := tr.Torrent.Info.hash() + if err != nil { + return "", err + } + params := url.Values{ + "info_hash": []string{string(infoHash)}, + "peer_id": []string{string(tr.PeerID)}, + "port": []string{strconv.Itoa(int(tr.Port))}, + "uploaded": []string{"0"}, + "downloaded": []string{"0"}, + "compact": []string{"1"}, + "left": []string{strconv.Itoa(tr.Torrent.Info.Length)}, + } + base.RawQuery = params.Encode() + return base.String(), nil +} + +func (tr *Tracker) getPeers() ([]Peer, error) { + url, err := tr.buildTrackerURL() + if err != nil { + return nil, err + } + + resp, err := http.Get(url) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + trackerResp := TrackerResponse{} + err = bencode.Unmarshal(resp.Body, &trackerResp) + if err != nil { + return nil, err + } + + peers, err := parsePeers(trackerResp.Peers) + if err != nil { + return nil, err + } + + return peers, nil +}