neo-go/pkg/network/message.go

282 lines
5.8 KiB
Go
Raw Normal View History

2018-01-26 18:04:13 +00:00
package network
import (
"bytes"
"crypto/sha256"
"encoding/binary"
"errors"
2018-01-28 10:12:05 +00:00
"fmt"
2018-01-26 18:04:13 +00:00
"io"
2018-01-27 15:00:28 +00:00
2018-02-01 18:54:23 +00:00
"github.com/CityOfZion/neo-go/pkg/core"
"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
)
var (
errChecksumMismatch = errors.New("checksum mismatch")
)
2018-01-26 20:39:34 +00:00
// NetMode type that is compatible with netModes below.
type NetMode uint32
// String implements the stringer interface.
func (n NetMode) String() string {
switch n {
case ModePrivNet:
return "privnet"
2018-01-26 20:39:34 +00:00
case ModeTestNet:
return "testnet"
case ModeMainNet:
return "mainnet"
default:
return ""
}
}
2018-01-26 18:04:13 +00:00
// Values used for the magic field, according to the docs.
const (
2018-01-26 20:39:34 +00:00
ModeMainNet NetMode = 0x00746e41 // 7630401
ModeTestNet NetMode = 0x74746e41 // 1953787457
ModePrivNet NetMode = 56753 // docker privnet
2018-01-26 18:04:13 +00:00
)
// Message is the complete message send between nodes.
type Message struct {
2018-01-26 20:39:34 +00:00
Magic 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
2018-01-26 18:04:13 +00:00
// Length of the payload
Length uint32
// Checksum is the first 4 bytes of the value that two times SHA256
// hash of the payload
Checksum uint32
// 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 commands used to send between nodes.
const (
CMDVersion CommandType = "version"
CMDVerack CommandType = "verack"
CMDGetAddr CommandType = "getaddr"
CMDAddr CommandType = "addr"
CMDGetHeaders CommandType = "getheaders"
CMDHeaders CommandType = "headers"
CMDGetBlocks CommandType = "getblocks"
CMDInv CommandType = "inv"
CMDGetData CommandType = "getdata"
CMDBlock CommandType = "block"
CMDTX CommandType = "tx"
CMDConsensus CommandType = "consensus"
CMDUnknown CommandType = "unknown"
2018-01-26 18:04:13 +00:00
)
// NewMessage returns a new message with the given payload.
func NewMessage(magic 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 {
2018-01-28 15:06:41 +00:00
buf := new(bytes.Buffer)
if err := p.EncodeBinary(buf); err != nil {
panic(err)
}
size = uint32(buf.Len())
2018-01-28 15:06:41 +00:00
checksum = sumSHA256(sumSHA256(buf.Bytes()))
2018-01-28 07:03:18 +00:00
} else {
checksum = sumSHA256(sumSHA256([]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 "version":
return CMDVersion
2018-01-26 18:04:13 +00:00
case "verack":
return CMDVerack
2018-01-27 07:37:07 +00:00
case "getaddr":
return CMDGetAddr
2018-01-26 18:04:13 +00:00
case "addr":
return CMDAddr
2018-01-26 18:04:13 +00:00
case "getheaders":
return CMDGetHeaders
case "headers":
return CMDHeaders
2018-01-26 18:04:13 +00:00
case "getblocks":
return CMDGetBlocks
2018-01-26 18:04:13 +00:00
case "inv":
return CMDInv
2018-01-26 18:04:13 +00:00
case "getdata":
return CMDGetData
2018-01-26 18:04:13 +00:00
case "block":
return CMDBlock
2018-01-26 18:04:13 +00:00
case "tx":
return CMDTX
2018-01-30 10:56:36 +00:00
case "consensus":
return CMDConsensus
2018-01-26 18:04:13 +00:00
default:
return CMDUnknown
2018-01-26 18:04:13 +00:00
}
}
// decode a Message from the given reader.
func (m *Message) decode(r io.Reader) error {
err := binary.Read(r, binary.LittleEndian, &m.Magic)
if err != nil {
return err
}
err = binary.Read(r, binary.LittleEndian, &m.Command)
if err != nil {
return err
}
err = binary.Read(r, binary.LittleEndian, &m.Length)
if err != nil {
return err
}
err = binary.Read(r, binary.LittleEndian, &m.Checksum)
if err != nil {
return err
}
2018-01-26 18:04:13 +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
}
2018-01-28 15:06:41 +00:00
return m.decodePayload(r)
2018-01-27 15:47:43 +00:00
}
2018-01-28 15:06:41 +00:00
func (m *Message) decodePayload(r io.Reader) error {
buf := new(bytes.Buffer)
n, err := io.CopyN(buf, r, int64(m.Length))
2018-01-28 10:12:05 +00:00
if err != nil {
2018-01-28 07:03:18 +00:00
return err
}
2018-01-28 10:12:05 +00:00
if uint32(n) != m.Length {
return fmt.Errorf("expected to have read exactly %d bytes got %d", m.Length, n)
}
2018-01-28 07:03:18 +00:00
// Compare the checksum of the payload.
if !compareChecksum(m.Checksum, buf.Bytes()) {
return errChecksumMismatch
2018-01-28 07:03:18 +00:00
}
2018-01-27 15:00:28 +00:00
r = 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{}
2018-01-28 17:42:22 +00:00
if err := p.DecodeBinary(r); err != nil {
return err
}
case CMDInv:
2018-01-28 17:42:22 +00:00
p = &payload.Inventory{}
if err := p.DecodeBinary(r); err != nil {
2018-01-28 10:12:05 +00:00
return err
}
case CMDAddr:
2018-01-28 15:18:48 +00:00
p = &payload.AddressList{}
2018-01-28 17:42:22 +00:00
if err := p.DecodeBinary(r); err != nil {
2018-01-28 15:18:48 +00:00
return err
}
case CMDBlock:
p = &core.Block{}
if err := p.DecodeBinary(r); err != nil {
return err
}
case CMDGetHeaders:
p = &payload.GetBlocks{}
if err := p.DecodeBinary(r); err != nil {
return err
}
case CMDHeaders:
p = &payload.Headers{}
if err := p.DecodeBinary(r); err != nil {
return err
}
2018-01-26 18:04:13 +00:00
}
2018-01-27 15:00:28 +00:00
m.Payload = p
2018-01-26 18:04:13 +00:00
return nil
}
// encode a Message to any given io.Writer.
func (m *Message) encode(w io.Writer) error {
2018-01-28 15:06:41 +00:00
binary.Write(w, binary.LittleEndian, m.Magic)
binary.Write(w, binary.LittleEndian, m.Command)
binary.Write(w, binary.LittleEndian, m.Length)
binary.Write(w, binary.LittleEndian, m.Checksum)
2018-01-26 18:04:13 +00:00
2018-01-28 07:03:18 +00:00
if m.Payload != nil {
2018-01-28 15:06:41 +00:00
return m.Payload.EncodeBinary(w)
2018-01-28 10:12:05 +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.
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 := []byte{}
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 sumSHA256(b []byte) []byte {
h := sha256.New()
h.Write(b)
return h.Sum(nil)
}
func compareChecksum(have uint32, b []byte) bool {
sum := sumSHA256(sumSHA256(b))[:4]
want := binary.LittleEndian.Uint32(sum)
return have == want
}