Some checks are pending
CI Pipeline / Run Tests (push) Waiting to run
CI Pipeline / Lint Code (push) Waiting to run
CI Pipeline / Security Scan (push) Waiting to run
CI Pipeline / Build Docker Images (push) Blocked by required conditions
CI Pipeline / E2E Tests (push) Blocked by required conditions
168 lines
4.0 KiB
Go
168 lines
4.0 KiB
Go
package torrent
|
|
|
|
import (
|
|
"crypto/sha1"
|
|
"fmt"
|
|
"net/url"
|
|
|
|
"github.com/anacrolix/torrent/bencode"
|
|
"github.com/anacrolix/torrent/metainfo"
|
|
)
|
|
|
|
type TorrentInfo struct {
|
|
InfoHash string
|
|
TorrentData []byte
|
|
Magnet string
|
|
}
|
|
|
|
type FileInfo struct {
|
|
Name string
|
|
Size int64
|
|
Pieces []PieceInfo
|
|
WebSeedURL string
|
|
}
|
|
|
|
type PieceInfo struct {
|
|
Index int
|
|
Hash [20]byte // SHA-1 hash for BitTorrent compatibility
|
|
SHA256 string // SHA-256 hash for Blossom
|
|
Length int
|
|
}
|
|
|
|
func CreateTorrent(fileInfo FileInfo, trackers []string, gatewayURL string, dhtNodes [][]interface{}) (*TorrentInfo, error) {
|
|
// Calculate piece length based on file size (following BUD-10 spec)
|
|
pieceLength := calculatePieceLength(fileInfo.Size)
|
|
|
|
// Create pieces buffer - concatenated SHA-1 hashes
|
|
var pieces []byte
|
|
for _, piece := range fileInfo.Pieces {
|
|
pieces = append(pieces, piece.Hash[:]...)
|
|
}
|
|
|
|
// Create metainfo
|
|
info := metainfo.Info{
|
|
Name: fileInfo.Name,
|
|
Length: fileInfo.Size,
|
|
PieceLength: pieceLength,
|
|
Pieces: pieces,
|
|
}
|
|
|
|
// Build announce list with gateway tracker first, then fallbacks
|
|
var announceList metainfo.AnnounceList
|
|
|
|
// Primary: Gateway's built-in tracker
|
|
if gatewayURL != "" {
|
|
gatewayTracker := fmt.Sprintf("%s/announce", gatewayURL)
|
|
announceList = append(announceList, []string{gatewayTracker})
|
|
}
|
|
|
|
// Fallbacks: External trackers
|
|
for _, tracker := range trackers {
|
|
announceList = append(announceList, []string{tracker})
|
|
}
|
|
|
|
// Primary announce URL (gateway tracker if available, otherwise first external)
|
|
primaryAnnounce := ""
|
|
if len(announceList) > 0 && len(announceList[0]) > 0 {
|
|
primaryAnnounce = announceList[0][0]
|
|
} else if len(trackers) > 0 {
|
|
primaryAnnounce = trackers[0]
|
|
}
|
|
|
|
// Convert DHT nodes to metainfo.Node format
|
|
var nodes []metainfo.Node
|
|
for _, nodeArray := range dhtNodes {
|
|
if len(nodeArray) >= 2 {
|
|
// Node format is "host:port" string
|
|
node := metainfo.Node(fmt.Sprintf("%v:%v", nodeArray[0], nodeArray[1]))
|
|
nodes = append(nodes, node)
|
|
}
|
|
}
|
|
|
|
mi := metainfo.MetaInfo{
|
|
InfoBytes: bencode.MustMarshal(info),
|
|
Announce: primaryAnnounce,
|
|
AnnounceList: announceList,
|
|
Nodes: nodes, // DHT bootstrap nodes (BEP-5)
|
|
}
|
|
|
|
// Add WebSeed support (BEP-19)
|
|
if fileInfo.WebSeedURL != "" {
|
|
mi.UrlList = []string{fileInfo.WebSeedURL}
|
|
}
|
|
|
|
// Calculate info hash
|
|
infoHash := mi.HashInfoBytes()
|
|
|
|
// Generate torrent data
|
|
torrentData, err := bencode.Marshal(mi)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error marshaling torrent: %w", err)
|
|
}
|
|
|
|
// Generate magnet link with all trackers
|
|
allTrackers := []string{}
|
|
for _, tier := range announceList {
|
|
allTrackers = append(allTrackers, tier...)
|
|
}
|
|
magnet := generateMagnetLink(infoHash, fileInfo.Name, allTrackers, fileInfo.WebSeedURL)
|
|
|
|
return &TorrentInfo{
|
|
InfoHash: fmt.Sprintf("%x", infoHash),
|
|
TorrentData: torrentData,
|
|
Magnet: magnet,
|
|
}, nil
|
|
}
|
|
|
|
func calculatePieceLength(fileSize int64) int64 {
|
|
// Following BUD-10 piece size strategy
|
|
const (
|
|
KB = 1024
|
|
MB = KB * 1024
|
|
GB = MB * 1024
|
|
)
|
|
|
|
switch {
|
|
case fileSize < 50*MB:
|
|
return 256 * KB
|
|
case fileSize < 500*MB:
|
|
return 512 * KB
|
|
case fileSize < 2*GB:
|
|
return 1 * MB
|
|
default:
|
|
return 2 * MB
|
|
}
|
|
}
|
|
|
|
func generateMagnetLink(infoHash [20]byte, name string, trackers []string, webSeedURL string) string {
|
|
params := url.Values{}
|
|
params.Set("xt", fmt.Sprintf("urn:btih:%x", infoHash))
|
|
params.Set("dn", name)
|
|
|
|
for _, tracker := range trackers {
|
|
params.Add("tr", tracker)
|
|
}
|
|
|
|
if webSeedURL != "" {
|
|
params.Set("ws", webSeedURL)
|
|
}
|
|
|
|
return "magnet:?" + params.Encode()
|
|
}
|
|
|
|
// ConvertSHA256ToSHA1 converts SHA-256 data to SHA-1 for BitTorrent compatibility
|
|
// This is used when we have chunk data and need both hashes
|
|
func ConvertSHA256ToSHA1(data []byte) [20]byte {
|
|
hash := sha1.Sum(data)
|
|
return hash
|
|
}
|
|
|
|
// CreatePieceInfo creates piece info from chunk data
|
|
func CreatePieceInfo(index int, data []byte, sha256Hash string) PieceInfo {
|
|
return PieceInfo{
|
|
Index: index,
|
|
Hash: ConvertSHA256ToSHA1(data),
|
|
SHA256: sha256Hash,
|
|
Length: len(data),
|
|
}
|
|
} |