some corrections and tests2

This commit is contained in:
ap-pauloafonso 2021-02-11 23:50:37 -03:00
parent f9ca05394d
commit 804fe5c0de
6 changed files with 356 additions and 83 deletions

View file

@ -45,7 +45,13 @@ type torrentDict struct {
} }
//TorrentDictParse decodes the bencoded bytes and builds the torrentInfo file //TorrentDictParse decodes the bencoded bytes and builds the torrentInfo file
func TorrentDictParse(dat []byte) (*TorrentInfo, error) { func TorrentDictParse(dat []byte) (torrent *TorrentInfo, err error) {
defer func() {
if e := recover(); e != nil {
err = e.(error)
}
}()
dict, _ := mapParse(0, &dat) dict, _ := mapParse(0, &dat)
torrentMap := torrentDict{resultMap: dict} torrentMap := torrentDict{resultMap: dict}
return &TorrentInfo{ return &TorrentInfo{
@ -54,7 +60,7 @@ func TorrentDictParse(dat []byte) (*TorrentInfo, error) {
TotalSize: torrentMap.extractTotalSize(), TotalSize: torrentMap.extractTotalSize(),
TrackerInfo: torrentMap.extractTrackerInfo(), TrackerInfo: torrentMap.extractTrackerInfo(),
InfoHashURLEncoded: torrentMap.extractInfoHashURLEncoded(dat), InfoHashURLEncoded: torrentMap.extractInfoHashURLEncoded(dat),
}, nil }, err
} }
func (T *torrentDict) extractInfoHashURLEncoded(rawData []byte) string { func (T *torrentDict) extractInfoHashURLEncoded(rawData []byte) string {
@ -115,10 +121,16 @@ func (T *torrentDict) extractTrackerInfo() *TrackerInfo {
return &trackerInfo return &trackerInfo
} }
//Decode accepts a byte slice and returns a map with information parsed.(panic if it fails) //Decode accepts a byte slice and returns a map with information parsed.
func Decode(data []byte) map[string]interface{} { func Decode(data []byte) (dataMap map[string]interface{}, err error) {
defer func() {
if e := recover(); e != nil {
err = e.(error)
}
}()
result, _ := findParse(0, &data) result, _ := findParse(0, &data)
return result.(map[string]interface{}) return result.(map[string]interface{}), err
} }
func findParse(currentIdx int, data *[]byte) (result interface{}, nextIdx int) { func findParse(currentIdx int, data *[]byte) (result interface{}, nextIdx int) {

View file

@ -106,7 +106,8 @@ func TestDecode(T *testing.T) {
for _, f := range files { for _, f := range files {
T.Run(f.Name(), func(t *testing.T) { T.Run(f.Name(), func(t *testing.T) {
data, _ := ioutil.ReadFile("./torrent_files_test/" + f.Name()) data, _ := ioutil.ReadFile("./torrent_files_test/" + f.Name())
t.Log(Decode(data)["info"].(map[string]interface{})["name"]) result, _ := Decode(data)
t.Log(result["info"].(map[string]interface{})["name"])
}) })
} }

View file

@ -10,6 +10,12 @@ import (
"github.com/ap-pauloafonso/ratio-spoof/internal/bencode" "github.com/ap-pauloafonso/ratio-spoof/internal/bencode"
) )
const (
minPortNumber = 1
maxPortNumber = 65535
speedSuffixLength = 4
)
type InputArgs struct { type InputArgs struct {
TorrentPath string TorrentPath string
InitialDownloaded string InitialDownloaded string
@ -51,8 +57,8 @@ func (I *InputArgs) ParseInput(torrentInfo *bencode.TorrentInfo) (*InputParsed,
return nil, err return nil, err
} }
if I.Port < 1 || I.Port > 65535 { if I.Port < minPortNumber || I.Port > maxPortNumber {
return nil, errors.New("port number must be between 1 and 65535") return nil, errors.New(fmt.Sprint("port number must be between %i and %i", minPortNumber, maxPortNumber))
} }
return &InputParsed{InitialDownloaded: downloaded, return &InputParsed{InitialDownloaded: downloaded,
@ -75,7 +81,10 @@ func checkSpeedSufix(input string) (valid bool, suffix string) {
} }
func extractInputInitialByteCount(initialSizeInput string, totalBytes int, errorIfHigher bool) (int, error) { func extractInputInitialByteCount(initialSizeInput string, totalBytes int, errorIfHigher bool) (int, error) {
byteCount := strSize2ByteSize(initialSizeInput, totalBytes) byteCount, err := strSize2ByteSize(initialSizeInput, totalBytes)
if err != nil {
return 0, err
}
if errorIfHigher && byteCount > totalBytes { if errorIfHigher && byteCount > totalBytes {
return 0, errors.New("initial downloaded can not be higher than the torrent size") return 0, errors.New("initial downloaded can not be higher than the torrent size")
} }
@ -84,66 +93,96 @@ func extractInputInitialByteCount(initialSizeInput string, totalBytes int, error
} }
return byteCount, nil return byteCount, nil
} }
//Takes an dirty speed input and returns the bytes per second based on the suffixes
// example 1kbps(string) > 1024 bytes per second (int)
func extractInputByteSpeed(initialSpeedInput string) (int, error) { func extractInputByteSpeed(initialSpeedInput string) (int, error) {
ok, suffix := checkSpeedSufix(initialSpeedInput) ok, suffix := checkSpeedSufix(initialSpeedInput)
if !ok { if !ok {
return 0, fmt.Errorf("speed must be in %v", validSpeedSufixes) return 0, fmt.Errorf("speed must be in %v", validSpeedSufixes)
} }
number, _ := strconv.ParseFloat(initialSpeedInput[:len(initialSpeedInput)-4], 64) speedVal, err := strconv.ParseFloat(initialSpeedInput[:len(initialSpeedInput)-speedSuffixLength], 64)
if number < 0 { if err != nil {
return 0, errors.New("invalid speed number")
}
if speedVal < 0 {
return 0, errors.New("speed can not be negative") return 0, errors.New("speed can not be negative")
} }
if suffix == "kbps" { if suffix == "kbps" {
number *= 1024 speedVal *= 1024
} else { } else {
number = number * 1024 * 1024 speedVal = speedVal * 1024 * 1024
} }
ret := int(number) ret := int(speedVal)
return ret, nil return ret, nil
} }
func strSize2ByteSize(input string, totalSize int) int { func extractByteSizeNumber(strWithSufix string, sufixLength, power int) (int, error) {
lowerInput := strings.ToLower(input) v, err := strconv.ParseFloat(strWithSufix[:len(strWithSufix)-sufixLength], 64)
if err != nil {
parseStrNumberFn := func(strWithSufix string, sufixLength, n int) int { return 0, err
v, _ := strconv.ParseFloat(strWithSufix[:len(lowerInput)-sufixLength], 64)
result := v * math.Pow(1024, float64(n))
return int(result)
} }
result := v * math.Pow(1024, float64(power))
return int(result), nil
}
func strSize2ByteSize(input string, totalSize int) (int, error) {
lowerInput := strings.ToLower(input)
invalidSizeError := errors.New("invalid input size")
switch { switch {
case strings.HasSuffix(lowerInput, "kb"): case strings.HasSuffix(lowerInput, "kb"):
{ {
return parseStrNumberFn(lowerInput, 2, 1) v, err := extractByteSizeNumber(lowerInput, 2, 1)
if err != nil {
return 0, invalidSizeError
}
return v, nil
} }
case strings.HasSuffix(lowerInput, "mb"): case strings.HasSuffix(lowerInput, "mb"):
{ {
return parseStrNumberFn(lowerInput, 2, 2) v, err := extractByteSizeNumber(lowerInput, 2, 2)
if err != nil {
return 0, invalidSizeError
}
return v, nil
} }
case strings.HasSuffix(lowerInput, "gb"): case strings.HasSuffix(lowerInput, "gb"):
{ {
return parseStrNumberFn(lowerInput, 2, 3) v, err := extractByteSizeNumber(lowerInput, 2, 3)
if err != nil {
return 0, invalidSizeError
}
return v, nil
} }
case strings.HasSuffix(lowerInput, "tb"): case strings.HasSuffix(lowerInput, "tb"):
{ {
return parseStrNumberFn(lowerInput, 2, 4) v, err := extractByteSizeNumber(lowerInput, 2, 4)
if err != nil {
return 0, invalidSizeError
}
return v, nil
} }
case strings.HasSuffix(lowerInput, "b"): case strings.HasSuffix(lowerInput, "b"):
{ {
return parseStrNumberFn(lowerInput, 1, 0) v, err := extractByteSizeNumber(lowerInput, 1, 0)
if err != nil {
return 0, invalidSizeError
}
return v, nil
} }
case strings.HasSuffix(lowerInput, "%"): case strings.HasSuffix(lowerInput, "%"):
{ {
v, _ := strconv.ParseFloat(lowerInput[:len(lowerInput)-1], 64) v, err := strconv.ParseFloat(lowerInput[:len(lowerInput)-1], 64)
if v < 0 || v > 100 { if v < 0 || v > 100 || err != nil {
panic("percent value must be in (0-100)") return 0, errors.New("percent value must be in (0-100)")
} }
result := int(float64(v/100) * float64(totalSize)) result := int(float64(v/100) * float64(totalSize))
return result return result, nil
} }
default: default:
panic("Size not found") return 0, errors.New("Size not found")
} }
} }

View file

@ -1,34 +1,245 @@
package input package input
import ( import (
"fmt" "errors"
"testing" "testing"
) )
func CheckError(out error, want error, t *testing.T) {
t.Helper()
if out == nil && want == nil {
return
}
if out != nil && want == nil {
t.Errorf("got %v, want %v", out.Error(), "")
}
if out == nil && want != nil {
t.Errorf("got %v, want %v", "", want.Error())
}
if out != nil && want != nil && out.Error() != want.Error() {
t.Errorf("got %v, want %v", out.Error(), want.Error())
}
}
func TestExtractInputInitialByteCount(T *testing.T) {
data := []struct {
name string
inSize string
inTotal int
inErrorIfHigher bool
err error
}{
{
name: "[Donwloaded - error if higher]100kb input with 200kb limit shouldn't return error test",
inSize: "100kb",
inTotal: 204800,
inErrorIfHigher: true,
},
{
name: "[Donwloaded - error if higher]300kb input with 200kb limit should return error test",
inSize: "300kb",
inTotal: 204800,
inErrorIfHigher: true,
err: errors.New("initial downloaded can not be higher than the torrent size"),
},
{
name: "[Uploaded]100kb input with 200kb limit shouldn't return error test",
inSize: "100kb",
inTotal: 204800,
inErrorIfHigher: false,
},
{
name: "[Uploaded]300kb input with 200kb limit shouldn't return error test",
inSize: "300kb",
inTotal: 204800,
inErrorIfHigher: false,
},
{
name: "[Donwloaded] -100kb should return negative number error test",
inSize: "-100kb",
inTotal: 204800,
inErrorIfHigher: true,
err: errors.New("initial value can not be negative"),
},
{
name: "[Uploaded] -100kb should return negative number error test",
inSize: "-100kb",
inTotal: 204800,
inErrorIfHigher: false,
err: errors.New("initial value can not be negative"),
},
}
for _, td := range data {
T.Run(td.name, func(t *testing.T) {
_, err := extractInputInitialByteCount(td.inSize, td.inTotal, td.inErrorIfHigher)
CheckError(err, td.err, t)
})
}
}
func TestStrSize2ByteSize(T *testing.T) { func TestStrSize2ByteSize(T *testing.T) {
data := []struct { data := []struct {
name string
in string in string
inTotalSize int inTotalSize int
out int out int
err error
}{ }{
{"100kb", 100, 102400}, {
{"1kb", 0, 1024}, name: "100kb test",
{"1mb", 0, 1048576}, in: "100kb",
{"1gb", 0, 1073741824}, inTotalSize: 100,
{"1.5gb", 0, 1610612736}, out: 102400,
{"1tb", 0, 1099511627776}, },
{"1b", 0, 1}, {
{"100%", 10737418240, 10737418240}, name: "1kb test",
{"55%", 943718400, 519045120}, in: "1kb",
inTotalSize: 0,
out: 1024,
},
{
name: "1mb test",
in: "1mb",
inTotalSize: 0,
out: 1048576,
},
{
name: "1gb test",
in: "1gb",
inTotalSize: 0,
out: 1073741824,
},
{
name: "1.5gb test",
in: "1.5gb",
inTotalSize: 0,
out: 1610612736,
},
{
name: "1tb test",
in: "1tb",
inTotalSize: 0,
out: 1099511627776,
},
{
name: "1b test",
in: "1b",
inTotalSize: 0,
out: 1,
},
{
name: "10xb test",
in: "10xb",
inTotalSize: 0,
err: errors.New("invalid input size"),
},
{
name: `100% test`,
in: "100%",
inTotalSize: 10737418240,
out: 10737418240,
},
{
name: `55% test`,
in: "55%",
inTotalSize: 943718400,
out: 519045120,
},
{
name: `5kg test`,
in: "5kg",
err: errors.New("Size not found"),
},
{
name: `-1% test`,
in: "-1%",
err: errors.New("percent value must be in (0-100)"),
},
{
name: `101% test`,
in: "101%",
err: errors.New("percent value must be in (0-100)"),
},
{
name: `a% test`,
in: "a%",
err: errors.New("percent value must be in (0-100)"),
},
} }
for idx, td := range data { for _, td := range data {
T.Run(fmt.Sprint(idx), func(t *testing.T) { T.Run(td.name, func(t *testing.T) {
got := strSize2ByteSize(td.in, td.inTotalSize) got, err := strSize2ByteSize(td.in, td.inTotalSize)
if td.err != nil {
if td.err.Error() != err.Error() {
t.Errorf("got %v, want %v", err.Error(), td.err.Error())
}
}
if got != td.out { if got != td.out {
t.Errorf("got %v, want %v", got, td.out) t.Errorf("got %v, want %v", got, td.out)
} }
}) })
} }
} }
func TestExtractInputByteSpeed(T *testing.T) {
data := []struct {
name string
speed string
expected int
err error
}{
{
name: "1kbps test",
speed: "1kbps",
expected: 1024,
},
{
name: "1024kbps test",
speed: "1024kbps",
expected: 1048576,
},
{
name: "1mbps test",
speed: "1mbps",
expected: 1048576,
},
{
name: "2.5mbps test",
speed: "2.5mbps",
expected: 2621440,
},
{
name: "2.5tbps test",
speed: "2.5tbps",
err: errors.New("speed must be in [kbps mbps]"),
},
{
name: "-akbps test",
speed: "-akbps",
err: errors.New("invalid speed number"),
},
{
name: "-10kbps test",
speed: "-10kbps",
err: errors.New("speed can not be negative"),
},
}
for _, td := range data {
T.Run(td.name, func(t *testing.T) {
got, err := extractInputByteSpeed(td.speed)
if td.err != nil {
if td.err.Error() != err.Error() {
t.Errorf("got %v, want %v", err.Error(), td.err.Error())
}
}
if got != td.expected {
t.Errorf("got %v, want %v", got, td.expected)
}
})
}
}

View file

@ -1,8 +1,10 @@
package ratiospoof package ratiospoof
import ( import (
"errors"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"log"
"math/rand" "math/rand"
"os" "os"
"os/signal" "os/signal"
@ -70,17 +72,17 @@ func NewRatioSpoofState(input input.InputArgs, torrentClient TorrentClientEmulat
torrentInfo, err := bencode.TorrentDictParse(dat) torrentInfo, err := bencode.TorrentDictParse(dat)
if err != nil { if err != nil {
panic(err) return nil, errors.New("failed to parse the torrent file")
} }
httpTracker, err := tracker.NewHttpTracker(torrentInfo, changeTimerCh) httpTracker, err := tracker.NewHttpTracker(torrentInfo, changeTimerCh)
if err != nil { if err != nil {
panic(err) return nil, err
} }
inputParsed, err := input.ParseInput(torrentInfo) inputParsed, err := input.ParseInput(torrentInfo)
if err != nil { if err != nil {
panic(err) return nil, err
} }
return &RatioSpoof{ return &RatioSpoof{
@ -108,6 +110,8 @@ func (R *RatioSpoof) gracefullyExit() {
R.Status = "stopped" R.Status = "stopped"
R.NumWant = 0 R.NumWant = 0
R.fireAnnounce(false) R.fireAnnounce(false)
fmt.Printf("Gracefully exited successfully.\n")
} }
func (R *RatioSpoof) Run() { func (R *RatioSpoof) Run() {
@ -150,7 +154,7 @@ func (R *RatioSpoof) addAnnounce(currentDownloaded, currentUploaded, currentLeft
R.AnnounceCount++ R.AnnounceCount++
R.AnnounceHistory.pushValueHistory(AnnounceEntry{Count: R.AnnounceCount, Downloaded: currentDownloaded, Uploaded: currentUploaded, Left: currentLeft, PercentDownloaded: percentDownloaded}) R.AnnounceHistory.pushValueHistory(AnnounceEntry{Count: R.AnnounceCount, Downloaded: currentDownloaded, Uploaded: currentUploaded, Left: currentLeft, PercentDownloaded: percentDownloaded})
} }
func (R *RatioSpoof) fireAnnounce(retry bool) { func (R *RatioSpoof) fireAnnounce(retry bool) error {
lastAnnounce := R.AnnounceHistory.Back().(AnnounceEntry) lastAnnounce := R.AnnounceHistory.Back().(AnnounceEntry)
replacer := strings.NewReplacer("{infohash}", R.TorrentInfo.InfoHashURLEncoded, replacer := strings.NewReplacer("{infohash}", R.TorrentInfo.InfoHashURLEncoded,
"{port}", fmt.Sprint(R.Input.Port), "{port}", fmt.Sprint(R.Input.Port),
@ -162,12 +166,16 @@ func (R *RatioSpoof) fireAnnounce(retry bool) {
"{event}", R.Status, "{event}", R.Status,
"{numwant}", fmt.Sprint(R.NumWant)) "{numwant}", fmt.Sprint(R.NumWant))
query := replacer.Replace(R.BitTorrentClient.Query()) query := replacer.Replace(R.BitTorrentClient.Query())
trackerResp := R.Tracker.Announce(query, R.BitTorrentClient.Headers(), retry, R.timerUpdateCh) trackerResp, err := R.Tracker.Announce(query, R.BitTorrentClient.Headers(), retry, R.timerUpdateCh)
if err != nil {
log.Fatalf("failed to reach the tracker:\n%s ", err.Error())
}
if trackerResp != nil { if trackerResp != nil {
R.updateSeedersAndLeechers(*trackerResp) R.updateSeedersAndLeechers(*trackerResp)
R.updateInterval(*trackerResp) R.updateInterval(*trackerResp)
} }
return nil
} }
func (R *RatioSpoof) generateNextAnnounce() { func (R *RatioSpoof) generateNextAnnounce() {
R.timerUpdateCh <- R.AnnounceInterval R.timerUpdateCh <- R.AnnounceInterval

View file

@ -46,42 +46,37 @@ func (T *HttpTracker) SwapFirst(currentIdx int) {
T.Urls[currentIdx] = aux T.Urls[currentIdx] = aux
} }
func (T *HttpTracker) Announce(query string, headers map[string]string, retry bool, timerUpdateChannel chan<- int) *TrackerResponse { func (T *HttpTracker) Announce(query string, headers map[string]string, retry bool, timerUpdateChannel chan<- int) (*TrackerResponse, error) {
var trackerResp *TrackerResponse defer func() {
T.RetryAttempt = 0
}()
if retry { if retry {
retryDelay := 30 * time.Second retryDelay := 30 * time.Second
for { for {
exit := false trackerResp, err := T.tryMakeRequest(query, headers)
func() { if err != nil {
defer func() { timerUpdateChannel <- int(retryDelay.Seconds())
if err := recover(); err != nil { T.RetryAttempt++
timerUpdateChannel <- int(retryDelay.Seconds()) time.Sleep(retryDelay)
T.RetryAttempt++ retryDelay *= 2
time.Sleep(retryDelay) if retryDelay.Seconds() > 900 {
retryDelay *= 2 retryDelay = 900
if retryDelay.Seconds() > 900 { }
retryDelay = 900 continue
}
}
}()
trackerResp = T.tryMakeRequest(query, headers)
exit = true
}()
if exit {
break
} }
return trackerResp, nil
} }
} else { } else {
trackerResp = T.tryMakeRequest(query, headers) resp, err := T.tryMakeRequest(query, headers)
if err != nil {
return nil, err
}
return resp, nil
} }
T.RetryAttempt = 0
return trackerResp
} }
func (t *HttpTracker) tryMakeRequest(query string, headers map[string]string) *TrackerResponse { func (t *HttpTracker) tryMakeRequest(query string, headers map[string]string) (*TrackerResponse, error) {
for idx, baseUrl := range t.Urls { for idx, baseUrl := range t.Urls {
completeURL := buildFullUrl(baseUrl, query) completeURL := buildFullUrl(baseUrl, query)
t.LastAnounceRequest = completeURL t.LastAnounceRequest = completeURL
@ -94,7 +89,7 @@ func (t *HttpTracker) tryMakeRequest(query string, headers map[string]string) *T
if resp.StatusCode == http.StatusOK { if resp.StatusCode == http.StatusOK {
bytesR, _ := ioutil.ReadAll(resp.Body) bytesR, _ := ioutil.ReadAll(resp.Body)
if len(bytesR) == 0 { if len(bytesR) == 0 {
return nil continue
} }
mimeType := http.DetectContentType(bytesR) mimeType := http.DetectContentType(bytesR)
if mimeType == "application/x-gzip" { if mimeType == "application/x-gzip" {
@ -103,17 +98,24 @@ func (t *HttpTracker) tryMakeRequest(query string, headers map[string]string) *T
gzipReader.Close() gzipReader.Close()
} }
t.LastTackerResponse = string(bytesR) t.LastTackerResponse = string(bytesR)
decodedResp := bencode.Decode(bytesR) decodedResp, err := bencode.Decode(bytesR)
if err != nil {
continue
}
ret, err := extractTrackerResponse(decodedResp)
if err != nil {
continue
}
if idx != 0 { if idx != 0 {
t.SwapFirst(idx) t.SwapFirst(idx)
} }
ret := extractTrackerResponse(decodedResp)
return &ret return &ret, nil
} }
resp.Body.Close() resp.Body.Close()
} }
} }
panic("Connection error with the tracker") return nil, errors.New("Connection error with the tracker")
} }
@ -124,15 +126,15 @@ func buildFullUrl(baseurl, query string) string {
return baseurl + "?" + strings.TrimLeft(query, "?") return baseurl + "?" + strings.TrimLeft(query, "?")
} }
func extractTrackerResponse(datatrackerResponse map[string]interface{}) TrackerResponse { func extractTrackerResponse(datatrackerResponse map[string]interface{}) (TrackerResponse, error) {
var result TrackerResponse var result TrackerResponse
if v, ok := datatrackerResponse["failure reason"].(string); ok && len(v) > 0 { if v, ok := datatrackerResponse["failure reason"].(string); ok && len(v) > 0 {
panic(errors.New(v)) return result, errors.New(v)
} }
result.MinInterval, _ = datatrackerResponse["min interval"].(int) result.MinInterval, _ = datatrackerResponse["min interval"].(int)
result.Interval, _ = datatrackerResponse["interval"].(int) result.Interval, _ = datatrackerResponse["interval"].(int)
result.Seeders, _ = datatrackerResponse["complete"].(int) result.Seeders, _ = datatrackerResponse["complete"].(int)
result.Leechers, _ = datatrackerResponse["incomplete"].(int) result.Leechers, _ = datatrackerResponse["incomplete"].(int)
return result return result, nil
} }