]> Sergey Matveev's repositories - btrtrc.git/blobdiff - peer_protocol/handshake.go
go1.19 compatibility
[btrtrc.git] / peer_protocol / handshake.go
index fedeaad19bcf59063867da1a36998cd86c23ba6a..a6f648cfe6e16ea3a6b147dd8bd5490a14c57022 100644 (file)
@@ -2,19 +2,33 @@ package peer_protocol
 
 import (
        "encoding/hex"
+       "errors"
        "fmt"
        "io"
+       "math/bits"
+       "strconv"
+       "strings"
+       "unsafe"
 
-       "github.com/anacrolix/missinggo"
        "github.com/anacrolix/torrent/metainfo"
 )
 
 type ExtensionBit uint
 
+// https://www.bittorrent.org/beps/bep_0004.html
+// https://wiki.theory.org/BitTorrentSpecification.html#Reserved_Bytes
 const (
-       ExtensionBitDHT      = 0  // http://www.bittorrent.org/beps/bep_0005.html
-       ExtensionBitExtended = 20 // http://www.bittorrent.org/beps/bep_0010.html
-       ExtensionBitFast     = 2  // http://www.bittorrent.org/beps/bep_0006.html
+       ExtensionBitDht                          = 0 // http://www.bittorrent.org/beps/bep_0005.html
+       ExtensionBitFast                         = 2 // http://www.bittorrent.org/beps/bep_0006.html
+       ExtensionBitV2                           = 7 // "Hybrid torrent legacy to v2 upgrade"
+       ExtensionBitAzureusExtensionNegotiation1 = 16
+       ExtensionBitAzureusExtensionNegotiation2 = 17
+       // LibTorrent Extension Protocol, http://www.bittorrent.org/beps/bep_0010.html
+       ExtensionBitLtep = 20
+       // https://wiki.theory.org/BitTorrent_Location-aware_Protocol_1
+       ExtensionBitLocationAwareProtocol    = 43
+       ExtensionBitAzureusMessagingProtocol = 63 // https://www.bittorrent.org/beps/bep_0004.html
+
 )
 
 func handshakeWriter(w io.Writer, bb <-chan []byte, done chan<- error) {
@@ -32,31 +46,63 @@ type (
        PeerExtensionBits [8]byte
 )
 
-func (me PeerExtensionBits) String() string {
-       return hex.EncodeToString(me[:])
+var bitTags = []struct {
+       bit ExtensionBit
+       tag string
+}{
+       // Ordered by their bit position left to right.
+       {ExtensionBitAzureusMessagingProtocol, "amp"},
+       {ExtensionBitLocationAwareProtocol, "loc"},
+       {ExtensionBitLtep, "ltep"},
+       {ExtensionBitAzureusExtensionNegotiation2, "azen2"},
+       {ExtensionBitAzureusExtensionNegotiation1, "azen1"},
+       {ExtensionBitV2, "v2"},
+       {ExtensionBitFast, "fast"},
+       {ExtensionBitDht, "dht"},
+}
+
+func (pex PeerExtensionBits) String() string {
+       pexHex := hex.EncodeToString(pex[:])
+       tags := make([]string, 0, len(bitTags)+1)
+       for _, bitTag := range bitTags {
+               if pex.GetBit(bitTag.bit) {
+                       tags = append(tags, bitTag.tag)
+                       pex.SetBit(bitTag.bit, false)
+               }
+       }
+       unknownCount := bits.OnesCount64(*(*uint64)((unsafe.Pointer(&pex[0]))))
+       if unknownCount != 0 {
+               tags = append(tags, fmt.Sprintf("%v unknown", unknownCount))
+       }
+       return fmt.Sprintf("%v (%s)", pexHex, strings.Join(tags, ", "))
+
 }
 
 func NewPeerExtensionBytes(bits ...ExtensionBit) (ret PeerExtensionBits) {
        for _, b := range bits {
-               ret.SetBit(b)
+               ret.SetBit(b, true)
        }
        return
 }
 
 func (pex PeerExtensionBits) SupportsExtended() bool {
-       return pex.GetBit(ExtensionBitExtended)
+       return pex.GetBit(ExtensionBitLtep)
 }
 
 func (pex PeerExtensionBits) SupportsDHT() bool {
-       return pex.GetBit(ExtensionBitDHT)
+       return pex.GetBit(ExtensionBitDht)
 }
 
 func (pex PeerExtensionBits) SupportsFast() bool {
        return pex.GetBit(ExtensionBitFast)
 }
 
-func (pex *PeerExtensionBits) SetBit(bit ExtensionBit) {
-       pex[7-bit/8] |= 1 << (bit % 8)
+func (pex *PeerExtensionBits) SetBit(bit ExtensionBit, on bool) {
+       if on {
+               pex[7-bit/8] |= 1 << (bit % 8)
+       } else {
+               pex[7-bit/8] &^= 1 << (bit % 8)
+       }
 }
 
 func (pex PeerExtensionBits) GetBit(bit ExtensionBit) bool {
@@ -69,11 +115,14 @@ type HandshakeResult struct {
        metainfo.Hash
 }
 
-// ih is nil if we expect the peer to declare the InfoHash, such as when the
-// peer initiated the connection. Returns ok if the Handshake was successful,
-// and err if there was an unexpected condition other than the peer simply
-// abandoning the Handshake.
-func Handshake(sock io.ReadWriter, ih *metainfo.Hash, peerID [20]byte, extensions PeerExtensionBits) (res HandshakeResult, ok bool, err error) {
+// ih is nil if we expect the peer to declare the InfoHash, such as when the peer initiated the
+// connection. Returns ok if the Handshake was successful, and err if there was an unexpected
+// condition other than the peer simply abandoning the Handshake.
+func Handshake(
+       sock io.ReadWriter, ih *metainfo.Hash, peerID [20]byte, extensions PeerExtensionBits,
+) (
+       res HandshakeResult, err error,
+) {
        // Bytes to be sent to the peer. Should never block the sender.
        postCh := make(chan []byte, 4)
        // A single error value sent when the writer completes.
@@ -83,16 +132,13 @@ func Handshake(sock io.ReadWriter, ih *metainfo.Hash, peerID [20]byte, extension
 
        defer func() {
                close(postCh) // Done writing.
-               if !ok {
-                       return
-               }
                if err != nil {
-                       panic(err)
+                       return
                }
                // Wait until writes complete before returning from handshake.
                err = <-writeDone
                if err != nil {
-                       err = fmt.Errorf("error writing: %s", err)
+                       err = fmt.Errorf("error writing: %w", err)
                }
        }()
 
@@ -113,16 +159,22 @@ func Handshake(sock io.ReadWriter, ih *metainfo.Hash, peerID [20]byte, extension
        var b [68]byte
        _, err = io.ReadFull(sock, b[:68])
        if err != nil {
-               err = nil
-               return
+               return res, fmt.Errorf("while reading: %w", err)
        }
        if string(b[:20]) != Protocol {
-               return
+               return res, errors.New("unexpected protocol string")
+       }
+
+       copyExact := func(dst, src []byte) {
+               if dstLen, srcLen := uint64(len(dst)), uint64(len(src)); dstLen != srcLen {
+                       panic("dst len " + strconv.FormatUint(dstLen, 10) + " != src len " + strconv.FormatUint(srcLen, 10))
+               }
+               copy(dst, src)
        }
-       missinggo.CopyExact(&res.PeerExtensionBits, b[20:28])
-       missinggo.CopyExact(&res.Hash, b[28:48])
-       missinggo.CopyExact(&res.PeerID, b[48:68])
-       // peerExtensions.Add(res.PeerExtensionBits.String(), 1) 
+       copyExact(res.PeerExtensionBits[:], b[20:28])
+       copyExact(res.Hash[:], b[28:48])
+       copyExact(res.PeerID[:], b[48:68])
+       // peerExtensions.Add(res.PeerExtensionBits.String(), 1)
 
        // TODO: Maybe we can just drop peers here if we're not interested. This
        // could prevent them trying to reconnect, falsely believing there was
@@ -132,6 +184,5 @@ func Handshake(sock io.ReadWriter, ih *metainfo.Hash, peerID [20]byte, extension
                post(peerID[:])
        }
 
-       ok = true
        return
 }