package btcwire import ( "bytes" "errors" "io" "math" "testing" "git.smesh.lol/nostrino/pkg/crypto/chainhash" ) func TestCompactSize(t *testing.T) { tests := []struct { value uint64 encoded []byte }{ {0, []byte{0x00}}, {1, []byte{0x01}}, {0xfc, []byte{0xfc}}, {0xfd, []byte{0xfd, 0xfd, 0x00}}, {0xffff, []byte{0xfd, 0xff, 0xff}}, {0x10000, []byte{0xfe, 0x00, 0x00, 0x01, 0x00}}, {0xffffffff, []byte{0xfe, 0xff, 0xff, 0xff, 0xff}}, {0x100000000, []byte{0xff, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00}}, {math.MaxUint64, []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}}, } for _, tc := range tests { var e Encoder e.CompactSize(tc.value) if !bytes.Equal(e.Bytes(), tc.encoded) { t.Fatalf("CompactSize(%d) = %x, want %x", tc.value, e.Bytes(), tc.encoded) } if e.Len() != len(tc.encoded) { t.Fatalf("Len() = %d, want %d", e.Len(), len(tc.encoded)) } d := NewDecoder(tc.encoded) got, err := d.CompactSize() if err != nil { t.Fatalf("decoding %x: %v", tc.encoded, err) } if got != tc.value { t.Fatalf("decoded %d, want %d", got, tc.value) } if d.Remaining() != 0 { t.Fatalf("%d bytes left after decoding %d", d.Remaining(), tc.value) } } } func TestCompactSizeNonCanonical(t *testing.T) { tests := []struct { name string encoded []byte }{ {"small value with 0xfd prefix", []byte{0xfd, 0xfc, 0x00}}, {"zero with 0xfd prefix", []byte{0xfd, 0x00, 0x00}}, {"0xffff with 0xfe prefix", []byte{0xfe, 0xff, 0xff, 0x00, 0x00}}, {"0xffffffff with 0xff prefix", []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00}}, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { if _, err := NewDecoder(tc.encoded).CompactSize(); !errors.Is(err, ErrNonCanonicalCompactSize) { t.Fatalf("error = %v, want ErrNonCanonicalCompactSize", err) } }) } } func TestCompactSizeTruncated(t *testing.T) { tests := [][]byte{ nil, {0xfd}, {0xfd, 0x01}, {0xfe}, {0xfe, 0x01, 0x02}, {0xff}, {0xff, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07}, } for _, encoded := range tests { if _, err := NewDecoder(encoded).CompactSize(); !errors.Is(err, io.ErrUnexpectedEOF) { t.Fatalf("decoding %x: error = %v, want io.ErrUnexpectedEOF", encoded, err) } } } func TestVarBytes(t *testing.T) { values := [][]byte{ nil, {}, []byte("a"), []byte("compact filters"), bytes.Repeat([]byte{0x5a}, 0xfc), bytes.Repeat([]byte{0x5a}, 0xfd), bytes.Repeat([]byte{0x5a}, 0x10000), } for _, value := range values { var e Encoder if err := WriteVarBytes(&e, value); err != nil { t.Fatalf("WriteVarBytes(%d bytes): %v", len(value), err) } d := NewDecoder(e.Bytes()) got, err := ReadVarBytes(d, MaxPayload) if err != nil { t.Fatalf("ReadVarBytes(%d bytes): %v", len(value), err) } if !bytes.Equal(got, value) { t.Fatalf("decoded %x, want %x", got, value) } if d.Remaining() != 0 { t.Fatalf("%d bytes left after decoding", d.Remaining()) } } } func TestVarBytesErrors(t *testing.T) { t.Run("too large for limit", func(t *testing.T) { var e Encoder if err := WriteVarBytes(&e, []byte("0123456789")); err != nil { t.Fatalf("WriteVarBytes: %v", err) } if _, err := ReadVarBytes(NewDecoder(e.Bytes()), 9); !errors.Is(err, ErrVarBytesTooLarge) { t.Fatalf("error = %v, want ErrVarBytesTooLarge", err) } }) t.Run("negative limit", func(t *testing.T) { if _, err := ReadVarBytes(NewDecoder(nil), -1); !errors.Is(err, ErrInvalidLength) { t.Fatalf("error = %v, want ErrInvalidLength", err) } }) t.Run("length above limit", func(t *testing.T) { if _, err := ReadVarBytes(NewDecoder([]byte{0x05}), 4); !errors.Is(err, ErrVarBytesTooLarge) { t.Fatalf("error = %v, want ErrVarBytesTooLarge", err) } }) t.Run("truncated data", func(t *testing.T) { if _, err := ReadVarBytes(NewDecoder([]byte{0x05, 0x01, 0x02}), 100); !errors.Is(err, io.ErrUnexpectedEOF) { t.Fatalf("error = %v, want io.ErrUnexpectedEOF", err) } }) t.Run("truncated length", func(t *testing.T) { if _, err := ReadVarBytes(NewDecoder([]byte{0xfd, 0x01}), 100); !errors.Is(err, io.ErrUnexpectedEOF) { t.Fatalf("error = %v, want io.ErrUnexpectedEOF", err) } }) t.Run("oversize write", func(t *testing.T) { var e Encoder err := WriteVarBytes(&e, make([]byte, MaxPayload+1)) if !errors.Is(err, ErrVarBytesTooLarge) { t.Fatalf("error = %v, want ErrVarBytesTooLarge", err) } }) } func TestPrimitives(t *testing.T) { var e Encoder e.Uint8(0x12) e.Uint16(0x3456) e.Uint16BE(0x789a) e.Uint32(0x01020304) e.Uint64(0x0102030405060708) e.Int32(-2) e.Int64(-2) e.Hash(chainhash.Hash{0xaa, 0xbb}) want := []byte{ 0x12, 0x56, 0x34, 0x78, 0x9a, 0x04, 0x03, 0x02, 0x01, 0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xaa, 0xbb, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, } if !bytes.Equal(e.Bytes(), want) { t.Fatalf("encoded = %x, want %x", e.Bytes(), want) } d := NewDecoder(want) if v, err := d.Uint8(); err != nil || v != 0x12 { t.Fatalf("Uint8 = %x, %v", v, err) } if v, err := d.Uint16(); err != nil || v != 0x3456 { t.Fatalf("Uint16 = %x, %v", v, err) } if v, err := d.Uint16BE(); err != nil || v != 0x789a { t.Fatalf("Uint16BE = %x, %v", v, err) } if v, err := d.Uint32(); err != nil || v != 0x01020304 { t.Fatalf("Uint32 = %x, %v", v, err) } if v, err := d.Uint64(); err != nil || v != 0x0102030405060708 { t.Fatalf("Uint64 = %x, %v", v, err) } if v, err := d.Int32(); err != nil || v != -2 { t.Fatalf("Int32 = %d, %v", v, err) } if v, err := d.Int64(); err != nil || v != -2 { t.Fatalf("Int64 = %d, %v", v, err) } h, err := d.Hash() if err != nil { t.Fatalf("Hash: %v", err) } if h[0] != 0xaa || h[1] != 0xbb { t.Fatalf("Hash = %x", h) } if d.Remaining() != 0 { t.Fatalf("Remaining = %d, want 0", d.Remaining()) } if d.Offset() != len(want) { t.Fatalf("Offset = %d, want %d", d.Offset(), len(want)) } } func TestDecoderTruncated(t *testing.T) { tests := []struct { name string read func(*Decoder) error data []byte }{ {"Uint8", func(d *Decoder) error { _, err := d.Uint8(); return err }, nil}, {"Uint16", func(d *Decoder) error { _, err := d.Uint16(); return err }, []byte{0x01}}, {"Uint16BE", func(d *Decoder) error { _, err := d.Uint16BE(); return err }, []byte{0x01}}, {"Uint32", func(d *Decoder) error { _, err := d.Uint32(); return err }, []byte{1, 2, 3}}, {"Uint64", func(d *Decoder) error { _, err := d.Uint64(); return err }, []byte{1, 2, 3, 4, 5, 6, 7}}, {"Int32", func(d *Decoder) error { _, err := d.Int32(); return err }, []byte{1}}, {"Int64", func(d *Decoder) error { _, err := d.Int64(); return err }, []byte{1}}, {"Hash", func(d *Decoder) error { _, err := d.Hash(); return err }, make([]byte, chainhash.HashSize-1)}, {"Bytes", func(d *Decoder) error { _, err := d.Bytes(5); return err }, []byte{1, 2}}, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { if err := tc.read(NewDecoder(tc.data)); !errors.Is(err, io.ErrUnexpectedEOF) { t.Fatalf("error = %v, want io.ErrUnexpectedEOF", err) } }) } if _, err := NewDecoder(nil).Bytes(-1); !errors.Is(err, ErrInvalidLength) { t.Fatalf("negative Bytes error = %v, want ErrInvalidLength", err) } } func TestEncoderBytesAlias(t *testing.T) { var e Encoder e.Uint8(0x01) b := e.Bytes() if len(b) != 1 || b[0] != 0x01 { t.Fatalf("Bytes() = %x", b) } }