2018-01-26 18:04:13 +00:00
|
|
|
package network
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/binary"
|
|
|
|
"errors"
|
2019-09-22 17:09:55 +00:00
|
|
|
"fmt"
|
2018-01-27 15:00:28 +00:00
|
|
|
|
2018-03-25 10:45:54 +00:00
|
|
|
"github.com/CityOfZion/neo-go/config"
|
2019-11-08 15:40:21 +00:00
|
|
|
"github.com/CityOfZion/neo-go/pkg/consensus"
|
2020-01-14 12:32:07 +00:00
|
|
|
"github.com/CityOfZion/neo-go/pkg/core/block"
|
2018-03-10 12:04:06 +00:00
|
|
|
"github.com/CityOfZion/neo-go/pkg/core/transaction"
|
2019-08-23 15:50:45 +00:00
|
|
|
"github.com/CityOfZion/neo-go/pkg/crypto/hash"
|
2019-09-16 09:18:13 +00:00
|
|
|
"github.com/CityOfZion/neo-go/pkg/io"
|
2018-02-01 18:54:23 +00:00
|
|
|
"github.com/CityOfZion/neo-go/pkg/network/payload"
|
2018-01-26 18:04:13 +00:00
|
|
|
)
|
|
|
|
|
2018-01-26 20:39:34 +00:00
|
|
|
const (
|
|
|
|
// The minimum size of a valid message.
|
|
|
|
minMessageSize = 24
|
2018-01-28 15:06:41 +00:00
|
|
|
cmdSize = 12
|
2018-01-26 20:39:34 +00:00
|
|
|
)
|
|
|
|
|
2018-01-29 07:42:49 +00:00
|
|
|
var (
|
|
|
|
errChecksumMismatch = errors.New("checksum mismatch")
|
|
|
|
)
|
|
|
|
|
2018-01-26 18:04:13 +00:00
|
|
|
// Message is the complete message send between nodes.
|
|
|
|
type Message struct {
|
2018-03-14 09:36:59 +00:00
|
|
|
// NetMode of the node that sends this message.
|
2018-03-25 10:45:54 +00:00
|
|
|
Magic config.NetMode
|
2018-03-14 09:36:59 +00:00
|
|
|
|
2018-01-26 18:04:13 +00:00
|
|
|
// Command is utf8 code, of which the length is 12 bytes,
|
|
|
|
// the extra part is filled with 0.
|
2018-01-28 15:06:41 +00:00
|
|
|
Command [cmdSize]byte
|
2018-03-14 09:36:59 +00:00
|
|
|
|
2019-10-22 14:56:03 +00:00
|
|
|
// Length of the payload.
|
2018-01-26 18:04:13 +00:00
|
|
|
Length uint32
|
2018-03-14 09:36:59 +00:00
|
|
|
|
2018-01-26 18:04:13 +00:00
|
|
|
// Checksum is the first 4 bytes of the value that two times SHA256
|
2019-10-22 14:56:03 +00:00
|
|
|
// hash of the payload.
|
2018-01-26 18:04:13 +00:00
|
|
|
Checksum uint32
|
2018-03-14 09:36:59 +00:00
|
|
|
|
2018-01-26 18:04:13 +00:00
|
|
|
// Payload send with the message.
|
2018-01-28 15:06:41 +00:00
|
|
|
Payload payload.Payload
|
2018-01-26 18:04:13 +00:00
|
|
|
}
|
|
|
|
|
2018-03-09 15:55:25 +00:00
|
|
|
// CommandType represents the type of a message command.
|
|
|
|
type CommandType string
|
2018-01-26 18:04:13 +00:00
|
|
|
|
2018-03-14 09:36:59 +00:00
|
|
|
// Valid protocol commands used to send between nodes.
|
2018-01-26 18:04:13 +00:00
|
|
|
const (
|
2018-08-10 14:32:49 +00:00
|
|
|
CMDAddr CommandType = "addr"
|
|
|
|
CMDBlock CommandType = "block"
|
|
|
|
CMDConsensus CommandType = "consensus"
|
|
|
|
CMDFilterAdd CommandType = "filteradd"
|
|
|
|
CMDFilterClear CommandType = "filterclear"
|
|
|
|
CMDFilterLoad CommandType = "filterload"
|
2019-08-27 17:12:01 +00:00
|
|
|
CMDGetAddr CommandType = "getaddr"
|
|
|
|
CMDGetBlocks CommandType = "getblocks"
|
|
|
|
CMDGetData CommandType = "getdata"
|
|
|
|
CMDGetHeaders CommandType = "getheaders"
|
|
|
|
CMDHeaders CommandType = "headers"
|
|
|
|
CMDInv CommandType = "inv"
|
2019-08-27 17:08:48 +00:00
|
|
|
CMDMempool CommandType = "mempool"
|
2019-08-27 17:12:01 +00:00
|
|
|
CMDMerkleBlock CommandType = "merkleblock"
|
2019-08-27 17:08:48 +00:00
|
|
|
CMDPing CommandType = "ping"
|
|
|
|
CMDPong CommandType = "pong"
|
2019-08-27 17:12:01 +00:00
|
|
|
CMDTX CommandType = "tx"
|
|
|
|
CMDUnknown CommandType = "unknown"
|
|
|
|
CMDVerack CommandType = "verack"
|
|
|
|
CMDVersion CommandType = "version"
|
2018-01-26 18:04:13 +00:00
|
|
|
)
|
|
|
|
|
2018-03-09 15:55:25 +00:00
|
|
|
// NewMessage returns a new message with the given payload.
|
2018-03-25 10:45:54 +00:00
|
|
|
func NewMessage(magic config.NetMode, cmd CommandType, p payload.Payload) *Message {
|
2018-01-28 07:03:18 +00:00
|
|
|
var (
|
|
|
|
size uint32
|
|
|
|
checksum []byte
|
|
|
|
)
|
|
|
|
|
2018-01-27 15:00:28 +00:00
|
|
|
if p != nil {
|
2019-09-16 09:18:13 +00:00
|
|
|
buf := io.NewBufBinWriter()
|
2019-09-16 16:31:49 +00:00
|
|
|
p.EncodeBinary(buf.BinWriter)
|
|
|
|
if buf.Err != nil {
|
|
|
|
panic(buf.Err)
|
2018-01-28 15:06:41 +00:00
|
|
|
}
|
2019-09-16 09:18:13 +00:00
|
|
|
b := buf.Bytes()
|
|
|
|
size = uint32(len(b))
|
|
|
|
checksum = hash.Checksum(b)
|
2018-01-28 07:03:18 +00:00
|
|
|
} else {
|
2019-08-23 15:50:45 +00:00
|
|
|
checksum = hash.Checksum([]byte{})
|
2018-01-27 15:00:28 +00:00
|
|
|
}
|
2018-01-28 07:03:18 +00:00
|
|
|
|
2018-01-26 18:04:13 +00:00
|
|
|
return &Message{
|
2018-01-28 07:03:18 +00:00
|
|
|
Magic: magic,
|
2018-01-28 15:06:41 +00:00
|
|
|
Command: cmdToByteArray(cmd),
|
2018-01-28 07:03:18 +00:00
|
|
|
Length: size,
|
|
|
|
Payload: p,
|
|
|
|
Checksum: binary.LittleEndian.Uint32(checksum[:4]),
|
2018-01-27 15:00:28 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-03-09 15:55:25 +00:00
|
|
|
// CommandType converts the 12 byte command slice to a CommandType.
|
|
|
|
func (m *Message) CommandType() CommandType {
|
2018-01-28 15:06:41 +00:00
|
|
|
cmd := cmdByteArrayToString(m.Command)
|
2018-01-26 18:04:13 +00:00
|
|
|
switch cmd {
|
|
|
|
case "addr":
|
2018-03-09 15:55:25 +00:00
|
|
|
return CMDAddr
|
2018-01-26 18:04:13 +00:00
|
|
|
case "block":
|
2018-03-09 15:55:25 +00:00
|
|
|
return CMDBlock
|
2018-01-30 10:56:36 +00:00
|
|
|
case "consensus":
|
2018-03-09 15:55:25 +00:00
|
|
|
return CMDConsensus
|
2018-08-10 14:32:49 +00:00
|
|
|
case "filteradd":
|
|
|
|
return CMDFilterAdd
|
|
|
|
case "filterclear":
|
|
|
|
return CMDFilterClear
|
2019-08-27 17:12:01 +00:00
|
|
|
case "filterload":
|
|
|
|
return CMDFilterLoad
|
|
|
|
case "getaddr":
|
|
|
|
return CMDGetAddr
|
|
|
|
case "getblocks":
|
|
|
|
return CMDGetBlocks
|
|
|
|
case "getdata":
|
|
|
|
return CMDGetData
|
|
|
|
case "getheaders":
|
|
|
|
return CMDGetHeaders
|
|
|
|
case "headers":
|
|
|
|
return CMDHeaders
|
|
|
|
case "inv":
|
|
|
|
return CMDInv
|
2019-08-27 17:08:48 +00:00
|
|
|
case "mempool":
|
|
|
|
return CMDMempool
|
2019-08-27 17:12:01 +00:00
|
|
|
case "merkleblock":
|
|
|
|
return CMDMerkleBlock
|
2019-08-27 17:08:48 +00:00
|
|
|
case "ping":
|
|
|
|
return CMDPing
|
|
|
|
case "pong":
|
|
|
|
return CMDPong
|
2019-08-27 17:12:01 +00:00
|
|
|
case "tx":
|
|
|
|
return CMDTX
|
|
|
|
case "verack":
|
|
|
|
return CMDVerack
|
|
|
|
case "version":
|
|
|
|
return CMDVersion
|
2018-01-26 18:04:13 +00:00
|
|
|
default:
|
2018-03-09 15:55:25 +00:00
|
|
|
return CMDUnknown
|
2018-01-26 18:04:13 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-10-22 14:56:03 +00:00
|
|
|
// Decode decodes a Message from the given reader.
|
2019-09-16 09:18:13 +00:00
|
|
|
func (m *Message) Decode(br *io.BinReader) error {
|
2019-12-12 15:52:23 +00:00
|
|
|
m.Magic = config.NetMode(br.ReadU32LE())
|
2019-12-06 15:37:46 +00:00
|
|
|
br.ReadBytes(m.Command[:])
|
2019-12-12 15:52:23 +00:00
|
|
|
m.Length = br.ReadU32LE()
|
|
|
|
m.Checksum = br.ReadU32LE()
|
2019-08-28 12:43:56 +00:00
|
|
|
if br.Err != nil {
|
|
|
|
return br.Err
|
2018-03-03 07:16:05 +00:00
|
|
|
}
|
2018-01-28 07:03:18 +00:00
|
|
|
// return if their is no payload.
|
|
|
|
if m.Length == 0 {
|
2018-01-27 15:00:28 +00:00
|
|
|
return nil
|
|
|
|
}
|
2019-09-16 09:18:13 +00:00
|
|
|
return m.decodePayload(br)
|
2018-01-27 15:47:43 +00:00
|
|
|
}
|
|
|
|
|
2019-09-16 09:18:13 +00:00
|
|
|
func (m *Message) decodePayload(br *io.BinReader) error {
|
|
|
|
buf := make([]byte, m.Length)
|
2019-12-12 15:52:23 +00:00
|
|
|
br.ReadBytes(buf)
|
2019-09-16 09:18:13 +00:00
|
|
|
if br.Err != nil {
|
|
|
|
return br.Err
|
2018-01-28 10:12:05 +00:00
|
|
|
}
|
2018-01-28 07:03:18 +00:00
|
|
|
// Compare the checksum of the payload.
|
2019-09-16 09:18:13 +00:00
|
|
|
if !compareChecksum(m.Checksum, buf) {
|
2018-01-29 07:42:49 +00:00
|
|
|
return errChecksumMismatch
|
2018-01-28 07:03:18 +00:00
|
|
|
}
|
2018-01-27 15:00:28 +00:00
|
|
|
|
2019-09-16 09:18:13 +00:00
|
|
|
r := io.NewBinReaderFromBuf(buf)
|
2018-01-28 15:06:41 +00:00
|
|
|
var p payload.Payload
|
2018-03-09 15:55:25 +00:00
|
|
|
switch m.CommandType() {
|
|
|
|
case CMDVersion:
|
2018-01-27 15:00:28 +00:00
|
|
|
p = &payload.Version{}
|
2019-08-30 10:14:12 +00:00
|
|
|
case CMDInv, CMDGetData:
|
2018-01-28 17:42:22 +00:00
|
|
|
p = &payload.Inventory{}
|
2018-03-09 15:55:25 +00:00
|
|
|
case CMDAddr:
|
2018-01-28 15:18:48 +00:00
|
|
|
p = &payload.AddressList{}
|
2018-03-09 15:55:25 +00:00
|
|
|
case CMDBlock:
|
2020-01-14 12:32:07 +00:00
|
|
|
p = &block.Block{}
|
2019-10-17 08:28:58 +00:00
|
|
|
case CMDConsensus:
|
2019-11-08 15:40:21 +00:00
|
|
|
p = &consensus.Payload{}
|
2019-08-29 16:43:23 +00:00
|
|
|
case CMDGetBlocks:
|
|
|
|
fallthrough
|
2018-03-09 15:55:25 +00:00
|
|
|
case CMDGetHeaders:
|
2018-02-04 19:54:51 +00:00
|
|
|
p = &payload.GetBlocks{}
|
2018-03-09 15:55:25 +00:00
|
|
|
case CMDHeaders:
|
2018-02-04 19:54:51 +00:00
|
|
|
p = &payload.Headers{}
|
2018-03-10 12:04:06 +00:00
|
|
|
case CMDTX:
|
|
|
|
p = &transaction.Transaction{}
|
2018-08-10 14:32:49 +00:00
|
|
|
case CMDMerkleBlock:
|
|
|
|
p = &payload.MerkleBlock{}
|
2020-01-17 10:17:19 +00:00
|
|
|
case CMDPing, CMDPong:
|
|
|
|
p = &payload.Ping{}
|
2019-09-22 17:09:55 +00:00
|
|
|
default:
|
|
|
|
return fmt.Errorf("can't decode command %s", cmdByteArrayToString(m.Command))
|
2019-09-16 16:31:49 +00:00
|
|
|
}
|
|
|
|
p.DecodeBinary(r)
|
2019-12-30 12:38:23 +00:00
|
|
|
if r.Err == nil || r.Err == payload.ErrTooManyHeaders {
|
|
|
|
m.Payload = p
|
2018-01-26 18:04:13 +00:00
|
|
|
}
|
|
|
|
|
2019-12-30 12:38:23 +00:00
|
|
|
return r.Err
|
2018-01-26 18:04:13 +00:00
|
|
|
}
|
|
|
|
|
2019-10-22 14:56:03 +00:00
|
|
|
// Encode encodes a Message to any given BinWriter.
|
2019-09-16 09:18:13 +00:00
|
|
|
func (m *Message) Encode(br *io.BinWriter) error {
|
2019-12-12 15:52:23 +00:00
|
|
|
br.WriteU32LE(uint32(m.Magic))
|
2019-12-06 15:22:21 +00:00
|
|
|
br.WriteBytes(m.Command[:])
|
2019-12-12 15:52:23 +00:00
|
|
|
br.WriteU32LE(m.Length)
|
|
|
|
br.WriteU32LE(m.Checksum)
|
2019-09-16 16:31:49 +00:00
|
|
|
if m.Payload != nil {
|
|
|
|
m.Payload.EncodeBinary(br)
|
|
|
|
|
|
|
|
}
|
2019-08-28 12:43:56 +00:00
|
|
|
if br.Err != nil {
|
|
|
|
return br.Err
|
2018-03-10 12:04:06 +00:00
|
|
|
}
|
2018-01-27 15:00:28 +00:00
|
|
|
return nil
|
2018-01-26 18:04:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// convert a command (string) to a byte slice filled with 0 bytes till
|
|
|
|
// size 12.
|
2018-03-09 15:55:25 +00:00
|
|
|
func cmdToByteArray(cmd CommandType) [cmdSize]byte {
|
2018-01-26 18:04:13 +00:00
|
|
|
cmdLen := len(cmd)
|
2018-01-28 15:06:41 +00:00
|
|
|
if cmdLen > cmdSize {
|
2018-01-26 18:04:13 +00:00
|
|
|
panic("exceeded command max length of size 12")
|
|
|
|
}
|
|
|
|
|
|
|
|
// The command can have max 12 bytes, rest is filled with 0.
|
2018-01-28 15:06:41 +00:00
|
|
|
b := [cmdSize]byte{}
|
|
|
|
for i := 0; i < cmdLen; i++ {
|
|
|
|
b[i] = cmd[i]
|
2018-01-26 18:04:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return b
|
|
|
|
}
|
|
|
|
|
2018-01-28 15:06:41 +00:00
|
|
|
func cmdByteArrayToString(cmd [cmdSize]byte) string {
|
2019-01-25 11:20:35 +00:00
|
|
|
buf := make([]byte, 0, cmdSize)
|
2018-01-28 15:06:41 +00:00
|
|
|
for i := 0; i < cmdSize; i++ {
|
|
|
|
if cmd[i] != 0 {
|
|
|
|
buf = append(buf, cmd[i])
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return string(buf)
|
|
|
|
}
|
|
|
|
|
2018-01-26 18:04:13 +00:00
|
|
|
func compareChecksum(have uint32, b []byte) bool {
|
2019-08-23 15:50:45 +00:00
|
|
|
sum := hash.Checksum(b)
|
2018-01-26 18:04:13 +00:00
|
|
|
want := binary.LittleEndian.Uint32(sum)
|
|
|
|
return have == want
|
|
|
|
}
|