enki b3204ea07a
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
first commit
2025-08-18 00:40:15 -07:00

291 lines
6.3 KiB
Go

package tracker
import (
"bufio"
"bytes"
"fmt"
"io"
"strconv"
)
// BencodeEncoder provides additional bencode utilities beyond the anacrolix library
type BencodeEncoder struct{}
// NewBencodeEncoder creates a new bencode encoder
func NewBencodeEncoder() *BencodeEncoder {
return &BencodeEncoder{}
}
// EncodeResponse encodes a tracker response with proper bencode formatting
func (e *BencodeEncoder) EncodeResponse(resp *AnnounceResponse) ([]byte, error) {
var buf bytes.Buffer
// Start dictionary
buf.WriteString("d")
// Add fields in alphabetical order (bencode requirement)
if resp.Complete >= 0 {
buf.WriteString("8:complete")
buf.WriteString(e.encodeInt(resp.Complete))
}
if resp.FailureReason != "" {
buf.WriteString("14:failure reason")
buf.WriteString(e.encodeString(resp.FailureReason))
}
if resp.Incomplete >= 0 {
buf.WriteString("10:incomplete")
buf.WriteString(e.encodeInt(resp.Incomplete))
}
if resp.Interval > 0 {
buf.WriteString("8:interval")
buf.WriteString(e.encodeInt(resp.Interval))
}
if resp.MinInterval > 0 {
buf.WriteString("12:min interval")
buf.WriteString(e.encodeInt(resp.MinInterval))
}
// Encode peers
if resp.Peers != nil {
buf.WriteString("5:peers")
if peerBytes, ok := resp.Peers.([]byte); ok {
// Compact format
buf.WriteString(e.encodeBytes(peerBytes))
} else if dictPeers, ok := resp.Peers.([]DictPeer); ok {
// Dictionary format
buf.WriteString("l") // Start list
for _, peer := range dictPeers {
buf.WriteString("d") // Start peer dict
buf.WriteString("2:ip")
buf.WriteString(e.encodeString(peer.IP))
buf.WriteString("7:peer id")
buf.WriteString(e.encodeString(peer.PeerID))
buf.WriteString("4:port")
buf.WriteString(e.encodeInt(peer.Port))
buf.WriteString("e") // End peer dict
}
buf.WriteString("e") // End list
}
}
if resp.TrackerID != "" {
buf.WriteString("10:tracker id")
buf.WriteString(e.encodeString(resp.TrackerID))
}
if resp.WarningMessage != "" {
buf.WriteString("15:warning message")
buf.WriteString(e.encodeString(resp.WarningMessage))
}
// End dictionary
buf.WriteString("e")
return buf.Bytes(), nil
}
// encodeString encodes a string in bencode format
func (e *BencodeEncoder) encodeString(s string) string {
return fmt.Sprintf("%d:%s", len(s), s)
}
// encodeBytes encodes bytes in bencode format
func (e *BencodeEncoder) encodeBytes(b []byte) string {
return fmt.Sprintf("%d:", len(b)) + string(b)
}
// encodeInt encodes an integer in bencode format
func (e *BencodeEncoder) encodeInt(i int) string {
return fmt.Sprintf("i%de", i)
}
// ParseAnnounceQuery parses URL-encoded announce parameters with proper bencode handling
func ParseAnnounceQuery(query map[string][]string) (map[string]interface{}, error) {
result := make(map[string]interface{})
for key, values := range query {
if len(values) == 0 {
continue
}
value := values[0]
switch key {
case "info_hash", "peer_id":
// These are binary data that may be URL-encoded
result[key] = value
case "port", "uploaded", "downloaded", "left", "numwant":
if i, err := strconv.ParseInt(value, 10, 64); err == nil {
result[key] = i
}
case "compact":
result[key] = value == "1"
default:
result[key] = value
}
}
return result, nil
}
// BencodeDecoder provides bencode decoding utilities
type BencodeDecoder struct {
reader *bufio.Reader
}
// NewBencodeDecoder creates a new bencode decoder
func NewBencodeDecoder(r io.Reader) *BencodeDecoder {
return &BencodeDecoder{
reader: bufio.NewReader(r),
}
}
// DecodeDictionary decodes a bencode dictionary
func (d *BencodeDecoder) DecodeDictionary() (map[string]interface{}, error) {
// Read 'd' marker
b, err := d.reader.ReadByte()
if err != nil {
return nil, err
}
if b != 'd' {
return nil, fmt.Errorf("expected dictionary marker 'd', got %c", b)
}
dict := make(map[string]interface{})
for {
// Check for end marker
b, err := d.reader.ReadByte()
if err != nil {
return nil, err
}
if b == 'e' {
break
}
// Put byte back
d.reader.UnreadByte()
// Read key (always a string)
key, err := d.decodeString()
if err != nil {
return nil, fmt.Errorf("error reading dictionary key: %w", err)
}
// Read value
value, err := d.decodeValue()
if err != nil {
return nil, fmt.Errorf("error reading dictionary value for key %s: %w", key, err)
}
dict[key] = value
}
return dict, nil
}
// decodeValue decodes any bencode value
func (d *BencodeDecoder) decodeValue() (interface{}, error) {
b, err := d.reader.ReadByte()
if err != nil {
return nil, err
}
switch {
case b >= '0' && b <= '9':
// String - put byte back and decode
d.reader.UnreadByte()
return d.decodeString()
case b == 'i':
// Integer
return d.decodeInteger()
case b == 'l':
// List
return d.decodeList()
case b == 'd':
// Dictionary - put byte back and decode
d.reader.UnreadByte()
return d.DecodeDictionary()
default:
return nil, fmt.Errorf("unexpected bencode marker: %c", b)
}
}
// decodeString decodes a bencode string
func (d *BencodeDecoder) decodeString() (string, error) {
// Read length
var lengthBytes []byte
for {
b, err := d.reader.ReadByte()
if err != nil {
return "", err
}
if b == ':' {
break
}
lengthBytes = append(lengthBytes, b)
}
length, err := strconv.Atoi(string(lengthBytes))
if err != nil {
return "", fmt.Errorf("invalid string length: %s", string(lengthBytes))
}
// Read string data
data := make([]byte, length)
_, err = io.ReadFull(d.reader, data)
if err != nil {
return "", err
}
return string(data), nil
}
// decodeInteger decodes a bencode integer
func (d *BencodeDecoder) decodeInteger() (int64, error) {
var intBytes []byte
for {
b, err := d.reader.ReadByte()
if err != nil {
return 0, err
}
if b == 'e' {
break
}
intBytes = append(intBytes, b)
}
return strconv.ParseInt(string(intBytes), 10, 64)
}
// decodeList decodes a bencode list
func (d *BencodeDecoder) decodeList() ([]interface{}, error) {
var list []interface{}
for {
// Check for end marker
b, err := d.reader.ReadByte()
if err != nil {
return nil, err
}
if b == 'e' {
break
}
// Put byte back
d.reader.UnreadByte()
// Read value
value, err := d.decodeValue()
if err != nil {
return nil, err
}
list = append(list, value)
}
return list, nil
}