mirror of
https://github.com/ap-pauloafonso/ratio-spoof.git
synced 2026-01-11 20:10:22 +00:00
initial multiple-client build
This commit is contained in:
parent
0dc1a674c7
commit
c1a86a8091
18 changed files with 354 additions and 150 deletions
17
build.sh
17
build.sh
|
|
@ -1,17 +0,0 @@
|
||||||
#!/bin/sh
|
|
||||||
|
|
||||||
version=$1
|
|
||||||
|
|
||||||
if [ -z "$version" ]; then
|
|
||||||
echo "usage: $0 <version>"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
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=linux GOARCH=amd64 go build -v -o ./out/linux/ratio-spoof github.com/ap-pauloafonso/ratio-spoof/cmd
|
|
||||||
env GOOS=windows GOARCH=amd64 go build -v -o ./out/windows/ratio-spoof.exe github.com/ap-pauloafonso/ratio-spoof/cmd
|
|
||||||
|
|
||||||
cd out/
|
|
||||||
zip ratio-spoof-$version\(linux-mac-windows\).zip -r .
|
|
||||||
11
cmd/main.go
11
cmd/main.go
|
|
@ -6,7 +6,6 @@ import (
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/ap-pauloafonso/ratio-spoof/internal/emulation"
|
|
||||||
"github.com/ap-pauloafonso/ratio-spoof/internal/input"
|
"github.com/ap-pauloafonso/ratio-spoof/internal/input"
|
||||||
"github.com/ap-pauloafonso/ratio-spoof/internal/printer"
|
"github.com/ap-pauloafonso/ratio-spoof/internal/printer"
|
||||||
"github.com/ap-pauloafonso/ratio-spoof/internal/ratiospoof"
|
"github.com/ap-pauloafonso/ratio-spoof/internal/ratiospoof"
|
||||||
|
|
@ -24,13 +23,15 @@ func main() {
|
||||||
//optional
|
//optional
|
||||||
port := flag.Int("p", 8999, "a PORT")
|
port := flag.Int("p", 8999, "a PORT")
|
||||||
debug := flag.Bool("debug", false, "")
|
debug := flag.Bool("debug", false, "")
|
||||||
|
client := flag.String("c", "qbit-4.0.3", "emulated client")
|
||||||
|
|
||||||
flag.Usage = func() {
|
flag.Usage = func() {
|
||||||
fmt.Printf("usage: %s -t <TORRENT_PATH> -d <INITIAL_DOWNLOADED> -ds <DOWNLOAD_SPEED> -u <INITIAL_UPLOADED> -us <UPLOAD_SPEED>\n", os.Args[0])
|
fmt.Printf("usage: %s -t <TORRENT_PATH> -d <INITIAL_DOWNLOADED> -ds <DOWNLOAD_SPEED> -u <INITIAL_UPLOADED> -us <UPLOAD_SPEED>\n", os.Args[0])
|
||||||
fmt.Print(`
|
fmt.Print(`
|
||||||
optional arguments:
|
optional arguments:
|
||||||
-h show this help message and exit
|
-h show this help message and exit
|
||||||
-p [PORT] change the port number, the default is 8999
|
-p [PORT] change the port number, default: 8999
|
||||||
|
-c [CLIENT_CODE] change the client emulation, default: qbit-4.0.3
|
||||||
|
|
||||||
required arguments:
|
required arguments:
|
||||||
-t <TORRENT_PATH>
|
-t <TORRENT_PATH>
|
||||||
|
|
@ -41,6 +42,7 @@ required arguments:
|
||||||
|
|
||||||
<INITIAL_DOWNLOADED> and <INITIAL_UPLOADED> must be in %, b, kb, mb, gb, tb
|
<INITIAL_DOWNLOADED> and <INITIAL_UPLOADED> must be in %, b, kb, mb, gb, tb
|
||||||
<DOWNLOAD_SPEED> and <UPLOAD_SPEED> must be in kbps, mbps
|
<DOWNLOAD_SPEED> and <UPLOAD_SPEED> must be in kbps, mbps
|
||||||
|
[CLIENT_CODE] options: qbit-4.0.3, qbit-4.3.2
|
||||||
`)
|
`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -51,7 +53,6 @@ required arguments:
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
qbit := emulation.NewQbitTorrent()
|
|
||||||
r, err := ratiospoof.NewRatioSpoofState(
|
r, err := ratiospoof.NewRatioSpoofState(
|
||||||
input.InputArgs{
|
input.InputArgs{
|
||||||
TorrentPath: *torrentPath,
|
TorrentPath: *torrentPath,
|
||||||
|
|
@ -61,8 +62,8 @@ required arguments:
|
||||||
UploadSpeed: *uploadSpeed,
|
UploadSpeed: *uploadSpeed,
|
||||||
Port: *port,
|
Port: *port,
|
||||||
Debug: *debug,
|
Debug: *debug,
|
||||||
},
|
Client: *client,
|
||||||
qbit)
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalln(err)
|
log.Fatalln(err)
|
||||||
|
|
|
||||||
6
go.mod
6
go.mod
|
|
@ -1,9 +1,13 @@
|
||||||
module github.com/ap-pauloafonso/ratio-spoof
|
module github.com/ap-pauloafonso/ratio-spoof
|
||||||
|
|
||||||
go 1.15
|
go 1.16
|
||||||
|
|
||||||
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/smartystreets/goconvey v1.6.4 // 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
|
||||||
)
|
)
|
||||||
|
|
|
||||||
27
go.sum
27
go.sum
|
|
@ -1,6 +1,33 @@
|
||||||
|
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/gammazero/deque v0.0.0-20201010052221-3932da5530cc h1:F7BbnLACph7UYiz9ZHi6npcROwKaZUyviDjsNERsoMM=
|
github.com/gammazero/deque v0.0.0-20201010052221-3932da5530cc h1:F7BbnLACph7UYiz9ZHi6npcROwKaZUyviDjsNERsoMM=
|
||||||
github.com/gammazero/deque v0.0.0-20201010052221-3932da5530cc/go.mod h1:IlBLfYXnuw9sspy1XS6ctu5exGb6WHGKQsyo4s7bOEA=
|
github.com/gammazero/deque v0.0.0-20201010052221-3932da5530cc/go.mod h1:IlBLfYXnuw9sspy1XS6ctu5exGb6WHGKQsyo4s7bOEA=
|
||||||
|
github.com/google/gxui v0.0.0-20151028112939-f85e0a97b3a4 h1:OL2d27ueTKnlQJoqLW2fc9pWYulFnJYLWzomGV7HqZo=
|
||||||
|
github.com/google/gxui v0.0.0-20151028112939-f85e0a97b3a4/go.mod h1:Pw1H1OjSNHiqeuxAduB1BKYXIwFtsyrY47nEqSgEiCM=
|
||||||
|
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
|
||||||
|
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||||
|
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
|
||||||
|
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||||
github.com/olekukonko/ts v0.0.0-20171002115256-78ecb04241c0 h1:LiZB1h0GIcudcDci2bxbqI6DXV8bF8POAnArqvRrIyw=
|
github.com/olekukonko/ts v0.0.0-20171002115256-78ecb04241c0 h1:LiZB1h0GIcudcDci2bxbqI6DXV8bF8POAnArqvRrIyw=
|
||||||
github.com/olekukonko/ts v0.0.0-20171002115256-78ecb04241c0/go.mod h1:F/7q8/HZz+TXjlsoZQQKVYvXTZaFH4QRa3y+j1p7MS0=
|
github.com/olekukonko/ts v0.0.0-20171002115256-78ecb04241c0/go.mod h1:F/7q8/HZz+TXjlsoZQQKVYvXTZaFH4QRa3y+j1p7MS0=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
|
||||||
|
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||||
|
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
|
||||||
|
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||||
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/zach-klippenstein/goregen v0.0.0-20160303162051-795b5e3961ea h1:CyhwejzVGvZ3Q2PSbQ4NRRYn+ZWv5eS1vlaEusT+bAI=
|
||||||
|
github.com/zach-klippenstein/goregen v0.0.0-20160303162051-795b5e3961ea/go.mod h1:eNr558nEUjP8acGw8FFjTeWvSgU1stO7FAO6eknhHe4=
|
||||||
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193GlqGZbnPFnPV/5Rsb4=
|
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193GlqGZbnPFnPV/5Rsb4=
|
||||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
|
|
||||||
84
internal/emulation/emulation.go
Normal file
84
internal/emulation/emulation.go
Normal file
|
|
@ -0,0 +1,84 @@
|
||||||
|
package emulation
|
||||||
|
|
||||||
|
import (
|
||||||
|
"embed"
|
||||||
|
"encoding/json"
|
||||||
|
"io/ioutil"
|
||||||
|
|
||||||
|
"github.com/ap-pauloafonso/ratio-spoof/internal/generator"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ClientInfo struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
PeerID struct {
|
||||||
|
Generator string `json:"generator"`
|
||||||
|
Regex string `json:"regex"`
|
||||||
|
} `json:"peerId"`
|
||||||
|
Key struct {
|
||||||
|
Generator string `json:"generator"`
|
||||||
|
Regex string `json:"regex"`
|
||||||
|
} `json:"key"`
|
||||||
|
Rounding struct {
|
||||||
|
Generator string `json:"generator"`
|
||||||
|
Regex string `json:"regex"`
|
||||||
|
} `json:"rounding"`
|
||||||
|
Query string `json:"query"`
|
||||||
|
Headers map[string]string `json:"headers"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Emulation struct {
|
||||||
|
PeerIdGenerator generator.PeerIdGenerator
|
||||||
|
KeyGenerator generator.KeyGenerator
|
||||||
|
Query string
|
||||||
|
Name string
|
||||||
|
Headers map[string]string
|
||||||
|
RoudingGenerator generator.RoundingGenerator
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewEmulation(code string) (*Emulation, error) {
|
||||||
|
c, err := extractClient(code)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
peerG, err := generator.NewPeerIdGenerator(c.PeerID.Generator, c.PeerID.Regex)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
keyG, err := generator.NewKeyGenerator(c.Key.Generator)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
roudingG, err := generator.NewRoundingGenerator(c.Rounding.Generator)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Emulation{PeerIdGenerator: peerG, KeyGenerator: keyG, RoudingGenerator: roudingG,
|
||||||
|
Headers: c.Headers, Name: c.Name, Query: c.Query}, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//go:embed static
|
||||||
|
var staticFiles embed.FS
|
||||||
|
|
||||||
|
func extractClient(code string) (*ClientInfo, error) {
|
||||||
|
|
||||||
|
f, err := staticFiles.Open("static/" + code + ".json")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
bytes, err := ioutil.ReadAll(f)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var client ClientInfo
|
||||||
|
|
||||||
|
json.Unmarshal(bytes, &client)
|
||||||
|
|
||||||
|
return &client, nil
|
||||||
|
}
|
||||||
74
internal/emulation/emulation_test.go
Normal file
74
internal/emulation/emulation_test.go
Normal file
|
|
@ -0,0 +1,74 @@
|
||||||
|
package emulation
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/fs"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNewEmulation(t *testing.T) {
|
||||||
|
var counter int
|
||||||
|
fs.WalkDir(staticFiles, ".", func(path string, d fs.DirEntry, err error) error {
|
||||||
|
if counter > 1 {
|
||||||
|
code := strings.TrimRight(strings.TrimLeft(path, "static/"), ".json")
|
||||||
|
e, err := NewEmulation(code)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("should not return error ")
|
||||||
|
}
|
||||||
|
|
||||||
|
peerId := e.PeerIdGenerator.PeerId()
|
||||||
|
key := e.KeyGenerator.Key()
|
||||||
|
|
||||||
|
d, u, l := e.RoudingGenerator.NextAmountReport(2*1024*1024*1024, 1024*1024*1024, 3*1024*1024*1024, 1024)
|
||||||
|
|
||||||
|
if peerId == "" {
|
||||||
|
t.Errorf("%s.json should be able to generate PeerId", code)
|
||||||
|
}
|
||||||
|
if key == "" {
|
||||||
|
t.Errorf("%s.json should be able to generate Key", code)
|
||||||
|
}
|
||||||
|
if d <= 0 || u <= 0 || l <= 0 {
|
||||||
|
t.Errorf("%s.json should be able to round candidates", code)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
counter++
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
func TestExtractClient(t *testing.T) {
|
||||||
|
var counter int
|
||||||
|
fs.WalkDir(staticFiles, ".", func(path string, d fs.DirEntry, err error) error {
|
||||||
|
if counter > 1 {
|
||||||
|
code := strings.TrimRight(strings.TrimLeft(path, "static/"), ".json")
|
||||||
|
c, e := extractClient(code)
|
||||||
|
if e != nil || err != nil {
|
||||||
|
t.Error("should not return error")
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.Key.Generator == "" && c.Key.Regex == "" {
|
||||||
|
t.Errorf("%s.json should have key generator properties", code)
|
||||||
|
}
|
||||||
|
if c.PeerID.Generator == "" && c.PeerID.Regex == "" {
|
||||||
|
t.Errorf("%s.json should have PeerId generator properties", code)
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.Rounding.Generator == "" && c.Rounding.Regex == "" {
|
||||||
|
t.Errorf("%s.json should have rouding generator properties", code)
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.Name == "" {
|
||||||
|
t.Errorf("%s.json should have a name", code)
|
||||||
|
}
|
||||||
|
if c.Query == "" {
|
||||||
|
t.Errorf("%s.json should have a query", code)
|
||||||
|
}
|
||||||
|
if len(c.Headers) == 0 {
|
||||||
|
t.Errorf("%s.json should have headers", code)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
counter++
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -1,83 +0,0 @@
|
||||||
package emulation
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/hex"
|
|
||||||
"math/rand"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_"
|
|
||||||
name = "qBittorrent v4.03"
|
|
||||||
query = "info_hash={infohash}&peer_id={peerid}&port={port}&uploaded={uploaded}&downloaded={downloaded}&left={left}&corrupt=0&key={key}&event={event}&numwant={numwant}&compact=1&no_peer_id=1&supportcrypto=1&redundant=0"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
rand.Seed(time.Now().UnixNano())
|
|
||||||
}
|
|
||||||
|
|
||||||
type qbitTorrent struct {
|
|
||||||
name string
|
|
||||||
query string
|
|
||||||
dictHeaders map[string]string
|
|
||||||
key string
|
|
||||||
peerID string
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewQbitTorrent() *qbitTorrent {
|
|
||||||
return &qbitTorrent{
|
|
||||||
name: name,
|
|
||||||
query: query,
|
|
||||||
dictHeaders: generateHeaders(),
|
|
||||||
key: generateKey(),
|
|
||||||
peerID: generatePeerID(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (qb *qbitTorrent) Name() string {
|
|
||||||
return qb.name
|
|
||||||
}
|
|
||||||
func (qb *qbitTorrent) PeerID() string {
|
|
||||||
return qb.peerID
|
|
||||||
}
|
|
||||||
|
|
||||||
func (qb *qbitTorrent) Key() string {
|
|
||||||
return qb.key
|
|
||||||
}
|
|
||||||
|
|
||||||
func (qb *qbitTorrent) Query() string {
|
|
||||||
return query
|
|
||||||
}
|
|
||||||
func (qb *qbitTorrent) Headers() map[string]string {
|
|
||||||
return qb.dictHeaders
|
|
||||||
}
|
|
||||||
func (qb *qbitTorrent) NextAmountReport(downloadCandidateNextAmount, uploadCandidateNextAmount, leftCandidateNextAmount, pieceSize int) (downloaded, uploaded, left int) {
|
|
||||||
|
|
||||||
d := downloadCandidateNextAmount
|
|
||||||
u := uploadCandidateNextAmount - (uploadCandidateNextAmount % (16 * 1024))
|
|
||||||
l := leftCandidateNextAmount - (leftCandidateNextAmount % pieceSize)
|
|
||||||
return d, u, l
|
|
||||||
}
|
|
||||||
|
|
||||||
func generateHeaders() map[string]string {
|
|
||||||
return map[string]string{"User-Agent": "qBittorrent/4.0.3", "Accept-Encoding": "gzip"}
|
|
||||||
}
|
|
||||||
|
|
||||||
func generatePeerID() string {
|
|
||||||
return "-qB4030-" + randStringBytes(12)
|
|
||||||
}
|
|
||||||
|
|
||||||
func randStringBytes(n int) string {
|
|
||||||
b := make([]byte, n)
|
|
||||||
for i := range b {
|
|
||||||
b[i] = letterBytes[rand.Intn(len(letterBytes))]
|
|
||||||
}
|
|
||||||
return string(b)
|
|
||||||
}
|
|
||||||
func generateKey() string {
|
|
||||||
randomBytes := make([]byte, 4)
|
|
||||||
rand.Read(randomBytes)
|
|
||||||
str := hex.EncodeToString(randomBytes)
|
|
||||||
return strings.ToUpper(str)
|
|
||||||
}
|
|
||||||
|
|
@ -1,26 +0,0 @@
|
||||||
package emulation
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestGenerateRandomKey(T *testing.T) {
|
|
||||||
T.Run("Key has 8 length", func(t *testing.T) {
|
|
||||||
obj := NewQbitTorrent()
|
|
||||||
key := obj.Key()
|
|
||||||
if len(key) != 8 {
|
|
||||||
t.Error("Keys must have length of 8")
|
|
||||||
}
|
|
||||||
|
|
||||||
})
|
|
||||||
T.Run("Key must be uppercase", func(t *testing.T) {
|
|
||||||
obj := NewQbitTorrent()
|
|
||||||
key := obj.Key()
|
|
||||||
if strings.ToUpper(key) != key {
|
|
||||||
t.Error("Keys must be uppercase")
|
|
||||||
}
|
|
||||||
|
|
||||||
})
|
|
||||||
|
|
||||||
}
|
|
||||||
17
internal/emulation/static/qbit-4.0.3.json
Normal file
17
internal/emulation/static/qbit-4.0.3.json
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
{
|
||||||
|
"name":"qBittorrent v4.0.3",
|
||||||
|
"peerId":{
|
||||||
|
"regex":"-qB4030-[A-Za-z0-9_~\\(\\)\\!\\.\\*-]{12}"
|
||||||
|
},
|
||||||
|
"key": {
|
||||||
|
"generator":"defaultKeyGenerator"
|
||||||
|
},
|
||||||
|
"rounding": {
|
||||||
|
"generator":"defaultRoudingGenerator"
|
||||||
|
},
|
||||||
|
"query":"info_hash={infohash}&peer_id={peerid}&port={port}&uploaded={uploaded}&downloaded={downloaded}&left={left}&corrupt=0&key={key}&event={event}&numwant={numwant}&compact=1&no_peer_id=1&supportcrypto=1&redundant=0",
|
||||||
|
"headers":{
|
||||||
|
"User-Agent" :"qBittorrent/4.0.3",
|
||||||
|
"Accept-Encoding": "gzip"
|
||||||
|
}
|
||||||
|
}
|
||||||
17
internal/emulation/static/qbit-4.3.2.json
Normal file
17
internal/emulation/static/qbit-4.3.2.json
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
{
|
||||||
|
"name":"qBittorrent v4.3.2",
|
||||||
|
"peerId":{
|
||||||
|
"regex":"-qB4320-[A-Za-z0-9_~\\(\\)\\!\\.\\*-]{12}"
|
||||||
|
},
|
||||||
|
"key": {
|
||||||
|
"generator":"defaultKeyGenerator"
|
||||||
|
},
|
||||||
|
"rounding": {
|
||||||
|
"generator":"defaultRoudingGenerator"
|
||||||
|
},
|
||||||
|
"query":"info_hash={infohash}&peer_id={peerid}&port={port}&uploaded={uploaded}&downloaded={downloaded}&left={left}&corrupt=0&key={key}&event={event}&numwant={numwant}&compact=1&no_peer_id=1&supportcrypto=1&redundant=0",
|
||||||
|
"headers":{
|
||||||
|
"User-Agent" :"qBittorrent/4.3.2",
|
||||||
|
"Accept-Encoding": "gzip"
|
||||||
|
}
|
||||||
|
}
|
||||||
27
internal/generator/key.go
Normal file
27
internal/generator/key.go
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
package generator
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/hex"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type KeyGenerator interface {
|
||||||
|
Key() string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewKeyGenerator(generatorCode string) (KeyGenerator, error) {
|
||||||
|
randomBytes := make([]byte, 4)
|
||||||
|
rand.Read(randomBytes)
|
||||||
|
str := hex.EncodeToString(randomBytes)
|
||||||
|
result := strings.ToUpper(str)
|
||||||
|
return &DefaultKeyGenerator{generated: result}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type DefaultKeyGenerator struct {
|
||||||
|
generated string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DefaultKeyGenerator) Key() string {
|
||||||
|
return d.generated
|
||||||
|
}
|
||||||
25
internal/generator/peerId.go
Normal file
25
internal/generator/peerId.go
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
package generator
|
||||||
|
|
||||||
|
import (
|
||||||
|
regen "github.com/zach-klippenstein/goregen"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PeerIdGenerator interface {
|
||||||
|
PeerId() string
|
||||||
|
}
|
||||||
|
|
||||||
|
type RegexPeerIdGenerator struct {
|
||||||
|
generated string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewPeerIdGenerator(generatorCode, pattern string) (PeerIdGenerator, error) {
|
||||||
|
result, err := regen.Generate(pattern)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &RegexPeerIdGenerator{generated: result}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *RegexPeerIdGenerator) PeerId() string {
|
||||||
|
return d.generated
|
||||||
|
}
|
||||||
20
internal/generator/rouding.go
Normal file
20
internal/generator/rouding.go
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
package generator
|
||||||
|
|
||||||
|
type RoundingGenerator interface {
|
||||||
|
NextAmountReport(downloadCandidateNextAmount, uploadCandidateNextAmount, leftCandidateNextAmount, pieceSize int) (downloaded, uploaded, left int)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRoundingGenerator(code string) (RoundingGenerator, error) {
|
||||||
|
return &DefaultRoundingGenerator{}, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
type DefaultRoundingGenerator struct{}
|
||||||
|
|
||||||
|
func (d *DefaultRoundingGenerator) NextAmountReport(downloadCandidateNextAmount, uploadCandidateNextAmount, leftCandidateNextAmount, pieceSize int) (downloaded, uploaded, left int) {
|
||||||
|
|
||||||
|
down := downloadCandidateNextAmount
|
||||||
|
up := uploadCandidateNextAmount - (uploadCandidateNextAmount % (16 * 1024))
|
||||||
|
l := leftCandidateNextAmount - (leftCandidateNextAmount % pieceSize)
|
||||||
|
return down, up, l
|
||||||
|
}
|
||||||
21
internal/generator/rounding_test.go
Normal file
21
internal/generator/rounding_test.go
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
package generator
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestNextAmountReport(t *testing.T) {
|
||||||
|
r, _ := NewRoundingGenerator("")
|
||||||
|
|
||||||
|
d, u, l := r.NextAmountReport(656497856, 46479878, 7879879, 1024)
|
||||||
|
//same
|
||||||
|
if d != 656497856 {
|
||||||
|
t.Errorf("[download]got %v want %v", d, 656497856)
|
||||||
|
}
|
||||||
|
//16kb round
|
||||||
|
if u != 46465024 {
|
||||||
|
t.Errorf("[upload]got %v want %v", u, 46465024)
|
||||||
|
}
|
||||||
|
//piece size round
|
||||||
|
if l != 7879680 {
|
||||||
|
t.Errorf("[left]got %v want %v", l, 7879680)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -21,6 +21,7 @@ type InputArgs struct {
|
||||||
InitialDownloaded string
|
InitialDownloaded string
|
||||||
DownloadSpeed string
|
DownloadSpeed string
|
||||||
InitialUploaded string
|
InitialUploaded string
|
||||||
|
Client string
|
||||||
UploadSpeed string
|
UploadSpeed string
|
||||||
Port int
|
Port int
|
||||||
Debug bool
|
Debug bool
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,7 @@ func PrintState(state *ratiospoof.RatioSpoof) {
|
||||||
Upload Speed: %v/s
|
Upload Speed: %v/s
|
||||||
Size: %v
|
Size: %v
|
||||||
Emulation: %v | Port: %v`, state.TorrentInfo.Name, state.TorrentInfo.TrackerInfo.Main, seedersStr, leechersStr, humanReadableSize(float64(state.Input.DownloadSpeed)),
|
Emulation: %v | Port: %v`, state.TorrentInfo.Name, state.TorrentInfo.TrackerInfo.Main, seedersStr, leechersStr, humanReadableSize(float64(state.Input.DownloadSpeed)),
|
||||||
humanReadableSize(float64(state.Input.UploadSpeed)), humanReadableSize(float64(state.TorrentInfo.TotalSize)), state.BitTorrentClient.Name(), state.Input.Port)
|
humanReadableSize(float64(state.Input.UploadSpeed)), humanReadableSize(float64(state.TorrentInfo.TotalSize)), state.BitTorrentClient.Name, state.Input.Port)
|
||||||
fmt.Printf("\n\n%s\n\n", center(" GITHUB.COM/AP-PAULOAFONSO/RATIO-SPOOF ", width-len(" GITHUB.COM/AP-PAULOAFONSO/RATIO-SPOOF "), "#"))
|
fmt.Printf("\n\n%s\n\n", center(" GITHUB.COM/AP-PAULOAFONSO/RATIO-SPOOF ", width-len(" GITHUB.COM/AP-PAULOAFONSO/RATIO-SPOOF "), "#"))
|
||||||
for i := 0; i <= state.AnnounceHistory.Len()-2; i++ {
|
for i := 0; i <= state.AnnounceHistory.Len()-2; i++ {
|
||||||
dequeItem := state.AnnounceHistory.At(i).(ratiospoof.AnnounceEntry)
|
dequeItem := state.AnnounceHistory.At(i).(ratiospoof.AnnounceEntry)
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ap-pauloafonso/ratio-spoof/internal/bencode"
|
"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/input"
|
||||||
"github.com/ap-pauloafonso/ratio-spoof/internal/tracker"
|
"github.com/ap-pauloafonso/ratio-spoof/internal/tracker"
|
||||||
"github.com/gammazero/deque"
|
"github.com/gammazero/deque"
|
||||||
|
|
@ -28,7 +29,7 @@ type RatioSpoof struct {
|
||||||
TorrentInfo *bencode.TorrentInfo
|
TorrentInfo *bencode.TorrentInfo
|
||||||
Input *input.InputParsed
|
Input *input.InputParsed
|
||||||
Tracker *tracker.HttpTracker
|
Tracker *tracker.HttpTracker
|
||||||
BitTorrentClient TorrentClientEmulation
|
BitTorrentClient *emulation.Emulation
|
||||||
AnnounceInterval int
|
AnnounceInterval int
|
||||||
EstimatedTimeToAnnounce time.Time
|
EstimatedTimeToAnnounce time.Time
|
||||||
EstimatedTimeToAnnounceUpdateCh chan int
|
EstimatedTimeToAnnounceUpdateCh chan int
|
||||||
|
|
@ -41,15 +42,6 @@ type RatioSpoof struct {
|
||||||
StopPrintCH chan interface{}
|
StopPrintCH chan interface{}
|
||||||
}
|
}
|
||||||
|
|
||||||
type TorrentClientEmulation interface {
|
|
||||||
PeerID() string
|
|
||||||
Key() string
|
|
||||||
Query() string
|
|
||||||
Name() string
|
|
||||||
Headers() map[string]string
|
|
||||||
NextAmountReport(DownloadCandidateNextAmount, UploadCandidateNextAmount, leftCandidateNextAmount, pieceSize int) (downloaded, uploaded, left int)
|
|
||||||
}
|
|
||||||
|
|
||||||
type AnnounceEntry struct {
|
type AnnounceEntry struct {
|
||||||
Count int
|
Count int
|
||||||
Downloaded int
|
Downloaded int
|
||||||
|
|
@ -62,7 +54,7 @@ type announceHistory struct {
|
||||||
deque.Deque
|
deque.Deque
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRatioSpoofState(input input.InputArgs, torrentClient TorrentClientEmulation) (*RatioSpoof, error) {
|
func NewRatioSpoofState(input input.InputArgs) (*RatioSpoof, error) {
|
||||||
EstimatedTimeToAnnounceUpdateCh := make(chan int)
|
EstimatedTimeToAnnounceUpdateCh := make(chan int)
|
||||||
stopPrintCh := make(chan interface{})
|
stopPrintCh := make(chan interface{})
|
||||||
dat, err := ioutil.ReadFile(input.TorrentPath)
|
dat, err := ioutil.ReadFile(input.TorrentPath)
|
||||||
|
|
@ -70,6 +62,11 @@ func NewRatioSpoofState(input input.InputArgs, torrentClient TorrentClientEmulat
|
||||||
return nil, err
|
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)
|
torrentInfo, err := bencode.TorrentDictParse(dat)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.New("failed to parse the torrent file")
|
return nil, errors.New("failed to parse the torrent file")
|
||||||
|
|
@ -86,7 +83,7 @@ func NewRatioSpoofState(input input.InputArgs, torrentClient TorrentClientEmulat
|
||||||
}
|
}
|
||||||
|
|
||||||
return &RatioSpoof{
|
return &RatioSpoof{
|
||||||
BitTorrentClient: torrentClient,
|
BitTorrentClient: client,
|
||||||
TorrentInfo: torrentInfo,
|
TorrentInfo: torrentInfo,
|
||||||
Tracker: httpTracker,
|
Tracker: httpTracker,
|
||||||
Input: inputParsed,
|
Input: inputParsed,
|
||||||
|
|
@ -171,15 +168,15 @@ 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),
|
||||||
"{peerid}", R.BitTorrentClient.PeerID(),
|
"{peerid}", R.BitTorrentClient.PeerIdGenerator.PeerId(),
|
||||||
"{uploaded}", fmt.Sprint(lastAnnounce.Uploaded),
|
"{uploaded}", fmt.Sprint(lastAnnounce.Uploaded),
|
||||||
"{downloaded}", fmt.Sprint(lastAnnounce.Downloaded),
|
"{downloaded}", fmt.Sprint(lastAnnounce.Downloaded),
|
||||||
"{left}", fmt.Sprint(lastAnnounce.Left),
|
"{left}", fmt.Sprint(lastAnnounce.Left),
|
||||||
"{key}", R.BitTorrentClient.Key(),
|
"{key}", R.BitTorrentClient.KeyGenerator.Key(),
|
||||||
"{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, err := R.Tracker.Announce(query, R.BitTorrentClient.Headers(), retry, R.EstimatedTimeToAnnounceUpdateCh)
|
trackerResp, err := R.Tracker.Announce(query, R.BitTorrentClient.Headers, retry, R.EstimatedTimeToAnnounceUpdateCh)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("failed to reach the tracker:\n%s ", err.Error())
|
log.Fatalf("failed to reach the tracker:\n%s ", err.Error())
|
||||||
}
|
}
|
||||||
|
|
@ -206,7 +203,7 @@ func (R *RatioSpoof) generateNextAnnounce() {
|
||||||
|
|
||||||
leftCandidate := calculateBytesLeft(downloadCandidate, R.TorrentInfo.TotalSize)
|
leftCandidate := calculateBytesLeft(downloadCandidate, R.TorrentInfo.TotalSize)
|
||||||
|
|
||||||
d, u, l := R.BitTorrentClient.NextAmountReport(downloadCandidate, uploadCandidate, leftCandidate, R.TorrentInfo.PieceSize)
|
d, u, l := R.BitTorrentClient.RoudingGenerator.NextAmountReport(downloadCandidate, uploadCandidate, leftCandidate, R.TorrentInfo.PieceSize)
|
||||||
|
|
||||||
R.addAnnounce(d, u, l, (float32(d)/float32(R.TorrentInfo.TotalSize))*100)
|
R.addAnnounce(d, u, l, (float32(d)/float32(R.TorrentInfo.TotalSize))*100)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
15
makefile
Normal file
15
makefile
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
test:
|
||||||
|
go test ./... --cover
|
||||||
|
|
||||||
|
torrent-test:
|
||||||
|
go run cmd/main.go -c qbit-4.3.2 -t internal/bencode/torrent_files_test/Fedora-Workstation-Live-x86_64-33.torrent -d 0% -ds 100kbps -u 0% -us 100kbps -debug
|
||||||
|
|
||||||
|
release:
|
||||||
|
@if test -z "$(rsversion)"; then echo "usage: make release rsversion=v1.2"; exit 1; fi
|
||||||
|
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=linux GOARCH=amd64 go build -v -o ./out/linux/ratio-spoof github.com/ap-pauloafonso/ratio-spoof/cmd
|
||||||
|
env GOOS=windows GOARCH=amd64 go build -v -o ./out/windows/ratio-spoof.exe github.com/ap-pauloafonso/ratio-spoof/cmd
|
||||||
|
|
||||||
|
cd out/ ; zip ratio-spoof-$(rsversion)\(linux-mac-windows\).zip -r .
|
||||||
Loading…
Reference in a new issue