client.go | 33 +++++++++++++++------------------ connection.go | 37 ++++++++++++++++++++++++++++++++++++- torrent.go | 13 +++++++++++-- diff --git a/client.go b/client.go index 0436dc3c1368dc963a48a989a36e46d69ea3c4d1..cc83037d9e021532f9bbbf824834715b59193895 100644 --- a/client.go +++ b/client.go @@ -1329,27 +1329,19 @@ func (cl *Client) requestPendingMetadata(t *torrent, c *connection) { if t.haveInfo() { return } + if c.PeerExtensionIDs["ut_metadata"] == 0 { + // Peer doesn't support this. + return + } + // Request metadata pieces that we don't have in a random order. var pending []int for index := 0; index < t.metadataPieceCount(); index++ { - if !t.haveMetadataPiece(index) { + if !t.haveMetadataPiece(index) && !c.requestedMetadataPiece(index) { pending = append(pending, index) } } for _, i := range mathRand.Perm(len(pending)) { - c.Post(pp.Message{ - Type: pp.Extended, - ExtendedID: byte(c.PeerExtensionIDs["ut_metadata"]), - ExtendedPayload: func() []byte { - b, err := bencode.Marshal(map[string]int{ - "msg_type": 0, - "piece": pending[i], - }) - if err != nil { - panic(err) - } - return b - }(), - }) + c.requestMetadataPiece(pending[i]) } } @@ -1405,6 +1397,11 @@ if begin < 0 || begin >= len(payload) { log.Printf("got bad metadata piece") break } + if !c.requestedMetadataPiece(piece) { + log.Printf("got unexpected metadata piece %d", piece) + break + } + c.metadataRequests[piece] = false t.saveMetadataPiece(piece, payload[begin:]) c.UsefulChunksReceived++ c.lastUsefulChunkReceived = time.Now() @@ -1600,7 +1597,7 @@ err = errors.New("handshake m value is not dict") break } if c.PeerExtensionIDs == nil { - c.PeerExtensionIDs = make(map[string]int64, len(mTyped)) + c.PeerExtensionIDs = make(map[string]byte, len(mTyped)) } for name, v := range mTyped { id, ok := v.(int64) @@ -1614,7 +1611,7 @@ } else { if c.PeerExtensionIDs[name] == 0 { supportedExtensionMessages.Add(name, 1) } - c.PeerExtensionIDs[name] = id + c.PeerExtensionIDs[name] = byte(id) } } metadata_sizeUntyped, ok := d["metadata_size"] @@ -1623,7 +1620,7 @@ metadata_size, ok := metadata_sizeUntyped.(int64) if !ok { log.Printf("bad metadata_size type: %T", metadata_sizeUntyped) } else { - t.setMetadataSize(metadata_size) + t.setMetadataSize(metadata_size, me) } } if _, ok := c.PeerExtensionIDs["ut_metadata"]; ok { diff --git a/connection.go b/connection.go index 2469894d9e90de5da5716c21941e31f7a62dea1a..b365f743d0e544f3834fc8dc6f727e37c6e7ec9f 100644 --- a/connection.go +++ b/connection.go @@ -57,6 +57,9 @@ Interested bool Choked bool Requests map[request]struct{} requestsLowWater int + // Indexed by metadata piece, set to true if posted and pending a + // response. + metadataRequests []bool // Stuff controlled by the remote peer. PeerID [20]byte @@ -70,7 +73,7 @@ PeerPieces []bool peerHasAll bool PeerMaxRequests int // Maximum pending requests the peer allows. - PeerExtensionIDs map[string]int64 + PeerExtensionIDs map[string]byte PeerClientName string } @@ -272,6 +275,38 @@ func (c *connection) RequestPending(r request) bool { _, ok := c.Requests[r] return ok +} + +func (c *connection) requestMetadataPiece(index int) { + eID := c.PeerExtensionIDs["ut_metadata"] + if eID == 0 { + return + } + if index < len(c.metadataRequests) && c.metadataRequests[index] { + return + } + c.Post(pp.Message{ + Type: pp.Extended, + ExtendedID: eID, + ExtendedPayload: func() []byte { + b, err := bencode.Marshal(map[string]int{ + "msg_type": pp.RequestMetadataExtensionMsgType, + "piece": index, + }) + if err != nil { + panic(err) + } + return b + }(), + }) + for index >= len(c.metadataRequests) { + c.metadataRequests = append(c.metadataRequests, false) + } + c.metadataRequests[index] = true +} + +func (c *connection) requestedMetadataPiece(index int) bool { + return index < len(c.metadataRequests) && c.metadataRequests[index] } // Returns true if more requests can be sent. diff --git a/torrent.go b/torrent.go index 7b9fff057d5d55974d1f533d31a19c0b6db59970..32fa54d7d27d95c9b6b5e900bd7914287cdf9596 100644 --- a/torrent.go +++ b/torrent.go @@ -342,15 +342,24 @@ } return true } -func (t *torrent) setMetadataSize(bytes int64) { - if t.MetaData != nil { +func (t *torrent) setMetadataSize(bytes int64, cl *Client) { + if t.haveInfo() { + // We already know the correct metadata size. return } if bytes <= 0 || bytes > 10000000 { // 10MB, pulled from my ass. + log.Printf("received bad metadata size: %d", bytes) + return + } + if t.MetaData != nil && len(t.MetaData) == int(bytes) { return } t.MetaData = make([]byte, bytes) t.metadataHave = make([]bool, (bytes+(1<<14)-1)/(1<<14)) + for _, c := range t.Conns { + cl.requestPendingMetadata(t, c) + } + } // The current working name for the torrent. Either the name in the info dict,