diff --git a/doc.go b/doc.go index 273691d6..84175720 100644 --- a/doc.go +++ b/doc.go @@ -103,7 +103,6 @@ switch or type assertion. An example of a type switch follows: case *btcwire.MsgBlock: // The message is a pointer to a MsgBlock struct. fmt.Printf("Number of tx in block: %v", msg.Header.TxnCount) - } Reading Messages @@ -144,8 +143,9 @@ Errors Most errors returned by this package are either the raw errors provided by underlying calls to read/write from streams, or raw strings that describe the error. See the documentation of each function for any exceptions. NOTE: -This will change soon as the package should return errors that can be -programatically tested. +This is currently undergoing change to return errors of type MessageError for +issues which are not io related so the caller can differentiate between +general io errors and malformed messages. Bitcoin Improvement Proposals diff --git a/error.go b/error.go new file mode 100644 index 00000000..da617138 --- /dev/null +++ b/error.go @@ -0,0 +1,34 @@ +// Copyright (c) 2013 Conformal Systems LLC. +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package btcwire + +import ( + "fmt" +) + +// MessageError describes an issue with a message. +// An example of some potential issues are messages from the wrong bitcoin +// network, invalid commands, mismatched checksums, and exceeding max payloads. +// +// This provides a mechanism for the caller to type assert the error to +// 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 +} + +// 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) + } + return e.Description +} + +// messageError creates an error for the given function and description. +func messageError(f string, desc string) *MessageError { + return &MessageError{Func: f, Description: desc} +} diff --git a/message.go b/message.go index 90afc844..00c3b185 100644 --- a/message.go +++ b/message.go @@ -130,13 +130,6 @@ func readMessageHeader(r io.Reader) (*messageHeader, error) { // Strip trailing zeros from command string. hdr.command = string(bytes.TrimRight(command[:], string(0))) - // Enforce maximum message payload. - if hdr.length > maxMessagePayload { - str := "readMessageHeader: message payload is too large - " + - "Header indicates %d bytes, but max message payload is %d bytes." - return nil, fmt.Errorf(str, hdr.length, maxMessagePayload) - } - return &hdr, nil } @@ -182,9 +175,10 @@ func WriteMessage(w io.Writer, msg Message, pver uint32, btcnet BitcoinNet) erro // Enforce maximum message payload. if lenp > maxMessagePayload { - str := "WriteMessage: message payload is too large - " + - "Encoded %d bytes, but maximum message payload is %d bytes." - return fmt.Errorf(str, lenp, maxMessagePayload) + str := fmt.Sprintf("message payload is too large - encoded "+ + "%d bytes, but maximum message payload is %d bytes", + lenp, maxMessagePayload) + return messageError("WriteMessage", str) } // Create header for the message. @@ -206,9 +200,9 @@ func WriteMessage(w io.Writer, msg Message, pver uint32, btcnet BitcoinNet) erro return err } if n != lenp { - str := "WriteMessage: failed to write payload. " + - "Wrote %v bytes, but payload size is %v bytes." - return fmt.Errorf(str, n, lenp) + str := fmt.Sprintf("failed to write payload - wrote %v bytes, "+ + "payload size is %v bytes", n, lenp) + return messageError("WriteMessage", str) } return nil } @@ -221,23 +215,34 @@ func ReadMessage(r io.Reader, pver uint32, btcnet BitcoinNet) (Message, []byte, return nil, nil, err } - if hdr.magic != btcnet { - str := "ReadMessage: message from other network [%v]" - return nil, nil, fmt.Errorf(str, hdr.magic) + // 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 nil, nil, messageError("ReadMessage", str) + } + // Check for messages from the wrong bitcoin network. + if hdr.magic != btcnet { + str := fmt.Sprintf("message from other network [%v]", hdr.magic) + return nil, nil, messageError("ReadMessage", str) + } + + // Check for malformed commands. command := hdr.command if !utf8.ValidString(command) { discardInput(r, hdr.length) - str := "readMessage: invalid command %v" - return nil, nil, fmt.Errorf(str, []byte(command)) + str := fmt.Sprintf("invalid command %v", []byte(command)) + return 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 nil, nil, fmt.Errorf("readMessage: %v", err) + return nil, nil, messageError("ReadMessage", err.Error()) } // Check for maximum length based on the message type as a malicious client @@ -246,10 +251,10 @@ func ReadMessage(r io.Reader, pver uint32, btcnet BitcoinNet) (Message, []byte, mpl := msg.MaxPayloadLength(pver) if hdr.length > mpl { discardInput(r, hdr.length) - str := "ReadMessage: payload exceeds max length - Header " + - "indicates %v bytes, but max payload size for messages of type " + - "[%v] is %v." - return nil, nil, fmt.Errorf(str, hdr.length, command, mpl) + 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 nil, nil, messageError("ReadMessage", str) } // Read payload. @@ -262,9 +267,10 @@ func ReadMessage(r io.Reader, pver uint32, btcnet BitcoinNet) (Message, []byte, // Test checksum. checksum := DoubleSha256(payload)[0:4] if !bytes.Equal(checksum[:], hdr.checksum[:]) { - str := "readMessage: payload checksum failed - Header " + - "indicates %v, but actual checksum is %v." - return nil, nil, fmt.Errorf(str, hdr.checksum, checksum) + str := fmt.Sprintf("payload checksum failed - header "+ + "indicates %v, but actual checksum is %v.", + hdr.checksum, checksum) + return nil, nil, messageError("ReadMessage", str) } // Unmarshal message.