neoneo-go/pkg/network/message.go

249 lines
6.3 KiB
Go
Raw Normal View History

2018-01-26 18:04:13 +00:00
package network
import (
"errors"
"fmt"
2018-01-27 15:00:28 +00:00
"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/io"
"github.com/nspcc-dev/neo-go/pkg/network/payload"
2018-01-26 18:04:13 +00:00
)
//go:generate stringer -type=CommandType -output=message_string.go
2018-01-26 20:39:34 +00:00
// CompressionMinSize is the lower bound to apply compression.
const CompressionMinSize = 1024
// Message is a complete message sent between nodes.
2018-01-26 18:04:13 +00:00
type Message struct {
// Flags that represents whether a message is compressed.
// 0 for None, 1 for Compressed.
Flags MessageFlag
// Command is a byte command code.
Command CommandType
2018-01-26 18:04:13 +00:00
// Payload send with the message.
2018-01-28 15:06:41 +00:00
Payload payload.Payload
// Compressed message payload.
compressedPayload []byte
// StateRootInHeader specifies if the state root is included in the block header.
// This is needed for correct decoding.
StateRootInHeader bool
2018-01-26 18:04:13 +00:00
}
// MessageFlag represents compression level of a message payload.
type MessageFlag byte
// Possible message flags.
const (
Compressed MessageFlag = 1 << iota
None MessageFlag = 0
)
// CommandType represents the type of a message command.
type CommandType byte
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 (
// Handshaking.
CMDVersion CommandType = 0x00
CMDVerack CommandType = 0x01
// Connectivity.
CMDGetAddr CommandType = 0x10
CMDAddr CommandType = 0x11
CMDPing CommandType = 0x18
CMDPong CommandType = 0x19
// Synchronization.
2020-11-27 10:55:48 +00:00
CMDGetHeaders CommandType = 0x20
CMDHeaders CommandType = 0x21
CMDGetBlocks CommandType = 0x24
CMDMempool CommandType = 0x25
CMDInv CommandType = 0x27
CMDGetData CommandType = 0x28
CMDGetBlockByIndex CommandType = 0x29
CMDNotFound CommandType = 0x2a
CMDTX = CommandType(payload.TXType)
CMDBlock = CommandType(payload.BlockType)
CMDExtensible = CommandType(payload.ExtensibleType)
2020-11-27 10:55:48 +00:00
CMDP2PNotaryRequest = CommandType(payload.P2PNotaryRequestType)
CMDGetMPTData CommandType = 0x51 // 0x5.. commands are used for extensions (P2PNotary, state exchange cmds)
CMDMPTData CommandType = 0x52
2020-11-27 10:55:48 +00:00
CMDReject CommandType = 0x2f
// SPV protocol.
CMDFilterLoad CommandType = 0x30
CMDFilterAdd CommandType = 0x31
CMDFilterClear CommandType = 0x32
CMDMerkleBlock CommandType = 0x38
// Others.
CMDAlert CommandType = 0x40
2018-01-26 18:04:13 +00:00
)
// NewMessage returns a new message with the given payload.
func NewMessage(cmd CommandType, p payload.Payload) *Message {
2018-01-26 18:04:13 +00:00
return &Message{
Command: cmd,
Payload: p,
Flags: None,
2018-01-27 15:00:28 +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.Flags = MessageFlag(br.ReadB())
m.Command = CommandType(br.ReadB())
l := br.ReadVarUint()
// check the length first in order not to allocate memory
// for an empty compressed payload
if l == 0 {
switch m.Command {
case CMDFilterClear, CMDGetAddr, CMDMempool, CMDVerack:
m.Payload = payload.NewNullPayload()
default:
return fmt.Errorf("unexpected empty payload: %s", m.Command)
}
return nil
}
if l > payload.MaxSize {
return errors.New("invalid payload size")
}
m.compressedPayload = make([]byte, l)
br.ReadBytes(m.compressedPayload)
if br.Err != nil {
return br.Err
}
return m.decodePayload()
2018-01-27 15:47:43 +00:00
}
func (m *Message) decodePayload() error {
buf := m.compressedPayload
// try decompression
if m.Flags&Compressed != 0 {
d, err := decompress(m.compressedPayload)
if err != nil {
return err
}
buf = d
2018-01-28 10:12:05 +00:00
}
2018-01-27 15:00:28 +00:00
2018-01-28 15:06:41 +00:00
var p payload.Payload
switch m.Command {
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 CMDGetMPTData:
p = &payload.MPTInventory{}
case CMDMPTData:
p = &payload.MPTData{}
case CMDAddr:
2018-01-28 15:18:48 +00:00
p = &payload.AddressList{}
case CMDBlock:
p = block.New(m.StateRootInHeader)
case CMDExtensible:
p = payload.NewExtensible()
2020-11-27 10:55:48 +00:00
case CMDP2PNotaryRequest:
p = &payload.P2PNotaryRequest{}
case CMDGetBlocks:
p = &payload.GetBlocks{}
case CMDGetHeaders:
fallthrough
case CMDGetBlockByIndex:
p = &payload.GetBlockByIndex{}
case CMDHeaders:
p = &payload.Headers{StateRootInHeader: m.StateRootInHeader}
case CMDTX:
p, err := transaction.NewTransactionFromBytes(buf)
if err != nil {
return err
}
m.Payload = p
return nil
case CMDMerkleBlock:
p = &payload.MerkleBlock{}
case CMDPing, CMDPong:
p = &payload.Ping{}
case CMDNotFound:
p = &payload.Inventory{}
default:
return fmt.Errorf("can't decode command %s", m.Command.String())
}
r := io.NewBinReaderFromBuf(buf)
p.DecodeBinary(r)
if r.Err == nil || errors.Is(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 {
if err := m.tryCompressPayload(); err != nil {
return err
}
growSize := 2 + 1 // header + empty payload
if m.compressedPayload != nil {
growSize += 8 + len(m.compressedPayload) // varint + byte-slice
}
br.Grow(growSize)
br.WriteB(byte(m.Flags))
br.WriteB(byte(m.Command))
if m.compressedPayload != nil {
br.WriteVarBytes(m.compressedPayload)
} else {
br.WriteB(0)
}
return br.Err
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
}
return w.Bytes(), nil
}
// tryCompressPayload sets the message's compressed payload to a serialized
// payload and compresses it in case its size exceeds CompressionMinSize.
func (m *Message) tryCompressPayload() error {
if m.Payload == nil {
return nil
}
buf := io.NewBufBinWriter()
m.Payload.EncodeBinary(buf.BinWriter)
if buf.Err != nil {
return buf.Err
}
compressedPayload := buf.Bytes()
if m.Flags&Compressed == 0 {
switch m.Payload.(type) {
case *payload.Headers, *payload.MerkleBlock, payload.NullPayload,
*payload.Inventory, *payload.MPTInventory:
break
default:
size := len(compressedPayload)
// try compression
if size > CompressionMinSize {
c, err := compress(compressedPayload)
if err == nil {
compressedPayload = c
m.Flags |= Compressed
} else {
return err
}
}
}
}
m.compressedPayload = compressedPayload
return nil
}