Skip to content

Commit

Permalink
Implemented alert message serialize/deserialize
Browse files Browse the repository at this point in the history
* Introduced common methods readVarBytes, writeVarBytes.
* Added type Alert which knows how to deserialize
the serialized payload and also serialize itself back.
* Updated MsgAlert BtcEncode/BtcDecode methods to handle the
new Alert.
* Sane limits are placed on variable length fields like SetCancel
and SetSubVer
  • Loading branch information
tuxcanfly committed May 5, 2014
1 parent 2cc89bb commit bdec7f8
Show file tree
Hide file tree
Showing 7 changed files with 839 additions and 100 deletions.
41 changes: 41 additions & 0 deletions common.go
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,32 @@ func writeElements(w io.Writer, elements ...interface{}) error {
return nil
}

// readVarBytes reads a variable length byte array
func readVarBytes(r io.Reader, pver uint32, maxAllowed uint32,
fieldName string) ([]byte, error) {

count, err := readVarInt(r, pver)
if err != nil {
return nil, err
}

// Prevent byte array larger than the max message size. It would
// 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 "+
"[count %d, max %d]", fieldName, count, maxAllowed)
return nil, messageError("readVarBytes", str)
}

b := make([]byte, count)
_, err = io.ReadFull(r, b)
if err != nil {
return nil, err
}
return b, nil
}

