neo-go/pkg/network/message.go

273 lines
6.1 KiB
Go
Raw Normal View History

2018-01-26 18:04:13 +00:00
package network
import (
"encoding/binary"
"errors"
"fmt"
2018-01-27 15:00:28 +00:00
"github.com/nspcc-dev/neo-go/config"
"github.com/nspcc-dev/neo-go/pkg/consensus"
"github.com/nspcc-dev/neo-go/pkg/core/block"
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
"github.com/nspcc-dev/neo-go/pkg/crypto/hash"
"github.com/nspcc-dev/neo-go/pkg/io"
"github.com/nspcc-dev/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
)
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 {
// NetMode of the node that sends this message.
Magic config.NetMode
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
2019-10-22 14:56:03 +00:00
// Length of the payload.
2018-01-26 18:04:13 +00:00
Length uint32
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-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
}
// CommandType represents the type of a message command.
type CommandType string
2018-01-26 18:04:13 +00:00
// Valid protocol commands used to send between nodes.
2018-01-26 18:04:13 +00:00
const (
CMDAddr CommandType = "addr"
CMDBlock CommandType = "block"
CMDConsensus CommandType = "consensus"
CMDFilterAdd CommandType = "filteradd"
CMDFilterClear CommandType = "filterclear"
CMDFilterLoad CommandType = "filterload"
CMDGetAddr CommandType = "getaddr"
CMDGetBlocks CommandType = "getblocks"
CMDGetData CommandType = "getdata"
CMDGetHeaders CommandType = "getheaders"
CMDHeaders CommandType = "headers"
CMDInv CommandType = "inv"
CMDMempool CommandType = "mempool"
CMDMerkleBlock CommandType = "merkleblock"
CMDPing CommandType = "ping"
CMDPong CommandType = "pong"
CMDTX CommandType = "tx"
CMDUnknown CommandType = "unknown"
CMDVerack CommandType = "verack"
CMDVersion CommandType = "version"
2018-01-26 18:04:13 +00:00
)
// NewMessage returns a new message with the given payload.
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 {
buf := io.NewBufBinWriter()
p.EncodeBinary(buf.BinWriter)
if buf.Err != nil {
panic(buf.Err)
2018-01-28 15:06:41 +00:00
}
b := buf.Bytes()
size = uint32(len(b))
checksum = hash.Checksum(b)
2018-01-28 07:03:18 +00:00
} else {
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
}
}
// 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":
return CMDAddr
2018-01-26 18:04:13 +00:00
case "block":
return CMDBlock
2018-01-30 10:56:36 +00:00
case "consensus":
return CMDConsensus
case "filteradd":
return CMDFilterAdd
case "filterclear":
return CMDFilterClear
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
case "mempool":
return CMDMempool
case "merkleblock":
return CMDMerkleBlock
case "ping":
return CMDPing
case "pong":
return CMDPong
case "tx":
return CMDTX
case "verack":
return CMDVerack
case "version":
return CMDVersion
2018-01-26 18:04:13 +00:00
default:
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.
func (m *Message) Decode(br *io.BinReader) error {
m.Magic = config.NetMode(br.ReadU32LE())
2019-12-06 15:37:46 +00:00
br.ReadBytes(m.Command[:])
m.Length = br.ReadU32LE()
m.Checksum = br.ReadU32LE()
if br.Err != nil {
return br.Err
}
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
}
return m.decodePayload(br)
2018-01-27 15:47:43 +00:00
}
func (m *Message) decodePayload(br *io.BinReader) error {
buf := make([]byte, m.Length)
br.ReadBytes(buf)
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.
if !compareChecksum(m.Checksum, buf) {
return errChecksumMismatch
2018-01-28 07:03:18 +00:00
}
2018-01-27 15:00:28 +00:00
r := io.NewBinReaderFromBuf(buf)
2018-01-28 15:06:41 +00:00
var p payload.Payload
switch m.CommandType() {
case CMDVersion:
2018-01-27 15:00:28 +00:00
p = &payload.Version{}
case CMDInv, CMDGetData:
2018-01-28 17:42:22 +00:00
p = &payload.Inventory{}
case CMDAddr:
2018-01-28 15:18:48 +00:00
p = &payload.AddressList{}
case CMDBlock:
p = &block.Block{}
case CMDConsensus:
2019-11-08 15:40:21 +00:00
p = &consensus.Payload{}
case CMDGetBlocks:
fallthrough
case CMDGetHeaders:
p = &payload.GetBlocks{}
case CMDHeaders:
p = &payload.Headers{}
case CMDTX:
p = &transaction.Transaction{}
case CMDMerkleBlock:
p = &payload.MerkleBlock{}
case CMDPing, CMDPong:
p = &payload.Ping{}
default:
return fmt.Errorf("can't decode command %s", cmdByteArrayToString(m.Command))
}
p.DecodeBinary(r)
if r.Err == nil || r.Err == payload.ErrTooManyHeaders {
m.Payload = p
2018-01-26 18:04:13 +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.
func (m *Message) Encode(br *io.BinWriter) error {
br.WriteU32LE(uint32(m.Magic))
br.WriteBytes(m.Command[:])
br.WriteU32LE(m.Length)
br.WriteU32LE(m.Checksum)
if m.Payload != nil {
m.Payload.EncodeBinary(br)
}
if br.Err != nil {
return br.Err
}
2018-01-27 15:00:28 +00:00
return nil
2018-01-26 18:04:13 +00:00
}
// Bytes serializes a Message into the new allocated buffer and returns it.
func (m *Message) Bytes() ([]byte, error) {
w := io.NewBufBinWriter()
if err := m.Encode(w.BinWriter); err != nil {
return nil, err
}
if w.Err != nil {
return nil, w.Err
}
return w.Bytes(), nil
}
2018-01-26 18:04:13 +00:00
// convert a command (string) to a byte slice filled with 0 bytes till
// size 12.
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 {
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 {
sum := hash.Checksum(b)
2018-01-26 18:04:13 +00:00
want := binary.LittleEndian.Uint32(sum)
return have == want
}