Merge pull request #20 from ap-pauloafonso/channel-removal

estimatedTime to tracker.go proposal
This commit is contained in:
ap-pauloafonso 2021-03-19 20:05:27 -03:00 committed by GitHub
commit 2bbf97d854
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 119 additions and 136 deletions

View file

@ -63,8 +63,8 @@ func TorrentDictParse(dat []byte) (torrent *TorrentInfo, err error) {
}, err
}
func (T *torrentDict) extractInfoHashURLEncoded(rawData []byte) string {
byteOffsets := T.resultMap["info"].(map[string]interface{})["byte_offsets"].([]int)
func (t *torrentDict) extractInfoHashURLEncoded(rawData []byte) string {
byteOffsets := t.resultMap["info"].(map[string]interface{})["byte_offsets"].([]int)
h := sha1.New()
h.Write([]byte(rawData[byteOffsets[0]:byteOffsets[1]]))
ret := h.Sum(nil)
@ -80,28 +80,28 @@ func (T *torrentDict) extractInfoHashURLEncoded(rawData []byte) string {
return buf.String()
}
func (T *torrentDict) extractTotalSize() int {
if value, ok := T.resultMap[torrentInfoKey].(map[string]interface{})[torrentLengthKey]; ok {
func (t *torrentDict) extractTotalSize() int {
if value, ok := t.resultMap[torrentInfoKey].(map[string]interface{})[torrentLengthKey]; ok {
return value.(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)
}
return total
}
func (T *torrentDict) extractTrackerInfo() *TrackerInfo {
func (t *torrentDict) extractTrackerInfo() *TrackerInfo {
uniqueUrls := make(map[string]int)
currentCount := 0
if main, ok := T.resultMap[mainAnnounceKey]; ok {
if main, ok := t.resultMap[mainAnnounceKey]; ok {
if _, found := uniqueUrls[main.(string)]; !found {
uniqueUrls[main.(string)] = currentCount
currentCount++
}
}
if list, ok := T.resultMap[announceListKey]; ok {
if list, ok := t.resultMap[announceListKey]; ok {
for _, innerList := range list.([]interface{}) {
for _, item := range innerList.([]interface{}) {
if _, found := uniqueUrls[item.(string)]; !found {

View file

@ -1,8 +1,8 @@
package bencode
import (
"io/ioutil"
"log"
"os"
"reflect"
"testing"
)
@ -99,13 +99,13 @@ func TestMapParse(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 {
log.Fatal(err)
}
for _, f := range files {
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)
t.Log(result["info"].(map[string]interface{})["name"])
})

View file

@ -3,7 +3,7 @@ package emulation
import (
"embed"
"encoding/json"
"io/ioutil"
"io"
"github.com/ap-pauloafonso/ratio-spoof/internal/generator"
)
@ -83,7 +83,7 @@ func extractClient(code string) (*ClientInfo, error) {
}
defer f.Close()
bytes, err := ioutil.ReadAll(f)
bytes, err := io.ReadAll(f)
if err != nil {
return nil, err
}

View file

@ -2,7 +2,7 @@ package generator
import "testing"
func TestNextAmountReport(t *testing.T) {
func TestDefaultRounding(t *testing.T) {
r, _ := NewDefaultRoudingGenerator()
d, u, l := r.Round(656497856, 46479878, 7879879, 1024)

View file

@ -40,25 +40,25 @@ type InputParsed struct {
var validInitialSufixes = [...]string{"%", "b", "kb", "mb", "gb", "tb"}
var validSpeedSufixes = [...]string{"kbps", "mbps"}
func (I *InputArgs) ParseInput(torrentInfo *bencode.TorrentInfo) (*InputParsed, error) {
downloaded, err := extractInputInitialByteCount(I.InitialDownloaded, torrentInfo.TotalSize, true)
func (i *InputArgs) ParseInput(torrentInfo *bencode.TorrentInfo) (*InputParsed, error) {
downloaded, err := extractInputInitialByteCount(i.InitialDownloaded, torrentInfo.TotalSize, true)
if err != nil {
return nil, err
}
uploaded, err := extractInputInitialByteCount(I.InitialUploaded, torrentInfo.TotalSize, false)
uploaded, err := extractInputInitialByteCount(i.InitialUploaded, torrentInfo.TotalSize, false)
if err != nil {
return nil, err
}
downloadSpeed, err := extractInputByteSpeed(I.DownloadSpeed)
downloadSpeed, err := extractInputByteSpeed(i.DownloadSpeed)
if err != nil {
return nil, err
}
uploadSpeed, err := extractInputByteSpeed(I.UploadSpeed)
uploadSpeed, err := extractInputByteSpeed(i.UploadSpeed)
if err != nil {
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))
}
@ -66,8 +66,8 @@ func (I *InputArgs) ParseInput(torrentInfo *bencode.TorrentInfo) (*InputParsed,
DownloadSpeed: downloadSpeed,
InitialUploaded: uploaded,
UploadSpeed: uploadSpeed,
Debug: I.Debug,
Port: I.Port,
Debug: i.Debug,
Port: i.Port,
}, nil
}

View file

@ -63,7 +63,7 @@ func PrintState(state *ratiospoof.RatioSpoof) {
}
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,
humanReadableSize(float64(lastDequeItem.Downloaded)),
lastDequeItem.PercentDownloaded,

View file

@ -3,13 +3,11 @@ package ratiospoof
import (
"errors"
"fmt"
"io/ioutil"
"log"
"math/rand"
"os"
"os/signal"
"strings"
"sync"
"syscall"
"time"
@ -25,21 +23,18 @@ const (
)
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{}
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
StopPrintCH chan interface{}
}
type AnnounceEntry struct {
@ -55,9 +50,8 @@ type announceHistory struct {
}
func NewRatioSpoofState(input input.InputArgs) (*RatioSpoof, error) {
EstimatedTimeToAnnounceUpdateCh := make(chan int)
stopPrintCh := make(chan interface{})
dat, err := ioutil.ReadFile(input.TorrentPath)
dat, err := os.ReadFile(input.TorrentPath)
if err != nil {
return nil, err
}
@ -83,129 +77,104 @@ func NewRatioSpoofState(input input.InputArgs) (*RatioSpoof, error) {
}
return &RatioSpoof{
BitTorrentClient: client,
TorrentInfo: torrentInfo,
Tracker: httpTracker,
Input: inputParsed,
NumWant: 200,
Status: "started",
mutex: &sync.Mutex{},
StopPrintCH: stopPrintCh,
EstimatedTimeToAnnounceUpdateCh: EstimatedTimeToAnnounceUpdateCh,
BitTorrentClient: client,
TorrentInfo: torrentInfo,
Tracker: httpTracker,
Input: inputParsed,
NumWant: 200,
Status: "started",
StopPrintCH: stopPrintCh,
}, nil
}
func (A *announceHistory) pushValueHistory(value AnnounceEntry) {
if A.Len() >= maxAnnounceHistory {
A.PopFront()
func (a *announceHistory) pushValueHistory(value AnnounceEntry) {
if a.Len() >= maxAnnounceHistory {
a.PopFront()
}
A.PushBack(value)
a.PushBack(value)
}
func (R *RatioSpoof) gracefullyExit() {
func (r *RatioSpoof) gracefullyExit() {
fmt.Printf("\nGracefully exiting...\n")
R.Status = "stopped"
R.NumWant = 0
R.fireAnnounce(false)
r.Status = "stopped"
r.NumWant = 0
r.fireAnnounce(false)
fmt.Printf("Gracefully exited successfully.\n")
}
func (R *RatioSpoof) Run() {
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()
r.firstAnnounce()
go func() {
for {
R.generateNextAnnounce()
time.Sleep(time.Duration(R.AnnounceInterval) * time.Second)
R.fireAnnounce(true)
r.generateNextAnnounce()
time.Sleep(time.Duration(r.AnnounceInterval) * time.Second)
r.fireAnnounce(true)
}
}()
<-sigCh
R.StopPrintCH <- "exit print"
R.gracefullyExit()
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) 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) updateSeedersAndLeechers(resp tracker.TrackerResponse) {
r.Seeders = resp.Seeders
r.Leechers = resp.Leechers
}
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) 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) 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(),
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)
"{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.updateInterval(trackerResp.Interval)
r.updateSeedersAndLeechers(*trackerResp)
r.AnnounceInterval = trackerResp.Interval
}
return nil
}
func (R *RatioSpoof) generateNextAnnounce() {
lastAnnounce := R.AnnounceHistory.Back().(AnnounceEntry)
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)
if currentDownloaded < r.TorrentInfo.TotalSize {
downloadCandidate = calculateNextTotalSizeByte(r.Input.DownloadSpeed, currentDownloaded, r.TorrentInfo.PieceSize, r.AnnounceInterval, r.TorrentInfo.TotalSize)
} else {
downloadCandidate = R.TorrentInfo.TotalSize
downloadCandidate = r.TorrentInfo.TotalSize
}
currentUploaded := lastAnnounce.Uploaded
uploadCandidate := calculateNextTotalSizeByte(R.Input.UploadSpeed, currentUploaded, R.TorrentInfo.PieceSize, R.AnnounceInterval, 0)
uploadCandidate := calculateNextTotalSizeByte(r.Input.UploadSpeed, currentUploaded, r.TorrentInfo.PieceSize, r.AnnounceInterval, 0)
leftCandidate := calculateBytesLeft(downloadCandidate, R.TorrentInfo.TotalSize)
leftCandidate := calculateBytesLeft(downloadCandidate, r.TorrentInfo.TotalSize)
d, u, l := R.BitTorrentClient.Round(downloadCandidate, uploadCandidate, leftCandidate, R.TorrentInfo.PieceSize)
d, u, l := r.BitTorrentClient.Round(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)
}
func calculateNextTotalSizeByte(speedBytePerSecond, currentByte, pieceSizeByte, seconds, limitTotalBytes int) int {

View file

@ -4,7 +4,7 @@ import (
"bytes"
"compress/gzip"
"errors"
"io/ioutil"
"io"
"net/http"
"strings"
"time"
@ -13,10 +13,11 @@ import (
)
type HttpTracker struct {
Urls []string
RetryAttempt int
LastAnounceRequest string
LastTackerResponse string
Urls []string
RetryAttempt int
LastAnounceRequest string
LastTackerResponse string
EstimatedTimeToAnnounce time.Time
}
type TrackerResponse struct {
@ -40,23 +41,34 @@ func NewHttpTracker(torrentInfo *bencode.TorrentInfo) (*HttpTracker, error) {
return &HttpTracker{Urls: torrentInfo.TrackerInfo.Urls}, nil
}
func (T *HttpTracker) SwapFirst(currentIdx int) {
aux := T.Urls[0]
T.Urls[0] = T.Urls[currentIdx]
T.Urls[currentIdx] = aux
func (t *HttpTracker) SwapFirst(currentIdx int) {
aux := t.Urls[0]
t.Urls[0] = t.Urls[currentIdx]
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() {
T.RetryAttempt = 0
t.RetryAttempt = 0
}()
if retry {
retryDelay := 30
for {
trackerResp, err := T.tryMakeRequest(query, headers)
trackerResp, err := t.tryMakeRequest(query, headers)
if err != nil {
estimatedTimeToAnnounceUpdateCh <- retryDelay
T.RetryAttempt++
t.updateEstimatedTimeToAnnounce(retryDelay)
t.RetryAttempt++
time.Sleep(time.Duration(retryDelay) * time.Second)
retryDelay *= 2
if retryDelay > 900 {
@ -64,14 +76,16 @@ func (T *HttpTracker) Announce(query string, headers map[string]string, retry bo
}
continue
}
t.HandleSuccessfulResponse(trackerResp)
return trackerResp, nil
}
} else {
resp, err := T.tryMakeRequest(query, headers)
resp, err := t.tryMakeRequest(query, headers)
if err != nil {
return nil, err
}
t.HandleSuccessfulResponse(resp)
return resp, nil
}
}
@ -87,14 +101,14 @@ func (t *HttpTracker) tryMakeRequest(query string, headers map[string]string) (*
resp, err := http.DefaultClient.Do(req)
if err == nil {
if resp.StatusCode == http.StatusOK {
bytesR, _ := ioutil.ReadAll(resp.Body)
bytesR, _ := io.ReadAll(resp.Body)
if len(bytesR) == 0 {
continue
}
mimeType := http.DetectContentType(bytesR)
if mimeType == "application/x-gzip" {
gzipReader, _ := gzip.NewReader(bytes.NewReader(bytesR))
bytesR, _ = ioutil.ReadAll(gzipReader)
bytesR, _ = io.ReadAll(gzipReader)
gzipReader.Close()
}
t.LastTackerResponse = string(bytesR)