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