mirror of
https://github.com/sot-tech/mochi.git
synced 2026-04-26 15:40:01 -07:00
134 lines
3.4 KiB
Go
134 lines
3.4 KiB
Go
package http
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"io"
|
|
"net"
|
|
"sort"
|
|
"strconv"
|
|
"time"
|
|
|
|
"github.com/valyala/fasthttp"
|
|
|
|
"github.com/sot-tech/mochi/bittorrent"
|
|
"github.com/sot-tech/mochi/pkg/bytepool"
|
|
)
|
|
|
|
var respBufferPool = bytepool.NewBufferPool()
|
|
|
|
func writeErrorResponse(w io.StringWriter, err error) {
|
|
message := "mochi internal error"
|
|
var clientErr bittorrent.ClientError
|
|
if errors.As(err, &clientErr) {
|
|
message = clientErr.Error()
|
|
} else {
|
|
logger.Error().Err(err).Msg("internal error")
|
|
}
|
|
_, _ = w.WriteString("d14:failure reason" + strconv.Itoa(len(message)) + ":" + message + "e")
|
|
}
|
|
|
|
func writeAnnounceResponse(w io.Writer, resp *bittorrent.AnnounceResponse, compact, includePeerID bool) {
|
|
bb := respBufferPool.Get()
|
|
defer respBufferPool.Put(bb)
|
|
|
|
if resp.Interval > 0 {
|
|
resp.Interval /= time.Second
|
|
}
|
|
if resp.Interval > 0 {
|
|
resp.MinInterval /= time.Second
|
|
}
|
|
|
|
bb.WriteString("d8:completei")
|
|
bb.Write(fasthttp.AppendUint(nil, int(resp.Complete)))
|
|
bb.WriteString("e10:incompletei")
|
|
bb.Write(fasthttp.AppendUint(nil, int(resp.Incomplete)))
|
|
bb.WriteString("e8:intervali")
|
|
bb.Write(fasthttp.AppendUint(nil, int(resp.Interval)))
|
|
bb.WriteString("e12:min intervali")
|
|
bb.Write(fasthttp.AppendUint(nil, int(resp.MinInterval)))
|
|
bb.WriteByte('e')
|
|
|
|
// Add the peers to the dictionary in the compact format.
|
|
if compact {
|
|
// Add the IPv4 peers to the dictionary.
|
|
compactAddresses(bb, resp.IPv4Peers, false)
|
|
// Add the IPv6 peers to the dictionary.
|
|
compactAddresses(bb, resp.IPv6Peers, true)
|
|
} else {
|
|
// Add the peers to the dictionary.
|
|
bb.WriteString("5:peersl")
|
|
for _, peer := range resp.IPv4Peers {
|
|
dictAddress(bb, peer, includePeerID)
|
|
}
|
|
for _, peer := range resp.IPv6Peers {
|
|
dictAddress(bb, peer, includePeerID)
|
|
}
|
|
bb.WriteByte('e')
|
|
}
|
|
bb.WriteByte('e')
|
|
|
|
_, _ = bb.WriteTo(w)
|
|
}
|
|
|
|
func compactAddresses(bb *bytes.Buffer, peers bittorrent.Peers, v6 bool) {
|
|
l := len(peers)
|
|
if l > 0 {
|
|
key, al := "5:peers", net.IPv4len
|
|
if v6 {
|
|
key, al = "6:peers6", net.IPv6len
|
|
}
|
|
bb.WriteString(key)
|
|
bb.Write(fasthttp.AppendUint(nil, (al+2)*l))
|
|
bb.WriteByte(':')
|
|
for _, peer := range peers {
|
|
bb.Write(peer.Addr().AsSlice())
|
|
port := peer.Port()
|
|
bb.Write([]byte{byte(port >> 8), byte(port)})
|
|
}
|
|
}
|
|
}
|
|
|
|
func dictAddress(bb *bytes.Buffer, peer bittorrent.Peer, includePeerID bool) {
|
|
bb.WriteString("d2:ip")
|
|
addr := peer.Addr().String()
|
|
bb.Write(fasthttp.AppendUint(nil, len(addr)))
|
|
bb.WriteByte(':')
|
|
bb.WriteString(addr)
|
|
if includePeerID {
|
|
bb.WriteString("7:peer id20:")
|
|
bb.Write(peer.ID.Bytes())
|
|
}
|
|
bb.WriteString("4:porti")
|
|
port := peer.Port()
|
|
bb.Write([]byte{byte(port >> 8), byte(port), 'e', 'e'})
|
|
}
|
|
|
|
func writeScrapeResponse(w io.Writer, resp *bittorrent.ScrapeResponse) {
|
|
bb := respBufferPool.Get()
|
|
defer respBufferPool.Put(bb)
|
|
bb.WriteString("d5:filesd")
|
|
l := len(resp.Data)
|
|
if l > 0 {
|
|
if l > 1 {
|
|
sort.Slice(resp.Data, func(i, j int) bool {
|
|
return resp.Data[i].InfoHash < resp.Data[j].InfoHash
|
|
})
|
|
}
|
|
for _, scrape := range resp.Data {
|
|
bb.Write(fasthttp.AppendUint(nil, len(scrape.InfoHash)))
|
|
bb.WriteByte(':')
|
|
bb.Write([]byte(scrape.InfoHash))
|
|
bb.WriteString("d8:completei")
|
|
bb.Write(fasthttp.AppendUint(nil, int(scrape.Complete)))
|
|
bb.WriteString("e10:downloadedi")
|
|
bb.Write(fasthttp.AppendUint(nil, int(scrape.Snatches)))
|
|
bb.WriteString("e10:incompletei")
|
|
bb.Write(fasthttp.AppendUint(nil, int(scrape.Incomplete)))
|
|
bb.Write([]byte{'e', 'e'})
|
|
}
|
|
}
|
|
bb.Write([]byte{'e', 'e'})
|
|
_, _ = bb.WriteTo(w)
|
|
}
|