2
0
mirror of synced 2025-02-24 14:48:27 +00:00

Add BEP 41 support

This commit is contained in:
Matt Joiner 2015-03-12 20:07:10 +11:00
parent e85b7e228b
commit b3c4afbe25
3 changed files with 89 additions and 7 deletions

View File

@ -17,7 +17,7 @@ type AnnounceRequest struct {
Key int32 Key int32
NumWant int32 // How many peer addresses are desired. -1 for default. NumWant int32 // How many peer addresses are desired. -1 for default.
Port int16 Port int16
} } // 82 bytes
type AnnounceResponse struct { type AnnounceResponse struct {
Interval int32 // Minimum seconds the local peer should wait before next announce. Interval int32 // Minimum seconds the local peer should wait before next announce.

View File

@ -4,6 +4,7 @@ import (
"bytes" "bytes"
"encoding/binary" "encoding/binary"
"errors" "errors"
"fmt"
"io" "io"
"math/rand" "math/rand"
"net" "net"
@ -20,6 +21,11 @@ const (
Announce Announce
Scrape Scrape
Error Error
// BEP 41
optionTypeEndOfOptions = 0
optionTypeNOP = 1
optionTypeURLData = 2
) )
type ConnectionRequest struct { type ConnectionRequest struct {
@ -41,7 +47,7 @@ type RequestHeader struct {
ConnectionId int64 ConnectionId int64
Action Action Action Action
TransactionId int32 TransactionId int32
} } // 16 bytes
type AnnounceResponseHeader struct { type AnnounceResponseHeader struct {
Interval int32 Interval int32
@ -100,13 +106,21 @@ func (c *client) Announce(req *tracker.AnnounceRequest) (res tracker.AnnounceRes
err = tracker.ErrNotConnected err = tracker.ErrNotConnected
return 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 { if err != nil {
return return
} }
var h AnnounceResponseHeader var h AnnounceResponseHeader
err = readBody(b, &h) err = readBody(b, &h)
if err != nil { if err != nil {
if err == io.EOF {
err = io.ErrUnexpectedEOF
}
err = fmt.Errorf("error parsing announce response: %s", err)
return return
} }
res.Interval = h.Interval 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{} buf := &bytes.Buffer{}
err = binary.Write(buf, binary.BigEndian, h) err = binary.Write(buf, binary.BigEndian, h)
if err != nil { if err != nil {
@ -142,6 +158,10 @@ func (c *client) write(h *RequestHeader, body interface{}) (err error) {
panic(err) panic(err)
} }
} }
_, err = buf.Write(trailer)
if err != nil {
return
}
n, err := c.socket.Write(buf.Bytes()) n, err := c.socket.Write(buf.Bytes())
if err != nil { if err != nil {
return return
@ -152,13 +172,23 @@ func (c *client) write(h *RequestHeader, body interface{}) (err error) {
return 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() tid := newTransactionId()
err = c.write(&RequestHeader{ err = c.write(&RequestHeader{
ConnectionId: c.connectionId, ConnectionId: c.connectionId,
Action: action, Action: action,
TransactionId: tid, TransactionId: tid,
}, args) }, args, options)
if err != nil { if err != nil {
return return
} }
@ -223,7 +253,7 @@ func (c *client) Connect() (err error) {
return return
} }
} }
b, err := c.request(Connect, nil) b, err := c.request(Connect, nil, nil)
if err != nil { if err != nil {
return return
} }

View File

@ -5,7 +5,10 @@ import (
"crypto/rand" "crypto/rand"
"encoding/binary" "encoding/binary"
"io" "io"
"io/ioutil"
"log"
"net" "net"
"net/url"
"sync" "sync"
"syscall" "syscall"
"testing" "testing"
@ -144,3 +147,52 @@ func TestAnnounceRandomInfoHash(t *testing.T) {
} }
wg.Wait() 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)
}