14 "github.com/anacrolix/dht/krpc"
15 "github.com/anacrolix/missinggo"
16 "github.com/anacrolix/missinggo/pproffd"
22 ActionConnect Action = iota
27 connectRequestConnectionId = 0x41727101980
30 optionTypeEndOfOptions = 0
35 type ConnectionRequest struct {
41 type ConnectionResponse struct {
45 type ResponseHeader struct {
50 type RequestHeader struct {
56 type AnnounceResponseHeader struct {
62 func newTransactionId() int32 {
63 return int32(rand.Uint32())
66 func timeout(contiguousTimeouts int) (d time.Duration) {
67 if contiguousTimeouts > 8 {
68 contiguousTimeouts = 8
71 for ; contiguousTimeouts > 0; contiguousTimeouts-- {
77 type udpAnnounce struct {
78 contiguousTimeouts int
79 connectionIdReceived time.Time
85 func (c *udpAnnounce) Close() error {
87 return c.socket.Close()
92 func (c *udpAnnounce) Do(req *AnnounceRequest) (res AnnounceResponse, err error) {
97 reqURI := c.url.RequestURI()
98 // Clearly this limits the request URI to 255 bytes. BEP 41 supports
99 // longer but I'm not fussed.
100 options := append([]byte{optionTypeURLData, byte(len(reqURI))}, []byte(reqURI)...)
101 b, err := c.request(ActionAnnounce, req, options)
105 var h AnnounceResponseHeader
106 err = readBody(b, &h)
109 err = io.ErrUnexpectedEOF
111 err = fmt.Errorf("error parsing announce response: %s", err)
114 res.Interval = h.Interval
115 res.Leechers = h.Leechers
116 res.Seeders = h.Seeders
117 var cps krpc.CompactIPv4NodeAddrs
118 err = cps.UnmarshalBinary(b.Bytes())
122 for _, cp := range cps {
123 res.Peers = append(res.Peers, Peer{
131 // body is the binary serializable request body. trailer is optional data
132 // following it, such as for BEP 41.
133 func (c *udpAnnounce) write(h *RequestHeader, body interface{}, trailer []byte) (err error) {
135 err = binary.Write(&buf, binary.BigEndian, h)
140 err = binary.Write(&buf, binary.BigEndian, body)
145 _, err = buf.Write(trailer)
149 n, err := c.socket.Write(buf.Bytes())
154 panic("write should send all or error")
159 func read(r io.Reader, data interface{}) error {
160 return binary.Read(r, binary.BigEndian, data)
163 func write(w io.Writer, data interface{}) error {
164 return binary.Write(w, binary.BigEndian, data)
167 // args is the binary serializable request body. trailer is optional data
168 // following it, such as for BEP 41.
169 func (c *udpAnnounce) request(action Action, args interface{}, options []byte) (responseBody *bytes.Buffer, err error) {
170 tid := newTransactionId()
171 err = c.write(&RequestHeader{
172 ConnectionId: c.connectionId,
179 c.socket.SetReadDeadline(time.Now().Add(timeout(c.contiguousTimeouts)))
180 b := make([]byte, 0x800) // 2KiB
183 n, err = c.socket.Read(b)
184 if opE, ok := err.(*net.OpError); ok {
186 c.contiguousTimeouts++
193 buf := bytes.NewBuffer(b[:n])
195 err = binary.Read(buf, binary.BigEndian, &h)
197 case io.ErrUnexpectedEOF:
203 if h.TransactionId != tid {
206 c.contiguousTimeouts = 0
207 if h.Action == ActionError {
208 err = errors.New(buf.String())
215 func readBody(r io.Reader, data ...interface{}) (err error) {
216 for _, datum := range data {
217 err = binary.Read(r, binary.BigEndian, datum)
225 func (c *udpAnnounce) connected() bool {
226 return !c.connectionIdReceived.IsZero() && time.Now().Before(c.connectionIdReceived.Add(time.Minute))
229 func (c *udpAnnounce) connect() (err error) {
233 c.connectionId = connectRequestConnectionId
235 hmp := missinggo.SplitHostMaybePort(c.url.Host)
240 c.socket, err = net.Dial("udp", hmp.String())
244 c.socket = pproffd.WrapNetConn(c.socket)
246 b, err := c.request(ActionConnect, nil, nil)
250 var res ConnectionResponse
251 err = readBody(b, &res)
255 c.connectionId = res.ConnectionId
256 c.connectionIdReceived = time.Now()
260 func announceUDP(ar *AnnounceRequest, _url *url.URL) (AnnounceResponse, error) {