mirror of
https://github.com/ap-pauloafonso/ratio-spoof.git
synced 2026-04-20 16:12:18 +00:00
Compare commits
17 commits
ratio-spoo
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
032536eda4 | ||
|
|
6c69b9ec45 | ||
|
|
662252950f | ||
|
|
4cd38be3ec | ||
|
|
6c1fb14a25 | ||
|
|
5f5a6195b5 | ||
|
|
c2146e3a31 | ||
|
|
986583c677 | ||
|
|
1544c0d568 | ||
|
|
5dd3724a53 | ||
|
|
00a239e02e | ||
|
|
aabcc229d1 | ||
|
|
c94fd60f77 | ||
|
|
2bbf97d854 | ||
|
|
6cb7064828 | ||
|
|
4fd2969d82 | ||
|
|
8a630643bb |
32 changed files with 355 additions and 342 deletions
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
|
|
@ -16,7 +16,7 @@ jobs:
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v2
|
uses: actions/setup-go@v2
|
||||||
with:
|
with:
|
||||||
go-version: 1.16
|
go-version: '1.20'
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: go build -v ./...
|
run: go build -v ./...
|
||||||
|
|
|
||||||
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -131,4 +131,5 @@ dmypy.json
|
||||||
#vscode folder
|
#vscode folder
|
||||||
/.vscode
|
/.vscode
|
||||||
|
|
||||||
out/
|
out/
|
||||||
|
.idea/
|
||||||
10
Makefile
10
Makefile
|
|
@ -1,15 +1,15 @@
|
||||||
test:
|
test:
|
||||||
go test ./... --cover
|
go test ./... -count=1 --cover
|
||||||
|
|
||||||
torrent-test:
|
torrent-test:
|
||||||
go run cmd/main.go -c qbit-4.3.3 -t internal/bencode/torrent_files_test/Fedora-Workstation-Live-x86_64-33.torrent -d 0% -ds 100kbps -u 0% -us 100kbps -debug
|
go run main.go -c qbit-4.3.3 -t bencode/torrent_files_test/debian-12.0.0-amd64-DVD-1.iso.torrent -d 0% -ds 100kbps -u 0% -us 100kbps
|
||||||
|
|
||||||
release:
|
release:
|
||||||
@if test -z "$(rsversion)"; then echo "usage: make release rsversion=v1.2"; exit 1; fi
|
@if test -z "$(rsversion)"; then echo "usage: make release rsversion=v1.2"; exit 1; fi
|
||||||
rm -rf ./out
|
rm -rf ./out
|
||||||
|
|
||||||
env GOOS=darwin GOARCH=amd64 go build -v -o ./out/mac/ratio-spoof github.com/ap-pauloafonso/ratio-spoof/cmd
|
env GOOS=darwin GOARCH=amd64 go build -v -o ./out/mac/ratio-spoof .
|
||||||
env GOOS=linux GOARCH=amd64 go build -v -o ./out/linux/ratio-spoof github.com/ap-pauloafonso/ratio-spoof/cmd
|
env GOOS=linux GOARCH=amd64 go build -v -o ./out/linux/ratio-spoof .
|
||||||
env GOOS=windows GOARCH=amd64 go build -v -o ./out/windows/ratio-spoof.exe github.com/ap-pauloafonso/ratio-spoof/cmd
|
env GOOS=windows GOARCH=amd64 go build -v -o ./out/windows/ratio-spoof.exe .
|
||||||
|
|
||||||
cd out/ ; zip ratio-spoof-$(rsversion)\(linux-mac-windows\).zip -r .
|
cd out/ ; zip ratio-spoof-$(rsversion)\(linux-mac-windows\).zip -r .
|
||||||
13
README.md
13
README.md
|
|
@ -4,9 +4,10 @@ Ratio-spoof is a cross-platform, free and open source tool to spoof the download
|
||||||

|

