aboutsummaryrefslogtreecommitdiff
path: root/cmd
diff options
context:
space:
mode:
Diffstat (limited to 'cmd')
-rw-r--r--cmd/mybittorrent/client.go136
-rw-r--r--cmd/mybittorrent/file.go13
-rw-r--r--cmd/mybittorrent/main.go41
-rw-r--r--cmd/mybittorrent/meta.go37
4 files changed, 200 insertions, 27 deletions
diff --git a/cmd/mybittorrent/client.go b/cmd/mybittorrent/client.go
new file mode 100644
index 0000000..f3988b6
--- /dev/null
+++ b/cmd/mybittorrent/client.go
@@ -0,0 +1,136 @@
+package main
+
+import (
+ "encoding/binary"
+ "fmt"
+ "log/slog"
+ "net/http"
+ "net/url"
+ "os"
+ "strconv"
+
+ bencode "github.com/jackpal/bencode-go"
+)
+
+type Client struct {
+ PeerId string
+ Port int
+ Torrents map[string]ClientTorrent
+}
+
+type ClientTorrent struct {
+ Meta Meta
+ Uploaded int
+ Downloaded int
+ Left int
+}
+
+type PeerResponse struct {
+ Interval int
+ Peers []string
+}
+
+func NewClient(peerId string, port int) *Client {
+ return &Client{
+ PeerId: peerId,
+ Port: port,
+ Torrents: make(map[string]ClientTorrent),
+ }
+}
+
+func (c *Client) AddTorrentFile(filename string) (ClientTorrent, error) {
+ f, err := os.Open(filename)
+ defer func(f *os.File) {
+ err := f.Close()
+ if err != nil {
+ slog.Error("Failed to close file", "filename", filename)
+ }
+ }(f)
+
+ if err != nil {
+ fmt.Println("Failed to open file: ", os.Args[2])
+ }
+
+ var meta Meta
+
+ if err = bencode.Unmarshal(f, &meta); err != nil {
+ return ClientTorrent{}, err
+ }
+
+ t := ClientTorrent{
+ Meta: meta,
+ Uploaded: 0,
+ Downloaded: 0,
+ Left: meta.Info.Length,
+ }
+
+ c.Torrents[filename] = t
+
+ return t, nil
+}
+
+func (ct ClientTorrent) getUrl(c Client) (string, error) {
+ u, err := url.Parse(ct.Meta.Announce)
+ if err != nil {
+ return "", err
+ }
+
+ q := u.Query()
+
+ infoHash, err := ct.Meta.InfoHash()
+ if err != nil {
+ return "", err
+ }
+
+ q.Set("info_hash", string(infoHash))
+ q.Set("peer_id", c.PeerId)
+ q.Set("port", strconv.Itoa(c.Port))
+ q.Set("uploaded", strconv.Itoa(ct.Uploaded))
+ q.Set("downloaded", strconv.Itoa(ct.Downloaded))
+ q.Set("left", strconv.Itoa(ct.Left))
+ q.Set("compact", "1")
+ u.RawQuery = q.Encode()
+
+ return u.String(), nil
+}
+
+func DecodePeers(b []byte) []string {
+ val := make([]string, 0, len(b)/6)
+ for i := 0; i < len(b); i += 6 {
+ val = append(val, fmt.Sprintf("%d.%d.%d.%d:%d", b[i], b[i+1], b[i+2], b[i+3], binary.BigEndian.Uint16(b[i+4:i+6])))
+ }
+
+ return val
+}
+
+func (c *Client) GetPeers(filename string) (PeerResponse, error) {
+ ct, ok := c.Torrents[filename]
+ if !ok {
+ return PeerResponse{}, fmt.Errorf("missing file from client: %s", filename)
+ }
+
+ u, err := ct.getUrl(*c)
+ if err != nil {
+ return PeerResponse{}, err
+ }
+
+ getResp, err := http.Get(u)
+ if err != nil {
+ return PeerResponse{}, err
+ }
+
+ var resp struct {
+ Interval int
+ Peers string
+ }
+
+ err = bencode.Unmarshal(getResp.Body, &resp)
+ if err != nil {
+ return PeerResponse{}, err
+ }
+
+ return PeerResponse{
+ Interval: resp.Interval,
+ Peers: DecodePeers([]byte(resp.Peers)),
+ }, err
+}
diff --git a/cmd/mybittorrent/file.go b/cmd/mybittorrent/file.go
deleted file mode 100644
index 498ea3f..0000000
--- a/cmd/mybittorrent/file.go
+++ /dev/null
@@ -1,13 +0,0 @@
-package main
-
-type Meta struct {
- Announce string `bencode:"announce"`
- Info FileInfo `bencode:"info"`
-}
-
-type FileInfo struct {
- Length int `bencode:"length"`
- Name string `bencode:"name"`
- PieceLength int `bencode:"piece length"`
- Pieces string `bencode:"pieces"`
-}
diff --git a/cmd/mybittorrent/main.go b/cmd/mybittorrent/main.go
index 5eecb20..c460c35 100644
--- a/cmd/mybittorrent/main.go
+++ b/cmd/mybittorrent/main.go
@@ -1,7 +1,6 @@
package main
import (
- "crypto/sha1"
"encoding/json"
"fmt"
"os"
@@ -26,31 +25,45 @@ func main() {
fmt.Println(string(jsonOutput))
case "info":
- f, err := os.Open(os.Args[2])
+ c := NewClient("00112233445566778899", 6881)
+ tf, err := c.AddTorrentFile(os.Args[2])
if err != nil {
- fmt.Println("Failed to open file: ", os.Args[2])
+ panic(err)
}
- var meta Meta
- if err = bencode.Unmarshal(f, &meta); err != nil {
+ fmt.Printf("Tracker URL: %s\n", tf.Meta.Announce)
+ fmt.Printf("Length: %d\n", tf.Meta.Info.Length)
+
+ infoHash, err := tf.Meta.InfoHash()
+ if err != nil {
panic(err)
}
- fmt.Printf("Tracker URL: %s\n", meta.Announce)
- fmt.Printf("Length: %d\n", meta.Info.Length)
+ fmt.Printf("Info Hash: %x", infoHash)
+
+ fmt.Printf("Piece Length: %d\n", tf.Meta.Info.PieceLength)
+ fmt.Println("Piece Hashes:")
+
+ for _, h := range tf.Meta.PieceHashes() {
+ fmt.Printf("%x\n", h)
+ }
- sha := sha1.New()
- if err = bencode.Marshal(sha, meta.Info); err != nil {
+ case "peers":
+ c := NewClient("00112233445566778899", 6881)
+
+ _, err := c.AddTorrentFile(os.Args[2])
+ if err != nil {
panic(err)
}
- fmt.Printf("Info Hash: %x", sha.Sum(nil))
+ pr, err := c.GetPeers(os.Args[2])
- fmt.Printf("Piece Length: %d\n", meta.Info.PieceLength)
- fmt.Println("Piece Hashes:")
+ if err != nil {
+ panic(err)
+ }
- for i := 0; i < len(meta.Info.Pieces); i += 20 {
- fmt.Printf("%x\n", meta.Info.Pieces[i:i+20])
+ for _, peer := range pr.Peers {
+ fmt.Println(peer)
}
default:
diff --git a/cmd/mybittorrent/meta.go b/cmd/mybittorrent/meta.go
new file mode 100644
index 0000000..34369c4
--- /dev/null
+++ b/cmd/mybittorrent/meta.go
@@ -0,0 +1,37 @@
+package main
+
+import (
+ "crypto/sha1"
+
+ bencode "github.com/jackpal/bencode-go"
+)
+
+type Meta struct {
+ Announce string `bencode:"announce"`
+ Info FileInfo `bencode:"info"`
+}
+
+type FileInfo struct {
+ Length int `bencode:"length"`
+ Name string `bencode:"name"`
+ PieceLength int `bencode:"piece length"`
+ Pieces string `bencode:"pieces"`
+}
+
+func (m Meta) InfoHash() ([]byte, error) {
+ sha := sha1.New()
+ if err := bencode.Marshal(sha, m.Info); err != nil {
+ return nil, err
+ }
+
+ return sha.Sum(nil), nil
+}
+
+func (m Meta) PieceHashes() []string {
+ hashes := make([]string, 0, len(m.Info.Pieces)/20)
+ for i := 0; i < len(m.Info.Pieces); i += 20 {
+ hashes = append(hashes, m.Info.Pieces[i:i+20])
+ }
+
+ return hashes
+}