+++ /dev/null
-// Copyright (c) 2013-2016 The btcsuite developers
-// Use of this source code is governed by an ISC
-// license that can be found in the LICENSE file.
-
-package wire
-
-import (
- "bytes"
- "fmt"
- "io"
- "unicode/utf8"
-
- "github.com/btcsuite/btcd/chaincfg/chainhash"
-)
-
-// MessageHeaderSize is the number of bytes in a bitcoin message header.
-// Bitcoin network (magic) 4 bytes + command 12 bytes + payload length 4 bytes +
-// checksum 4 bytes.
-const MessageHeaderSize = 24
-
-// CommandSize is the fixed size of all commands in the common bitcoin message
-// header. Shorter commands must be zero padded.
-const CommandSize = 12
-
-// MaxMessagePayload is the maximum bytes a message can be regardless of other
-// individual limits imposed by messages themselves.
-const MaxMessagePayload = (1024 * 1024 * 32) // 32MB
-
-// Commands used in bitcoin message headers which describe the type of message.
-const (
- CmdVersion = "version"
- CmdVerAck = "verack"
- CmdGetAddr = "getaddr"
- CmdAddr = "addr"
- CmdGetBlocks = "getblocks"
- CmdInv = "inv"
- CmdGetData = "getdata"
- CmdNotFound = "notfound"
- CmdBlock = "block"
- CmdTx = "tx"
- CmdGetHeaders = "getheaders"
- CmdHeaders = "headers"
- CmdPing = "ping"
- CmdPong = "pong"
- CmdAlert = "alert"
- CmdMemPool = "mempool"
- CmdFilterAdd = "filteradd"
- CmdFilterClear = "filterclear"
- CmdFilterLoad = "filterload"
- CmdMerkleBlock = "merkleblock"
- CmdReject = "reject"
- CmdSendHeaders = "sendheaders"
- CmdFeeFilter = "feefilter"
-)
-
-// MessageEncoding represents the wire message encoding format to be used.
-type MessageEncoding uint32
-
-const (
- // BaseEncoding encodes all messages in the default format specified
- // for the Bitcoin wire protocol.
- BaseEncoding MessageEncoding = 1 << iota
-
- // WitnessEncoding encodes all messages other than transaction messages
- // using the default Bitcoin wire protocol specification. For transaction
- // messages, the new encoding format detailed in BIP0144 will be used.
- WitnessEncoding
-)
-
-// LatestEncoding is the most recently specified encoding for the Bitcoin wire
-// protocol.
-var LatestEncoding = WitnessEncoding
-
-// Message is an interface that describes a bitcoin message. A type that
-// implements Message has complete control over the representation of its data
-// and may therefore contain additional or fewer fields than those which
-// are used directly in the protocol encoded message.
-type Message interface {
- BtcDecode(io.Reader, uint32, MessageEncoding) error
- BtcEncode(io.Writer, uint32, MessageEncoding) error
- Command() string
- MaxPayloadLength(uint32) uint32
-}
-
-// makeEmptyMessage creates a message of the appropriate concrete type based
-// on the command.
-func makeEmptyMessage(command string) (Message, error) {
- var msg Message
- switch command {
- case CmdVersion:
- msg = &MsgVersion{}
-
- case CmdVerAck:
- msg = &MsgVerAck{}
-
- case CmdGetAddr:
- msg = &MsgGetAddr{}
-
- case CmdAddr:
- msg = &MsgAddr{}
-
- case CmdGetBlocks:
- msg = &MsgGetBlocks{}
-
- case CmdBlock:
- msg = &MsgBlock{}
-
- case CmdInv:
- msg = &MsgInv{}
-
- case CmdGetData:
- msg = &MsgGetData{}
-
- case CmdNotFound:
- msg = &MsgNotFound{}
-
- case CmdTx:
- msg = &MsgTx{}
-
- case CmdPing:
- msg = &MsgPing{}
-
- case CmdPong:
- msg = &MsgPong{}
-
- case CmdGetHeaders:
- msg = &MsgGetHeaders{}
-
- case CmdHeaders:
- msg = &MsgHeaders{}
-
- case CmdAlert:
- msg = &MsgAlert{}
-
- case CmdMemPool:
- msg = &MsgMemPool{}
-
- case CmdFilterAdd:
- msg = &MsgFilterAdd{}
-
- case CmdFilterClear:
- msg = &MsgFilterClear{}
-
- case CmdFilterLoad:
- msg = &MsgFilterLoad{}
-
- case CmdMerkleBlock:
- msg = &MsgMerkleBlock{}
-
- case CmdReject:
- msg = &MsgReject{}
-
- case CmdSendHeaders:
- msg = &MsgSendHeaders{}
-
- case CmdFeeFilter:
- msg = &MsgFeeFilter{}
-
- default:
- return nil, fmt.Errorf("unhandled command [%s]", command)
- }
- return msg, nil
-}
-
-// messageHeader defines the header structure for all bitcoin protocol messages.
-type messageHeader struct {
- magic BitcoinNet // 4 bytes
- command string // 12 bytes
- length uint32 // 4 bytes
- checksum [4]byte // 4 bytes
-}
-
-// readMessageHeader reads a bitcoin message header from r.
-func readMessageHeader(r io.Reader) (int, *messageHeader, error) {
- // Since readElements doesn't return the amount of bytes read, attempt
- // to read the entire header into a buffer first in case there is a
- // short read so the proper amount of read bytes are known. This works
- // since the header is a fixed size.
- var headerBytes [MessageHeaderSize]byte
- n, err := io.ReadFull(r, headerBytes[:])
- if err != nil {
- return n, nil, err
- }
- hr := bytes.NewReader(headerBytes[:])
-
- // Create and populate a messageHeader struct from the raw header bytes.
- hdr := messageHeader{}
- var command [CommandSize]byte
- readElements(hr, &hdr.magic, &command, &hdr.length, &hdr.checksum)
-
- // Strip trailing zeros from command string.
- hdr.command = string(bytes.TrimRight(command[:], string(0)))
-
- return n, &hdr, nil
-}
-
-// discardInput reads n bytes from reader r in chunks and discards the read
-// bytes. This is used to skip payloads when various errors occur and helps
-// prevent rogue nodes from causing massive memory allocation through forging
-// header length.
-func discardInput(r io.Reader, n uint32) {
- maxSize := uint32(10 * 1024) // 10k at a time
- numReads := n / maxSize
- bytesRemaining := n % maxSize
- if n > 0 {
- buf := make([]byte, maxSize)
- for i := uint32(0); i < numReads; i++ {
- io.ReadFull(r, buf)
- }
- }
- if bytesRemaining > 0 {
- buf := make([]byte, bytesRemaining)
- io.ReadFull(r, buf)
- }
-}
-
-// WriteMessageN writes a bitcoin Message to w including the necessary header
-// information and returns the number of bytes written. This function is the
-// same as WriteMessage except it also returns the number of bytes written.
-func WriteMessageN(w io.Writer, msg Message, pver uint32, btcnet BitcoinNet) (int, error) {
- return WriteMessageWithEncodingN(w, msg, pver, btcnet, BaseEncoding)
-}
-
-// WriteMessage writes a bitcoin Message to w including the necessary header
-// information. This function is the same as WriteMessageN except it doesn't
-// doesn't return the number of bytes written. This function is mainly provided
-// for backwards compatibility with the original API, but it's also useful for
-// callers that don't care about byte counts.
-func WriteMessage(w io.Writer, msg Message, pver uint32, btcnet BitcoinNet) error {
- _, err := WriteMessageN(w, msg, pver, btcnet)
- return err
-}
-
-// WriteMessageWithEncodingN writes a bitcoin Message to w including the
-// necessary header information and returns the number of bytes written.
-// This function is the same as WriteMessageN except it also allows the caller
-// to specify the message encoding format to be used when serializing wire
-// messages.
-func WriteMessageWithEncodingN(w io.Writer, msg Message, pver uint32,
- btcnet BitcoinNet, encoding MessageEncoding) (int, error) {
-
- totalBytes := 0
-
- // Enforce max command size.
- var command [CommandSize]byte
- cmd := msg.Command()
- if len(cmd) > CommandSize {
- str := fmt.Sprintf("command [%s] is too long [max %v]",
- cmd, CommandSize)
- return totalBytes, messageError("WriteMessage", str)
- }
- copy(command[:], []byte(cmd))
-
- // Encode the message payload.
- var bw bytes.Buffer
- err := msg.BtcEncode(&bw, pver, encoding)
- if err != nil {
- return totalBytes, err
- }
- payload := bw.Bytes()
- lenp := len(payload)
-
- // Enforce maximum overall message payload.
- if lenp > MaxMessagePayload {
- str := fmt.Sprintf("message payload is too large - encoded "+
- "%d bytes, but maximum message payload is %d bytes",
- lenp, MaxMessagePayload)
- return totalBytes, messageError("WriteMessage", str)
- }
-
- // Enforce maximum message payload based on the message type.
- mpl := msg.MaxPayloadLength(pver)
- if uint32(lenp) > mpl {
- str := fmt.Sprintf("message payload is too large - encoded "+
- "%d bytes, but maximum message payload size for "+
- "messages of type [%s] is %d.", lenp, cmd, mpl)
- return totalBytes, messageError("WriteMessage", str)
- }
-
- // Create header for the message.
- hdr := messageHeader{}
- hdr.magic = btcnet
- hdr.command = cmd
- hdr.length = uint32(lenp)
- copy(hdr.checksum[:], chainhash.DoubleHashB(payload)[0:4])
-
- // Encode the header for the message. This is done to a buffer
- // rather than directly to the writer since writeElements doesn't
- // return the number of bytes written.
- hw := bytes.NewBuffer(make([]byte, 0, MessageHeaderSize))
- writeElements(hw, hdr.magic, command, hdr.length, hdr.checksum)
-
- // Write header.
- n, err := w.Write(hw.Bytes())
- totalBytes += n
- if err != nil {
- return totalBytes, err
- }
-
- // Write payload.
- n, err = w.Write(payload)
- totalBytes += n
- return totalBytes, err
-}
-
-// ReadMessageWithEncodingN reads, validates, and parses the next bitcoin Message
-// from r for the provided protocol version and bitcoin network. It returns the
-// number of bytes read in addition to the parsed Message and raw bytes which
-// comprise the message. This function is the same as ReadMessageN except it
-// allows the caller to specify which message encoding is to to consult when
-// decoding wire messages.
-func ReadMessageWithEncodingN(r io.Reader, pver uint32, btcnet BitcoinNet,
- enc MessageEncoding) (int, Message, []byte, error) {
-
- totalBytes := 0
- n, hdr, err := readMessageHeader(r)
- totalBytes += n
- if err != nil {
- return totalBytes, nil, nil, err
- }
-
- // Enforce maximum message payload.
- if hdr.length > MaxMessagePayload {
- str := fmt.Sprintf("message payload is too large - header "+
- "indicates %d bytes, but max message payload is %d "+
- "bytes.", hdr.length, MaxMessagePayload)
- return totalBytes, nil, nil, messageError("ReadMessage", str)
-
- }
-
- // Check for messages from the wrong bitcoin network.
- if hdr.magic != btcnet {
- discardInput(r, hdr.length)
- str := fmt.Sprintf("message from other network [%v]", hdr.magic)
- return totalBytes, nil, nil, messageError("ReadMessage", str)
- }
-
- // Check for malformed commands.
- command := hdr.command
- if !utf8.ValidString(command) {
- discardInput(r, hdr.length)
- str := fmt.Sprintf("invalid command %v", []byte(command))
- return totalBytes, nil, nil, messageError("ReadMessage", str)
- }
-
- // Create struct of appropriate message type based on the command.
- msg, err := makeEmptyMessage(command)
- if err != nil {
- discardInput(r, hdr.length)
- return totalBytes, nil, nil, messageError("ReadMessage",
- err.Error())
- }
-
- // Check for maximum length based on the message type as a malicious client
- // could otherwise create a well-formed header and set the length to max
- // numbers in order to exhaust the machine's memory.
- mpl := msg.MaxPayloadLength(pver)
- if hdr.length > mpl {
- discardInput(r, hdr.length)
- str := fmt.Sprintf("payload exceeds max length - header "+
- "indicates %v bytes, but max payload size for "+
- "messages of type [%v] is %v.", hdr.length, command, mpl)
- return totalBytes, nil, nil, messageError("ReadMessage", str)
- }
-
- // Read payload.
- payload := make([]byte, hdr.length)
- n, err = io.ReadFull(r, payload)
- totalBytes += n
- if err != nil {
- return totalBytes, nil, nil, err
- }
-
- // Test checksum.
- checksum := chainhash.DoubleHashB(payload)[0:4]
- if !bytes.Equal(checksum[:], hdr.checksum[:]) {
- str := fmt.Sprintf("payload checksum failed - header "+
- "indicates %v, but actual checksum is %v.",
- hdr.checksum, checksum)
- return totalBytes, nil, nil, messageError("ReadMessage", str)
- }
-
- // Unmarshal message. NOTE: This must be a *bytes.Buffer since the
- // MsgVersion BtcDecode function requires it.
- pr := bytes.NewBuffer(payload)
- err = msg.BtcDecode(pr, pver, enc)
- if err != nil {
- return totalBytes, nil, nil, err
- }
-
- return totalBytes, msg, payload, nil
-}
-
-// ReadMessageN reads, validates, and parses the next bitcoin Message from r for
-// the provided protocol version and bitcoin network. It returns the number of
-// bytes read in addition to the parsed Message and raw bytes which comprise the
-// message. This function is the same as ReadMessage except it also returns the
-// number of bytes read.
-func ReadMessageN(r io.Reader, pver uint32, btcnet BitcoinNet) (int, Message, []byte, error) {
- return ReadMessageWithEncodingN(r, pver, btcnet, BaseEncoding)
-}
-
-// ReadMessage reads, validates, and parses the next bitcoin Message from r for
-// the provided protocol version and bitcoin network. It returns the parsed
-// Message and raw bytes which comprise the message. This function only differs
-// from ReadMessageN in that it doesn't return the number of bytes read. This
-// function is mainly provided for backwards compatibility with the original
-// API, but it's also useful for callers that don't care about byte counts.
-func ReadMessage(r io.Reader, pver uint32, btcnet BitcoinNet) (Message, []byte, error) {
- _, msg, buf, err := ReadMessageN(r, pver, btcnet)
- return msg, buf, err
-}