14 "github.com/anacrolix/missinggo"
15 "github.com/anacrolix/missinggo/pproffd"
17 "github.com/anacrolix/torrent/util"
23 ActionConnect Action = iota
28 connectRequestConnectionId = 0x41727101980
31 optionTypeEndOfOptions = 0
36 type ConnectionRequest struct {
42 type ConnectionResponse struct {
46 type ResponseHeader struct {
51 type RequestHeader struct {
57 type AnnounceResponseHeader struct {
64 registerClientScheme("udp", newUDPClient)
67 func newUDPClient(url *url.URL) client {
73 func newTransactionId() int32 {
74 return int32(rand.Uint32())
77 func timeout(contiguousTimeouts int) (d time.Duration) {
78 if contiguousTimeouts > 8 {
79 contiguousTimeouts = 8
82 for ; contiguousTimeouts > 0; contiguousTimeouts-- {
88 type udpClient struct {
89 contiguousTimeouts int
90 connectionIdReceived time.Time
96 func (me *udpClient) Close() error {
98 return me.socket.Close()
103 func (c *udpClient) URL() string {
104 return c.url.String()
107 func (c *udpClient) String() string {
111 func (c *udpClient) Announce(req *AnnounceRequest) (res AnnounceResponse, err error) {
113 err = ErrNotConnected
116 reqURI := c.url.RequestURI()
117 // Clearly this limits the request URI to 255 bytes. BEP 41 supports
118 // longer but I'm not fussed.
119 options := append([]byte{optionTypeURLData, byte(len(reqURI))}, []byte(reqURI)...)
120 b, err := c.request(ActionAnnounce, req, options)
124 var h AnnounceResponseHeader
125 err = readBody(b, &h)
128 err = io.ErrUnexpectedEOF
130 err = fmt.Errorf("error parsing announce response: %s", err)
133 res.Interval = h.Interval
134 res.Leechers = h.Leechers
135 res.Seeders = h.Seeders
136 cps, err := util.UnmarshalIPv4CompactPeers(b.Bytes())
140 for _, cp := range cps {
141 res.Peers = append(res.Peers, Peer{
149 // body is the binary serializable request body. trailer is optional data
150 // following it, such as for BEP 41.
151 func (c *udpClient) write(h *RequestHeader, body interface{}, trailer []byte) (err error) {
153 err = binary.Write(&buf, binary.BigEndian, h)
158 err = binary.Write(&buf, binary.BigEndian, body)
163 _, err = buf.Write(trailer)
167 n, err := c.socket.Write(buf.Bytes())
172 panic("write should send all or error")
177 func read(r io.Reader, data interface{}) error {
178 return binary.Read(r, binary.BigEndian, data)
181 func write(w io.Writer, data interface{}) error {
182 return binary.Write(w, binary.BigEndian, data)
185 // args is the binary serializable request body. trailer is optional data
186 // following it, such as for BEP 41.
187 func (c *udpClient) request(action Action, args interface{}, options []byte) (responseBody *bytes.Buffer, err error) {
188 tid := newTransactionId()
189 err = c.write(&RequestHeader{
190 ConnectionId: c.connectionId,
197 c.socket.SetReadDeadline(time.Now().Add(timeout(c.contiguousTimeouts)))
198 b := make([]byte, 0x800) // 2KiB
201 n, err = c.socket.Read(b)
202 if opE, ok := err.(*net.OpError); ok {
204 c.contiguousTimeouts++
211 buf := bytes.NewBuffer(b[:n])
213 err = binary.Read(buf, binary.BigEndian, &h)
215 case io.ErrUnexpectedEOF:
221 if h.TransactionId != tid {
224 c.contiguousTimeouts = 0
225 if h.Action == ActionError {
226 err = errors.New(buf.String())
233 func readBody(r io.Reader, data ...interface{}) (err error) {
234 for _, datum := range data {
235 err = binary.Read(r, binary.BigEndian, datum)
243 func (c *udpClient) connected() bool {
244 return !c.connectionIdReceived.IsZero() && time.Now().Before(c.connectionIdReceived.Add(time.Minute))
247 func (c *udpClient) Connect() (err error) {
251 c.connectionId = connectRequestConnectionId
253 hmp := missinggo.SplitHostPort(c.url.Host)
258 c.socket, err = net.Dial("udp", hmp.String())
262 c.socket = pproffd.WrapNetConn(c.socket)
264 b, err := c.request(ActionConnect, nil, nil)
268 var res ConnectionResponse
269 err = readBody(b, &res)
273 c.connectionId = res.ConnectionId
274 c.connectionIdReceived = time.Now()