diff --git a/bittorrent/bittorrent.go b/bittorrent/bittorrent.go index 8475fa3..7e7f69c 100644 --- a/bittorrent/bittorrent.go +++ b/bittorrent/bittorrent.go @@ -14,11 +14,13 @@ import ( "time" ) +// PeerIDLen is length of peer id field in bytes const PeerIDLen = 20 // PeerID represents a peer ID. type PeerID [PeerIDLen]byte +// InvalidPeerIDSizeError holds error about invalid PeerID size var InvalidPeerIDSizeError = errors.New("peer ID must be 20 bytes") // NewPeerID creates a PeerID from a byte slice. @@ -47,13 +49,20 @@ func (p PeerID) RawString() string { type InfoHash string const ( + // InfoHashV1Len is the same as sha1.Size InfoHashV1Len = sha1.Size + // InfoHashV2Len ... sha256.Size InfoHashV2Len = sha256.Size + // NoneInfoHash dummy invalid InfoHash NoneInfoHash InfoHash = "" ) -var InvalidHashTypeError = errors.New("info hash must be provided as byte slice or raw/hex string") -var InvalidHashSizeError = errors.New("info hash must be either 20 (for torrent V1) or 32 (V2) bytes") +var ( + // InvalidHashTypeError holds error about invalid InfoHash input type + InvalidHashTypeError = errors.New("info hash must be provided as byte slice or raw/hex string") + // InvalidHashSizeError holds error about invalid InfoHash size + InvalidHashSizeError = errors.New("info hash must be either 20 (for torrent V1) or 32 (V2) bytes") +) // TruncateV1 returns truncated to 20-bytes length array of the corresponding InfoHash. // If InfoHash is V2 (32 bytes), it will be truncated to 20 bytes @@ -89,10 +98,8 @@ func NewInfoHash(b interface{}) (InfoHash, error) { l := len(ba) if l != InfoHashV1Len && l != InfoHashV2Len { return NoneInfoHash, InvalidHashSizeError - } else { - buf := InfoHash(ba) - return buf, nil } + return InfoHash(ba), nil } // String implements fmt.Stringer, returning the base16 encoded InfoHash. diff --git a/cmd/chihaya/config.go b/cmd/chihaya/config.go index 56d79c1..1106f58 100644 --- a/cmd/chihaya/config.go +++ b/cmd/chihaya/config.go @@ -70,12 +70,11 @@ func ParseConfigFile(path string) (*ConfigFile, error) { } f, err := os.Open(os.ExpandEnv(path)) - if err != nil { - return nil, err - } else { + if err == nil { defer f.Close() cfgFile := new(ConfigFile) err = yaml.NewDecoder(f).Decode(cfgFile) return cfgFile, err } + return nil, err } diff --git a/frontend/udp/parser.go b/frontend/udp/parser.go index 59151fc..4c01c7b 100644 --- a/frontend/udp/parser.go +++ b/frontend/udp/parser.go @@ -81,7 +81,7 @@ func ParseAnnounce(r Request, v6Action bool, opts ParseOptions) (*bittorrent.Ann } infohash := r.Packet[16:36] - peerID := r.Packet[36:56] + peerIDBytes := r.Packet[36:56] downloaded := binary.BigEndian.Uint64(r.Packet[56:64]) left := binary.BigEndian.Uint64(r.Packet[64:72]) uploaded := binary.BigEndian.Uint64(r.Packet[72:80]) @@ -117,7 +117,7 @@ func ParseAnnounce(r Request, v6Action bool, opts ParseOptions) (*bittorrent.Ann return nil, err } - peerId, err := bittorrent.NewPeerID(peerID) + peerID, err := bittorrent.NewPeerID(peerIDBytes) if err != nil { return nil, err } @@ -133,7 +133,7 @@ func ParseAnnounce(r Request, v6Action bool, opts ParseOptions) (*bittorrent.Ann NumWantProvided: true, EventProvided: true, Peer: bittorrent.Peer{ - ID: peerId, + ID: peerID, IP: bittorrent.IP{IP: ip}, Port: port, }, @@ -227,24 +227,26 @@ func ParseScrape(r Request, opts ParseOptions) (*bittorrent.ScrapeRequest, error // Allocate a list of infohashes and append it to the list until we're out. var infohashes []bittorrent.InfoHash + var err error + var request *bittorrent.ScrapeRequest pageSize := bittorrent.InfoHashV1Len if isV2 { pageSize = bittorrent.InfoHashV2Len } for len(r.Packet) >= pageSize { - if ih, err := bittorrent.NewInfoHash(r.Packet[:pageSize]); err != nil { - return nil, err - } else { + var ih bittorrent.InfoHash + if ih, err = bittorrent.NewInfoHash(r.Packet[:pageSize]); err == nil { infohashes = append(infohashes, ih) r.Packet = r.Packet[pageSize:] + } else { + break } } - - // Sanitize the request. - request := &bittorrent.ScrapeRequest{InfoHashes: infohashes} - if err := bittorrent.SanitizeScrape(request, opts.MaxScrapeInfoHashes); err != nil { - return nil, err + if err == nil { + // Sanitize the request. + request = &bittorrent.ScrapeRequest{InfoHashes: infohashes} + err = bittorrent.SanitizeScrape(request, opts.MaxScrapeInfoHashes) } - return request, nil + return request, err } diff --git a/middleware/clientapproval/client_id_test.go b/middleware/clientapproval/client_id_test.go index c82c710..91ed007 100644 --- a/middleware/clientapproval/client_id_test.go +++ b/middleware/clientapproval/client_id_test.go @@ -47,11 +47,11 @@ func TestClientID(t *testing.T) { t.Run(tt.peerID, func(t *testing.T) { var clientID ClientID copy(clientID[:], tt.clientID) - peerId, err := bittorrent.NewPeerID([]byte(tt.peerID)) + peerID, err := bittorrent.NewPeerID([]byte(tt.peerID)) if err != nil { t.Error(err) } - parsedID := NewClientID(peerId) + parsedID := NewClientID(peerID) if parsedID != clientID { t.Error("Incorrectly parsed peer ID", tt.peerID, "as", parsedID) } diff --git a/middleware/torrentapproval/container/container.go b/middleware/torrentapproval/container/container.go index 5e10045..d1bfd5f 100644 --- a/middleware/torrentapproval/container/container.go +++ b/middleware/torrentapproval/container/container.go @@ -7,17 +7,21 @@ import ( "sync" ) +// DefaultStorageCtxName default ctx name if value from configuration is not set const DefaultStorageCtxName = "MW_APPROVAL" +// Builder function that creates and configures specific container type Builder func([]byte, storage.Storage) (Container, error) var ( buildersMU sync.Mutex builders = make(map[string]Builder) + // ErrContainerDoesNotExist holds error about nonexistent container ErrContainerDoesNotExist = errors.New("torrent hash container with that name does not exist") ) +// Register used to register specific Builder in registry func Register(n string, c Builder) { if len(n) == 0 { panic("middleware: could not register a Container with an empty name") @@ -31,12 +35,13 @@ func Register(n string, c Builder) { builders[n] = c } +// Container holds InfoHash and checks if value approved or not type Container interface { Approved(bittorrent.InfoHash) bool } +// GetContainer creates Container by its name and provided confBytes func GetContainer(name string, confBytes []byte, storage storage.Storage) (Container, error) { - buildersMU.Lock() defer buildersMU.Unlock() var err error diff --git a/middleware/torrentapproval/container/directory/directory.go b/middleware/torrentapproval/container/directory/directory.go index 2269b9a..6f39d59 100644 --- a/middleware/torrentapproval/container/directory/directory.go +++ b/middleware/torrentapproval/container/directory/directory.go @@ -1,6 +1,7 @@ // Package directory implements container which // checks if hash present in any of torrent file -// placed in some directory +// placed in some directory. +// Note: Unlike List, this container also stores torrent name as value package directory import ( @@ -17,14 +18,18 @@ import ( "gopkg.in/yaml.v2" ) +// Name of this container for registry const Name = "directory" func init() { container.Register(Name, build) } +// Config - implementation of directory container configuration. +// Extends list.Config because uses the same storage and Approved function. type Config struct { list.Config + // Path in filesystem where torrent files stored and should be watched Path string `yaml:"path"` } @@ -100,6 +105,7 @@ type directory struct { watcher *dirwatch.Instance } +// Stop closes watching of torrent directory func (d *directory) Stop() stop.Result { d.watcher.Close() return stop.AlreadyStopped diff --git a/middleware/torrentapproval/container/list/list.go b/middleware/torrentapproval/container/list/list.go index 51cebc6..0ec785c 100644 --- a/middleware/torrentapproval/container/list/list.go +++ b/middleware/torrentapproval/container/list/list.go @@ -11,18 +11,25 @@ import ( "gopkg.in/yaml.v2" ) +// Name of this container for registry. const Name = "list" func init() { container.Register(Name, build) } +// Config - implementation of list container configuration. type Config struct { + // HashList static list of HEX-encoded InfoHashes. HashList []string `yaml:"hash_list"` + // If Invert set to true, all InfoHashes stored in HashList should be blacklisted. Invert bool `yaml:"invert"` + // StorageCtx is the name of storage context where to store hash list. + // It might be table name, REDIS record key or something else, depending on storage. StorageCtx string `yaml:"storage_ctx"` } +// DUMMY used as value placeholder if storage needs some value with const DUMMY = "_" func build(confBytes []byte, st storage.Storage) (container.Container, error) { @@ -58,12 +65,19 @@ func build(confBytes []byte, st storage.Storage) (container.Container, error) { return l, nil } +// List work structure of hash list. Might be reused in another containers. type List struct { + // Invert see Config.Invert description. Invert bool + // Storage implementation where hashes are stored for approval checks. Storage storage.Storage + // StorageCtx see Config.StorageCtx description. StorageCtx string } +// Approved checks if specified hash is approved or not. +// If List.Invert set to true and hash found in storage, function will return false, +// that means that hash is blacklisted. func (l *List) Approved(hash bittorrent.InfoHash) bool { b := l.Storage.Contains(l.StorageCtx, hash.RawString()) if len(hash) == bittorrent.InfoHashV2Len { diff --git a/middleware/torrentapproval/torrentapproval.go b/middleware/torrentapproval/torrentapproval.go index 18a02af..56ad764 100644 --- a/middleware/torrentapproval/torrentapproval.go +++ b/middleware/torrentapproval/torrentapproval.go @@ -8,7 +8,9 @@ import ( "github.com/chihaya/chihaya/bittorrent" "github.com/chihaya/chihaya/middleware" "github.com/chihaya/chihaya/middleware/torrentapproval/container" + // import directory watcher to enable appropriate support _ "github.com/chihaya/chihaya/middleware/torrentapproval/container/directory" + // import static list to enable appropriate support _ "github.com/chihaya/chihaya/middleware/torrentapproval/container/list" "github.com/chihaya/chihaya/pkg/stop" "github.com/chihaya/chihaya/storage" diff --git a/storage/misc.go b/storage/misc.go index ca2ba5a..5985e57 100644 --- a/storage/misc.go +++ b/storage/misc.go @@ -6,12 +6,15 @@ import ( "net" ) +// Pair - some key-value pair, used for BulkPut type Pair struct { Left, Right interface{} } +// SerializedPeer concatenation of PeerID, net port and IP-address type SerializedPeer string +// NewSerializedPeer builds SerializedPeer from bittorrent.Peer func NewSerializedPeer(p bittorrent.Peer) SerializedPeer { b := make([]byte, bittorrent.PeerIDLen+2+len(p.IP.IP)) copy(b[:bittorrent.PeerIDLen], p.ID[:]) @@ -21,13 +24,14 @@ func NewSerializedPeer(p bittorrent.Peer) SerializedPeer { return SerializedPeer(b) } +// ToPeer parses SerializedPeer to bittorrent.Peer func (pk SerializedPeer) ToPeer() bittorrent.Peer { - peerId, err := bittorrent.NewPeerID([]byte(pk[:bittorrent.PeerIDLen])) + peerID, err := bittorrent.NewPeerID([]byte(pk[:bittorrent.PeerIDLen])) if err != nil { panic(err) } peer := bittorrent.Peer{ - ID: peerId, + ID: peerID, Port: binary.BigEndian.Uint16([]byte(pk[bittorrent.PeerIDLen : bittorrent.PeerIDLen+2])), IP: bittorrent.IP{IP: net.IP(pk[bittorrent.PeerIDLen+2:])}} diff --git a/storage/test/storage_bench.go b/storage/test/storage_bench.go index db207c1..1fa1c6f 100644 --- a/storage/test/storage_bench.go +++ b/storage/test/storage_bench.go @@ -452,6 +452,7 @@ func (bh *benchHolder) ScrapeSwarm1kInfohash(b *testing.B) { }) } +// RunBenchmarks starts series of benchmarks func RunBenchmarks(b *testing.B, newStorage benchStorageConstructor) { bh := benchHolder{st: newStorage} b.Run("BenchmarkNop", bh.Nop) diff --git a/storage/test/storage_test_data.go b/storage/test/storage_test_data.go index e3289bb..26c3af0 100644 --- a/storage/test/storage_test_data.go +++ b/storage/test/storage_test_data.go @@ -7,7 +7,7 @@ import ( var ( testIh1, testIh2 bittorrent.InfoHash - testPeerId bittorrent.PeerID + testPeerID bittorrent.PeerID testData []hashPeer v4Peer, v6Peer bittorrent.Peer ) @@ -15,18 +15,18 @@ var ( func init() { testIh1, _ = bittorrent.NewInfoHash("00000000000000000001") testIh2, _ = bittorrent.NewInfoHash("00000000000000000002") - testPeerId, _ = bittorrent.NewPeerID([]byte("00000000000000000001")) + testPeerID, _ = bittorrent.NewPeerID([]byte("00000000000000000001")) testData = []hashPeer{ { testIh1, - bittorrent.Peer{ID: testPeerId, Port: 1, IP: bittorrent.IP{IP: net.ParseIP("1.1.1.1").To4(), AddressFamily: bittorrent.IPv4}}, + bittorrent.Peer{ID: testPeerID, Port: 1, IP: bittorrent.IP{IP: net.ParseIP("1.1.1.1").To4(), AddressFamily: bittorrent.IPv4}}, }, { testIh2, - bittorrent.Peer{ID: testPeerId, Port: 2, IP: bittorrent.IP{IP: net.ParseIP("abab::0001"), AddressFamily: bittorrent.IPv6}}, + bittorrent.Peer{ID: testPeerID, Port: 2, IP: bittorrent.IP{IP: net.ParseIP("abab::0001"), AddressFamily: bittorrent.IPv6}}, }, } - v4Peer = bittorrent.Peer{ID: testPeerId, IP: bittorrent.IP{IP: net.ParseIP("99.99.99.99").To4(), AddressFamily: bittorrent.IPv4}, Port: 9994} - v6Peer = bittorrent.Peer{ID: testPeerId, IP: bittorrent.IP{IP: net.ParseIP("fc00::0001"), AddressFamily: bittorrent.IPv6}, Port: 9996} + v4Peer = bittorrent.Peer{ID: testPeerID, IP: bittorrent.IP{IP: net.ParseIP("99.99.99.99").To4(), AddressFamily: bittorrent.IPv4}, Port: 9994} + v6Peer = bittorrent.Peer{ID: testPeerID, IP: bittorrent.IP{IP: net.ParseIP("fc00::0001"), AddressFamily: bittorrent.IPv6}, Port: 9996} }