neoneo-go/pkg/network/message.go

261 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-27 15:47:43 +00:00
"fmt"
2018-01-26 18:04:13 +00:00
"io"
2018-01-27 15:00:28 +00:00
"github.com/anthdm/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
)
// NetMode type that is compatible with netModes below.
type NetMode uint32
// String implements the stringer interface.
func (n NetMode) String() string {
switch n {
case ModeDevNet:
return "devnet"
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 = 0x74746e41 // 1953787457
ModeDevNet = 56753 // docker privnet
2018-01-26 18:04:13 +00:00
)
// Message is the complete message send between nodes.
//
// Size Field DataType Description
// ------------------------------------------------------
// 4 Magic uint32 Protocol ID
// 12 Command char[12] Command
// 4 length uint32 Length of payload
// 4 Checksum uint32 Checksum
// length Payload uint8[length] Content of message
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.
Command []byte
// 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-27 15:00:28 +00:00
Payload payload.Payloader
2018-01-26 18:04:13 +00:00
}
type commandType string
// valid commands used to send between nodes.
const (
cmdVersion commandType = "version"
cmdVerack = "verack"
cmdGetAddr = "getaddr"
cmdAddr = "addr"
cmdGetHeaders = "getheaders"
cmdHeaders = "headers"
cmdGetBlocks = "getblocks"
cmdInv = "inv"
cmdGetData = "getdata"
cmdBlock = "block"
cmdTX = "tx"
)
2018-01-27 15:00:28 +00:00
func newMessage(magic NetMode, cmd commandType, p payload.Payloader) *Message {
var size uint32
if p != nil {
size = p.Size()
}
2018-01-26 18:04:13 +00:00
return &Message{
2018-01-27 15:00:28 +00:00
Magic: magic,
Command: cmdToByteSlice(cmd),
Length: size,
Payload: p,
}
}
2018-01-26 18:04:13 +00:00
// Converts the 12 byte command slice to a commandType.
func (m *Message) commandType() commandType {
cmd := string(bytes.TrimRight(m.Command, "\x00"))
switch cmd {
case "version":
return cmdVersion
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
case "getheaders":
return cmdGetHeaders
case "header":
return cmdHeaders
case "getblocks":
return cmdGetBlocks
case "inv":
return cmdInv
case "getdata":
return cmdGetData
case "block":
return cmdBlock
case "tx":
return cmdTX
default:
return ""
}
}
// decode a Message from the given reader.
func (m *Message) decode(r io.Reader) error {
// 24 bytes for the fixed sized fields.
2018-01-26 20:39:34 +00:00
buf := make([]byte, minMessageSize)
2018-01-26 18:04:13 +00:00
if _, err := r.Read(buf); err != nil {
return err
}
2018-01-26 20:39:34 +00:00
m.Magic = NetMode(binary.LittleEndian.Uint32(buf[0:4]))
2018-01-26 18:04:13 +00:00
m.Command = buf[4:16]
m.Length = binary.LittleEndian.Uint32(buf[16:20])
m.Checksum = binary.LittleEndian.Uint32(buf[20:24])
2018-01-27 15:47:43 +00:00
// if their is no payload.
if m.Length == 0 || !needPayloadDecode(m.commandType()) {
2018-01-27 15:00:28 +00:00
return nil
}
2018-01-27 15:47:43 +00:00
return m.decodePayload(r)
}
func (m *Message) decodePayload(r io.Reader) error {
// write to a buffer what we read to calculate the checksum.
2018-01-27 15:00:28 +00:00
buffer := new(bytes.Buffer)
tr := io.TeeReader(r, buffer)
var p payload.Payloader
switch m.commandType() {
case cmdVersion:
p = &payload.Version{}
if err := p.Decode(tr); err != nil {
return err
}
2018-01-27 16:40:06 +00:00
case cmdInv:
p = payload.Inventories{}
if err := p.Decode(tr); err != nil {
return err
}
2018-01-27 15:47:43 +00:00
default:
return fmt.Errorf("unknown command to decode: %s", m.commandType())
2018-01-26 18:04:13 +00:00
}
// Compare the checksum of the payload.
2018-01-27 15:00:28 +00:00
if !compareChecksum(m.Checksum, buffer.Bytes()) {
2018-01-26 18:04:13 +00:00
return errors.New("checksum mismatch error")
}
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-27 15:00:28 +00:00
buf := make([]byte, minMessageSize)
pbuf := new(bytes.Buffer)
2018-01-26 18:04:13 +00:00
2018-01-27 15:00:28 +00:00
// if there is a payload fill its allocated buffer.
2018-01-27 15:47:43 +00:00
var checksum []byte
2018-01-27 15:00:28 +00:00
if m.Payload != nil {
if err := m.Payload.Encode(pbuf); err != nil {
return err
}
2018-01-27 15:47:43 +00:00
checksum = sumSHA256(sumSHA256(pbuf.Bytes()))[:4]
} else {
checksum = sumSHA256(sumSHA256([]byte{}))[:4]
2018-01-27 15:00:28 +00:00
}
2018-01-27 15:47:43 +00:00
m.Checksum = binary.LittleEndian.Uint32(checksum)
2018-01-27 15:00:28 +00:00
// fill the message buffer
2018-01-26 20:39:34 +00:00
binary.LittleEndian.PutUint32(buf[0:4], uint32(m.Magic))
2018-01-26 18:04:13 +00:00
copy(buf[4:16], m.Command)
binary.LittleEndian.PutUint32(buf[16:20], m.Length)
binary.LittleEndian.PutUint32(buf[20:24], m.Checksum)
2018-01-27 15:00:28 +00:00
// write the message
n, err := w.Write(buf)
if err != nil {
return err
2018-01-26 18:04:13 +00:00
}
2018-01-27 15:00:28 +00:00
// we need to have at least writen exactly minMessageSize bytes.
if n != minMessageSize {
return errors.New("long/short read error when encoding message")
2018-01-26 18:04:13 +00:00
}
2018-01-27 15:00:28 +00:00
// write the payload if there was any
if pbuf.Len() > 0 {
n, err = w.Write(pbuf.Bytes())
if err != nil {
return err
}
2018-01-26 18:04:13 +00:00
2018-01-27 15:00:28 +00:00
if uint32(n) != m.Payload.Size() {
return errors.New("long/short read error when encoding payload")
}
2018-01-26 18:04:13 +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 cmdToByteSlice(cmd commandType) []byte {
cmdLen := len(cmd)
if cmdLen > 12 {
panic("exceeded command max length of size 12")
}
// The command can have max 12 bytes, rest is filled with 0.
b := []byte(cmd)
for i := 0; i < 12-cmdLen; i++ {
b = append(b, byte('\x00'))
}
return b
}
2018-01-27 15:47:43 +00:00
func needPayloadDecode(cmd commandType) bool {
return cmd != cmdVerack && cmd != cmdGetAddr
}
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
}