Skip to content
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
53 changes: 45 additions & 8 deletions wire/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ const (

// strictAsciiRangeUpper is the upper limit of the strict ASCII range.
strictAsciiRangeUpper = 0x7e

// unixToInternal is the number of seconds between year 1 of the Go time
// value and the unix epoch.
unixToInternal = 62135596800
)

var (
Expand Down Expand Up @@ -150,14 +154,17 @@ type uint32Time time.Time

// int64Time represents a unix timestamp encoded with an int64. It is used as
// a way to signal the readElement function how to decode a timestamp into a Go
// time.Time since it is otherwise ambiguous.
// time.Time since it is otherwise ambiguous. The value is rejected if it is
// larger than the maximum usable seconds for a Go time value for worry-free
// comparisons.
type int64Time time.Time

// uint64Time represents a unix timestamp encoded with a uint64. It is used as
// a way to signal the readElement function how to decode a timestamp into a Go
// time.Time since it is otherwise ambiguous. The uint64 value is rejected if
// it is larger than the maximum int64 value since it would overflow when
// converted to an int64 for the time.Unix call.
// it is larger than the maximum usable seconds for a Go time value for
// worry-free comparisons which also has the side effect of preventing overflow
// when converting to an int64 for the time.Unix call.
type uint64Time time.Time

// readElement reads the next sequence of bytes from r using little endian
Expand Down Expand Up @@ -241,6 +248,13 @@ func readElement(r io.Reader, element interface{}) error {
if err != nil {
return err
}

// Reject timestamps that would overflow the maximum usable number of
// seconds for worry-free comparisons.
if rv > math.MaxInt64-unixToInternal {
const str = "timestamp exceeds maximum allowed value"
return messageError("readElement", ErrInvalidTimestamp, str)
}
*e = int64Time(time.Unix(int64(rv), 0))
return nil

Expand All @@ -249,10 +263,12 @@ func readElement(r io.Reader, element interface{}) error {
if err != nil {
return err
}
// Reject timestamps that would overflow when converted to int64.
if rv > math.MaxInt64 {
return messageError("readElement", ErrInvalidMsg,
"timestamp exceeds maximum allowed value")

// Reject timestamps that would overflow the maximum usable number of
// seconds for worry-free comparisons.
if rv > math.MaxInt64-unixToInternal {
const str = "timestamp exceeds maximum allowed value"
return messageError("readElement", ErrInvalidTimestamp, str)
}
*e = uint64Time(time.Unix(int64(rv), 0))
return nil
Expand Down Expand Up @@ -534,8 +550,29 @@ func writeElement(w io.Writer, element interface{}) error {
}
return nil

case *int64Time:
// Reject timestamps that would overflow the maximum usable number of
// seconds for worry-free comparisons.
secs := uint64(time.Time(*e).Unix())
if secs > math.MaxInt64-unixToInternal {
const str = "timestamp exceeds maximum allowed value"
return messageError("writeElement", ErrInvalidTimestamp, str)
}
err := writeUint64LE(w, secs)
if err != nil {
return err
}
return nil

case *uint64Time:
err := writeUint64LE(w, uint64(time.Time(*e).Unix()))
// Reject timestamps that would overflow the maximum usable number of
// seconds for worry-free comparisons.
secs := uint64(time.Time(*e).Unix())
if secs > math.MaxInt64-unixToInternal {
const str = "timestamp exceeds maximum allowed value"
return messageError("writeElement", ErrInvalidTimestamp, str)
}
err := writeUint64LE(w, secs)
if err != nil {
return err
}
Expand Down
9 changes: 9 additions & 0 deletions wire/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,14 @@ const (
// ErrUnknownNetAddrType is returned when a network address type is not
// recognized or supported.
ErrUnknownNetAddrType

// ErrInvalidTimestamp is returned when a message that involves a timestamp
// is not in the allowable range.
ErrInvalidTimestamp

// numErrorCodes is the total number of error codes defined above. This
// entry MUST be the last entry in the enum.
numErrorCodes
)

// Map of ErrorCode values back to their constant names for pretty printing.
Expand Down Expand Up @@ -203,6 +211,7 @@ var errorCodeStrings = map[ErrorCode]string{
ErrTooManyCFilters: "ErrTooManyCFilters",
ErrTooFewAddrs: "ErrTooFewAddrs",
ErrUnknownNetAddrType: "ErrUnknownNetAddrType",
ErrInvalidTimestamp: "ErrInvalidTimestamp",
}

// String returns the ErrorCode as a human-readable name.
Expand Down
9 changes: 9 additions & 0 deletions wire/error_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,19 @@ func TestMessageErrorCodeStringer(t *testing.T) {
{ErrTooManyMixPairReqUTXOs, "ErrTooManyMixPairReqUTXOs"},
{ErrTooManyPrevMixMsgs, "ErrTooManyPrevMixMsgs"},
{ErrTooManyCFilters, "ErrTooManyCFilters"},
{ErrTooFewAddrs, "ErrTooFewAddrs"},
{ErrUnknownNetAddrType, "ErrUnknownNetAddrType"},
{ErrInvalidTimestamp, "ErrInvalidTimestamp"},

{0xffff, "Unknown ErrorCode (65535)"},
}

// Detect additional defines that don't have the stringer added.
if len(tests)-1 != int(numErrorCodes) {
t.Fatal("It appears an error code was added without adding an " +
"associated stringer test")
}

t.Logf("Running %d tests", len(tests))
for i, test := range tests {
result := test.in.String()
Expand Down
16 changes: 14 additions & 2 deletions wire/msgaddrv2_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"bytes"
"errors"
"io"
"math"
"reflect"
"testing"
"time"
Expand Down Expand Up @@ -260,14 +261,14 @@ func TestAddrV2BtcDecode(t *testing.T) {
pver: pver,
wireBytes: []byte{
0x01, // Varint address count
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, // Timestamp (MaxInt64+1)
0x00, 0x09, 0x6e, 0x88, 0xf1, 0xff, 0xff, 0x7f, // Timestamp (max+1)
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Services
0x01, // Type (IPv4)
0x7f, 0x00, 0x00, 0x01, // IP
0x8d, 0x20, // Port 8333 (little-endian)
},
wantAddrs: nil,
wantErr: ErrInvalidMsg,
wantErr: ErrInvalidTimestamp,
}, {
name: "message with valid types and unknown type",
pver: pver,
Expand Down Expand Up @@ -391,6 +392,17 @@ func TestAddrV2BtcEncode(t *testing.T) {
Port: 8333,
}},
wantErr: ErrUnknownNetAddrType,
}, {
name: "message with invalid timestamp",
pver: pver,
addrs: []NetAddressV2{{
Timestamp: time.Unix(math.MaxInt64-unixToInternal+1, 0),
Services: SFNodeNetwork,
Type: IPv4Address,
EncodedAddr: []byte{0x7f, 0x00, 0x00, 0x01},
Port: 8333,
}},
wantErr: ErrInvalidTimestamp,
}}

for _, test := range tests {
Expand Down
13 changes: 4 additions & 9 deletions wire/msgversion.go
Original file line number Diff line number Diff line change
Expand Up @@ -157,13 +157,8 @@ func (msg *MsgVersion) BtcEncode(w io.Writer, pver uint32) error {
return err
}

var elems struct {
ts int64
relayTx bool
}
elems.ts = msg.Timestamp.Unix()

err = writeElements(w, &msg.ProtocolVersion, &msg.Services, &elems.ts)
err = writeElements(w, &msg.ProtocolVersion, &msg.Services,
(*int64Time)(&msg.Timestamp))
if err != nil {
return err
}
Expand Down Expand Up @@ -193,8 +188,8 @@ func (msg *MsgVersion) BtcEncode(w io.Writer, pver uint32) error {
return err
}

elems.relayTx = !msg.DisableRelayTx
return writeElement(w, &elems.relayTx)
var relayTx = !msg.DisableRelayTx
return writeElement(w, &relayTx)
}

// Command returns the protocol command string for the message. This is part
Expand Down