This updates the error handling in the wire package to be more consistent with the rest of the code base, tightens some of the error handling in message parsing to harden the protocol, and adds tests to ensure the error implementations works correctly with the standard library errors.Is and errors.As functions introduced in Go 1.13. In particular, the error code semantics have been changed so the first error starts at 0 instead of treating 0 is a sentinel "no error" value because it goes against Go best practices which designed the errors specifically to avoid the use of sentinel values. Further, the use of 0 as a sentinel means that there are two different ways to indicate "no error", namely nil and an error code of zero. Thus callers have to know that they need to check for both conditions which is incredibly error prone. In fact, there is already at least one instance in the code base that has that mistake in the tests. Finally, while here, this hardens the protocol by enforcing more strict parsing requirements lower in the software stack instead of relying on upper layers to take care of it.
290 lines
8.3 KiB
Go
290 lines
8.3 KiB
Go
// Copyright (c) 2013-2016 The btcsuite developers
|
|
// Copyright (c) 2015-2020 The Decred 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"
|
|
"net"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// MaxUserAgentLen is the maximum allowed length for the user agent field in a
|
|
// version message (MsgVersion).
|
|
const MaxUserAgentLen = 256
|
|
|
|
// DefaultUserAgent for wire in the stack
|
|
const DefaultUserAgent = "/dcrwire:0.4.0/"
|
|
|
|
// MsgVersion implements the Message interface and represents a Decred version
|
|
// message. It is used for a peer to advertise itself as soon as an outbound
|
|
// connection is made. The remote peer then uses this information along with
|
|
// its own to negotiate. The remote peer must then respond with a version
|
|
// message of its own containing the negotiated values followed by a verack
|
|
// message (MsgVerAck). This exchange must take place before any further
|
|
// communication is allowed to proceed.
|
|
type MsgVersion struct {
|
|
// Version of the protocol the node is using.
|
|
ProtocolVersion int32
|
|
|
|
// Bitfield which identifies the enabled services.
|
|
Services ServiceFlag
|
|
|
|
// Time the message was generated. This is encoded as an int64 on the wire.
|
|
Timestamp time.Time
|
|
|
|
// Address of the remote peer.
|
|
AddrYou NetAddress
|
|
|
|
// Address of the local peer.
|
|
AddrMe NetAddress
|
|
|
|
// Unique value associated with message that is used to detect self
|
|
// connections.
|
|
Nonce uint64
|
|
|
|
// The user agent that generated message. This is encoded as a varString
|
|
// on the wire. This has a max length of MaxUserAgentLen.
|
|
UserAgent string
|
|
|
|
// Last block seen by the generator of the version message.
|
|
LastBlock int32
|
|
|
|
// Don't announce transactions to peer.
|
|
DisableRelayTx bool
|
|
}
|
|
|
|
// HasService returns whether the specified service is supported by the peer
|
|
// that generated the message.
|
|
func (msg *MsgVersion) HasService(service ServiceFlag) bool {
|
|
return msg.Services&service == service
|
|
}
|
|
|
|
// AddService adds service as a supported service by the peer generating the
|
|
// message.
|
|
func (msg *MsgVersion) AddService(service ServiceFlag) {
|
|
msg.Services |= service
|
|
}
|
|
|
|
// BtcDecode decodes r using the Decred protocol encoding into the receiver.
|
|
// The version message is special in that the protocol version hasn't been
|
|
// negotiated yet. As a result, the pver field is ignored and any fields which
|
|
// are added in new versions are optional. This also mean that r must be a
|
|
// *bytes.Buffer so the number of remaining bytes can be ascertained.
|
|
//
|
|
// This is part of the Message interface implementation.
|
|
func (msg *MsgVersion) BtcDecode(r io.Reader, pver uint32) error {
|
|
const op = "MsgVersion.BtcDecode"
|
|
buf, ok := r.(*bytes.Buffer)
|
|
if !ok {
|
|
return messageError(op, ErrInvalidMsg, "reader is not a *bytes.Buffer")
|
|
}
|
|
|
|
err := readElements(buf, &msg.ProtocolVersion, &msg.Services,
|
|
(*int64Time)(&msg.Timestamp))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = readNetAddress(buf, pver, &msg.AddrYou, false)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Protocol versions >= 106 added a from address, nonce, and user agent
|
|
// field and they are only considered present if there are bytes
|
|
// remaining in the message.
|
|
if buf.Len() > 0 {
|
|
err = readNetAddress(buf, pver, &msg.AddrMe, false)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if buf.Len() > 0 {
|
|
err = readElement(buf, &msg.Nonce)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if buf.Len() > 0 {
|
|
userAgent, err := ReadVarString(buf, pver)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = validateUserAgent(userAgent)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
msg.UserAgent = userAgent
|
|
}
|
|
|
|
// Protocol versions >= 209 added a last known block field. It is only
|
|
// considered present if there are bytes remaining in the message.
|
|
if buf.Len() > 0 {
|
|
err = readElement(buf, &msg.LastBlock)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// There was no relay transactions field before BIP0037Version, but
|
|
// the default behavior prior to the addition of the field was to always
|
|
// relay transactions.
|
|
if buf.Len() > 0 {
|
|
// It's safe to ignore the error here since the buffer has at
|
|
// least one byte and that byte will result in a boolean value
|
|
// regardless of its value. Also, the wire encoding for the
|
|
// field is true when transactions should be relayed, so reverse
|
|
// it for the DisableRelayTx field.
|
|
var relayTx bool
|
|
readElement(r, &relayTx)
|
|
msg.DisableRelayTx = !relayTx
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// BtcEncode encodes the receiver to w using the Decred protocol encoding.
|
|
// This is part of the Message interface implementation.
|
|
func (msg *MsgVersion) BtcEncode(w io.Writer, pver uint32) error {
|
|
err := validateUserAgent(msg.UserAgent)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = writeElements(w, msg.ProtocolVersion, msg.Services,
|
|
msg.Timestamp.Unix())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = writeNetAddress(w, pver, &msg.AddrYou, false)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = writeNetAddress(w, pver, &msg.AddrMe, false)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = writeElement(w, msg.Nonce)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = WriteVarString(w, pver, msg.UserAgent)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = writeElement(w, msg.LastBlock)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return writeElement(w, !msg.DisableRelayTx)
|
|
}
|
|
|
|
// Command returns the protocol command string for the message. This is part
|
|
// of the Message interface implementation.
|
|
func (msg *MsgVersion) Command() string {
|
|
return CmdVersion
|
|
}
|
|
|
|
// MaxPayloadLength returns the maximum length the payload can be for the
|
|
// receiver. This is part of the Message interface implementation.
|
|
func (msg *MsgVersion) MaxPayloadLength(pver uint32) uint32 {
|
|
// Protocol version 4 bytes + services 8 bytes + timestamp 8 bytes +
|
|
// remote and local net addresses + nonce 8 bytes + length of user
|
|
// agent (varInt) + max allowed useragent length + last block 4 bytes +
|
|
// relay transactions flag 1 byte.
|
|
return 33 + (maxNetAddressPayload(pver) * 2) + MaxVarIntPayload +
|
|
MaxUserAgentLen
|
|
}
|
|
|
|
// NewMsgVersion returns a new Decred version message that conforms to the
|
|
// Message interface using the passed parameters and defaults for the remaining
|
|
// fields.
|
|
func NewMsgVersion(me *NetAddress, you *NetAddress, nonce uint64,
|
|
lastBlock int32) *MsgVersion {
|
|
|
|
// Limit the timestamp to one second precision since the protocol
|
|
// doesn't support better.
|
|
return &MsgVersion{
|
|
ProtocolVersion: int32(ProtocolVersion),
|
|
Services: 0,
|
|
Timestamp: time.Unix(time.Now().Unix(), 0),
|
|
AddrYou: *you,
|
|
AddrMe: *me,
|
|
Nonce: nonce,
|
|
UserAgent: DefaultUserAgent,
|
|
LastBlock: lastBlock,
|
|
DisableRelayTx: false,
|
|
}
|
|
}
|
|
|
|
// NewMsgVersionFromConn is a convenience function that extracts the remote
|
|
// and local address from conn and returns a new Decred version message that
|
|
// conforms to the Message interface. See NewMsgVersion.
|
|
func NewMsgVersionFromConn(conn net.Conn, nonce uint64,
|
|
lastBlock int32) (*MsgVersion, error) {
|
|
|
|
// Don't assume any services until we know otherwise.
|
|
lna, err := NewNetAddress(conn.LocalAddr(), 0)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Don't assume any services until we know otherwise.
|
|
rna, err := NewNetAddress(conn.RemoteAddr(), 0)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return NewMsgVersion(lna, rna, nonce, lastBlock), nil
|
|
}
|
|
|
|
// validateUserAgent ensures the provided user agent conforms to the results
|
|
// imposed by the protocol.
|
|
func validateUserAgent(userAgent string) error {
|
|
const op = "MsgVersion.validateUserAgent"
|
|
if len(userAgent) > MaxUserAgentLen {
|
|
msg := fmt.Sprintf("user agent too long [len %v, max %v]",
|
|
len(userAgent), MaxUserAgentLen)
|
|
return messageError(op, ErrUserAgentTooLong, msg)
|
|
}
|
|
|
|
if !isStrictAscii(userAgent) {
|
|
msg := "user agent is not strict ASCII"
|
|
return messageError(op, ErrMalformedStrictString, msg)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// AddUserAgent adds a user agent to the user agent string for the version
|
|
// message. The version string is not defined to any strict format, although
|
|
// it is recommended to use the form "major.minor.revision" e.g. "2.6.41".
|
|
func (msg *MsgVersion) AddUserAgent(name string, version string,
|
|
comments ...string) error {
|
|
|
|
newUserAgent := fmt.Sprintf("%s:%s", name, version)
|
|
if len(comments) != 0 {
|
|
newUserAgent = fmt.Sprintf("%s(%s)", newUserAgent,
|
|
strings.Join(comments, "; "))
|
|
}
|
|
newUserAgent = fmt.Sprintf("%s%s/", msg.UserAgent, newUserAgent)
|
|
err := validateUserAgent(newUserAgent)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
msg.UserAgent = newUserAgent
|
|
return nil
|
|
}
|