diff --git a/config/config.go b/config/config.go index d7a0dd7..e6a94df 100644 --- a/config/config.go +++ b/config/config.go @@ -12,14 +12,17 @@ import ( "time" ) +// Duration wraps a time.Duration and adds JSON marshalling. type Duration struct { time.Duration } +// MarshalJSON transforms a duration into JSON. func (d *Duration) MarshalJSON() ([]byte, error) { return json.Marshal(d.String()) } +// UnmarshalJSON transform JSON into a Duration. func (d *Duration) UnmarshalJSON(b []byte) error { var str string err := json.Unmarshal(b, &str) @@ -61,8 +64,7 @@ type Config struct { // Open is a shortcut to open a file, read it, and generate a Config. // It supports relative and absolute paths. func Open(path string) (*Config, error) { - expandedPath := os.ExpandEnv(path) - f, err := os.Open(expandedPath) + f, err := os.Open(os.ExpandEnv(path)) if err != nil { return nil, err } diff --git a/server/announce.go b/server/announce.go index 930960b..6ffb444 100644 --- a/server/announce.go +++ b/server/announce.go @@ -44,7 +44,7 @@ func (s Server) serveAnnounce(w http.ResponseWriter, r *http.Request) { log.Panicf("server: %s", err) } if !whitelisted { - fail(errors.New("Your client is not approved"), w, r) + fail(errors.New("client is not approved"), w, r) return } @@ -54,7 +54,7 @@ func (s Server) serveAnnounce(w http.ResponseWriter, r *http.Request) { log.Panicf("server: %s", err) } if !exists { - fail(errors.New("This torrent does not exist"), w, r) + fail(errors.New("torrent does not exist"), w, r) return } @@ -270,7 +270,7 @@ func (s Server) validateAnnounceQuery(r *http.Request) (compact bool, numWant in uploadedErr != nil || downloadedErr != nil || leftErr != nil { - return false, 0, "", "", "", "", 0, 0, 0, 0, errors.New("Malformed request") + return false, 0, "", "", "", "", 0, 0, 0, 0, errors.New("malformed request") } return } @@ -306,7 +306,7 @@ func requestedIP(r *http.Request, pq *parsedQuery) (string, error) { if portIndex != -1 { return r.RemoteAddr[0:portIndex], nil } - return "", errors.New("Failed to parse IP address") + return "", errors.New("failed to parse IP address") } func minInt(a, b int) int { diff --git a/server/query_test.go b/server/query_test.go index cf51fab..4f573cb 100644 --- a/server/query_test.go +++ b/server/query_test.go @@ -8,18 +8,18 @@ import ( var ( baseAddr = "https://www.subdomain.tracker.com:80/" testInfoHash = "01234567890123456789" - testPeerId = "-TEST01-6wfG2wk6wWLc" + testPeerID = "-TEST01-6wfG2wk6wWLc" ValidAnnounceArguments = []url.Values{ - url.Values{"info_hash": {testInfoHash}, "peer_id": {testPeerId}, "port": {"6881"}, "downloaded": {"1234"}, "left": {"4321"}}, - url.Values{"info_hash": {testInfoHash}, "peer_id": {testPeerId}, "ip": {"192.168.0.1"}, "port": {"6881"}, "downloaded": {"1234"}, "left": {"4321"}}, - url.Values{"info_hash": {testInfoHash}, "peer_id": {testPeerId}, "ip": {"192.168.0.1"}, "port": {"6881"}, "downloaded": {"1234"}, "left": {"4321"}, "numwant": {"28"}}, - url.Values{"info_hash": {testInfoHash}, "peer_id": {testPeerId}, "ip": {"192.168.0.1"}, "port": {"6881"}, "downloaded": {"1234"}, "left": {"4321"}, "event": {"stopped"}}, - url.Values{"info_hash": {testInfoHash}, "peer_id": {testPeerId}, "ip": {"192.168.0.1"}, "port": {"6881"}, "downloaded": {"1234"}, "left": {"4321"}, "event": {"started"}, "numwant": {"13"}}, - url.Values{"info_hash": {testInfoHash}, "peer_id": {testPeerId}, "port": {"6881"}, "downloaded": {"1234"}, "left": {"4321"}, "no_peer_id": {"1"}}, - url.Values{"info_hash": {testInfoHash}, "peer_id": {testPeerId}, "port": {"6881"}, "downloaded": {"1234"}, "left": {"4321"}, "compact": {"0"}, "no_peer_id": {"1"}}, - url.Values{"info_hash": {testInfoHash}, "peer_id": {testPeerId}, "port": {"6881"}, "downloaded": {"1234"}, "left": {"4321"}, "compact": {"0"}, "no_peer_id": {"1"}, "key": {"peerKey"}}, - url.Values{"info_hash": {testInfoHash}, "peer_id": {testPeerId}, "port": {"6881"}, "downloaded": {"1234"}, "left": {"4321"}, "compact": {"0"}, "no_peer_id": {"1"}, "key": {"peerKey"}, "trackerid": {"trackerId"}}, + url.Values{"info_hash": {testInfoHash}, "peer_id": {testPeerID}, "port": {"6881"}, "downloaded": {"1234"}, "left": {"4321"}}, + url.Values{"info_hash": {testInfoHash}, "peer_id": {testPeerID}, "ip": {"192.168.0.1"}, "port": {"6881"}, "downloaded": {"1234"}, "left": {"4321"}}, + url.Values{"info_hash": {testInfoHash}, "peer_id": {testPeerID}, "ip": {"192.168.0.1"}, "port": {"6881"}, "downloaded": {"1234"}, "left": {"4321"}, "numwant": {"28"}}, + url.Values{"info_hash": {testInfoHash}, "peer_id": {testPeerID}, "ip": {"192.168.0.1"}, "port": {"6881"}, "downloaded": {"1234"}, "left": {"4321"}, "event": {"stopped"}}, + url.Values{"info_hash": {testInfoHash}, "peer_id": {testPeerID}, "ip": {"192.168.0.1"}, "port": {"6881"}, "downloaded": {"1234"}, "left": {"4321"}, "event": {"started"}, "numwant": {"13"}}, + url.Values{"info_hash": {testInfoHash}, "peer_id": {testPeerID}, "port": {"6881"}, "downloaded": {"1234"}, "left": {"4321"}, "no_peer_id": {"1"}}, + url.Values{"info_hash": {testInfoHash}, "peer_id": {testPeerID}, "port": {"6881"}, "downloaded": {"1234"}, "left": {"4321"}, "compact": {"0"}, "no_peer_id": {"1"}}, + url.Values{"info_hash": {testInfoHash}, "peer_id": {testPeerID}, "port": {"6881"}, "downloaded": {"1234"}, "left": {"4321"}, "compact": {"0"}, "no_peer_id": {"1"}, "key": {"peerKey"}}, + url.Values{"info_hash": {testInfoHash}, "peer_id": {testPeerID}, "port": {"6881"}, "downloaded": {"1234"}, "left": {"4321"}, "compact": {"0"}, "no_peer_id": {"1"}, "key": {"peerKey"}, "trackerid": {"trackerId"}}, url.Values{"info_hash": {testInfoHash}, "peer_id": {"%3Ckey%3A+0x90%3E"}, "port": {"6881"}, "downloaded": {"1234"}, "left": {"4321"}, "compact": {"0"}, "no_peer_id": {"1"}, "key": {"peerKey"}, "trackerid": {"trackerId"}}, url.Values{"info_hash": {testInfoHash}, "peer_id": {"%3Ckey%3A+0x90%3E"}, "compact": {"1"}}, } diff --git a/server/scrape.go b/server/scrape.go index 15fdf0b..44f1643 100644 --- a/server/scrape.go +++ b/server/scrape.go @@ -18,7 +18,7 @@ func (s *Server) serveScrape(w http.ResponseWriter, r *http.Request) { // Parse the query pq, err := parseQuery(r.URL.RawQuery) if err != nil { - fail(errors.New("Error parsing query"), w, r) + fail(errors.New("error parsing query"), w, r) return } diff --git a/server/server.go b/server/server.go index 01b1bc7..513059d 100644 --- a/server/server.go +++ b/server/server.go @@ -24,6 +24,7 @@ import ( "github.com/chihaya/chihaya/storage/tracker" ) +// Server represents BitTorrent tracker server. type Server struct { conf *config.Config listener *stoppableListener.StoppableListener @@ -38,6 +39,7 @@ type Server struct { http.Server } +// New creates a new Server. func New(conf *config.Config) (*Server, error) { trackerPool, err := tracker.Open(&conf.Tracker) if err != nil { @@ -66,6 +68,7 @@ func New(conf *config.Config) (*Server, error) { return s, nil } +// ListenAndServe starts listening and handling incoming HTTP requests. func (s *Server) ListenAndServe() error { l, err := net.Listen("tcp", s.Addr) if err != nil { @@ -81,6 +84,7 @@ func (s *Server) ListenAndServe() error { return nil } +// Stop cleanly ends the handling of incoming HTTP requests. func (s *Server) Stop() error { s.listener.Stop <- true err := s.trackerPool.Close() @@ -106,7 +110,7 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { s.serveStats(w, r) return default: - fail(errors.New("Unknown action"), w, r) + fail(errors.New("unknown action"), w, r) return } } @@ -121,7 +125,7 @@ func fail(err error, w http.ResponseWriter, r *http.Request) { func validateUser(conn tracker.Conn, dir string) (*storage.User, error) { if len(dir) != 34 { - return nil, errors.New("Passkey is invalid") + return nil, errors.New("passkey is invalid") } passkey := dir[1:33] @@ -130,7 +134,7 @@ func validateUser(conn tracker.Conn, dir string) (*storage.User, error) { log.Panicf("server: %s", err) } if !exists { - return nil, errors.New("User not found") + return nil, errors.New("user not found") } return user, nil diff --git a/server/server_test.go b/server/server_test.go index bd30c20..fe1408e 100644 --- a/server/server_test.go +++ b/server/server_test.go @@ -9,8 +9,8 @@ import ( ) type PeerClientPair struct { - peerId string - clientId string + peerID string + clientID string } var TestClients = []PeerClientPair{ @@ -59,8 +59,8 @@ var TestClients = []PeerClientPair{ func TestParseClientID(t *testing.T) { for _, pair := range TestClients { - if parsedId := parsePeerID(pair.peerId); parsedId != pair.clientId { - t.Error("Incorrectly parsed peer ID", pair.peerId, "as", parsedId) + if parsedID := parsePeerID(pair.peerID); parsedID != pair.clientID { + t.Error("Incorrectly parsed peer ID", pair.peerID, "as", parsedID) } } } diff --git a/storage/backend/backend.go b/storage/backend/backend.go index 7934995..07b7edb 100644 --- a/storage/backend/backend.go +++ b/storage/backend/backend.go @@ -16,6 +16,8 @@ import ( var drivers = make(map[string]Driver) +// Driver represents an interface to a long-running connection with a +// consistent data store. type Driver interface { New(*config.DataStore) Conn } diff --git a/storage/tracker/mock/conn.go b/storage/tracker/mock/conn.go index c175f48..b8b057a 100644 --- a/storage/tracker/mock/conn.go +++ b/storage/tracker/mock/conn.go @@ -16,6 +16,7 @@ type Conn struct { func (c *Conn) FindUser(passkey string) (*storage.User, bool, error) { c.usersM.RLock() defer c.usersM.RUnlock() + user, ok := c.users[passkey] if !ok { return nil, false, nil @@ -27,6 +28,7 @@ func (c *Conn) FindUser(passkey string) (*storage.User, bool, error) { func (c *Conn) FindTorrent(infohash string) (*storage.Torrent, bool, error) { c.torrentsM.RLock() defer c.torrentsM.RUnlock() + torrent, ok := c.torrents[infohash] if !ok { return nil, false, nil @@ -38,6 +40,7 @@ func (c *Conn) FindTorrent(infohash string) (*storage.Torrent, bool, error) { func (c *Conn) ClientWhitelisted(peerID string) (bool, error) { c.whitelistM.RLock() defer c.whitelistM.RUnlock() + _, ok := c.whitelist[peerID] if !ok { return false, nil @@ -48,108 +51,134 @@ func (c *Conn) ClientWhitelisted(peerID string) (bool, error) { func (c *Conn) RecordSnatch(u *storage.User, t *storage.Torrent) error { c.torrentsM.Lock() defer c.torrentsM.Unlock() + torrent, ok := c.torrents[t.Infohash] if !ok { return tracker.ErrMissingResource } torrent.Snatches++ t.Snatches++ + return nil } func (c *Conn) MarkActive(t *storage.Torrent) error { c.torrentsM.Lock() defer c.torrentsM.Unlock() + torrent, ok := c.torrents[t.Infohash] if !ok { return tracker.ErrMissingResource } + torrent.Active = true t.Active = true + return nil } func (c *Conn) MarkInactive(t *storage.Torrent) error { c.torrentsM.Lock() defer c.torrentsM.Unlock() + torrent, ok := c.torrents[t.Infohash] if !ok { return tracker.ErrMissingResource } + torrent.Active = false t.Active = false + return nil } func (c *Conn) AddLeecher(t *storage.Torrent, p *storage.Peer) error { c.torrentsM.Lock() defer c.torrentsM.Unlock() + torrent, ok := c.torrents[t.Infohash] if !ok { return tracker.ErrMissingResource } + torrent.Leechers[storage.PeerMapKey(p)] = *p t.Leechers[storage.PeerMapKey(p)] = *p + return nil } func (c *Conn) AddSeeder(t *storage.Torrent, p *storage.Peer) error { c.torrentsM.Lock() defer c.torrentsM.Unlock() + torrent, ok := c.torrents[t.Infohash] if !ok { return tracker.ErrMissingResource } + torrent.Leechers[storage.PeerMapKey(p)] = *p t.Leechers[storage.PeerMapKey(p)] = *p + return nil } func (c *Conn) RemoveLeecher(t *storage.Torrent, p *storage.Peer) error { c.torrentsM.Lock() defer c.torrentsM.Unlock() + torrent, ok := c.torrents[t.Infohash] if !ok { return tracker.ErrMissingResource } + delete(torrent.Leechers, storage.PeerMapKey(p)) delete(t.Leechers, storage.PeerMapKey(p)) + return nil } func (c *Conn) RemoveSeeder(t *storage.Torrent, p *storage.Peer) error { c.torrentsM.Lock() defer c.torrentsM.Unlock() + torrent, ok := c.torrents[t.Infohash] if !ok { return tracker.ErrMissingResource } + delete(torrent.Seeders, storage.PeerMapKey(p)) delete(t.Seeders, storage.PeerMapKey(p)) + return nil } func (c *Conn) SetLeecher(t *storage.Torrent, p *storage.Peer) error { c.torrentsM.Lock() defer c.torrentsM.Unlock() + torrent, ok := c.torrents[t.Infohash] if !ok { return tracker.ErrMissingResource } + torrent.Leechers[storage.PeerMapKey(p)] = *p t.Leechers[storage.PeerMapKey(p)] = *p + return nil } func (c *Conn) SetSeeder(t *storage.Torrent, p *storage.Peer) error { c.torrentsM.Lock() defer c.torrentsM.Unlock() + torrent, ok := c.torrents[t.Infohash] if !ok { return tracker.ErrMissingResource } + torrent.Seeders[storage.PeerMapKey(p)] = *p t.Seeders[storage.PeerMapKey(p)] = *p + return nil } @@ -164,51 +193,65 @@ func (c *Conn) LeecherFinished(t *storage.Torrent, p *storage.Peer) error { torrent.Seeders[storage.PeerMapKey(p)] = *p delete(torrent.Leechers, storage.PeerMapKey(p)) + t.Seeders[storage.PeerMapKey(p)] = *p delete(t.Leechers, storage.PeerMapKey(p)) + return nil } func (c *Conn) AddTorrent(t *storage.Torrent) error { c.torrentsM.Lock() defer c.torrentsM.Unlock() + torrent := *t c.torrents[t.Infohash] = &torrent + return nil } func (c *Conn) RemoveTorrent(t *storage.Torrent) error { c.torrentsM.Lock() defer c.torrentsM.Unlock() + delete(c.torrents, t.Infohash) + return nil } func (c *Conn) AddUser(u *storage.User) error { c.usersM.Lock() defer c.usersM.Unlock() + user := *u c.users[u.Passkey] = &user + return nil } func (c *Conn) RemoveUser(u *storage.User) error { c.usersM.Lock() defer c.usersM.Unlock() + delete(c.users, u.Passkey) + return nil } func (c *Conn) WhitelistClient(peerID string) error { c.whitelistM.Lock() defer c.whitelistM.Unlock() + c.whitelist[peerID] = true + return nil } func (c *Conn) UnWhitelistClient(peerID string) error { c.whitelistM.Lock() defer c.whitelistM.Unlock() + delete(c.whitelist, peerID) + return nil } diff --git a/storage/tracker/tracker.go b/storage/tracker/tracker.go index 18e3a66..ea767c8 100644 --- a/storage/tracker/tracker.go +++ b/storage/tracker/tracker.go @@ -15,10 +15,14 @@ import ( ) var ( + // ErrMissingResource is an error returned when a resource does not exist. ErrMissingResource = errors.New("tracker: resource missing") - drivers = make(map[string]Driver) + + drivers = make(map[string]Driver) ) +// Driver represents an interface to pool of connections to storage used for +// the tracker. type Driver interface { New(*config.DataStore) Pool }