]> Sergey Matveev's repositories - btrtrc.git/blob - peer_protocol/handshake.go
go1.19 compatibility
[btrtrc.git] / peer_protocol / handshake.go
1 package peer_protocol
2
3 import (
4         "encoding/hex"
5         "errors"
6         "fmt"
7         "io"
8         "math/bits"
9         "strconv"
10         "strings"
11         "unsafe"
12
13         "github.com/anacrolix/torrent/metainfo"
14 )
15
16 type ExtensionBit uint
17
18 // https://www.bittorrent.org/beps/bep_0004.html
19 // https://wiki.theory.org/BitTorrentSpecification.html#Reserved_Bytes
20 const (
21         ExtensionBitDht                          = 0 // http://www.bittorrent.org/beps/bep_0005.html
22         ExtensionBitFast                         = 2 // http://www.bittorrent.org/beps/bep_0006.html
23         ExtensionBitV2                           = 7 // "Hybrid torrent legacy to v2 upgrade"
24         ExtensionBitAzureusExtensionNegotiation1 = 16
25         ExtensionBitAzureusExtensionNegotiation2 = 17
26         // LibTorrent Extension Protocol, http://www.bittorrent.org/beps/bep_0010.html
27         ExtensionBitLtep = 20
28         // https://wiki.theory.org/BitTorrent_Location-aware_Protocol_1
29         ExtensionBitLocationAwareProtocol    = 43
30         ExtensionBitAzureusMessagingProtocol = 63 // https://www.bittorrent.org/beps/bep_0004.html
31
32 )
33
34 func handshakeWriter(w io.Writer, bb <-chan []byte, done chan<- error) {
35         var err error
36         for b := range bb {
37                 _, err = w.Write(b)
38                 if err != nil {
39                         break
40                 }
41         }
42         done <- err
43 }
44
45 type (
46         PeerExtensionBits [8]byte
47 )
48
49 var bitTags = []struct {
50         bit ExtensionBit
51         tag string
52 }{
53         // Ordered by their bit position left to right.
54         {ExtensionBitAzureusMessagingProtocol, "amp"},
55         {ExtensionBitLocationAwareProtocol, "loc"},
56         {ExtensionBitLtep, "ltep"},
57         {ExtensionBitAzureusExtensionNegotiation2, "azen2"},
58         {ExtensionBitAzureusExtensionNegotiation1, "azen1"},
59         {ExtensionBitV2, "v2"},
60         {ExtensionBitFast, "fast"},
61         {ExtensionBitDht, "dht"},
62 }
63
64 func (pex PeerExtensionBits) String() string {
65         pexHex := hex.EncodeToString(pex[:])
66         tags := make([]string, 0, len(bitTags)+1)
67         for _, bitTag := range bitTags {
68                 if pex.GetBit(bitTag.bit) {
69                         tags = append(tags, bitTag.tag)
70                         pex.SetBit(bitTag.bit, false)
71                 }
72         }
73         unknownCount := bits.OnesCount64(*(*uint64)((unsafe.Pointer(&pex[0]))))
74         if unknownCount != 0 {
75                 tags = append(tags, fmt.Sprintf("%v unknown", unknownCount))
76         }
77         return fmt.Sprintf("%v (%s)", pexHex, strings.Join(tags, ", "))
78
79 }
80
81 func NewPeerExtensionBytes(bits ...ExtensionBit) (ret PeerExtensionBits) {
82         for _, b := range bits {
83                 ret.SetBit(b, true)
84         }
85         return
86 }
87
88 func (pex PeerExtensionBits) SupportsExtended() bool {
89         return pex.GetBit(ExtensionBitLtep)
90 }
91
92 func (pex PeerExtensionBits) SupportsDHT() bool {
93         return pex.GetBit(ExtensionBitDht)
94 }
95
96 func (pex PeerExtensionBits) SupportsFast() bool {
97         return pex.GetBit(ExtensionBitFast)
98 }
99
100 func (pex *PeerExtensionBits) SetBit(bit ExtensionBit, on bool) {
101         if on {
102                 pex[7-bit/8] |= 1 << (bit % 8)
103         } else {
104                 pex[7-bit/8] &^= 1 << (bit % 8)
105         }
106 }
107
108 func (pex PeerExtensionBits) GetBit(bit ExtensionBit) bool {
109         return pex[7-bit/8]&(1<<(bit%8)) != 0
110 }
111
112 type HandshakeResult struct {
113         PeerExtensionBits
114         PeerID [20]byte
115         metainfo.Hash
116 }
117
118 // ih is nil if we expect the peer to declare the InfoHash, such as when the peer initiated the
119 // connection. Returns ok if the Handshake was successful, and err if there was an unexpected
120 // condition other than the peer simply abandoning the Handshake.
121 func Handshake(
122         sock io.ReadWriter, ih *metainfo.Hash, peerID [20]byte, extensions PeerExtensionBits,
123 ) (
124         res HandshakeResult, err error,
125 ) {
126         // Bytes to be sent to the peer. Should never block the sender.
127         postCh := make(chan []byte, 4)
128         // A single error value sent when the writer completes.
129         writeDone := make(chan error, 1)
130         // Performs writes to the socket and ensures posts don't block.
131         go handshakeWriter(sock, postCh, writeDone)
132
133         defer func() {
134                 close(postCh) // Done writing.
135                 if err != nil {
136                         return
137                 }
138                 // Wait until writes complete before returning from handshake.
139                 err = <-writeDone
140                 if err != nil {
141                         err = fmt.Errorf("error writing: %w", err)
142                 }
143         }()
144
145         post := func(bb []byte) {
146                 select {
147                 case postCh <- bb:
148                 default:
149                         panic("mustn't block while posting")
150                 }
151         }
152
153         post([]byte(Protocol))
154         post(extensions[:])
155         if ih != nil { // We already know what we want.
156                 post(ih[:])
157                 post(peerID[:])
158         }
159         var b [68]byte
160         _, err = io.ReadFull(sock, b[:68])
161         if err != nil {
162                 return res, fmt.Errorf("while reading: %w", err)
163         }
164         if string(b[:20]) != Protocol {
165                 return res, errors.New("unexpected protocol string")
166         }
167
168         copyExact := func(dst, src []byte) {
169                 if dstLen, srcLen := uint64(len(dst)), uint64(len(src)); dstLen != srcLen {
170                         panic("dst len " + strconv.FormatUint(dstLen, 10) + " != src len " + strconv.FormatUint(srcLen, 10))
171                 }
172                 copy(dst, src)
173         }
174         copyExact(res.PeerExtensionBits[:], b[20:28])
175         copyExact(res.Hash[:], b[28:48])
176         copyExact(res.PeerID[:], b[48:68])
177         // peerExtensions.Add(res.PeerExtensionBits.String(), 1)
178
179         // TODO: Maybe we can just drop peers here if we're not interested. This
180         // could prevent them trying to reconnect, falsely believing there was
181         // just a problem.
182         if ih == nil { // We were waiting for the peer to tell us what they wanted.
183                 post(res.Hash[:])
184                 post(peerID[:])
185         }
186
187         return
188 }