2018-03-09 15:55:25 +00:00
|
|
|
package network
|
|
|
|
|
|
|
|
import (
|
2019-09-13 12:43:22 +00:00
|
|
|
"errors"
|
|
|
|
"fmt"
|
2018-03-09 15:55:25 +00:00
|
|
|
"net"
|
2018-03-10 12:04:06 +00:00
|
|
|
"sync"
|
2018-03-09 15:55:25 +00:00
|
|
|
|
|
|
|
"github.com/CityOfZion/neo-go/pkg/network/payload"
|
|
|
|
)
|
|
|
|
|
2019-09-13 12:43:22 +00:00
|
|
|
type handShakeStage uint8
|
|
|
|
|
|
|
|
//go:generate stringer -type=handShakeStage
|
|
|
|
const (
|
|
|
|
nothingDone handShakeStage = 0
|
|
|
|
versionSent handShakeStage = 1
|
|
|
|
versionReceived handShakeStage = 2
|
|
|
|
verAckSent handShakeStage = 3
|
|
|
|
verAckReceived handShakeStage = 4
|
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
|
|
|
errStateMismatch = errors.New("tried to send protocol message before handshake completed")
|
|
|
|
)
|
|
|
|
|
2018-03-09 15:55:25 +00:00
|
|
|
// TCPPeer represents a connected remote node in the
|
|
|
|
// network over TCP.
|
|
|
|
type TCPPeer struct {
|
2018-03-14 09:36:59 +00:00
|
|
|
// underlying TCP connection.
|
2019-09-09 14:54:38 +00:00
|
|
|
conn net.Conn
|
|
|
|
addr net.TCPAddr
|
2018-03-09 15:55:25 +00:00
|
|
|
|
2018-03-14 09:36:59 +00:00
|
|
|
// The version of the peer.
|
2018-03-09 15:55:25 +00:00
|
|
|
version *payload.Version
|
|
|
|
|
2019-09-13 12:43:22 +00:00
|
|
|
handShake handShakeStage
|
|
|
|
|
2018-04-13 10:14:08 +00:00
|
|
|
done chan error
|
2018-03-09 15:55:25 +00:00
|
|
|
|
2018-03-14 09:36:59 +00:00
|
|
|
wg sync.WaitGroup
|
2018-03-09 15:55:25 +00:00
|
|
|
}
|
|
|
|
|
2019-09-03 14:51:37 +00:00
|
|
|
// NewTCPPeer returns a TCPPeer structure based on the given connection.
|
2018-04-13 10:14:08 +00:00
|
|
|
func NewTCPPeer(conn net.Conn) *TCPPeer {
|
2019-09-09 14:54:38 +00:00
|
|
|
raddr := conn.RemoteAddr()
|
|
|
|
// can't fail because raddr is a real connection
|
|
|
|
tcpaddr, _ := net.ResolveTCPAddr(raddr.Network(), raddr.String())
|
2018-03-09 15:55:25 +00:00
|
|
|
return &TCPPeer{
|
2019-09-09 14:54:38 +00:00
|
|
|
conn: conn,
|
|
|
|
done: make(chan error, 1),
|
|
|
|
addr: *tcpaddr,
|
2018-03-09 15:55:25 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-04-13 10:14:08 +00:00
|
|
|
// WriteMsg implements the Peer interface. This will write/encode the message
|
2019-09-13 12:43:22 +00:00
|
|
|
// to the underlying connection, this only works for messages other than Version
|
|
|
|
// or VerAck.
|
2018-04-13 10:14:08 +00:00
|
|
|
func (p *TCPPeer) WriteMsg(msg *Message) error {
|
2019-09-13 12:43:22 +00:00
|
|
|
if !p.Handshaked() {
|
|
|
|
return errStateMismatch
|
|
|
|
}
|
|
|
|
return p.writeMsg(msg)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *TCPPeer) writeMsg(msg *Message) error {
|
2018-04-13 10:14:08 +00:00
|
|
|
select {
|
|
|
|
case err := <-p.done:
|
|
|
|
return err
|
|
|
|
default:
|
|
|
|
return msg.Encode(p.conn)
|
2018-03-14 09:36:59 +00:00
|
|
|
}
|
2018-03-09 15:55:25 +00:00
|
|
|
}
|
|
|
|
|
2019-09-13 12:43:22 +00:00
|
|
|
// Handshaked returns status of the handshake, whether it's completed or not.
|
|
|
|
func (p *TCPPeer) Handshaked() bool {
|
|
|
|
return p.handShake == verAckReceived
|
|
|
|
}
|
|
|
|
|
|
|
|
// SendVersion checks for the handshake state and sends a message to the peer.
|
|
|
|
func (p *TCPPeer) SendVersion(msg *Message) error {
|
|
|
|
if p.handShake != nothingDone {
|
|
|
|
return fmt.Errorf("invalid handshake: tried to send Version in %s state", p.handShake.String())
|
|
|
|
}
|
|
|
|
err := p.writeMsg(msg)
|
|
|
|
if err == nil {
|
|
|
|
p.handShake = versionSent
|
|
|
|
}
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// HandleVersion checks for the handshake state and version message contents.
|
|
|
|
func (p *TCPPeer) HandleVersion(version *payload.Version) error {
|
|
|
|
if p.handShake != versionSent {
|
|
|
|
return fmt.Errorf("invalid handshake: received Version in %s state", p.handShake.String())
|
|
|
|
}
|
|
|
|
p.version = version
|
|
|
|
p.handShake = versionReceived
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// SendVersionAck checks for the handshake state and sends a message to the peer.
|
|
|
|
func (p *TCPPeer) SendVersionAck(msg *Message) error {
|
|
|
|
if p.handShake != versionReceived {
|
|
|
|
return fmt.Errorf("invalid handshake: tried to send VersionAck in %s state", p.handShake.String())
|
|
|
|
}
|
|
|
|
err := p.writeMsg(msg)
|
|
|
|
if err == nil {
|
|
|
|
p.handShake = verAckSent
|
|
|
|
}
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// HandleVersionAck checks handshake sequence correctness when VerAck message
|
|
|
|
// is received.
|
|
|
|
func (p *TCPPeer) HandleVersionAck() error {
|
|
|
|
if p.handShake != verAckSent {
|
|
|
|
return fmt.Errorf("invalid handshake: received VersionAck in %s state", p.handShake.String())
|
|
|
|
}
|
|
|
|
p.handShake = verAckReceived
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-09-09 14:54:38 +00:00
|
|
|
// NetAddr implements the Peer interface.
|
|
|
|
func (p *TCPPeer) NetAddr() *net.TCPAddr {
|
|
|
|
return &p.addr
|
2018-03-09 15:55:25 +00:00
|
|
|
}
|
|
|
|
|
2018-03-14 09:36:59 +00:00
|
|
|
// Done implements the Peer interface and notifies
|
|
|
|
// all other resources operating on it that this peer
|
|
|
|
// is no longer running.
|
|
|
|
func (p *TCPPeer) Done() chan error {
|
2018-03-09 15:55:25 +00:00
|
|
|
return p.done
|
|
|
|
}
|
|
|
|
|
2018-04-13 10:14:08 +00:00
|
|
|
// Disconnect will fill the peer's done channel with the given error.
|
|
|
|
func (p *TCPPeer) Disconnect(err error) {
|
2019-09-13 12:41:41 +00:00
|
|
|
p.conn.Close()
|
2019-09-13 12:36:53 +00:00
|
|
|
select {
|
|
|
|
case p.done <- err:
|
|
|
|
// one message to the queue
|
|
|
|
default:
|
|
|
|
// the other side may already be gone, it's OK
|
|
|
|
}
|
2018-04-13 10:14:08 +00:00
|
|
|
}
|
|
|
|
|
2018-03-14 09:36:59 +00:00
|
|
|
// Version implements the Peer interface.
|
|
|
|
func (p *TCPPeer) Version() *payload.Version {
|
|
|
|
return p.version
|
2018-03-10 12:04:06 +00:00
|
|
|
}
|