// Copyright 2014 The Chihaya Authors. All rights reserved. // Use of this source code is governed by the BSD 2-Clause license, // which can be found in the LICENSE file. package http import ( "net/http" "net/http/httptest" "net/url" "reflect" "strconv" "testing" "time" "github.com/chihaya/bencode" "github.com/chihaya/chihaya/config" "github.com/chihaya/chihaya/tracker" "github.com/chihaya/chihaya/tracker/models" ) func TestPublicAnnounce(t *testing.T) { srv, err := setupTracker(&config.DefaultConfig) if err != nil { t.Fatal(err) } defer srv.Close() peer1 := makePeerParams("peer1", true) peer2 := makePeerParams("peer2", true) peer3 := makePeerParams("peer3", false) // Add one seeder. expected := makeResponse(1, 0) checkAnnounce(peer1, expected, srv, t) // Add another seeder. expected = makeResponse(2, 0) checkAnnounce(peer2, expected, srv, t) // Add a leecher. expected = makeResponse(2, 1, peer1, peer2) checkAnnounce(peer3, expected, srv, t) // Remove seeder. peer1["event"] = "stopped" expected = makeResponse(1, 1, nil) checkAnnounce(peer1, expected, srv, t) // Check seeders. expected = makeResponse(1, 1, peer2) checkAnnounce(peer3, expected, srv, t) } func TestTorrentPurging(t *testing.T) { cfg := config.DefaultConfig srv, err := setupTracker(&cfg) if err != nil { t.Fatal(err) } defer srv.Close() torrentApiPath := srv.URL + "/torrents/" + url.QueryEscape(infoHash) // Add one seeder. peer := makePeerParams("peer1", true) announce(peer, srv) _, status, err := fetchPath(torrentApiPath) if err != nil { t.Fatal(err) } else if status != http.StatusOK { t.Fatalf("expected torrent to exist (got %s)", http.StatusText(status)) } // Remove seeder. peer = makePeerParams("peer1", true) peer["event"] = "stopped" announce(peer, srv) _, status, err = fetchPath(torrentApiPath) if err != nil { t.Fatal(err) } else if status != http.StatusNotFound { t.Fatalf("expected torrent to have been purged (got %s)", http.StatusText(status)) } } func TestStalePeerPurging(t *testing.T) { cfg := config.DefaultConfig cfg.Announce = config.Duration{10 * time.Millisecond} srv, err := setupTracker(&cfg) if err != nil { t.Fatal(err) } defer srv.Close() torrentApiPath := srv.URL + "/torrents/" + url.QueryEscape(infoHash) // Add one seeder. peer1 := makePeerParams("peer1", true) announce(peer1, srv) _, status, err := fetchPath(torrentApiPath) if err != nil { t.Fatal(err) } else if status != http.StatusOK { t.Fatalf("expected torrent to exist (got %s)", http.StatusText(status)) } // Add a leecher. peer2 := makePeerParams("peer2", false) expected := makeResponse(1, 1, peer1) expected["interval"] = int64(0) checkAnnounce(peer2, expected, srv, t) // Let them both expire. time.Sleep(30 * time.Millisecond) _, status, err = fetchPath(torrentApiPath) if err != nil { t.Fatal(err) } else if status != http.StatusNotFound { t.Fatalf("expected torrent to have been purged (got %s)", http.StatusText(status)) } } func TestPrivateAnnounce(t *testing.T) { cfg := config.DefaultConfig cfg.Private = true tkr, err := tracker.New(&cfg) if err != nil { t.Fatal(err) } err = loadPrivateTestData(tkr) if err != nil { t.Fatal(err) } srv, err := createServer(tkr, &cfg) if err != nil { t.Fatal(err) } defer srv.Close() baseURL := srv.URL peer1 := makePeerParams("-TR2820-peer1", false) peer2 := makePeerParams("-TR2820-peer2", false) peer3 := makePeerParams("-TR2820-peer3", true) expected := makeResponse(0, 1) srv.URL = baseURL + "/users/vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv1" checkAnnounce(peer1, expected, srv, t) expected = makeResponse(0, 2, peer1) srv.URL = baseURL + "/users/vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv2" checkAnnounce(peer2, expected, srv, t) expected = makeResponse(1, 2, peer1, peer2) srv.URL = baseURL + "/users/vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv3" checkAnnounce(peer3, expected, srv, t) expected = makeResponse(1, 2, peer2, peer3) srv.URL = baseURL + "/users/vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv1" checkAnnounce(peer1, expected, srv, t) } func TestPreferredSubnet(t *testing.T) { cfg := config.DefaultConfig cfg.PreferredSubnet = true cfg.PreferredIPv4Subnet = 8 cfg.PreferredIPv6Subnet = 16 srv, err := setupTracker(&cfg) if err != nil { t.Fatal(err) } defer srv.Close() peerA1 := makePeerParams("peerA1", false) peerA1["ip"] = "44.0.0.1" peerA2 := makePeerParams("peerA2", false) peerA2["ip"] = "44.0.0.2" peerA3 := makePeerParams("peerA3", false) peerA3["ip"] = "44.0.0.3" peerA4 := makePeerParams("peerA4", false) peerA4["ip"] = "44.0.0.4" peerB1 := makePeerParams("peerB1", false) peerB1["ip"] = "45.0.0.1" peerB2 := makePeerParams("peerB2", false) peerB2["ip"] = "45.0.0.2" expected := makeResponse(0, 1) checkAnnounce(peerA1, expected, srv, t) expected = makeResponse(0, 2, peerA1) checkAnnounce(peerA2, expected, srv, t) expected = makeResponse(0, 3, peerA1, peerA2) checkAnnounce(peerB1, expected, srv, t) peerB2["numwant"] = "1" expected = makeResponse(0, 4, peerB1) checkAnnounce(peerB2, expected, srv, t) checkAnnounce(peerB2, expected, srv, t) checkAnnounce(peerB2, expected, srv, t) peerA3["numwant"] = "2" expected = makeResponse(0, 5, peerA1, peerA2) checkAnnounce(peerA3, expected, srv, t) peerA4["numwant"] = "3" expected = makeResponse(0, 6, peerA1, peerA2, peerA3) checkAnnounce(peerA4, expected, srv, t) } func makePeerParams(id string, seed bool) params { left := "1" if seed { left = "0" } return params{ "info_hash": infoHash, "peer_id": id, "ip": "10.0.0.1", "port": "1234", "uploaded": "0", "downloaded": "0", "left": left, "compact": "0", "numwant": "50", } } func peerFromParams(peer params) bencode.Dict { port, _ := strconv.ParseInt(peer["port"], 10, 64) return bencode.Dict{ "peer id": peer["peer_id"], "ip": peer["ip"], "port": port, } } func makeResponse(seeders, leechers int64, peers ...params) bencode.Dict { dict := bencode.Dict{ "complete": seeders, "incomplete": leechers, "interval": int64(1800), "min interval": int64(900), } if !(len(peers) == 1 && peers[0] == nil) { peerList := bencode.List{} for _, peer := range peers { peerList = append(peerList, peerFromParams(peer)) } dict["peers"] = peerList } return dict } func checkAnnounce(p params, expected interface{}, srv *httptest.Server, t *testing.T) bool { body, err := announce(p, srv) if err != nil { t.Error(err) return false } if e, ok := expected.(bencode.Dict); ok { sortPeersInResponse(e) } got, err := bencode.Unmarshal(body) if e, ok := got.(bencode.Dict); ok { sortPeersInResponse(e) } if !reflect.DeepEqual(got, expected) { t.Errorf("\ngot: %#v\nwanted: %#v", got, expected) return false } return true } func loadPrivateTestData(tkr *tracker.Tracker) error { conn, err := tkr.Pool.Get() if err != nil { return err } users := []string{ "vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv1", "vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv2", "vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv3", } for i, passkey := range users { err = conn.PutUser(&models.User{ ID: uint64(i + 1), Passkey: passkey, }) if err != nil { return err } } err = conn.PutClient("TR2820") if err != nil { return err } torrent := &models.Torrent{ ID: 1, Infohash: infoHash, Seeders: models.PeerMap{}, Leechers: models.PeerMap{}, } return conn.PutTorrent(torrent) }