Add BEP 41 support
This commit is contained in:
parent
e85b7e228b
commit
b3c4afbe25
@ -17,7 +17,7 @@ type AnnounceRequest struct {
|
||||
Key int32
|
||||
NumWant int32 // How many peer addresses are desired. -1 for default.
|
||||
Port int16
|
||||
}
|
||||
} // 82 bytes
|
||||
|
||||
type AnnounceResponse struct {
|
||||
Interval int32 // Minimum seconds the local peer should wait before next announce.
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/rand"
|
||||
"net"
|
||||
@ -20,6 +21,11 @@ const (
|
||||
Announce
|
||||
Scrape
|
||||
Error
|
||||
|
||||
// BEP 41
|
||||
optionTypeEndOfOptions = 0
|
||||
optionTypeNOP = 1
|
||||
optionTypeURLData = 2
|
||||
)
|
||||
|
||||
type ConnectionRequest struct {
|
||||
@ -41,7 +47,7 @@ type RequestHeader struct {
|
||||
ConnectionId int64
|
||||
Action Action
|
||||
TransactionId int32
|
||||
}
|
||||
} // 16 bytes
|
||||
|
||||
type AnnounceResponseHeader struct {
|
||||
Interval int32
|
||||
@ -100,13 +106,21 @@ func (c *client) Announce(req *tracker.AnnounceRequest) (res tracker.AnnounceRes
|
||||
err = tracker.ErrNotConnected
|
||||
return
|
||||
}
|
||||
b, err := c.request(Announce, req)
|
||||
reqURI := c.url.RequestURI()
|
||||
// Clearly this limits the request URI to 255 bytes. BEP 41 supports
|
||||
// longer but I'm not fussed.
|
||||
options := append([]byte{optionTypeURLData, byte(len(reqURI))}, []byte(reqURI)...)
|
||||
b, err := c.request(Announce, req, options)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var h AnnounceResponseHeader
|
||||
err = readBody(b, &h)
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
err = io.ErrUnexpectedEOF
|
||||
}
|
||||
err = fmt.Errorf("error parsing announce response: %s", err)
|
||||
return
|
||||
}
|
||||
res.Interval = h.Interval
|
||||
@ -130,7 +144,9 @@ func (c *client) Announce(req *tracker.AnnounceRequest) (res tracker.AnnounceRes
|
||||
}
|
||||
}
|
||||
|
||||
func (c *client) write(h *RequestHeader, body interface{}) (err error) {
|
||||
// body is the binary serializable request body. trailer is optional data
|
||||
// following it, such as for BEP 41.
|
||||
func (c *client) write(h *RequestHeader, body interface{}, trailer []byte) (err error) {
|
||||
buf := &bytes.Buffer{}
|
||||
err = binary.Write(buf, binary.BigEndian, h)
|
||||
if err != nil {
|
||||
@ -142,6 +158,10 @@ func (c *client) write(h *RequestHeader, body interface{}) (err error) {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
_, err = buf.Write(trailer)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
n, err := c.socket.Write(buf.Bytes())
|
||||
if err != nil {
|
||||
return
|
||||
@ -152,13 +172,23 @@ func (c *client) write(h *RequestHeader, body interface{}) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (c *client) request(action Action, args interface{}) (responseBody *bytes.Reader, err error) {
|
||||
func read(r io.Reader, data interface{}) error {
|
||||
return binary.Read(r, binary.BigEndian, data)
|
||||
}
|
||||
|
||||
func write(w io.Writer, data interface{}) error {
|
||||
return binary.Write(w, binary.BigEndian, data)
|
||||
}
|
||||
|
||||
// args is the binary serializable request body. trailer is optional data
|
||||
// following it, such as for BEP 41.
|
||||
func (c *client) request(action Action, args interface{}, options []byte) (responseBody *bytes.Reader, err error) {
|
||||
tid := newTransactionId()
|
||||
err = c.write(&RequestHeader{
|
||||
ConnectionId: c.connectionId,
|
||||
Action: action,
|
||||
TransactionId: tid,
|
||||
}, args)
|
||||
}, args, options)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@ -223,7 +253,7 @@ func (c *client) Connect() (err error) {
|
||||
return
|
||||
}
|
||||
}
|
||||
b, err := c.request(Connect, nil)
|
||||
b, err := c.request(Connect, nil, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
@ -5,7 +5,10 @@ import (
|
||||
"crypto/rand"
|
||||
"encoding/binary"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"net/url"
|
||||
"sync"
|
||||
"syscall"
|
||||
"testing"
|
||||
@ -144,3 +147,52 @@ func TestAnnounceRandomInfoHash(t *testing.T) {
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
// Check that URLPath option is done correctly.
|
||||
func TestURLPathOption(t *testing.T) {
|
||||
conn, err := net.ListenUDP("udp", nil)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer conn.Close()
|
||||
cl := newClient(&url.URL{
|
||||
Host: conn.LocalAddr().String(),
|
||||
Path: "/announce",
|
||||
})
|
||||
go func() {
|
||||
err = cl.Connect()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
log.Print("connected")
|
||||
_, err = cl.Announce(&tracker.AnnounceRequest{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}()
|
||||
var b [512]byte
|
||||
_, addr, _ := conn.ReadFrom(b[:])
|
||||
r := bytes.NewReader(b[:])
|
||||
var h RequestHeader
|
||||
read(r, &h)
|
||||
w := &bytes.Buffer{}
|
||||
write(w, ResponseHeader{
|
||||
TransactionId: h.TransactionId,
|
||||
})
|
||||
write(w, ConnectionResponse{42})
|
||||
conn.WriteTo(w.Bytes(), addr)
|
||||
n, _, _ := conn.ReadFrom(b[:])
|
||||
r = bytes.NewReader(b[:n])
|
||||
read(r, &h)
|
||||
read(r, &tracker.AnnounceRequest{})
|
||||
all, _ := ioutil.ReadAll(r)
|
||||
if string(all) != "\x02\x09/announce" {
|
||||
t.FailNow()
|
||||
}
|
||||
w = &bytes.Buffer{}
|
||||
write(w, ResponseHeader{
|
||||
TransactionId: h.TransactionId,
|
||||
})
|
||||
write(w, AnnounceResponseHeader{})
|
||||
conn.WriteTo(w.Bytes(), addr)
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user