)
// This is a lazy union representing all the possible fields for messages. Go doesn't have ADTs, and
-// I didn't choose to use type-assertions.
+// I didn't choose to use type-assertions. Fields are ordered to minimize struct size and padding.
type Message struct {
- Keepalive bool
- Type MessageType
- Index, Begin, Length Integer
+ PiecesRoot [32]byte
Piece []byte
Bitfield []bool
- ExtendedID ExtensionNumber
ExtendedPayload []byte
+ Hashes [][32]byte
+ Index, Begin, Length Integer
+ BaseLayer Integer
+ ProofLayers Integer
Port uint16
+ Type MessageType
+ ExtendedID ExtensionNumber
+ Keepalive bool
}
var _ interface {
}
func (msg Message) MarshalBinary() (data []byte, err error) {
+ // It might look like you could have a pool of buffers and preallocate the message length
+ // prefix, but because we have to return []byte, it becomes non-trivial to make this fast. You
+ // will need a benchmark.
var buf bytes.Buffer
+ mustWrite := func(data any) {
+ err := binary.Write(&buf, binary.BigEndian, data)
+ if err != nil {
+ panic(err)
+ }
+ }
+ writeConsecutive := func(data ...any) {
+ for _, d := range data {
+ mustWrite(d)
+ }
+ }
if !msg.Keepalive {
err = buf.WriteByte(byte(msg.Type))
if err != nil {
_, err = buf.Write(msg.ExtendedPayload)
case Port:
err = binary.Write(&buf, binary.BigEndian, msg.Port)
+ case HashRequest:
+ buf.Write(msg.PiecesRoot[:])
+ writeConsecutive(msg.BaseLayer, msg.Index, msg.Length, msg.ProofLayers)
default:
err = fmt.Errorf("unknown message type: %v", msg.Type)
}
"context"
"errors"
"fmt"
+ "github.com/anacrolix/torrent/merkle"
"io"
"math/rand"
"net"
peerRequestDataAllocLimiter alloclim.Limiter
outstandingHolepunchingRendezvous map[netip.AddrPort]struct{}
+
+ sentHashRequests map[hashRequest]struct{}
}
func (cn *PeerConn) pexStatus() string {
// knowledge of write buffers.
return
}
+ cn.requestMissingHashes()
cn.maybeUpdateActualRequestState()
if cn.pex.IsEnabled() {
if flow := cn.pex.Share(cn.write); !flow {
})
return nil
}
+
+func (pc *PeerConn) requestMissingHashes() {
+ if !pc.t.haveInfo() {
+ return
+ }
+ info := pc.t.info
+ if !info.HasV2() {
+ return
+ }
+ baseLayer := pp.Integer(merkle.Log2RoundingUp(merkle.RoundUpToPowerOfTwo(
+ uint((pc.t.usualPieceSize() + merkle.BlockSize - 1) / merkle.BlockSize)),
+ ))
+ for _, file := range info.UpvertedFiles() {
+ piecesRoot := file.PiecesRoot.Unwrap()
+ fileNumPieces := int((file.Length + info.PieceLength - 1) / info.PieceLength)
+ proofLayers := pp.Integer(0)
+ // We would be requesting the leaves, the file must be short enough that we can just do with
+ // the pieces root as the piece hash.
+ if fileNumPieces <= 1 {
+ continue
+ }
+ for index := 0; index < fileNumPieces; index += 512 {
+ // Minimizing to the number of pieces in a file conflicts with the BEP.
+ length := merkle.RoundUpToPowerOfTwo(uint(min(512, fileNumPieces-index)))
+ if length < 2 {
+ // This should have been filtered out by baseLayer and pieces root as piece hash
+ // checks.
+ panic(length)
+ }
+ msg := pp.Message{
+ Type: pp.HashRequest,
+ PiecesRoot: piecesRoot,
+ BaseLayer: baseLayer,
+ Index: pp.Integer(index),
+ Length: pp.Integer(length),
+ ProofLayers: proofLayers,
+ }
+ hr := hashRequestFromMessage(msg)
+ if generics.MapContains(pc.sentHashRequests, hr) {
+ continue
+ }
+ pc.write(msg)
+ generics.MakeMapIfNil(&pc.sentHashRequests)
+ pc.sentHashRequests[hr] = struct{}{}
+ }
+ }
+}
+
+type hashRequest struct {
+ piecesRoot [32]byte
+ baseLayer, index, length, proofLayers pp.Integer
+}
+
+func (hr hashRequest) toMessage() pp.Message {
+ return pp.Message{
+ Type: pp.HashRequest,
+ PiecesRoot: hr.piecesRoot,
+ BaseLayer: hr.baseLayer,
+ Index: hr.index,
+ Length: hr.length,
+ ProofLayers: hr.proofLayers,
+ }
+}
+
+func hashRequestFromMessage(m pp.Message) hashRequest {
+ return hashRequest{
+ piecesRoot: m.PiecesRoot,
+ baseLayer: m.BaseLayer,
+ index: m.Index,
+ length: m.Length,
+ proofLayers: m.ProofLayers,
+ }
+}
if numFiles != 1 {
panic(fmt.Sprintf("%v:%v", beginFile, endFile))
}
+ if t.info.HasV2() {
+ file := piece.mustGetOnlyFile()
+ if file.numPieces() == 1 {
+ piece.hashV2.Set(file.piecesRoot.Unwrap())
+ }
+ }
}
}
}
if !ret.Complete && t.piecePartiallyDownloaded(index) {
ret.Partial = true
}
+ if t.info.HasV2() && !p.hashV2.Ok {
+ ret.MissingPieceLayerHash = true
+ }
return
}
if !psr.Ok {
ret += "?"
}
+ if psr.MissingPieceLayerHash {
+ ret += "h"
+ }
return
}