diff --git a/tlv/varint_test.go b/tlv/varint_test.go new file mode 100644 index 000000000..75cc1085f --- /dev/null +++ b/tlv/varint_test.go @@ -0,0 +1,217 @@ +package tlv_test + +import ( + "bytes" + "io" + "math" + "testing" + + "github.com/lightningnetwork/lnd/tlv" +) + +type varIntTest struct { + Name string + Value uint64 + Bytes []byte + ExpErr error +} + +var writeVarIntTests = []varIntTest{ + { + Name: "zero", + Value: 0x00, + Bytes: []byte{0x00}, + }, + { + Name: "one byte high", + Value: 0xfc, + Bytes: []byte{0xfc}, + }, + { + Name: "two byte low", + Value: 0xfd, + Bytes: []byte{0xfd, 0x00, 0xfd}, + }, + { + Name: "two byte high", + Value: 0xffff, + Bytes: []byte{0xfd, 0xff, 0xff}, + }, + { + Name: "four byte low", + Value: 0x10000, + Bytes: []byte{0xfe, 0x00, 0x01, 0x00, 0x00}, + }, + { + Name: "four byte high", + Value: 0xffffffff, + Bytes: []byte{0xfe, 0xff, 0xff, 0xff, 0xff}, + }, + { + Name: "eight byte low", + Value: 0x100000000, + Bytes: []byte{0xff, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00}, + }, + { + Name: "eight byte high", + Value: math.MaxUint64, + Bytes: []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, + }, +} + +// TestWriteVarInt asserts the behavior of tlv.WriteVarInt under various +// positive and negative test cases. +func TestWriteVarInt(t *testing.T) { + for _, test := range writeVarIntTests { + t.Run(test.Name, func(t *testing.T) { + testWriteVarInt(t, test) + }) + } +} + +func testWriteVarInt(t *testing.T, test varIntTest) { + var ( + w bytes.Buffer + buf [8]byte + ) + err := tlv.WriteVarInt(&w, test.Value, &buf) + if err != nil { + t.Fatalf("unable to encode %d as varint: %v", + test.Value, err) + } + + if bytes.Compare(w.Bytes(), test.Bytes) != 0 { + t.Fatalf("expected bytes: %v, got %v", + test.Bytes, w.Bytes()) + } +} + +var readVarIntTests = []varIntTest{ + { + Name: "zero", + Value: 0x00, + Bytes: []byte{0x00}, + }, + { + Name: "one byte high", + Value: 0xfc, + Bytes: []byte{0xfc}, + }, + { + Name: "two byte low", + Value: 0xfd, + Bytes: []byte{0xfd, 0x00, 0xfd}, + }, + { + Name: "two byte high", + Value: 0xffff, + Bytes: []byte{0xfd, 0xff, 0xff}, + }, + { + Name: "four byte low", + Value: 0x10000, + Bytes: []byte{0xfe, 0x00, 0x01, 0x00, 0x00}, + }, + { + Name: "four byte high", + Value: 0xffffffff, + Bytes: []byte{0xfe, 0xff, 0xff, 0xff, 0xff}, + }, + { + Name: "eight byte low", + Value: 0x100000000, + Bytes: []byte{0xff, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00}, + }, + { + Name: "eight byte high", + Value: math.MaxUint64, + Bytes: []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, + }, + { + Name: "two byte not canonical", + Bytes: []byte{0xfd, 0x00, 0xfc}, + ExpErr: tlv.ErrVarIntNotCanonical, + }, + { + Name: "four byte not canonical", + Bytes: []byte{0xfe, 0x00, 0x00, 0xff, 0xff}, + ExpErr: tlv.ErrVarIntNotCanonical, + }, + { + Name: "eight byte not canonical", + Bytes: []byte{0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff}, + ExpErr: tlv.ErrVarIntNotCanonical, + }, + { + Name: "two byte short read", + Bytes: []byte{0xfd, 0x00}, + ExpErr: io.ErrUnexpectedEOF, + }, + { + Name: "four byte short read", + Bytes: []byte{0xfe, 0xff, 0xff}, + ExpErr: io.ErrUnexpectedEOF, + }, + { + Name: "eight byte short read", + Bytes: []byte{0xff, 0xff, 0xff, 0xff, 0xff}, + ExpErr: io.ErrUnexpectedEOF, + }, + { + Name: "one byte no read", + Bytes: []byte{}, + ExpErr: io.EOF, + }, + // The following cases are the reason for needing to make a custom + // version of the varint for the tlv package. For the varint encodings + // in btcd's wire package these would return io.EOF, since it is + // actually a composite of two calls to io.ReadFull. In TLV, we need to + // be able to distinguish whether no bytes were read at all from no + // Bytes being read on the second read as the latter is not a proper TLV + // stream. We handle this by returning io.ErrUnexpectedEOF if we + // encounter io.EOF on any of these secondary reads for larger values. + { + Name: "two byte no read", + Bytes: []byte{0xfd}, + ExpErr: io.ErrUnexpectedEOF, + }, + { + Name: "four byte no read", + Bytes: []byte{0xfe}, + ExpErr: io.ErrUnexpectedEOF, + }, + { + Name: "eight byte no read", + Bytes: []byte{0xff}, + ExpErr: io.ErrUnexpectedEOF, + }, +} + +// TestReadVarInt asserts the behavior of tlv.ReadVarInt under various positive +// and negative test cases. +func TestReadVarInt(t *testing.T) { + for _, test := range readVarIntTests { + t.Run(test.Name, func(t *testing.T) { + testReadVarInt(t, test) + }) + } +} + +func testReadVarInt(t *testing.T, test varIntTest) { + var buf [8]byte + r := bytes.NewReader(test.Bytes) + val, err := tlv.ReadVarInt(r, &buf) + if err != nil && err != test.ExpErr { + t.Fatalf("expected decoding error: %v, got: %v", + test.ExpErr, err) + } + + // If we expected a decoding error, there's no point checking the value. + if test.ExpErr != nil { + return + } + + if val != test.Value { + t.Fatalf("expected value: %d, got %d", test.Value, val) + } +}