]> Sergey Matveev's repositories - btrtrc.git/blob - peer_protocol/handshake.go
Always return errors when handshakes or dialing fails
[btrtrc.git] / peer_protocol / handshake.go
1 package peer_protocol
2
3 import (
4         "encoding/hex"
5         "fmt"
6         "io"
7
8         "golang.org/x/xerrors"
9
10         "github.com/anacrolix/missinggo"
11         "github.com/anacrolix/torrent/metainfo"
12 )
13
14 type ExtensionBit uint
15
16 const (
17         ExtensionBitDHT      = 0  // http://www.bittorrent.org/beps/bep_0005.html
18         ExtensionBitExtended = 20 // http://www.bittorrent.org/beps/bep_0010.html
19         ExtensionBitFast     = 2  // http://www.bittorrent.org/beps/bep_0006.html
20 )
21
22 func handshakeWriter(w io.Writer, bb <-chan []byte, done chan<- error) {
23         var err error
24         for b := range bb {
25                 _, err = w.Write(b)
26                 if err != nil {
27                         break
28                 }
29         }
30         done <- err
31 }
32
33 type (
34         PeerExtensionBits [8]byte
35 )
36
37 func (me PeerExtensionBits) String() string {
38         return hex.EncodeToString(me[:])
39 }
40
41 func NewPeerExtensionBytes(bits ...ExtensionBit) (ret PeerExtensionBits) {
42         for _, b := range bits {
43                 ret.SetBit(b)
44         }
45         return
46 }
47
48 func (pex PeerExtensionBits) SupportsExtended() bool {
49         return pex.GetBit(ExtensionBitExtended)
50 }
51
52 func (pex PeerExtensionBits) SupportsDHT() bool {
53         return pex.GetBit(ExtensionBitDHT)
54 }
55
56 func (pex PeerExtensionBits) SupportsFast() bool {
57         return pex.GetBit(ExtensionBitFast)
58 }
59
60 func (pex *PeerExtensionBits) SetBit(bit ExtensionBit) {
61         pex[7-bit/8] |= 1 << (bit % 8)
62 }
63
64 func (pex PeerExtensionBits) GetBit(bit ExtensionBit) bool {
65         return pex[7-bit/8]&(1<<(bit%8)) != 0
66 }
67
68 type HandshakeResult struct {
69         PeerExtensionBits
70         PeerID [20]byte
71         metainfo.Hash
72 }
73
74 // ih is nil if we expect the peer to declare the InfoHash, such as when the peer initiated the
75 // connection. Returns ok if the Handshake was successful, and err if there was an unexpected
76 // condition other than the peer simply abandoning the Handshake.
77 func Handshake(
78         sock io.ReadWriter, ih *metainfo.Hash, peerID [20]byte, extensions PeerExtensionBits,
79 ) (
80         res HandshakeResult, err error,
81 ) {
82         // Bytes to be sent to the peer. Should never block the sender.
83         postCh := make(chan []byte, 4)
84         // A single error value sent when the writer completes.
85         writeDone := make(chan error, 1)
86         // Performs writes to the socket and ensures posts don't block.
87         go handshakeWriter(sock, postCh, writeDone)
88
89         defer func() {
90                 close(postCh) // Done writing.
91                 if err != nil {
92                         return
93                 }
94                 // Wait until writes complete before returning from handshake.
95                 err = <-writeDone
96                 if err != nil {
97                         err = fmt.Errorf("error writing: %s", err)
98                 }
99         }()
100
101         post := func(bb []byte) {
102                 select {
103                 case postCh <- bb:
104                 default:
105                         panic("mustn't block while posting")
106                 }
107         }
108
109         post([]byte(Protocol))
110         post(extensions[:])
111         if ih != nil { // We already know what we want.
112                 post(ih[:])
113                 post(peerID[:])
114         }
115         var b [68]byte
116         _, err = io.ReadFull(sock, b[:68])
117         if err != nil {
118                 err = xerrors.Errorf("while reading: %w", err)
119                 return
120         }
121         if string(b[:20]) != Protocol {
122                 err = xerrors.Errorf("unexpected protocol string")
123                 return
124         }
125         missinggo.CopyExact(&res.PeerExtensionBits, b[20:28])
126         missinggo.CopyExact(&res.Hash, b[28:48])
127         missinggo.CopyExact(&res.PeerID, b[48:68])
128         // peerExtensions.Add(res.PeerExtensionBits.String(), 1)
129
130         // TODO: Maybe we can just drop peers here if we're not interested. This
131         // could prevent them trying to reconnect, falsely believing there was
132         // just a problem.
133         if ih == nil { // We were waiting for the peer to tell us what they wanted.
134                 post(res.Hash[:])
135                 post(peerID[:])
136         }
137
138         return
139 }