|
||||||
|
|
||||||
## Motivation
|
## Motivation
|
||||||
Here in brazil, not everybody has a great upload speed, and most of the private trackers requires a ratio to be greater than or equal to 1 (e.g. if you downloaded 1gb you must upload 1gb as well) in order to survive. Plus, i have always been fascinated by the bittorrent protocol, [i even made a bittorrent webclient to learn a bit about it ](https://github.com/ap-pauloafonso/rwTorrent) so with the current global covid-19 lockdown i got some free time and decided to code my own simple cli tool to spoof bittorrent trackers.
|
Here in Brazil, not everybody has a great upload speed, and most private trackers require a ratio greater than or equal to 1. For example, if you downloaded 1GB, you must also upload 1GB in order to survive. Additionally, I have always been fascinated by the BitTorrent protocol. In fact, [I even made a BitTorrent web client to learn more about it](https://github.com/ap-pauloafonso/rwTorrent). So, if you have a bad internet connection, feel free to use this tool. Otherwise, please consider seeding the files with a real torrent client.
|
||||||
|
|
||||||
## How does it works?
|
## How does it work?
|
||||||
|

|
||||||
Bittorrent protocol works in such a way that there is no way that a tracker knows how much certain peer have downloaded or uploaded, so the tracker depends on the peer itself telling the amounts.
|
Bittorrent protocol works in such a way that there is no way that a tracker knows how much certain peer have downloaded or uploaded, so the tracker depends on the peer itself telling the amounts.
|
||||||
|
|
||||||
Ratio-spoof acts like a normal bittorrent client but without downloading or uploading anything, in fact it just tricks the tracker pretending that.
|
Ratio-spoof acts like a normal bittorrent client but without downloading or uploading anything, in fact it just tricks the tracker pretending that.
|
||||||
|
|
@ -45,14 +46,14 @@ required arguments:
|
||||||
* Will start "downloading" with the initial value of 2gb downloaded if possible at 500kbps speed until it reaches 100% mark.
|
* Will start "downloading" with the initial value of 2gb downloaded if possible at 500kbps speed until it reaches 100% mark.
|
||||||
* Will start "uploading" with the initial value of 1gb uplodead at 1024kbps (aka 1mb/s) indefinitely.
|
* Will start "uploading" with the initial value of 1gb uplodead at 1024kbps (aka 1mb/s) indefinitely.
|
||||||
|
|
||||||
## Will i get caught using it ?
|
## Will I get caught using it ?
|
||||||
Depends wether you use it carefuly, Its a hard task to catch cheaters, but if you start uploading crazy amounts out of nowhere or seeding something with no active leecher on the swarm you may be in risk.
|
Depends on whether you use it carefully, It's a hard task to catch cheaters, but if you start uploading crazy amounts out of nowhere or seeding something with no active leecher on the swarm you may be in risk.
|
||||||
|
|
||||||
## Bittorrent client supported
|
## Bittorrent client supported
|
||||||
The currently emulation is hard coded to be a popular and accepted client qbittorrent v4.0.3.
|
The default client emulation is qbittorrent v4.0.3, however you can change it by using the -c argument
|
||||||
|
|
||||||
## Resources
|
## Resources
|
||||||
http://www.bittorrent.org/beps/bep_0003.html
|
http://www.bittorrent.org/beps/bep_0003.html
|
||||||
|
|
||||||
https://wiki.theory.org/index.php/BitTorrentSpecification
|
https://wiki.theory.org/BitTorrentSpecification
|
||||||
|
|
||||||
|
|
|
||||||
BIN
assets/how-it-works.png
Normal file
BIN
assets/how-it-works.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 108 KiB |
|
|
@ -63,8 +63,8 @@ func TorrentDictParse(dat []byte) (torrent *TorrentInfo, err error) {
|
||||||
}, err
|
}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (T *torrentDict) extractInfoHashURLEncoded(rawData []byte) string {
|
func (t *torrentDict) extractInfoHashURLEncoded(rawData []byte) string {
|
||||||
byteOffsets := T.resultMap["info"].(map[string]interface{})["byte_offsets"].([]int)
|
byteOffsets := t.resultMap["info"].(map[string]interface{})["byte_offsets"].([]int)
|
||||||
h := sha1.New()
|
h := sha1.New()
|
||||||
h.Write([]byte(rawData[byteOffsets[0]:byteOffsets[1]]))
|
h.Write([]byte(rawData[byteOffsets[0]:byteOffsets[1]]))
|
||||||
ret := h.Sum(nil)
|
ret := h.Sum(nil)
|
||||||
|
|
@ -80,28 +80,28 @@ func (T *torrentDict) extractInfoHashURLEncoded(rawData []byte) string {
|
||||||
return buf.String()
|
return buf.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (T *torrentDict) extractTotalSize() int {
|
func (t *torrentDict) extractTotalSize() int {
|
||||||
if value, ok := T.resultMap[torrentInfoKey].(map[string]interface{})[torrentLengthKey]; ok {
|
if value, ok := t.resultMap[torrentInfoKey].(map[string]interface{})[torrentLengthKey]; ok {
|
||||||
return value.(int)
|
return value.(int)
|
||||||
}
|
}
|
||||||
var total int
|
var total int
|
||||||
|
|
||||||
for _, file := range T.resultMap[torrentInfoKey].(map[string]interface{})[torrentFilesKey].([]interface{}) {
|
for _, file := range t.resultMap[torrentInfoKey].(map[string]interface{})[torrentFilesKey].([]interface{}) {
|
||||||
total += file.(map[string]interface{})[torrentLengthKey].(int)
|
total += file.(map[string]interface{})[torrentLengthKey].(int)
|
||||||
}
|
}
|
||||||
return total
|
return total
|
||||||
}
|
}
|
||||||
|
|
||||||
func (T *torrentDict) extractTrackerInfo() *TrackerInfo {
|
func (t *torrentDict) extractTrackerInfo() *TrackerInfo {
|
||||||
uniqueUrls := make(map[string]int)
|
uniqueUrls := make(map[string]int)
|
||||||
currentCount := 0
|
currentCount := 0
|
||||||
if main, ok := T.resultMap[mainAnnounceKey]; ok {
|
if main, ok := t.resultMap[mainAnnounceKey]; ok {
|
||||||
if _, found := uniqueUrls[main.(string)]; !found {
|
if _, found := uniqueUrls[main.(string)]; !found {
|
||||||
uniqueUrls[main.(string)] = currentCount
|
uniqueUrls[main.(string)] = currentCount
|
||||||
currentCount++
|
currentCount++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if list, ok := T.resultMap[announceListKey]; ok {
|
if list, ok := t.resultMap[announceListKey]; ok {
|
||||||
for _, innerList := range list.([]interface{}) {
|
for _, innerList := range list.([]interface{}) {
|
||||||
for _, item := range innerList.([]interface{}) {
|
for _, item := range innerList.([]interface{}) {
|
||||||
if _, found := uniqueUrls[item.(string)]; !found {
|
if _, found := uniqueUrls[item.(string)]; !found {
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
package bencode
|
package bencode
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
"log"
|
||||||
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
@ -99,13 +99,13 @@ func TestMapParse(T *testing.T) {
|
||||||
|
|
||||||
func TestDecode(T *testing.T) {
|
func TestDecode(T *testing.T) {
|
||||||
|
|
||||||
files, err := ioutil.ReadDir("./torrent_files_test")
|
files, err := os.ReadDir("./torrent_files_test")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
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, _ := os.ReadFile("./torrent_files_test/" + f.Name())
|
||||||
result, _ := Decode(data)
|
result, _ := Decode(data)
|
||||||
t.Log(result["info"].(map[string]interface{})["name"])
|
t.Log(result["info"].(map[string]interface{})["name"])
|
||||||
})
|
})
|
||||||
BIN
bencode/torrent_files_test/debian-12.0.0-amd64-DVD-1.iso.torrent
Normal file
BIN
bencode/torrent_files_test/debian-12.0.0-amd64-DVD-1.iso.torrent
Normal file
Binary file not shown.
|
|
@ -3,9 +3,8 @@ package emulation
|
||||||
import (
|
import (
|
||||||
"embed"
|
"embed"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"io/ioutil"
|
generator2 "github.com/ap-pauloafonso/ratio-spoof/generator"
|
||||||
|
"io"
|
||||||
"github.com/ap-pauloafonso/ratio-spoof/internal/generator"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type ClientInfo struct {
|
type ClientInfo struct {
|
||||||
|
|
@ -52,17 +51,17 @@ func NewEmulation(code string) (*Emulation, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
peerG, err := generator.NewRegexPeerIdGenerator(c.PeerID.Regex)
|
peerG, err := generator2.NewRegexPeerIdGenerator(c.PeerID.Regex)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
keyG, err := generator.NewDefaultKeyGenerator()
|
keyG, err := generator2.NewDefaultKeyGenerator()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
roudingG, err := generator.NewDefaultRoudingGenerator()
|
roudingG, err := generator2.NewDefaultRoudingGenerator()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -83,7 +82,7 @@ func extractClient(code string) (*ClientInfo, error) {
|
||||||
}
|
}
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
|
|
||||||
bytes, err := ioutil.ReadAll(f)
|
bytes, err := io.ReadAll(f)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -2,7 +2,7 @@ package generator
|
||||||
|
|
||||||
import "testing"
|
import "testing"
|
||||||
|
|
||||||
func TestNextAmountReport(t *testing.T) {
|
func TestDefaultRounding(t *testing.T) {
|
||||||
r, _ := NewDefaultRoudingGenerator()
|
r, _ := NewDefaultRoudingGenerator()
|
||||||
|
|
||||||
d, u, l := r.Round(656497856, 46479878, 7879879, 1024)
|
d, u, l := r.Round(656497856, 46479878, 7879879, 1024)
|
||||||
9
go.mod
9
go.mod
|
|
@ -1,13 +1,16 @@
|
||||||
module github.com/ap-pauloafonso/ratio-spoof
|
module github.com/ap-pauloafonso/ratio-spoof
|
||||||
|
|
||||||
go 1.16
|
go 1.20
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/gammazero/deque v0.0.0-20201010052221-3932da5530cc
|
github.com/gammazero/deque v0.0.0-20201010052221-3932da5530cc
|
||||||
github.com/google/gxui v0.0.0-20151028112939-f85e0a97b3a4 // indirect
|
|
||||||
github.com/olekukonko/ts v0.0.0-20171002115256-78ecb04241c0
|
github.com/olekukonko/ts v0.0.0-20171002115256-78ecb04241c0
|
||||||
|
github.com/zach-klippenstein/goregen v0.0.0-20160303162051-795b5e3961ea
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/google/gxui v0.0.0-20151028112939-f85e0a97b3a4 // indirect
|
||||||
github.com/smartystreets/goconvey v1.6.4 // indirect
|
github.com/smartystreets/goconvey v1.6.4 // indirect
|
||||||
github.com/stretchr/testify v1.7.0 // indirect
|
github.com/stretchr/testify v1.7.0 // indirect
|
||||||
github.com/zach-klippenstein/goregen v0.0.0-20160303162051-795b5e3961ea
|
|
||||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 // indirect
|
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 // indirect
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -3,11 +3,10 @@ package input
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/ap-pauloafonso/ratio-spoof/bencode"
|
||||||
"math"
|
"math"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/ap-pauloafonso/ratio-spoof/internal/bencode"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
@ -40,25 +39,25 @@ type InputParsed struct {
|
||||||
var validInitialSufixes = [...]string{"%", "b", "kb", "mb", "gb", "tb"}
|
var validInitialSufixes = [...]string{"%", "b", "kb", "mb", "gb", "tb"}
|
||||||
var validSpeedSufixes = [...]string{"kbps", "mbps"}
|
var validSpeedSufixes = [...]string{"kbps", "mbps"}
|
||||||
|
|
||||||
func (I *InputArgs) ParseInput(torrentInfo *bencode.TorrentInfo) (*InputParsed, error) {
|
func (i *InputArgs) ParseInput(torrentInfo *bencode.TorrentInfo) (*InputParsed, error) {
|
||||||
downloaded, err := extractInputInitialByteCount(I.InitialDownloaded, torrentInfo.TotalSize, true)
|
downloaded, err := extractInputInitialByteCount(i.InitialDownloaded, torrentInfo.TotalSize, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
uploaded, err := extractInputInitialByteCount(I.InitialUploaded, torrentInfo.TotalSize, false)
|
uploaded, err := extractInputInitialByteCount(i.InitialUploaded, torrentInfo.TotalSize, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
downloadSpeed, err := extractInputByteSpeed(I.DownloadSpeed)
|
downloadSpeed, err := extractInputByteSpeed(i.DownloadSpeed)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
uploadSpeed, err := extractInputByteSpeed(I.UploadSpeed)
|
uploadSpeed, err := extractInputByteSpeed(i.UploadSpeed)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if I.Port < minPortNumber || I.Port > maxPortNumber {
|
if i.Port < minPortNumber || i.Port > maxPortNumber {
|
||||||
return nil, errors.New(fmt.Sprint("port number must be between %i and %i", minPortNumber, maxPortNumber))
|
return nil, errors.New(fmt.Sprint("port number must be between %i and %i", minPortNumber, maxPortNumber))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -66,8 +65,8 @@ func (I *InputArgs) ParseInput(torrentInfo *bencode.TorrentInfo) (*InputParsed,
|
||||||
DownloadSpeed: downloadSpeed,
|
DownloadSpeed: downloadSpeed,
|
||||||
InitialUploaded: uploaded,
|
InitialUploaded: uploaded,
|
||||||
UploadSpeed: uploadSpeed,
|
UploadSpeed: uploadSpeed,
|
||||||
Debug: I.Debug,
|
Debug: i.Debug,
|
||||||
Port: I.Port,
|
Port: i.Port,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -95,7 +94,7 @@ 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
|
// Takes an dirty speed input and returns the bytes per second based on the suffixes
|
||||||
// example 1kbps(string) > 1024 bytes per second (int)
|
// 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)
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -1,227 +0,0 @@
|
||||||
package ratiospoof
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
|
||||||
"math/rand"
|
|
||||||
"os"
|
|
||||||
"os/signal"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
"syscall"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/ap-pauloafonso/ratio-spoof/internal/bencode"
|
|
||||||
"github.com/ap-pauloafonso/ratio-spoof/internal/emulation"
|
|
||||||
"github.com/ap-pauloafonso/ratio-spoof/internal/input"
|
|
||||||
"github.com/ap-pauloafonso/ratio-spoof/internal/tracker"
|
|
||||||
"github.com/gammazero/deque"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
maxAnnounceHistory = 10
|
|
||||||
)
|
|
||||||
|
|
||||||
type RatioSpoof struct {
|
|
||||||
mutex *sync.Mutex
|
|
||||||
TorrentInfo *bencode.TorrentInfo
|
|
||||||
Input *input.InputParsed
|
|
||||||
Tracker *tracker.HttpTracker
|
|
||||||
BitTorrentClient *emulation.Emulation
|
|
||||||
AnnounceInterval int
|
|
||||||
EstimatedTimeToAnnounce time.Time
|
|
||||||
EstimatedTimeToAnnounceUpdateCh chan int
|
|
||||||
NumWant int
|
|
||||||
Seeders int
|
|
||||||
Leechers int
|
|
||||||
AnnounceCount int
|
|
||||||
Status string
|
|
||||||
AnnounceHistory announceHistory
|
|
||||||
StopPrintCH chan interface{}
|
|
||||||
}
|
|
||||||
|
|
||||||
type AnnounceEntry struct {
|
|
||||||
Count int
|
|
||||||
Downloaded int
|
|
||||||
PercentDownloaded float32
|
|
||||||
Uploaded int
|
|
||||||
Left int
|
|
||||||
}
|
|
||||||
|
|
||||||
type announceHistory struct {
|
|
||||||
deque.Deque
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewRatioSpoofState(input input.InputArgs) (*RatioSpoof, error) {
|
|
||||||
EstimatedTimeToAnnounceUpdateCh := make(chan int)
|
|
||||||
stopPrintCh := make(chan interface{})
|
|
||||||
dat, err := ioutil.ReadFile(input.TorrentPath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
client, err := emulation.NewEmulation(input.Client)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.New("Error building the emulated client with the code")
|
|
||||||
}
|
|
||||||
|
|
||||||
torrentInfo, err := bencode.TorrentDictParse(dat)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.New("failed to parse the torrent file")
|
|
||||||
}
|
|
||||||
|
|
||||||
httpTracker, err := tracker.NewHttpTracker(torrentInfo)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
inputParsed, err := input.ParseInput(torrentInfo)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &RatioSpoof{
|
|
||||||
BitTorrentClient: client,
|
|
||||||
TorrentInfo: torrentInfo,
|
|
||||||
Tracker: httpTracker,
|
|
||||||
Input: inputParsed,
|
|
||||||
NumWant: 200,
|
|
||||||
Status: "started",
|
|
||||||
mutex: &sync.Mutex{},
|
|
||||||
StopPrintCH: stopPrintCh,
|
|
||||||
EstimatedTimeToAnnounceUpdateCh: EstimatedTimeToAnnounceUpdateCh,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (A *announceHistory) pushValueHistory(value AnnounceEntry) {
|
|
||||||
if A.Len() >= maxAnnounceHistory {
|
|
||||||
A.PopFront()
|
|
||||||
}
|
|
||||||
A.PushBack(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (R *RatioSpoof) gracefullyExit() {
|
|
||||||
fmt.Printf("\nGracefully exiting...\n")
|
|
||||||
R.Status = "stopped"
|
|
||||||
R.NumWant = 0
|
|
||||||
R.fireAnnounce(false)
|
|
||||||
fmt.Printf("Gracefully exited successfully.\n")
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func (R *RatioSpoof) Run() {
|
|
||||||
rand.Seed(time.Now().UnixNano())
|
|
||||||
sigCh := make(chan os.Signal)
|
|
||||||
|
|
||||||
signal.Notify(sigCh, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
|
|
||||||
R.firstAnnounce()
|
|
||||||
go R.updateEstimatedTimeToAnnounceListener()
|
|
||||||
go func() {
|
|
||||||
for {
|
|
||||||
R.generateNextAnnounce()
|
|
||||||
time.Sleep(time.Duration(R.AnnounceInterval) * time.Second)
|
|
||||||
R.fireAnnounce(true)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
<-sigCh
|
|
||||||
R.StopPrintCH <- "exit print"
|
|
||||||
R.gracefullyExit()
|
|
||||||
}
|
|
||||||
func (R *RatioSpoof) firstAnnounce() {
|
|
||||||
R.addAnnounce(R.Input.InitialDownloaded, R.Input.InitialUploaded, calculateBytesLeft(R.Input.InitialDownloaded, R.TorrentInfo.TotalSize), (float32(R.Input.InitialDownloaded)/float32(R.TorrentInfo.TotalSize))*100)
|
|
||||||
R.fireAnnounce(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (R *RatioSpoof) updateInterval(interval int) {
|
|
||||||
if interval > 0 {
|
|
||||||
R.AnnounceInterval = interval
|
|
||||||
} else {
|
|
||||||
R.AnnounceInterval = 1800
|
|
||||||
}
|
|
||||||
R.updateEstimatedTimeToAnnounce(R.AnnounceInterval)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (R *RatioSpoof) updateEstimatedTimeToAnnounce(interval int) {
|
|
||||||
R.mutex.Lock()
|
|
||||||
defer R.mutex.Unlock()
|
|
||||||
R.EstimatedTimeToAnnounce = time.Now().Add(time.Duration(interval) * time.Second)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (R *RatioSpoof) updateEstimatedTimeToAnnounceListener() {
|
|
||||||
for {
|
|
||||||
interval := <-R.EstimatedTimeToAnnounceUpdateCh
|
|
||||||
R.updateEstimatedTimeToAnnounce(interval)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (R *RatioSpoof) updateSeedersAndLeechers(resp tracker.TrackerResponse) {
|
|
||||||
R.Seeders = resp.Seeders
|
|
||||||
R.Leechers = resp.Leechers
|
|
||||||
}
|
|
||||||
func (R *RatioSpoof) addAnnounce(currentDownloaded, currentUploaded, currentLeft int, percentDownloaded float32) {
|
|
||||||
R.AnnounceCount++
|
|
||||||
R.AnnounceHistory.pushValueHistory(AnnounceEntry{Count: R.AnnounceCount, Downloaded: currentDownloaded, Uploaded: currentUploaded, Left: currentLeft, PercentDownloaded: percentDownloaded})
|
|
||||||
}
|
|
||||||
func (R *RatioSpoof) fireAnnounce(retry bool) error {
|
|
||||||
lastAnnounce := R.AnnounceHistory.Back().(AnnounceEntry)
|
|
||||||
replacer := strings.NewReplacer("{infohash}", R.TorrentInfo.InfoHashURLEncoded,
|
|
||||||
"{port}", fmt.Sprint(R.Input.Port),
|
|
||||||
"{peerid}", R.BitTorrentClient.PeerId(),
|
|
||||||
"{uploaded}", fmt.Sprint(lastAnnounce.Uploaded),
|
|
||||||
"{downloaded}", fmt.Sprint(lastAnnounce.Downloaded),
|
|
||||||
"{left}", fmt.Sprint(lastAnnounce.Left),
|
|
||||||
"{key}", R.BitTorrentClient.Key(),
|
|
||||||
"{event}", R.Status,
|
|
||||||
"{numwant}", fmt.Sprint(R.NumWant))
|
|
||||||
query := replacer.Replace(R.BitTorrentClient.Query)
|
|
||||||
trackerResp, err := R.Tracker.Announce(query, R.BitTorrentClient.Headers, retry, R.EstimatedTimeToAnnounceUpdateCh)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("failed to reach the tracker:\n%s ", err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
if trackerResp != nil {
|
|
||||||
R.updateSeedersAndLeechers(*trackerResp)
|
|
||||||
R.updateInterval(trackerResp.Interval)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
func (R *RatioSpoof) generateNextAnnounce() {
|
|
||||||
lastAnnounce := R.AnnounceHistory.Back().(AnnounceEntry)
|
|
||||||
currentDownloaded := lastAnnounce.Downloaded
|
|
||||||
var downloadCandidate int
|
|
||||||
|
|
||||||
if currentDownloaded < R.TorrentInfo.TotalSize {
|
|
||||||
downloadCandidate = calculateNextTotalSizeByte(R.Input.DownloadSpeed, currentDownloaded, R.TorrentInfo.PieceSize, R.AnnounceInterval, R.TorrentInfo.TotalSize)
|
|
||||||
} else {
|
|
||||||
downloadCandidate = R.TorrentInfo.TotalSize
|
|
||||||
}
|
|
||||||
|
|
||||||
currentUploaded := lastAnnounce.Uploaded
|
|
||||||
uploadCandidate := calculateNextTotalSizeByte(R.Input.UploadSpeed, currentUploaded, R.TorrentInfo.PieceSize, R.AnnounceInterval, 0)
|
|
||||||
|
|
||||||
leftCandidate := calculateBytesLeft(downloadCandidate, R.TorrentInfo.TotalSize)
|
|
||||||
|
|
||||||
d, u, l := R.BitTorrentClient.Round(downloadCandidate, uploadCandidate, leftCandidate, R.TorrentInfo.PieceSize)
|
|
||||||
|
|
||||||
R.addAnnounce(d, u, l, (float32(d)/float32(R.TorrentInfo.TotalSize))*100)
|
|
||||||
}
|
|
||||||
|
|
||||||
func calculateNextTotalSizeByte(speedBytePerSecond, currentByte, pieceSizeByte, seconds, limitTotalBytes int) int {
|
|
||||||
if speedBytePerSecond == 0 {
|
|
||||||
return currentByte
|
|
||||||
}
|
|
||||||
totalCandidate := currentByte + (speedBytePerSecond * seconds)
|
|
||||||
randomPieces := rand.Intn(10-1) + 1
|
|
||||||
totalCandidate = totalCandidate + (pieceSizeByte * randomPieces)
|
|
||||||
|
|
||||||
if limitTotalBytes != 0 && totalCandidate > limitTotalBytes {
|
|
||||||
return limitTotalBytes
|
|
||||||
}
|
|
||||||
return totalCandidate
|
|
||||||
}
|
|
||||||
|
|
||||||
func calculateBytesLeft(currentBytes, totalBytes int) int {
|
|
||||||
return totalBytes - currentBytes
|
|
||||||
}
|
|
||||||
|
|
@ -1,36 +0,0 @@
|
||||||
package ratiospoof
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func assertAreEqual(t *testing.T, got, want interface{}) {
|
|
||||||
t.Helper()
|
|
||||||
if got != want {
|
|
||||||
t.Errorf("\ngot : %v\nwant: %v", got, want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestClculateNextTotalSizeByte(T *testing.T) {
|
|
||||||
|
|
||||||
got := calculateNextTotalSizeByte(100*1024, 0, 512, 30, 87979879)
|
|
||||||
want := 3075072
|
|
||||||
|
|
||||||
assertAreEqual(T, got, want)
|
|
||||||
}
|
|
||||||
|
|
||||||
// func TestUrlEncodeInfoHash(T *testing.T) {
|
|
||||||
|
|
||||||
// b, _ := ioutil.ReadFile("")
|
|
||||||
// got := extractInfoHashURLEncoded(b, bencode.Decode(b))
|
|
||||||
// want := "%60N%7d%1f%8b%3a%9bT%d5%fc%ad%d1%27%ab5%02%1c%fb%03%b0"
|
|
||||||
// assertAreEqual(T, got, want)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func TestUrlEncodeInfoHash2(T *testing.T) {
|
|
||||||
|
|
||||||
// b, _ := ioutil.ReadFile("")
|
|
||||||
// got := extractInfoHashURLEncoded(b, bencode.Decode(b))
|
|
||||||
// want := "%02r%fd%fe%bf%fbt%d0%0f%cf%d9%8c%e0%a9%97%f8%08%9b%00%b2"
|
|
||||||
// assertAreEqual(T, got, want)
|
|
||||||
// }
|
|
||||||
|
|
@ -3,12 +3,11 @@ package main
|
||||||
import (
|
import (
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/ap-pauloafonso/ratio-spoof/input"
|
||||||
|
"github.com/ap-pauloafonso/ratio-spoof/printer"
|
||||||
|
"github.com/ap-pauloafonso/ratio-spoof/ratiospoof"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/ap-pauloafonso/ratio-spoof/internal/input"
|
|
||||||
"github.com/ap-pauloafonso/ratio-spoof/internal/printer"
|
|
||||||
"github.com/ap-pauloafonso/ratio-spoof/internal/ratiospoof"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|
@ -2,25 +2,19 @@ package printer
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/ap-pauloafonso/ratio-spoof/ratiospoof"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ap-pauloafonso/ratio-spoof/internal/ratiospoof"
|
|
||||||
"github.com/olekukonko/ts"
|
"github.com/olekukonko/ts"
|
||||||
)
|
)
|
||||||
|
|
||||||
func PrintState(state *ratiospoof.RatioSpoof) {
|
func PrintState(state *ratiospoof.RatioSpoof) {
|
||||||
exit := false
|
|
||||||
go func() {
|
|
||||||
_ = <-state.StopPrintCH
|
|
||||||
exit = true
|
|
||||||
}()
|
|
||||||
|
|
||||||
for {
|
for {
|
||||||
if exit {
|
if !state.Print {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
width := terminalSize()
|
width := terminalSize()
|
||||||
|
|
@ -63,7 +57,7 @@ func PrintState(state *ratiospoof.RatioSpoof) {
|
||||||
}
|
}
|
||||||
lastDequeItem := state.AnnounceHistory.At(state.AnnounceHistory.Len() - 1).(ratiospoof.AnnounceEntry)
|
lastDequeItem := state.AnnounceHistory.At(state.AnnounceHistory.Len() - 1).(ratiospoof.AnnounceEntry)
|
||||||
|
|
||||||
remaining := time.Until(state.EstimatedTimeToAnnounce)
|
remaining := time.Until(state.Tracker.EstimatedTimeToAnnounce)
|
||||||
fmt.Printf("#%v downloaded: %v(%.2f%%) | left: %v | uploaded: %v | next announce in: %v %v\n", lastDequeItem.Count,
|
fmt.Printf("#%v downloaded: %v(%.2f%%) | left: %v | uploaded: %v | next announce in: %v %v\n", lastDequeItem.Count,
|
||||||
humanReadableSize(float64(lastDequeItem.Downloaded)),
|
humanReadableSize(float64(lastDequeItem.Downloaded)),
|
||||||
lastDequeItem.PercentDownloaded,
|
lastDequeItem.PercentDownloaded,
|
||||||
195
ratiospoof/ratiospoof.go
Normal file
195
ratiospoof/ratiospoof.go
Normal file
|
|
@ -0,0 +1,195 @@
|
||||||
|
package ratiospoof
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"github.com/ap-pauloafonso/ratio-spoof/bencode"
|
||||||
|
"github.com/ap-pauloafonso/ratio-spoof/emulation"
|
||||||
|
"github.com/ap-pauloafonso/ratio-spoof/input"
|
||||||
|
"github.com/ap-pauloafonso/ratio-spoof/tracker"
|
||||||
|
"log"
|
||||||
|
"math/rand"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gammazero/deque"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
maxAnnounceHistory = 10
|
||||||
|
)
|
||||||
|
|
||||||
|
type RatioSpoof struct {
|
||||||
|
TorrentInfo *bencode.TorrentInfo
|
||||||
|
Input *input.InputParsed
|
||||||
|
Tracker *tracker.HttpTracker
|
||||||
|
BitTorrentClient *emulation.Emulation
|
||||||
|
AnnounceInterval int
|
||||||
|
NumWant int
|
||||||
|
Seeders int
|
||||||
|
Leechers int
|
||||||
|
AnnounceCount int
|
||||||
|
Status string
|
||||||
|
AnnounceHistory announceHistory
|
||||||
|
Print bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type AnnounceEntry struct {
|
||||||
|
Count int
|
||||||
|
Downloaded int
|
||||||
|
PercentDownloaded float32
|
||||||
|
Uploaded int
|
||||||
|
Left int
|
||||||
|
}
|
||||||
|
|
||||||
|
type announceHistory struct {
|
||||||
|
deque.Deque
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRatioSpoofState(input input.InputArgs) (*RatioSpoof, error) {
|
||||||
|
dat, err := os.ReadFile(input.TorrentPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := emulation.NewEmulation(input.Client)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New("Error building the emulated client with the code")
|
||||||
|
}
|
||||||
|
|
||||||
|
torrentInfo, err := bencode.TorrentDictParse(dat)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New("failed to parse the torrent file")
|
||||||
|
}
|
||||||
|
|
||||||
|
httpTracker, err := tracker.NewHttpTracker(torrentInfo)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
inputParsed, err := input.ParseInput(torrentInfo)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &RatioSpoof{
|
||||||
|
BitTorrentClient: client,
|
||||||
|
TorrentInfo: torrentInfo,
|
||||||
|
Tracker: httpTracker,
|
||||||
|
Input: inputParsed,
|
||||||
|
NumWant: 200,
|
||||||
|
Status: "started",
|
||||||
|
Print: true,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *announceHistory) pushValueHistory(value AnnounceEntry) {
|
||||||
|
if a.Len() >= maxAnnounceHistory {
|
||||||
|
a.PopFront()
|
||||||
|
}
|
||||||
|
a.PushBack(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RatioSpoof) gracefullyExit() {
|
||||||
|
fmt.Printf("\nGracefully exiting...\n")
|
||||||
|
r.Status = "stopped"
|
||||||
|
r.NumWant = 0
|
||||||
|
r.fireAnnounce(false)
|
||||||
|
fmt.Printf("Gracefully exited successfully.\n")
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RatioSpoof) Run() {
|
||||||
|
sigCh := make(chan os.Signal)
|
||||||
|
|
||||||
|
signal.Notify(sigCh, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
|
||||||
|
r.firstAnnounce()
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
r.generateNextAnnounce()
|
||||||
|
time.Sleep(time.Duration(r.AnnounceInterval) * time.Second)
|
||||||
|
r.fireAnnounce(true)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
<-sigCh
|
||||||
|
r.Print = false
|
||||||
|
r.gracefullyExit()
|
||||||
|
}
|
||||||
|
func (r *RatioSpoof) firstAnnounce() {
|
||||||
|
r.addAnnounce(r.Input.InitialDownloaded, r.Input.InitialUploaded, calculateBytesLeft(r.Input.InitialDownloaded, r.TorrentInfo.TotalSize), (float32(r.Input.InitialDownloaded)/float32(r.TorrentInfo.TotalSize))*100)
|
||||||
|
r.fireAnnounce(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RatioSpoof) updateSeedersAndLeechers(resp tracker.TrackerResponse) {
|
||||||
|
r.Seeders = resp.Seeders
|
||||||
|
r.Leechers = resp.Leechers
|
||||||
|
}
|
||||||
|
func (r *RatioSpoof) addAnnounce(currentDownloaded, currentUploaded, currentLeft int, percentDownloaded float32) {
|
||||||
|
r.AnnounceCount++
|
||||||
|
r.AnnounceHistory.pushValueHistory(AnnounceEntry{Count: r.AnnounceCount, Downloaded: currentDownloaded, Uploaded: currentUploaded, Left: currentLeft, PercentDownloaded: percentDownloaded})
|
||||||
|
}
|
||||||
|
func (r *RatioSpoof) fireAnnounce(retry bool) error {
|
||||||
|
lastAnnounce := r.AnnounceHistory.Back().(AnnounceEntry)
|
||||||
|
replacer := strings.NewReplacer("{infohash}", r.TorrentInfo.InfoHashURLEncoded,
|
||||||
|
"{port}", fmt.Sprint(r.Input.Port),
|
||||||
|
"{peerid}", r.BitTorrentClient.PeerId(),
|
||||||
|
"{uploaded}", fmt.Sprint(lastAnnounce.Uploaded),
|
||||||
|
"{downloaded}", fmt.Sprint(lastAnnounce.Downloaded),
|
||||||
|
"{left}", fmt.Sprint(lastAnnounce.Left),
|
||||||
|
"{key}", r.BitTorrentClient.Key(),
|
||||||
|
"{event}", r.Status,
|
||||||
|
"{numwant}", fmt.Sprint(r.NumWant))
|
||||||
|
query := replacer.Replace(r.BitTorrentClient.Query)
|
||||||
|
trackerResp, err := r.Tracker.Announce(query, r.BitTorrentClient.Headers, retry)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("failed to reach the tracker:\n%s ", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if trackerResp != nil {
|
||||||
|
r.updateSeedersAndLeechers(*trackerResp)
|
||||||
|
r.AnnounceInterval = trackerResp.Interval
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (r *RatioSpoof) generateNextAnnounce() {
|
||||||
|
lastAnnounce := r.AnnounceHistory.Back().(AnnounceEntry)
|
||||||
|
currentDownloaded := lastAnnounce.Downloaded
|
||||||
|
var downloadCandidate int
|
||||||
|
|
||||||
|
if currentDownloaded < r.TorrentInfo.TotalSize {
|
||||||
|
randomPiecesDownload := rand.Intn(10-1) + 1
|
||||||
|
downloadCandidate = calculateNextTotalSizeByte(r.Input.DownloadSpeed, currentDownloaded, r.TorrentInfo.PieceSize, r.AnnounceInterval, r.TorrentInfo.TotalSize, randomPiecesDownload)
|
||||||
|
} else {
|
||||||
|
downloadCandidate = r.TorrentInfo.TotalSize
|
||||||
|
}
|
||||||
|
|
||||||
|
currentUploaded := lastAnnounce.Uploaded
|
||||||
|
randomPiecesUpload := rand.Intn(10-1) + 1
|
||||||
|
uploadCandidate := calculateNextTotalSizeByte(r.Input.UploadSpeed, currentUploaded, r.TorrentInfo.PieceSize, r.AnnounceInterval, 0, randomPiecesUpload)
|
||||||
|
|
||||||
|
leftCandidate := calculateBytesLeft(downloadCandidate, r.TorrentInfo.TotalSize)
|
||||||
|
|
||||||
|
d, u, l := r.BitTorrentClient.Round(downloadCandidate, uploadCandidate, leftCandidate, r.TorrentInfo.PieceSize)
|
||||||
|
|
||||||
|
r.addAnnounce(d, u, l, (float32(d)/float32(r.TorrentInfo.TotalSize))*100)
|
||||||
|
}
|
||||||
|
|
||||||
|
func calculateNextTotalSizeByte(speedBytePerSecond, currentByte, pieceSizeByte, seconds, limitTotalBytes, randomPieces int) int {
|
||||||
|
if speedBytePerSecond == 0 {
|
||||||
|
return currentByte
|
||||||
|
}
|
||||||
|
totalCandidate := currentByte + (speedBytePerSecond * seconds)
|
||||||
|
totalCandidate = totalCandidate + (pieceSizeByte * randomPieces)
|
||||||
|
|
||||||
|
if limitTotalBytes != 0 && totalCandidate > limitTotalBytes {
|
||||||
|
return limitTotalBytes
|
||||||
|
}
|
||||||
|
return totalCandidate
|
||||||
|
}
|
||||||
|
|
||||||
|
func calculateBytesLeft(currentBytes, totalBytes int) int {
|
||||||
|
return totalBytes - currentBytes
|
||||||
|
}
|
||||||
15
ratiospoof/ratiospoof_test.go
Normal file
15
ratiospoof/ratiospoof_test.go
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
package ratiospoof
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCalculateNextTotalSizeByte(t *testing.T) {
|
||||||
|
randomPieces := 8
|
||||||
|
got := calculateNextTotalSizeByte(100*1024, 0, 512, 30, 87979879, randomPieces)
|
||||||
|
want := 3076096
|
||||||
|
|
||||||
|
if got != want {
|
||||||
|
t.Errorf("\ngot : %v\nwant: %v", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -4,19 +4,19 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"compress/gzip"
|
"compress/gzip"
|
||||||
"errors"
|
"errors"
|
||||||
"io/ioutil"
|
"github.com/ap-pauloafonso/ratio-spoof/bencode"
|
||||||
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ap-pauloafonso/ratio-spoof/internal/bencode"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type HttpTracker struct {
|
type HttpTracker struct {
|
||||||
Urls []string
|
Urls []string
|
||||||
RetryAttempt int
|
RetryAttempt int
|
||||||
LastAnounceRequest string
|
LastAnounceRequest string
|
||||||
LastTackerResponse string
|
LastTackerResponse string
|
||||||
|
EstimatedTimeToAnnounce time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
type TrackerResponse struct {
|
type TrackerResponse struct {
|
||||||
|
|
@ -40,23 +40,34 @@ func NewHttpTracker(torrentInfo *bencode.TorrentInfo) (*HttpTracker, error) {
|
||||||
return &HttpTracker{Urls: torrentInfo.TrackerInfo.Urls}, nil
|
return &HttpTracker{Urls: torrentInfo.TrackerInfo.Urls}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (T *HttpTracker) SwapFirst(currentIdx int) {
|
func (t *HttpTracker) swapFirst(currentIdx int) {
|
||||||
aux := T.Urls[0]
|
aux := t.Urls[0]
|
||||||
T.Urls[0] = T.Urls[currentIdx]
|
t.Urls[0] = t.Urls[currentIdx]
|
||||||
T.Urls[currentIdx] = aux
|
t.Urls[currentIdx] = aux
|
||||||
}
|
}
|
||||||
|
|
||||||
func (T *HttpTracker) Announce(query string, headers map[string]string, retry bool, estimatedTimeToAnnounceUpdateCh chan<- int) (*TrackerResponse, error) {
|
func (t *HttpTracker) updateEstimatedTimeToAnnounce(interval int) {
|
||||||
|
t.EstimatedTimeToAnnounce = time.Now().Add(time.Duration(interval) * time.Second)
|
||||||
|
}
|
||||||
|
func (t *HttpTracker) handleSuccessfulResponse(resp *TrackerResponse) {
|
||||||
|
if resp.Interval <= 0 {
|
||||||
|
resp.Interval = 1800
|
||||||
|
}
|
||||||
|
|
||||||
|
t.updateEstimatedTimeToAnnounce(resp.Interval)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *HttpTracker) Announce(query string, headers map[string]string, retry bool) (*TrackerResponse, error) {
|
||||||
defer func() {
|
defer func() {
|
||||||
T.RetryAttempt = 0
|
t.RetryAttempt = 0
|
||||||
}()
|
}()
|
||||||
if retry {
|
if retry {
|
||||||
retryDelay := 30
|
retryDelay := 30
|
||||||
for {
|
for {
|
||||||
trackerResp, err := T.tryMakeRequest(query, headers)
|
trackerResp, err := t.tryMakeRequest(query, headers)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
estimatedTimeToAnnounceUpdateCh <- retryDelay
|
t.updateEstimatedTimeToAnnounce(retryDelay)
|
||||||
T.RetryAttempt++
|
t.RetryAttempt++
|
||||||
time.Sleep(time.Duration(retryDelay) * time.Second)
|
time.Sleep(time.Duration(retryDelay) * time.Second)
|
||||||
retryDelay *= 2
|
retryDelay *= 2
|
||||||
if retryDelay > 900 {
|
if retryDelay > 900 {
|
||||||
|
|
@ -64,14 +75,16 @@ func (T *HttpTracker) Announce(query string, headers map[string]string, retry bo
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
t.handleSuccessfulResponse(trackerResp)
|
||||||
return trackerResp, nil
|
return trackerResp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
resp, err := T.tryMakeRequest(query, headers)
|
resp, err := t.tryMakeRequest(query, headers)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
t.handleSuccessfulResponse(resp)
|
||||||
return resp, nil
|
return resp, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -87,14 +100,14 @@ func (t *HttpTracker) tryMakeRequest(query string, headers map[string]string) (*
|
||||||
resp, err := http.DefaultClient.Do(req)
|
resp, err := http.DefaultClient.Do(req)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
if resp.StatusCode == http.StatusOK {
|
if resp.StatusCode == http.StatusOK {
|
||||||
bytesR, _ := ioutil.ReadAll(resp.Body)
|
bytesR, _ := io.ReadAll(resp.Body)
|
||||||
if len(bytesR) == 0 {
|
if len(bytesR) == 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
mimeType := http.DetectContentType(bytesR)
|
mimeType := http.DetectContentType(bytesR)
|
||||||
if mimeType == "application/x-gzip" {
|
if mimeType == "application/x-gzip" {
|
||||||
gzipReader, _ := gzip.NewReader(bytes.NewReader(bytesR))
|
gzipReader, _ := gzip.NewReader(bytes.NewReader(bytesR))
|
||||||
bytesR, _ = ioutil.ReadAll(gzipReader)
|
bytesR, _ = io.ReadAll(gzipReader)
|
||||||
gzipReader.Close()
|
gzipReader.Close()
|
||||||
}
|
}
|
||||||
t.LastTackerResponse = string(bytesR)
|
t.LastTackerResponse = string(bytesR)
|
||||||
|
|
@ -107,7 +120,7 @@ func (t *HttpTracker) tryMakeRequest(query string, headers map[string]string) (*
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if idx != 0 {
|
if idx != 0 {
|
||||||
t.SwapFirst(idx)
|
t.swapFirst(idx)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &ret, nil
|
return &ret, nil
|
||||||
57
tracker/tracker_test.go
Normal file
57
tracker/tracker_test.go
Normal file
|
|
@ -0,0 +1,57 @@
|
||||||
|
package tracker
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/ap-pauloafonso/ratio-spoof/bencode"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNewHttpTracker(t *testing.T) {
|
||||||
|
_, err := NewHttpTracker(&bencode.TorrentInfo{TrackerInfo: &bencode.TrackerInfo{Urls: []string{"udp://url1", "udp://url2"}}})
|
||||||
|
got := err.Error()
|
||||||
|
want := "No tcp/http tracker url announce found"
|
||||||
|
|
||||||
|
if got != want {
|
||||||
|
t.Errorf("got: %v want %v", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSwapFirst(t *testing.T) {
|
||||||
|
tracker, _ := NewHttpTracker(&bencode.TorrentInfo{TrackerInfo: &bencode.TrackerInfo{Urls: []string{"http://url1", "http://url2", "http://url3", "http://url4"}}})
|
||||||
|
tracker.swapFirst(3)
|
||||||
|
|
||||||
|
got := tracker.Urls
|
||||||
|
want := []string{"http://url4", "http://url2", "http://url3", "http://url1"}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(got, want) {
|
||||||
|
t.Errorf("got: %v want %v", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHandleSuccessfulResponse(t *testing.T) {
|
||||||
|
|
||||||
|
t.Run("Empty interval should be overided with 1800 ", func(t *testing.T) {
|
||||||
|
tracker, _ := NewHttpTracker(&bencode.TorrentInfo{TrackerInfo: &bencode.TrackerInfo{Urls: []string{"http://url1", "http://url2", "http://url3", "http://url4"}}})
|
||||||
|
r := TrackerResponse{}
|
||||||
|
tracker.handleSuccessfulResponse(&r)
|
||||||
|
got := r.Interval
|
||||||
|
want := 1800
|
||||||
|
if !reflect.DeepEqual(got, want) {
|
||||||
|
t.Errorf("got: %v want %v", got, want)
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Valid interval shouldn't be overwritten", func(t *testing.T) {
|
||||||
|
tracker, _ := NewHttpTracker(&bencode.TorrentInfo{TrackerInfo: &bencode.TrackerInfo{Urls: []string{"http://url1", "http://url2", "http://url3", "http://url4"}}})
|
||||||
|
r := TrackerResponse{Interval: 900}
|
||||||
|
tracker.handleSuccessfulResponse(&r)
|
||||||
|
got := r.Interval
|
||||||
|
want := 900
|
||||||
|
if !reflect.DeepEqual(got, want) {
|
||||||
|
t.Errorf("got: %v want %v", got, want)
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue