WIP Add support for custom torrents' approval storages

* migrate torrentapproval to list storage
* add initial support for torrent file storage (watch directory with fsnotify)
* replace frontend/http/bencode package with github.com/zeebo/bencode module
* sanitize code (fix warnings)

TODO:
* parse torrent files to get hashes,
* watch directory event types

DON'T use for now
This commit is contained in:
Širhoe Biazhkovič
2021-09-04 01:45:34 +03:00
parent d57c348b6c
commit 8580bb37e0
22 changed files with 309 additions and 644 deletions
-41
View File
@@ -1,41 +0,0 @@
// Package bencode implements bencoding of data as defined in BEP 3 using
// type assertion over reflection for performance.
package bencode
import "bytes"
// Enforce that Dict implements the Marshaler interface.
var _ Marshaler = Dict{}
// Dict represents a bencode dictionary.
type Dict map[string]interface{}
// NewDict allocates the memory for a Dict.
func NewDict() Dict {
return make(Dict)
}
// MarshalBencode implements the Marshaler interface for Dict.
func (d Dict) MarshalBencode() ([]byte, error) {
var buf bytes.Buffer
err := marshalMap(&buf, map[string]interface{}(d))
return buf.Bytes(), err
}
// Enforce that List implements the Marshaler interface.
var _ Marshaler = List{}
// List represents a bencode list.
type List []interface{}
// MarshalBencode implements the Marshaler interface for List.
func (l List) MarshalBencode() ([]byte, error) {
var buf bytes.Buffer
err := marshalList(&buf, []interface{}(l))
return buf.Bytes(), err
}
// NewList allocates the memory for a List.
func NewList() List {
return make(List, 0)
}
-141
View File
@@ -1,141 +0,0 @@
package bencode
import (
"bufio"
"bytes"
"errors"
"io"
"strconv"
)
// A Decoder reads bencoded objects from an input stream.
type Decoder struct {
r *bufio.Reader
}
// NewDecoder returns a new decoder that reads from r.
func NewDecoder(r io.Reader) *Decoder {
return &Decoder{r: bufio.NewReader(r)}
}
// Decode unmarshals the next bencoded value in the stream.
func (dec *Decoder) Decode() (interface{}, error) {
return unmarshal(dec.r)
}
// Unmarshal deserializes and returns the bencoded value in buf.
func Unmarshal(buf []byte) (interface{}, error) {
r := bufio.NewReader(bytes.NewBuffer(buf))
return unmarshal(r)
}
// unmarshal reads bencoded values from a bufio.Reader
func unmarshal(r *bufio.Reader) (interface{}, error) {
tok, err := r.ReadByte()
if err != nil {
return nil, err
}
switch tok {
case 'i':
return readTerminatedInt(r, 'e')
case 'l':
return readList(r)
case 'd':
return readDict(r)
default:
err = r.UnreadByte()
if err != nil {
return nil, err
}
length, err := readTerminatedInt(r, ':')
if err != nil {
return nil, errors.New("bencode: unknown input sequence")
}
buf := make([]byte, length)
n, err := r.Read(buf)
if err != nil {
return nil, err
} else if int64(n) != length {
return nil, errors.New("bencode: short read")
}
return string(buf), nil
}
}
func readTerminator(r io.ByteScanner, term byte) (bool, error) {
tok, err := r.ReadByte()
if err != nil {
return false, err
} else if tok == term {
return true, nil
}
return false, r.UnreadByte()
}
func readTerminatedInt(r *bufio.Reader, term byte) (int64, error) {
buf, err := r.ReadSlice(term)
if err != nil {
return 0, err
} else if len(buf) <= 1 {
return 0, errors.New("bencode: empty integer field")
}
return strconv.ParseInt(string(buf[:len(buf)-1]), 10, 64)
}
func readList(r *bufio.Reader) (List, error) {
list := NewList()
for {
ok, err := readTerminator(r, 'e')
if err != nil {
return nil, err
} else if ok {
break
}
v, err := unmarshal(r)
if err != nil {
return nil, err
}
list = append(list, v)
}
return list, nil
}
func readDict(r *bufio.Reader) (Dict, error) {
dict := NewDict()
for {
ok, err := readTerminator(r, 'e')
if err != nil {
return nil, err
} else if ok {
break
}
v, err := unmarshal(r)
if err != nil {
return nil, err
}
key, ok := v.(string)
if !ok {
return nil, errors.New("bencode: non-string map key")
}
dict[key], err = unmarshal(r)
if err != nil {
return nil, err
}
}
return dict, nil
}
-84
View File
@@ -1,84 +0,0 @@
package bencode
import (
"testing"
"github.com/stretchr/testify/require"
)
var unmarshalTests = []struct {
input string
expected interface{}
}{
{"i42e", int64(42)},
{"i-42e", int64(-42)},
{"7:example", "example"},
{"l3:one3:twoe", List{"one", "two"}},
{"le", List{}},
{"d3:one2:aa3:two2:bbe", Dict{"one": "aa", "two": "bb"}},
{"de", Dict{}},
}
func TestUnmarshal(t *testing.T) {
for _, tt := range unmarshalTests {
t.Run(tt.input, func(t *testing.T) {
got, err := Unmarshal([]byte(tt.input))
require.Nil(t, err, "unmarshal should not fail")
require.Equal(t, got, tt.expected, "unmarshalled values should match the expected results")
})
}
}
type bufferLoop struct {
val string
}
func (r *bufferLoop) Read(b []byte) (int, error) {
n := copy(b, r.val)
return n, nil
}
func BenchmarkUnmarshalScalar(b *testing.B) {
d1 := NewDecoder(&bufferLoop{"7:example"})
d2 := NewDecoder(&bufferLoop{"i42e"})
for i := 0; i < b.N; i++ {
d1.Decode()
d2.Decode()
}
}
func TestUnmarshalLarge(t *testing.T) {
data := Dict{
"k1": List{"a", "b", "c"},
"k2": int64(42),
"k3": "val",
"k4": int64(-42),
}
buf, _ := Marshal(data)
dec := NewDecoder(&bufferLoop{string(buf)})
got, err := dec.Decode()
require.Nil(t, err, "decode should not fail")
require.Equal(t, got, data, "encoding and decoding should equal the original value")
}
func BenchmarkUnmarshalLarge(b *testing.B) {
data := map[string]interface{}{
"k1": []string{"a", "b", "c"},
"k2": 42,
"k3": "val",
"k4": uint(42),
}
buf, _ := Marshal(data)
dec := NewDecoder(&bufferLoop{string(buf)})
for i := 0; i < b.N; i++ {
dec.Decode()
}
}
-196
View File
@@ -1,196 +0,0 @@
package bencode
import (
"bytes"
"fmt"
"io"
"strconv"
"time"
)
// An Encoder writes bencoded objects to an output stream.
type Encoder struct {
w io.Writer
}
// NewEncoder returns a new encoder that writes to w.
func NewEncoder(w io.Writer) *Encoder {
return &Encoder{w: w}
}
// Encode writes the bencoding of v to the stream.
func (enc *Encoder) Encode(v interface{}) error {
return marshal(enc.w, v)
}
// Marshal returns the bencoding of v.
func Marshal(v interface{}) ([]byte, error) {
var buf bytes.Buffer
err := marshal(&buf, v)
return buf.Bytes(), err
}
// Marshaler is the interface implemented by objects that can marshal
// themselves.
type Marshaler interface {
MarshalBencode() ([]byte, error)
}
// marshal writes types bencoded to an io.Writer.
func marshal(w io.Writer, data interface{}) (err error) {
switch v := data.(type) {
case Marshaler:
var bencoded []byte
bencoded, err = v.MarshalBencode()
if err != nil {
return err
}
_, err = w.Write(bencoded)
case []byte:
err = marshalBytes(w, v)
case string:
err = marshalString(w, v)
case []string:
err = marshalStringSlice(w, v)
case int:
err = marshalInt(w, int64(v))
case int16:
err = marshalInt(w, int64(v))
case int32:
err = marshalInt(w, int64(v))
case int64:
err = marshalInt(w, int64(v))
case uint:
err = marshalUint(w, uint64(v))
case uint16:
err = marshalUint(w, uint64(v))
case uint32:
err = marshalUint(w, uint64(v))
case uint64:
err = marshalUint(w, uint64(v))
case time.Duration: // Assume seconds
err = marshalInt(w, int64(v/time.Second))
case map[string]interface{}:
err = marshalMap(w, v)
case []interface{}:
err = marshalList(w, v)
case []Dict:
var interfaceSlice = make([]interface{}, len(v))
for i, d := range v {
interfaceSlice[i] = d
}
err = marshalList(w, interfaceSlice)
default:
return fmt.Errorf("attempted to marshal unsupported type:\n%T", v)
}
return err
}
func marshalInt(w io.Writer, v int64) error {
if _, err := w.Write([]byte{'i'}); err != nil {
return err
}
if _, err := w.Write([]byte(strconv.FormatInt(v, 10))); err != nil {
return err
}
_, err := w.Write([]byte{'e'})
return err
}
func marshalUint(w io.Writer, v uint64) error {
if _, err := w.Write([]byte{'i'}); err != nil {
return err
}
if _, err := w.Write([]byte(strconv.FormatUint(v, 10))); err != nil {
return err
}
_, err := w.Write([]byte{'e'})
return err
}
func marshalBytes(w io.Writer, v []byte) error {
if _, err := w.Write([]byte(strconv.Itoa(len(v)))); err != nil {
return err
}
if _, err := w.Write([]byte{':'}); err != nil {
return err
}
_, err := w.Write(v)
return err
}
func marshalString(w io.Writer, v string) error {
return marshalBytes(w, []byte(v))
}
func marshalStringSlice(w io.Writer, v []string) error {
if _, err := w.Write([]byte{'l'}); err != nil {
return err
}
for _, val := range v {
if err := marshal(w, val); err != nil {
return err
}
}
_, err := w.Write([]byte{'e'})
return err
}
func marshalList(w io.Writer, v []interface{}) error {
if _, err := w.Write([]byte{'l'}); err != nil {
return err
}
for _, val := range v {
if err := marshal(w, val); err != nil {
return err
}
}
_, err := w.Write([]byte{'e'})
return err
}
func marshalMap(w io.Writer, v map[string]interface{}) error {
if _, err := w.Write([]byte{'d'}); err != nil {
return err
}
for key, val := range v {
if err := marshalString(w, key); err != nil {
return err
}
if err := marshal(w, val); err != nil {
return err
}
}
_, err := w.Write([]byte{'e'})
return err
}
-72
View File
@@ -1,72 +0,0 @@
package bencode
import (
"bytes"
"fmt"
"testing"
"time"
"github.com/stretchr/testify/require"
)
var marshalTests = []struct {
input interface{}
expected []string
}{
{int(42), []string{"i42e"}},
{int(-42), []string{"i-42e"}},
{uint(43), []string{"i43e"}},
{int64(44), []string{"i44e"}},
{uint64(45), []string{"i45e"}},
{int16(44), []string{"i44e"}},
{uint16(45), []string{"i45e"}},
{"example", []string{"7:example"}},
{[]byte("example"), []string{"7:example"}},
{30 * time.Minute, []string{"i1800e"}},
{[]string{"one", "two"}, []string{"l3:one3:twoe", "l3:two3:onee"}},
{[]interface{}{"one", "two"}, []string{"l3:one3:twoe", "l3:two3:onee"}},
{[]string{}, []string{"le"}},
{map[string]interface{}{"one": "aa", "two": "bb"}, []string{"d3:one2:aa3:two2:bbe", "d3:two2:bb3:one2:aae"}},
{map[string]interface{}{}, []string{"de"}},
{[]Dict{{"a": "b"}, {"c": "d"}}, []string{"ld1:a1:bed1:c1:dee", "ld1:c1:ded1:a1:bee"}},
}
func TestMarshal(t *testing.T) {
for _, tt := range marshalTests {
t.Run(fmt.Sprintf("%#v", tt.input), func(t *testing.T) {
got, err := Marshal(tt.input)
require.Nil(t, err, "marshal should not fail")
require.Contains(t, tt.expected, string(got), "the marshaled result should be one of the expected permutations")
})
}
}
func BenchmarkMarshalScalar(b *testing.B) {
buf := &bytes.Buffer{}
encoder := NewEncoder(buf)
for i := 0; i < b.N; i++ {
encoder.Encode("test")
encoder.Encode(123)
}
}
func BenchmarkMarshalLarge(b *testing.B) {
data := map[string]interface{}{
"k1": []string{"a", "b", "c"},
"k2": 42,
"k3": "val",
"k4": uint(42),
}
buf := &bytes.Buffer{}
encoder := NewEncoder(buf)
for i := 0; i < b.N; i++ {
encoder.Encode(data)
}
}
+11 -9
View File
@@ -1,13 +1,15 @@
package http
import (
"github.com/zeebo/bencode"
"net/http"
"github.com/chihaya/chihaya/bittorrent"
"github.com/chihaya/chihaya/frontend/http/bencode"
"github.com/chihaya/chihaya/pkg/log"
)
type strMap map[string]interface{}
// WriteError communicates an error to a BitTorrent client over HTTP.
func WriteError(w http.ResponseWriter, err error) error {
message := "internal server error"
@@ -18,7 +20,7 @@ func WriteError(w http.ResponseWriter, err error) error {
}
w.WriteHeader(http.StatusOK)
return bencode.NewEncoder(w).Encode(bencode.Dict{
return bencode.NewEncoder(w).Encode(map[string]interface{}{
"failure reason": message,
})
}
@@ -26,7 +28,7 @@ func WriteError(w http.ResponseWriter, err error) error {
// WriteAnnounceResponse communicates the results of an Announce to a
// BitTorrent client over HTTP.
func WriteAnnounceResponse(w http.ResponseWriter, resp *bittorrent.AnnounceResponse) error {
bdict := bencode.Dict{
bdict := strMap{
"complete": resp.Complete,
"incomplete": resp.Incomplete,
"interval": resp.Interval,
@@ -57,7 +59,7 @@ func WriteAnnounceResponse(w http.ResponseWriter, resp *bittorrent.AnnounceRespo
}
// Add the peers to the dictionary.
var peers []bencode.Dict
var peers []strMap
for _, peer := range resp.IPv4Peers {
peers = append(peers, dict(peer))
}
@@ -72,15 +74,15 @@ func WriteAnnounceResponse(w http.ResponseWriter, resp *bittorrent.AnnounceRespo
// WriteScrapeResponse communicates the results of a Scrape to a BitTorrent
// client over HTTP.
func WriteScrapeResponse(w http.ResponseWriter, resp *bittorrent.ScrapeResponse) error {
filesDict := bencode.NewDict()
filesDict := make(strMap)
for _, scrape := range resp.Files {
filesDict[string(scrape.InfoHash[:])] = bencode.Dict{
filesDict[string(scrape.InfoHash[:])] = strMap{
"complete": scrape.Complete,
"incomplete": scrape.Incomplete,
}
}
return bencode.NewEncoder(w).Encode(bencode.Dict{
return bencode.NewEncoder(w).Encode(strMap{
"files": filesDict,
})
}
@@ -107,8 +109,8 @@ func compact6(peer bittorrent.Peer) (buf []byte) {
return
}
func dict(peer bittorrent.Peer) bencode.Dict {
return bencode.Dict{
func dict(peer bittorrent.Peer) strMap {
return strMap{
"peer id": string(peer.ID[:]),
"ip": peer.IP.String(),
"port": peer.Port,