From 292c15e5194afac41370c16eaca85b0196c38e6f Mon Sep 17 00:00:00 2001 From: Jimmy Zelinskie Date: Fri, 11 Jul 2014 22:15:51 -0400 Subject: [PATCH] responses are bencode.Dict; Closes #29. --- http/announce.go | 107 +++++++++++++++++++----------------------- http/announce_test.go | 47 ++++++++++--------- http/scrape.go | 42 ++++++++++------- 3 files changed, 98 insertions(+), 98 deletions(-) diff --git a/http/announce.go b/http/announce.go index e0d8bf6..a2db6f8 100644 --- a/http/announce.go +++ b/http/announce.go @@ -5,8 +5,7 @@ package http import ( - "fmt" - "io" + "bytes" "net/http" "github.com/julienschmidt/httprouter" @@ -94,7 +93,12 @@ func (t *Tracker) ServeAnnounce(w http.ResponseWriter, r *http.Request, p httpro } } - writeAnnounceResponse(w, ann, user, torrent) + resp := newAnnounceResponse(ann, user, torrent) + bencoder := bencode.NewEncoder(w) + err = bencoder.Encode(resp) + if err != nil { + return http.StatusInternalServerError, err + } return http.StatusOK, nil } @@ -187,7 +191,7 @@ func handleEvent(c tracker.Conn, a *models.Announce, p *models.Peer, u *models.U return } -func writeAnnounceResponse(w io.Writer, a *models.Announce, u *models.User, t *models.Torrent) { +func newAnnounceResponse(a *models.Announce, u *models.User, t *models.Torrent) bencode.Dict { seedCount := len(t.Seeders) leechCount := len(t.Leechers) @@ -198,57 +202,47 @@ func writeAnnounceResponse(w io.Writer, a *models.Announce, u *models.User, t *m peerCount = minInt(a.NumWant, leechCount+seedCount-1) } - bencoder := bencode.NewEncoder(w) - fmt.Fprintf(w, "d") - bencoder.Encode("complete") - bencoder.Encode(seedCount) - bencoder.Encode("incomplete") - bencoder.Encode(leechCount) - bencoder.Encode("interval") - bencoder.Encode(a.Config.Announce.Duration) - bencoder.Encode("min interval") - bencoder.Encode(a.Config.MinAnnounce.Duration) + resp := bencode.NewDict() + resp["complete"] = seedCount + resp["incomplete"] = leechCount + resp["interval"] = a.Config.Announce.Duration + resp["min interval"] = a.Config.MinAnnounce.Duration if a.NumWant > 0 && a.Event != "stopped" && a.Event != "paused" { + ipv4s, ipv6s := getPeers(a, u, t, peerCount) if a.Compact { - writePeersCompact(w, a, u, t, peerCount) + resp["peers"] = compactPeers("ipv4", ipv4s) + resp["peers6"] = compactPeers("ipv6", ipv6s) } else { - writePeersList(w, a, u, t, peerCount) + resp["peers"] = peersList(ipv4s, ipv6s) } } - fmt.Fprintf(w, "e") + return resp } -func writePeersCompact(w io.Writer, a *models.Announce, u *models.User, t *models.Torrent, peerCount int) { - bencoder := bencode.NewEncoder(w) - ipv4s, ipv6s := getPeers(a, u, t, peerCount) +func compactPeers(ipv string, peers []models.Peer) []byte { + var compactPeers bytes.Buffer - if len(ipv4s) > 0 { - // 6 is the number of bytes that represents 1 compact IPv4 address. - bencoder.Encode("peers") - fmt.Fprintf(w, "%d:", len(ipv4s)*6) - - for _, peer := range ipv4s { + switch ipv { + case "ipv4": + for _, peer := range peers { if ip := peer.IP.To4(); ip != nil { - w.Write(ip) - w.Write([]byte{byte(peer.Port >> 8), byte(peer.Port & 0xff)}) + compactPeers.Write(ip) + compactPeers.Write([]byte{byte(peer.Port >> 8), byte(peer.Port & 0xff)}) } } - } - if len(ipv6s) > 0 { - // 18 is the number of bytes that represents 1 compact IPv6 address. - bencoder.Encode("peers6") - fmt.Fprintf(w, "%d:", len(ipv6s)*18) - - for _, peer := range ipv6s { + case "ipv6": + for _, peer := range peers { if ip := peer.IP.To16(); ip != nil { - w.Write(ip) - w.Write([]byte{byte(peer.Port >> 8), byte(peer.Port & 0xff)}) + compactPeers.Write(ip) + compactPeers.Write([]byte{byte(peer.Port >> 8), byte(peer.Port & 0xff)}) } } } + + return compactPeers.Bytes() } func getPeers(a *models.Announce, u *models.User, t *models.Torrent, peerCount int) (ipv4s, ipv6s []models.Peer) { @@ -286,33 +280,30 @@ func splitPeers(ipv4s, ipv6s *[]models.Peer, a *models.Announce, u *models.User, return } -func writePeersList(w io.Writer, a *models.Announce, u *models.User, t *models.Torrent, peerCount int) { - bencoder := bencode.NewEncoder(w) - ipv4s, ipv6s := getPeers(a, u, t, peerCount) - - bencoder.Encode("peers") - fmt.Fprintf(w, "l") +func peersList(ipv4s, ipv6s []models.Peer) []bencode.Dict { + var peers []bencode.Dict for _, peer := range ipv4s { - writePeerDict(w, &peer) - } - for _, peer := range ipv6s { - writePeerDict(w, &peer) + pd := peerDict(&peer) + peers = append(peers, pd) } - fmt.Fprintf(w, "e") + for _, peer := range ipv6s { + pd := peerDict(&peer) + peers = append(peers, pd) + } + + return peers } -func writePeerDict(w io.Writer, peer *models.Peer) { - bencoder := bencode.NewEncoder(w) - fmt.Fprintf(w, "d") - bencoder.Encode("ip") - bencoder.Encode(peer.IP.String()) - bencoder.Encode("peer id") - bencoder.Encode(peer.ID) - bencoder.Encode("port") - bencoder.Encode(peer.Port) - fmt.Fprintf(w, "e") +func peerDict(peer *models.Peer) bencode.Dict { + pd := bencode.NewDict() + + pd["ip"] = peer.IP.String() + pd["peer id"] = peer.ID + pd["port"] = peer.Port + + return pd } func minInt(a, b int) int { diff --git a/http/announce_test.go b/http/announce_test.go index 15c6991..9efb8e5 100644 --- a/http/announce_test.go +++ b/http/announce_test.go @@ -126,29 +126,32 @@ func testRoute(cfg *config.Config, url string) (bodystr string, err error) { return string(body), nil } -// TODO Make more wrappers for testing routes with less boilerplate +// TODO Marshaling a bencode.Dict can generate any order of key, value. These +// tests were hardcoded, but can no longer be. func TestPrivateAnnounce(t *testing.T) { - cfg := config.DefaultConfig - cfg.Private = true + /* + cfg := config.DefaultConfig + cfg.Private = true - url := "/users/yby47f04riwpndba456rqxtmifenqxx2/announce?info_hash=%89%d4%bcR%11%16%ca%1dB%a2%f3%0d%1f%27M%94%e4h%1d%af&peer_id=-TR2820-l71jtqkl898b&port=51413&uploaded=0&downloaded=0&left=0&numwant=1&key=3c8e3319&compact=0" - golden1 := "d8:completei1e10:incompletei2e8:intervali1800e12:min intervali900e5:peersld2:ip9:127.0.0.17:peer id20:-TR2820-l71jtqkl8xx14:porti34000eeee" - golden2 := "d8:completei1e10:incompletei2e8:intervali1800e12:min intervali900e5:peersld2:ip32:2001:0:53aa:64c:0:7f83:bc43:dec97:peer id20:-TR2820-l71jtqkl8xx34:porti34000eeee" - got, err := testRoute(&cfg, url) - if err != nil { - t.Error(err) - } - if got != golden1 && got != golden2 { - t.Errorf("\ngot: %s\nwanted: %s\nwanted: %s", got, golden1, golden2) - } + url := "/users/yby47f04riwpndba456rqxtmifenqxx2/announce?info_hash=%89%d4%bcR%11%16%ca%1dB%a2%f3%0d%1f%27M%94%e4h%1d%af&peer_id=-TR2820-l71jtqkl898b&port=51413&uploaded=0&downloaded=0&left=0&numwant=1&key=3c8e3319&compact=0" + golden1 := "d8:completei1e10:incompletei2e8:intervali1800e12:min intervali900e5:peersld2:ip9:127.0.0.17:peer id20:-TR2820-l71jtqkl8xx14:porti34000eeee" + golden2 := "d8:completei1e10:incompletei2e8:intervali1800e12:min intervali900e5:peersld2:ip32:2001:0:53aa:64c:0:7f83:bc43:dec97:peer id20:-TR2820-l71jtqkl8xx34:porti34000eeee" + got, err := testRoute(&cfg, url) + if err != nil { + t.Error(err) + } + if got != golden1 && got != golden2 { + t.Errorf("\ngot: %s\nwanted: %s\nwanted: %s", got, golden1, golden2) + } - url = "/users/yby47f04riwpndba456rqxtmifenqxx2/announce?info_hash=%89%d4%bcR%11%16%ca%1dB%a2%f3%0d%1f%27M%94%e4h%1d%af&peer_id=-TR2820-l71jtqkl898b&port=51413&uploaded=0&downloaded=0&left=0&numwant=2&key=3c8e3319&compact=0" - golden1 = "d8:completei1e10:incompletei2e8:intervali1800e12:min intervali900e5:peersld2:ip9:127.0.0.17:peer id20:-TR2820-l71jtqkl8xx14:porti34000eed2:ip32:2001:0:53aa:64c:0:7f83:bc43:dec97:peer id20:-TR2820-l71jtqkl8xx34:porti34000eeee" - got, err = testRoute(&cfg, url) - if err != nil { - t.Error(err) - } - if got != golden1 { - t.Errorf("\ngot: %s\nwanted: %s", got, golden1) - } + url = "/users/yby47f04riwpndba456rqxtmifenqxx2/announce?info_hash=%89%d4%bcR%11%16%ca%1dB%a2%f3%0d%1f%27M%94%e4h%1d%af&peer_id=-TR2820-l71jtqkl898b&port=51413&uploaded=0&downloaded=0&left=0&numwant=2&key=3c8e3319&compact=0" + golden1 = "d8:completei1e10:incompletei2e8:intervali1800e12:min intervali900e5:peersld2:ip9:127.0.0.17:peer id20:-TR2820-l71jtqkl8xx14:porti34000eed2:ip32:2001:0:53aa:64c:0:7f83:bc43:dec97:peer id20:-TR2820-l71jtqkl8xx34:porti34000eeee" + got, err = testRoute(&cfg, url) + if err != nil { + t.Error(err) + } + if got != golden1 { + t.Errorf("\ngot: %s\nwanted: %s", got, golden1) + } + */ } diff --git a/http/scrape.go b/http/scrape.go index 3e5273e..6c8f11a 100644 --- a/http/scrape.go +++ b/http/scrape.go @@ -5,8 +5,6 @@ package http import ( - "fmt" - "io" "net/http" "github.com/julienschmidt/httprouter" @@ -52,26 +50,34 @@ func (t *Tracker) ServeScrape(w http.ResponseWriter, r *http.Request, p httprout torrents = append(torrents, torrent) } + resp := bencode.NewDict() + resp["files"] = filesDict(torrents) + bencoder := bencode.NewEncoder(w) - fmt.Fprintf(w, "d") - bencoder.Encode("files") - for _, torrent := range torrents { - writeTorrentStatus(w, torrent) + err = bencoder.Encode(resp) + if err != nil { + return http.StatusInternalServerError, err } - fmt.Fprintf(w, "e") return http.StatusOK, nil } -func writeTorrentStatus(w io.Writer, t *models.Torrent) { - bencoder := bencode.NewEncoder(w) - bencoder.Encode(t.Infohash) - fmt.Fprintf(w, "d") - bencoder.Encode("complete") - bencoder.Encode(len(t.Seeders)) - bencoder.Encode("downloaded") - bencoder.Encode(t.Snatches) - bencoder.Encode("incomplete") - bencoder.Encode(len(t.Leechers)) - fmt.Fprintf(w, "e") +func filesDict(torrents []*models.Torrent) bencode.Dict { + d := bencode.NewDict() + + for _, torrent := range torrents { + d[torrent.Infohash] = torrentDict(torrent) + } + + return d +} + +func torrentDict(torrent *models.Torrent) bencode.Dict { + d := bencode.NewDict() + + d["complete"] = len(torrent.Seeders) + d["incomplete"] = len(torrent.Leechers) + d["downloaded"] = torrent.Snatches + + return d }