// readVarInt reads a variable length integer from r and returns it as a uint64.
func readVarInt(r io.Reader, pver uint32) (uint64, error) {
var b [8]byte
Expand Down Expand Up @@ -320,6 +346,21 @@ func readVarInt(r io.Reader, pver uint32) (uint64, error) {
return rv, nil
}

// writeVarBytes writes a variable length byte array
func writeVarBytes(w io.Writer, pver uint32, bytes []byte) error {
slen := uint64(len(bytes))
err := writeVarInt(w, pver, slen)
if err != nil {
return err
}

_, err = w.Write(bytes)
if err != nil {
return err
}
return nil
}

// writeVarInt serializes val to w using a variable number of bytes depending
// on its value.
func writeVarInt(w io.Writer, pver uint32, val uint64) error {
Expand Down
133 changes: 133 additions & 0 deletions common_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -501,6 +501,139 @@ func TestVarStringOverflowErrors(t *testing.T) {

}

// TestVarBytesWire tests wire encode and decode for variable length byte array.
func TestVarBytesWire(t *testing.T) {
pver := btcwire.ProtocolVersion

// bytes256 is a byte array that takes a 2-byte varint to encode.
bytes256 := bytes.Repeat([]byte{0x01}, 256)

tests := []struct {
in []byte // Byte Array to write
buf []byte // Wire encoding
pver uint32 // Protocol version for wire encoding
}{
// Latest protocol version.
// Empty byte array
{[]byte{}, []byte{0x00}, pver},
// Single byte varint + byte array
{[]byte{0x01}, []byte{0x01, 0x01}, pver},
// 2-byte varint + byte array
{bytes256, append([]byte{0xfd, 0x00, 0x01}, bytes256...), pver},
}

t.Logf("Running %d tests", len(tests))
for i, test := range tests {
// Encode to wire format.
var buf bytes.Buffer
err := btcwire.TstWriteVarBytes(&buf, test.pver, test.in)
if err != nil {
t.Errorf("writeVarBytes #%d error %v", i, err)
continue
}
if !bytes.Equal(buf.Bytes(), test.buf) {
t.Errorf("writeVarBytes #%d\n got: %s want: %s", i,
spew.Sdump(buf.Bytes()), spew.Sdump(test.buf))
continue
}

// Decode from wire format.
rbuf := bytes.NewBuffer(test.buf)
val, err := btcwire.TstReadVarBytes(rbuf, test.pver, btcwire.MaxMessagePayload,
"alert serialized payload")
if err != nil {
t.Errorf("readVarBytes #%d error %v", i, err)
continue
}
if !bytes.Equal(buf.Bytes(), test.buf) {
t.Errorf("readVarBytes #%d\n got: %s want: %s", i,
val, test.buf)
continue
}
}
}

// TestVarBytesWireErrors performs negative tests against wire encode and
// decode of variable length byte arrays to confirm error paths work correctly.
func TestVarBytesWireErrors(t *testing.T) {
pver := btcwire.ProtocolVersion

// bytes256 is a byte array that takes a 2-byte varint to encode.
bytes256 := bytes.Repeat([]byte{0x01}, 256)

tests := []struct {
in []byte // Byte Array to write
buf []byte // Wire encoding
pver uint32 // Protocol version for wire encoding
max int // Max size of fixed buffer to induce errors
writeErr error // Expected write error
readErr error // Expected read error
}{
// Latest protocol version with intentional read/write errors.
// Force errors on empty byte array.
{[]byte{}, []byte{0x00}, pver, 0, io.ErrShortWrite, io.EOF},
// Force error on single byte varint + byte array.
{[]byte{0x01, 0x02, 0x03}, []byte{0x04}, pver, 2, io.ErrShortWrite, io.ErrUnexpectedEOF},
// Force errors on 2-byte varint + byte array.
{bytes256, []byte{0xfd}, pver, 2, io.ErrShortWrite, io.ErrUnexpectedEOF},
}

t.Logf("Running %d tests", len(tests))
for i, test := range tests {
// Encode to wire format.
w := newFixedWriter(test.max)
err := btcwire.TstWriteVarBytes(w, test.pver, test.in)
if err != test.writeErr {
t.Errorf("writeVarBytes #%d wrong error got: %v, want: %v",
i, err, test.writeErr)
continue
}

// Decode from wire format.
r := newFixedReader(test.max, test.buf)
_, err = btcwire.TstReadVarBytes(r, test.pver, btcwire.MaxMessagePayload,
"alert serialized payload")
if err != test.readErr {
t.Errorf("readVarBytes #%d wrong error got: %v, want: %v",
i, err, test.readErr)
continue
}
}
}

// TestVarBytesOverflowErrors performs tests to ensure deserializing variable
// length byte arrays intentionally crafted to use large values for the array
// length are handled properly. This could otherwise potentially be used as an
// attack vector.
func TestVarBytesOverflowErrors(t *testing.T) {
pver := btcwire.ProtocolVersion

tests := []struct {
buf []byte // Wire encoding
pver uint32 // Protocol version for wire encoding
err error // Expected error
}{
{[]byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
pver, &btcwire.MessageError{}},
{[]byte{0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01},
pver, &btcwire.MessageError{}},
}

t.Logf("Running %d tests", len(tests))
for i, test := range tests {
// Decode from wire format.
rbuf := bytes.NewBuffer(test.buf)
_, err := btcwire.TstReadVarBytes(rbuf, test.pver, btcwire.MaxMessagePayload,
"alert serialized payload")
if reflect.TypeOf(err) != reflect.TypeOf(test.err) {
t.Errorf("readVarBytes #%d wrong error got: %v, "+
"want: %v", i, err, reflect.TypeOf(test.err))
continue
}
}

}

// TestRandomUint64 exercises the randomness of the random number generator on
// the system by ensuring the probability of the generated numbers. If the RNG
// is evenly distributed as a proper cryptographic RNG should be, there really
Expand Down
20 changes: 20 additions & 0 deletions internal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,14 @@ import (
// the test package.
const MaxMessagePayload uint32 = maxMessagePayload

// MaxCountSetCancel makes the internal maxCountSetCancel constant available to
// the test package.
const MaxCountSetCancel uint32 = maxCountSetCancel

// MaxCountSetSubVer makes the internal maxCountSetSubVer constant available to
// the test package.
const MaxCountSetSubVer uint32 = maxCountSetSubVer

// CommandSize makes the internal commandSize constant available to the test
// package.
const CommandSize = commandSize
Expand Down Expand Up @@ -65,6 +73,18 @@ func TstWriteVarString(w io.Writer, pver uint32, str string) error {
return writeVarString(w, pver, str)
}

// TstReadVarBytes makes the internal readVarBytes function available to the
// test package.
func TstReadVarBytes(r io.Reader, pver uint32, maxAllowed uint32, fieldName string) ([]byte, error) {
return readVarBytes(r, pver, maxAllowed, fieldName)
}

// TstWriteVarBytes makes the internal writeVarBytes function available to the
// test package.
func TstWriteVarBytes(w io.Writer, pver uint32, bytes []byte) error {
return writeVarBytes(w, pver, bytes)
}

// TstReadNetAddress makes the internal readNetAddress function available to
// the test package.
func TstReadNetAddress(r io.Reader, pver uint32, na *NetAddress, ts bool) error {
Expand Down
2 changes: 1 addition & 1 deletion message_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ func TestMessage(t *testing.T) {
msgPong := btcwire.NewMsgPong(123123)
msgGetHeaders := btcwire.NewMsgGetHeaders()
msgHeaders := btcwire.NewMsgHeaders()
msgAlert := btcwire.NewMsgAlert("payload", "signature")
msgAlert := btcwire.NewMsgAlert([]byte("payload"), []byte("signature"))
msgMemPool := btcwire.NewMsgMemPool()

tests := []struct {
Expand Down
Loading

0 comments on commit bdec7f8

Please sign in to comment.