"bytes"
"encoding/binary"
"errors"
+ "fmt"
"io"
"math/rand"
"net"
Announce
Scrape
Error
+
+ // BEP 41
+ optionTypeEndOfOptions = 0
+ optionTypeNOP = 1
+ optionTypeURLData = 2
)
type ConnectionRequest struct {
ConnectionId int64
Action Action
TransactionId int32
-}
+} // 16 bytes
type AnnounceResponseHeader struct {
Interval int32
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
}
}
-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 {
panic(err)
}
}
+ _, err = buf.Write(trailer)
+ if err != nil {
+ return
+ }
n, err := c.socket.Write(buf.Bytes())
if err != nil {
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()
err = c.write(&RequestHeader{
ConnectionId: c.connectionId,
Action: action,
TransactionId: tid,
- }, args)
+ }, args, options)
if err != nil {
return
}
return
}
}
- b, err := c.request(Connect, nil)
+ b, err := c.request(Connect, nil, nil)
if err != nil {
return
}
"crypto/rand"
"encoding/binary"
"io"
+ "io/ioutil"
+ "log"
"net"
+ "net/url"
"sync"
"syscall"
"testing"
}
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)
+}