Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

multi: define wire error types. #2055

Merged
merged 1 commit into from
Feb 14, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 16 additions & 14 deletions wire/common.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// Copyright (c) 2013-2016 The btcsuite developers
// Copyright (c) 2015-2016 The Decred 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.

Expand Down Expand Up @@ -174,8 +174,8 @@ var binarySerializer binaryFreeList = make(chan []byte, binaryFreeListMaxItems)

// errNonCanonicalVarInt is the common format string used for non-canonically
// encoded variable length integer errors.
var errNonCanonicalVarInt = "non-canonical varint %x - discriminant %x must " +
"encode a value greater than %x"
var nonCanonicalVarIntFormat = "non-canonical varint %x - discriminant " +
"%x must encode a value greater than %x"

// uint32Time represents a unix timestamp encoded with a uint32. It is used as
// a way to signal the readElement function how to decode a timestamp into a Go
Expand Down Expand Up @@ -490,6 +490,7 @@ func writeElements(w io.Writer, elements ...interface{}) error {

// ReadVarInt reads a variable length integer from r and returns it as a uint64.
func ReadVarInt(r io.Reader, pver uint32) (uint64, error) {
const op = "ReadVarInt"
discriminant, err := binarySerializer.Uint8(r)
if err != nil {
return 0, err
Expand All @@ -508,8 +509,8 @@ func ReadVarInt(r io.Reader, pver uint32) (uint64, error) {
// encoded using fewer bytes.
min := uint64(0x100000000)
if rv < min {
return 0, messageError("ReadVarInt", fmt.Sprintf(
errNonCanonicalVarInt, rv, discriminant, min))
msg := fmt.Sprintf(nonCanonicalVarIntFormat, rv, discriminant, min)
return 0, messageError(op, ErrNonCanonicalVarInt, msg)
}

case 0xfe:
Expand All @@ -523,8 +524,8 @@ func ReadVarInt(r io.Reader, pver uint32) (uint64, error) {
// encoded using fewer bytes.
min := uint64(0x10000)
if rv < min {
return 0, messageError("ReadVarInt", fmt.Sprintf(
errNonCanonicalVarInt, rv, discriminant, min))
msg := fmt.Sprintf(nonCanonicalVarIntFormat, rv, discriminant, min)
return 0, messageError(op, ErrNonCanonicalVarInt, msg)
}

case 0xfd:
Expand All @@ -538,8 +539,8 @@ func ReadVarInt(r io.Reader, pver uint32) (uint64, error) {
// encoded using fewer bytes.
min := uint64(0xfd)
if rv < min {
return 0, messageError("ReadVarInt", fmt.Sprintf(
errNonCanonicalVarInt, rv, discriminant, min))
msg := fmt.Sprintf(nonCanonicalVarIntFormat, rv, discriminant, min)
return 0, messageError(op, ErrNonCanonicalVarInt, msg)
}

default:
Expand Down Expand Up @@ -609,6 +610,7 @@ func VarIntSerializeSize(val uint64) int {
// maximum block payload size since it helps protect against memory exhaustion
// attacks and forced panics through malformed messages.
func ReadVarString(r io.Reader, pver uint32) (string, error) {
const op = "ReadVarString"
count, err := ReadVarInt(r, pver)
if err != nil {
return "", err
Expand All @@ -618,9 +620,9 @@ func ReadVarString(r io.Reader, pver uint32) (string, error) {
// message size. It would be possible to cause memory exhaustion and
// panics without a sane upper bound on this count.
if count > MaxMessagePayload {
str := fmt.Sprintf("variable length string is too long "+
msg := fmt.Sprintf("variable length string is too long "+
"[count %d, max %d]", count, MaxMessagePayload)
return "", messageError("ReadVarString", str)
return "", messageError(op, ErrVarStringTooLong, msg)
}

buf := make([]byte, count)
Expand Down Expand Up @@ -652,7 +654,7 @@ func WriteVarString(w io.Writer, pver uint32, str string) error {
// the error.
func ReadVarBytes(r io.Reader, pver uint32, maxAllowed uint32,
fieldName string) ([]byte, error) {

const op = "ReadVarBytes"
count, err := ReadVarInt(r, pver)
if err != nil {
return nil, err
Expand All @@ -662,9 +664,9 @@ func ReadVarBytes(r io.Reader, pver uint32, maxAllowed uint32,
// be possible to cause memory exhaustion and panics without a sane
// upper bound on this count.
if count > uint64(maxAllowed) {
str := fmt.Sprintf("%s is larger than the max allowed size "+
msg := fmt.Sprintf("%s is larger than the max allowed size "+
"[count %d, max %d]", fieldName, count, maxAllowed)
return nil, messageError("ReadVarBytes", str)
return nil, messageError(op, ErrVarBytesTooLong, msg)
}

b := make([]byte, count)
Expand Down
213 changes: 203 additions & 10 deletions wire/error.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// Copyright (c) 2013-2015 The btcsuite developers
// Copyright (c) 2015-2016 The Decred 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.

Expand All @@ -9,6 +9,174 @@ import (
"fmt"
)

// ErrorCode describes a kind of message error.
type ErrorCode int

// These constants are used to identify a specific Error.
const (
// ErrNonCanonicalVarInt is returned when a variable length integer is
// not canonically encoded.
ErrNonCanonicalVarInt ErrorCode = iota + 1

// ErrVarStringTooLong is returned when a variable string exceeds the
dnldd marked this conversation as resolved.
Show resolved Hide resolved
// maximum message size allowed.
ErrVarStringTooLong

// ErrVarBytesTooLong is returned when a variable-length byte slice
// exceeds the maximum message size allowed.
ErrVarBytesTooLong

// ErrCmdTooLong is returned when a command exceeds the maximum command
// size allowed.
ErrCmdTooLong

// ErrPayloadTooLarge is returned when a payload exceeds the maximum
// payload size allowed.
ErrPayloadTooLarge

// ErrWrongNetwork is returned when a message intended for a different
// network is received.
ErrWrongNetwork

// ErrMalformedCmd is returned when a malformed command is received.
ErrMalformedCmd

// ErrUnknownCmd is returned when an unknown command is received.
ErrUnknownCmd

// ErrPayloadChecksum is returned when a message with an invalid checksum
// is received.
ErrPayloadChecksum

// ErrTooManyAddrs is returned when an address list exceeds the maximum
// allowed.
ErrTooManyAddrs

// ErrTooManyTxs is returned when a the number of transactions exceed the
// maximum allowed.
ErrTooManyTxs

// ErrMsgInvalidForPVer is returned when a message is invalid for
// the expected protocol version.
ErrMsgInvalidForPVer

// ErrFilterTooLarge is returned when a committed filter exceeds
// the maximum size allowed.
ErrFilterTooLarge

// ErrTooManyProofs is returned when the numeber of proof hashes
// exceeds the maximum allowed.
ErrTooManyProofs

// ErrTooManyFilterTypes is returned when the number of filter types
// exceeds the maximum allowed.
ErrTooManyFilterTypes

// ErrTooManyLocators is returned when the number of block locators exceed
// the maximum allowed.
ErrTooManyLocators

// ErrTooManyVectors is returned when the number of inventory vectors
// exceed the maximum allowed.
ErrTooManyVectors

// ErrTooManyHeaders is returned when the number of block headers exceed
// the maximum allowed.
ErrTooManyHeaders

// ErrHeaderContainsTxs is returned when a header's transactions
// count is greater than zero.
ErrHeaderContainsTxs

// ErrTooManyVotes is returned when the number of vote hashes exceed the
// maximum allowed.
ErrTooManyVotes

// ErrTooManyBlocks is returned when the number of block hashes exceed the
// maximum allowed.
ErrTooManyBlocks

// ErrMismatchedWitnessCount returned when a transaction has unequal witness
// and prefix txin quantities.
ErrMismatchedWitnessCount

// ErrUnknownTxType is returned when a transaction type is unknown.
ErrUnknownTxType

// ErrReadInPrefixFromWitnessOnlyTx is returned when attempting to read a
// transaction input prefix from a witness only transaction.
ErrReadInPrefixFromWitnessOnlyTx

// ErrInvalidMsg is returned when for an invalid message structure.
ErrInvalidMsg

// ErrUserAgentTooLong is returned when the provided user agent exceeds
// the maximum allowed.
ErrUserAgentTooLong

// ErrTooManyFilterHeaders is returned when the number of committed filter
// headers exceed the maximum allowed.
ErrTooManyFilterHeaders
)

// Map of ErrorCode values back to their constant names for pretty printing.
var errorCodeStrings = map[ErrorCode]string{
ErrNonCanonicalVarInt: "ErrNonCanonicalVarInt",
ErrVarStringTooLong: "ErrVarStringTooLong",
ErrVarBytesTooLong: "ErrVarBytesTooLong",
ErrCmdTooLong: "ErrCmdTooLong",
ErrPayloadTooLarge: "ErrPayloadTooLarge",
ErrWrongNetwork: "ErrWrongNetwork",
ErrMalformedCmd: "ErrMalformedCmd",
ErrUnknownCmd: "ErrUnknownCmd",
ErrPayloadChecksum: "ErrPayloadChecksum",
ErrTooManyAddrs: "ErrTooManyAddrs",
ErrTooManyTxs: "ErrTooManyTxs",
ErrMsgInvalidForPVer: "ErrMsgInvalidForPVer",
ErrFilterTooLarge: "ErrFilterTooLarge",
ErrTooManyProofs: "ErrTooManyProofs",
ErrTooManyFilterTypes: "ErrTooManyFilterTypes",
ErrTooManyLocators: "ErrTooManyLocators",
ErrTooManyVectors: "ErrTooManyVectors",
ErrTooManyHeaders: "ErrTooManyHeaders",
ErrHeaderContainsTxs: "ErrHeaderContainsTxs",
ErrTooManyVotes: "ErrTooManyVotes",
ErrTooManyBlocks: "ErrTooManyBlocks",
ErrMismatchedWitnessCount: "ErrMismatchedWitnessCount",
ErrUnknownTxType: "ErrUnknownTxType",
ErrReadInPrefixFromWitnessOnlyTx: "ErrReadInPrefixFromWitnessOnlyTx",
ErrInvalidMsg: "ErrInvalidMsg",
ErrUserAgentTooLong: "ErrUserAgentTooLong",
ErrTooManyFilterHeaders: "ErrTooManyFilterHeaders",
}

// String returns the ErrorCode as a human-readable name.
func (e ErrorCode) String() string {
dnldd marked this conversation as resolved.
Show resolved Hide resolved
if s := errorCodeStrings[e]; s != "" {
return s
}
return fmt.Sprintf("Unknown ErrorCode (%d)", int(e))
}

// Error implements the error interface.
func (e ErrorCode) Error() string {
return e.String()
}

// Is implements the interface to work with the standard library's errors.Is.
// It returns true if the error codes match for targets *MessageError and
// ErrorCode. Else, Is returns false.
func (e ErrorCode) Is(target error) bool {
switch target := target.(type) {
case *MessageError:
return e == target.ErrorCode
case ErrorCode:
return e == target
default:
return false
}
}

// MessageError describes an issue with a message.
// An example of some potential issues are messages from the wrong decred
// network, invalid commands, mismatched checksums, and exceeding max payloads.
Expand All @@ -17,19 +185,44 @@ import (
// differentiate between general io errors such as io.EOF and issues that
// resulted from malformed messages.
type MessageError struct {
Func string // Function name
Description string // Human readable description of the issue
Func string // Function name
ErrorCode ErrorCode // Describes the kind of error
Description string // Human readable description of the issue
}

// Error satisfies the error interface and prints human-readable errors.
func (e *MessageError) Error() string {
if e.Func != "" {
return fmt.Sprintf("%v: %v", e.Func, e.Description)
func (m *MessageError) Error() string {
if m.Func != "" {
return fmt.Sprintf("%v: [%s] %v", m.Func, m.ErrorCode, m.Description)
}
return e.Description
return fmt.Sprintf("%v: %v", m.ErrorCode, m.Description)
}

// messageError creates an error for the given function and description.
func messageError(f string, desc string) *MessageError {
return &MessageError{Func: f, Description: desc}
// messageError creates an Error given a set of arguments.
func messageError(Func string, c ErrorCode, desc string) *MessageError {
return &MessageError{Func: Func, ErrorCode: c, Description: desc}
}

// Is implements the interface to work with the standard library's errors.Is.
// If target is a *MessageError, Is returns true if the error codes match.
// If target is an ErrorCode, Is returns true if the error codes match.
// Else, Is returns false.
func (m *MessageError) Is(target error) bool {
switch target := target.(type) {
case *MessageError:
return m.ErrorCode != 0 && m.ErrorCode == target.ErrorCode
case ErrorCode:
return m.ErrorCode != 0 && target == m.ErrorCode
default:
return false
}
}

// Unwrap returns the underlying wrapped error if it is not ErrOther.
// Unwrap returns the ErrorCode. Else, it returns nil.
func (m *MessageError) Unwrap() error {
if m.ErrorCode != 0 {
return m.ErrorCode
}
return nil
}
Loading