// 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) peer1["event"] = "started" expected := makeResponse(1, 0) checkAnnounce(peer1, expected, srv, t) expected = makeResponse(2, 0) checkAnnounce(peer2, expected, srv, t) expected = makeResponse(2, 1, peer1, peer2) checkAnnounce(peer3, expected, srv, t) peer1["event"] = "stopped" expected = makeResponse(1, 1, nil) checkAnnounce(peer1, expected, srv, t) 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.PrivateEnabled = 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 cfg.DualStackedPeers = false srv, err := setupTracker(&cfg) if err != nil { t.Fatal(err) } defer srv.Close() peerA1 := makePeerParams("peerA1", false, "44.0.0.1") peerA2 := makePeerParams("peerA2", false, "44.0.0.2") peerA3 := makePeerParams("peerA3", false, "44.0.0.3") peerA4 := makePeerParams("peerA4", false, "44.0.0.4") peerB1 := makePeerParams("peerB1", false, "45.0.0.1") peerB2 := makePeerParams("peerB2", false, "45.0.0.2") peerC1 := makePeerParams("peerC1", false, "fc01::1") peerC2 := makePeerParams("peerC2", false, "fc01::2") peerC3 := makePeerParams("peerC3", false, "fc01::3") peerD1 := makePeerParams("peerD1", false, "fc02::1") peerD2 := makePeerParams("peerD2", false, "fc02::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) peerA3["numwant"] = "2" expected = makeResponse(0, 5, peerA1, peerA2) checkAnnounce(peerA3, expected, srv, t) checkAnnounce(peerA3, expected, srv, t) peerA4["numwant"] = "3" expected = makeResponse(0, 6, peerA1, peerA2, peerA3) checkAnnounce(peerA4, expected, srv, t) checkAnnounce(peerA4, expected, srv, t) expected = makeResponse(0, 7, peerA1, peerA2, peerA3, peerA4, peerB1, peerB2) checkAnnounce(peerC1, expected, srv, t) peerC2["numwant"] = "1" expected = makeResponse(0, 8, peerC1) checkAnnounce(peerC2, expected, srv, t) checkAnnounce(peerC2, expected, srv, t) peerC3["numwant"] = "2" expected = makeResponse(0, 9, peerC1, peerC2) checkAnnounce(peerC3, expected, srv, t) checkAnnounce(peerC3, expected, srv, t) expected = makeResponse(0, 10, peerA1, peerA2, peerA3, peerA4, peerB1, peerB2, peerC1, peerC2, peerC3) checkAnnounce(peerD1, expected, srv, t) peerD2["numwant"] = "1" expected = makeResponse(0, 11, peerD1) checkAnnounce(peerD2, expected, srv, t) checkAnnounce(peerD2, expected, srv, t) } func TestCompactAnnounce(t *testing.T) { srv, err := setupTracker(&config.DefaultConfig) if err != nil { t.Fatal(err) } defer srv.Close() compact := "\xff\x09\x7f\x05\x04\xd2" ip := "255.9.127.5" // Use the same IP for all of them so we don't have to worry about order. peer1 := makePeerParams("peer1", false, ip) peer1["compact"] = "1" peer2 := makePeerParams("peer2", false, ip) peer2["compact"] = "1" peer3 := makePeerParams("peer3", false, ip) peer3["compact"] = "1" expected := makeResponse(0, 1) expected["peers"] = "" checkAnnounce(peer1, expected, srv, t) expected = makeResponse(0, 2) expected["peers"] = compact checkAnnounce(peer2, expected, srv, t) expected = makeResponse(0, 3) expected["peers"] = compact + compact checkAnnounce(peer3, expected, srv, t) } func makePeerParams(id string, seed bool, extra ...string) params { left := "1" if seed { left = "0" } ip := "10.0.0.1" if len(extra) >= 1 { ip = extra[0] } return params{ "info_hash": infoHash, "peer_id": id, "ip": ip, "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) }