Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Decoding/Encoding for []uint16, []uint32 and []uint64 #84

Merged
merged 3 commits into from
Apr 4, 2024
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
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,10 @@ Object{} | concatenation of fields
uint8 | compact u8 [TODO no need for compact u8]
uint16 | compact u16
uint32 | compact u32
uint32 | compact u64
uint34 | compact u64
[]uint16 | length prefixed (compact u32) followed by compact u16s
[]uint32 | length prefixed (compact u32) followed by compact u32s
[]uint64 | length prefixed (compact u32) followed by compact u64s
[...]Object | array with objects. encoded by consecutively encoding every object
[]Object | slice with objects. prefixed with compact u32

Expand Down
78 changes: 78 additions & 0 deletions decoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,84 @@
return d.read(value)
}

func DecodeUint16Slice(d *Decoder) ([]uint16, int, error) {
return DecodeUint16SliceWithLimit(d, d.maxElements)
}

func DecodeUint16SliceWithLimit(d *Decoder, limit uint32) ([]uint16, int, error) {
lth, total, err := DecodeLen(d, limit)
if err != nil {
return nil, 0, err

Check warning on line 340 in decoder.go

View check run for this annotation

Codecov / codecov/patch

decoder.go#L340

Added line #L340 was not covered by tests
}
if lth == 0 {
return nil, total, nil

Check warning on line 343 in decoder.go

View check run for this annotation

Codecov / codecov/patch

decoder.go#L343

Added line #L343 was not covered by tests
}
values := make([]uint16, lth)

for i := uint32(0); i < lth; i++ {
v, n, err := DecodeCompact16(d)
if err != nil {
return nil, 0, err

Check warning on line 350 in decoder.go

View check run for this annotation

Codecov / codecov/patch

decoder.go#L350

Added line #L350 was not covered by tests
}
total += n
values[i] = v
}

return values, total, nil
}

func DecodeUint32Slice(d *Decoder) ([]uint32, int, error) {
return DecodeUint32SliceWithLimit(d, d.maxElements)
}

func DecodeUint32SliceWithLimit(d *Decoder, limit uint32) ([]uint32, int, error) {
lth, total, err := DecodeLen(d, limit)
if err != nil {
return nil, 0, err

Check warning on line 366 in decoder.go

View check run for this annotation

Codecov / codecov/patch

decoder.go#L366

Added line #L366 was not covered by tests
}
if lth == 0 {
return nil, total, nil

Check warning on line 369 in decoder.go

View check run for this annotation

Codecov / codecov/patch

decoder.go#L369

Added line #L369 was not covered by tests
}
values := make([]uint32, lth)

for i := uint32(0); i < lth; i++ {
v, n, err := DecodeCompact32(d)
if err != nil {
return nil, 0, err

Check warning on line 376 in decoder.go

View check run for this annotation

Codecov / codecov/patch

decoder.go#L376

Added line #L376 was not covered by tests
}
total += n
values[i] = v
}

return values, total, nil
}

func DecodeUint64Slice(d *Decoder) ([]uint64, int, error) {
return DecodeUint64SliceWithLimit(d, d.maxElements)
}

func DecodeUint64SliceWithLimit(d *Decoder, limit uint32) ([]uint64, int, error) {
lth, total, err := DecodeLen(d, limit)
if err != nil {
return nil, 0, err

Check warning on line 392 in decoder.go

View check run for this annotation

Codecov / codecov/patch

decoder.go#L392

Added line #L392 was not covered by tests
}
if lth == 0 {
return nil, total, nil

Check warning on line 395 in decoder.go

View check run for this annotation

Codecov / codecov/patch

decoder.go#L395

Added line #L395 was not covered by tests
}
values := make([]uint64, lth)

for i := uint32(0); i < lth; i++ {
v, n, err := DecodeCompact64(d)
if err != nil {
return nil, 0, err

Check warning on line 402 in decoder.go

View check run for this annotation

Codecov / codecov/patch

decoder.go#L402

Added line #L402 was not covered by tests
}
total += n
values[i] = v
}

return values, total, nil
}

func DecodeString(d *Decoder) (string, int, error) {
return DecodeStringWithLimit(d, d.maxElements)
}
Expand Down
24 changes: 24 additions & 0 deletions decoder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,12 @@ func testEncode(tb testing.TB, value any) []byte {
_, err = EncodeCompact64(enc, val)
case []byte:
_, err = EncodeByteSlice(enc, val)
case []uint16:
_, err = EncodeUint16Slice(enc, val)
case []uint32:
_, err = EncodeUint32Slice(enc, val)
case []uint64:
_, err = EncodeUint64Slice(enc, val)
case string:
_, err = EncodeString(enc, val)
case []string:
Expand All @@ -65,6 +71,12 @@ func expectEqual(tb testing.TB, value any, r io.Reader) {
rst, _, err = DecodeCompact64(dec)
case []byte:
rst, _, err = DecodeByteSlice(dec)
case []uint16:
rst, _, err = DecodeUint16Slice(dec)
case []uint32:
rst, _, err = DecodeUint32Slice(dec)
case []uint64:
rst, _, err = DecodeUint64Slice(dec)
case string:
rst, _, err = DecodeString(dec)
case []string:
Expand Down Expand Up @@ -107,6 +119,18 @@ func TestReadFull(t *testing.T) {
desc: "string slice",
expect: []string{"qwe123", "dsa456"},
},
{
desc: "uint16 slice",
expect: []uint16{0, 1, 2, math.MaxUint8, math.MaxUint16},
},
{
desc: "uint32 slice",
expect: []uint32{0, 1, 2, math.MaxUint8, math.MaxUint16, math.MaxUint32},
},
{
desc: "uint64 slice",
expect: []uint64{0, 1, 2, math.MaxUint32, math.MaxUint64},
},
} {
t.Run(tc.desc, func(t *testing.T) {
t.Run("full", func(t *testing.T) {
Expand Down
60 changes: 60 additions & 0 deletions encoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,66 @@
return e.w.Write(value)
}

func EncodeUint16Slice(e *Encoder, value []uint16) (int, error) {
return EncodeUint16SliceWithLimit(e, value, e.maxElements)
}

func EncodeUint16SliceWithLimit(e *Encoder, value []uint16, limit uint32) (int, error) {
total, err := EncodeLen(e, uint32(len(value)), limit)
if err != nil {
return 0, err

Check warning on line 115 in encoder.go

View check run for this annotation

Codecov / codecov/patch

encoder.go#L115

Added line #L115 was not covered by tests
}
for _, v := range value {
n, err := EncodeCompact16(e, v)
if err != nil {
return 0, err

Check warning on line 120 in encoder.go

View check run for this annotation

Codecov / codecov/patch

encoder.go#L120

Added line #L120 was not covered by tests
}
total += n
}

return total, nil
}

func EncodeUint32Slice(e *Encoder, value []uint32) (int, error) {
return EncodeUint32SliceWithLimit(e, value, e.maxElements)
}

func EncodeUint32SliceWithLimit(e *Encoder, value []uint32, limit uint32) (int, error) {
total, err := EncodeLen(e, uint32(len(value)), limit)
if err != nil {
return 0, err

Check warning on line 135 in encoder.go

View check run for this annotation

Codecov / codecov/patch

encoder.go#L135

Added line #L135 was not covered by tests
}
for _, v := range value {
n, err := EncodeCompact32(e, v)
if err != nil {
return 0, err

Check warning on line 140 in encoder.go

View check run for this annotation

Codecov / codecov/patch

encoder.go#L140

Added line #L140 was not covered by tests
}
total += n
}

return total, nil
}

func EncodeUint64Slice(e *Encoder, value []uint64) (int, error) {
return EncodeUint64SliceWithLimit(e, value, e.maxElements)
}

func EncodeUint64SliceWithLimit(e *Encoder, value []uint64, limit uint32) (int, error) {
total, err := EncodeLen(e, uint32(len(value)), limit)
if err != nil {
return 0, err

Check warning on line 155 in encoder.go

View check run for this annotation

Codecov / codecov/patch

encoder.go#L155

Added line #L155 was not covered by tests
}
for _, v := range value {
n, err := EncodeCompact64(e, v)
if err != nil {
return 0, err

Check warning on line 160 in encoder.go

View check run for this annotation

Codecov / codecov/patch

encoder.go#L160

Added line #L160 was not covered by tests
}
total += n
}

return total, nil
}

func EncodeString(e *Encoder, value string) (int, error) {
return EncodeStringWithLimit(e, value, e.maxElements)
}
Expand Down
56 changes: 56 additions & 0 deletions encoder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package scale

import (
"bytes"
"encoding/hex"
"math"
"testing"

Expand Down Expand Up @@ -128,6 +129,32 @@ func uint64TestCases() []compactTestCase[uint64] {
}
}

func mustDecodeHex(hexStr string) []byte {
b, err := hex.DecodeString(hexStr)
if err != nil {
panic(err)
}
return b
}

func uint16SliceTestCases() []compactTestCase[[]uint16] {
return []compactTestCase[[]uint16]{
{[]uint16{4, 15, 23, math.MaxUint16}, mustDecodeHex("10103c5cfeff0300")},
}
}

func uint32SliceTestCases() []compactTestCase[[]uint32] {
return []compactTestCase[[]uint32]{
{[]uint32{4, 15, 23, math.MaxUint32}, mustDecodeHex("10103c5c03ffffffff")},
}
}

func uint64SliceTestCases() []compactTestCase[[]uint64] {
return []compactTestCase[[]uint64]{
{[]uint64{4, 15, 23, math.MaxUint64}, mustDecodeHex("10103c5c13ffffffffffffffff")},
}
}

func encodeTest[T any](t *testing.T, value T, expect []byte) {
buf := bytes.NewBuffer(nil)
enc := NewEncoder(buf)
Expand All @@ -141,6 +168,14 @@ func encodeTest[T any](t *testing.T, value T, expect []byte) {
_, err = EncodeCompact32(enc, typed)
case uint64:
_, err = EncodeCompact64(enc, typed)
case []uint16:
_, err = EncodeUint16Slice(enc, typed)
case []uint32:
_, err = EncodeUint32Slice(enc, typed)
case []uint64:
_, err = EncodeUint64Slice(enc, typed)
default:
t.Fatal("unsupported type")
}
require.NoError(t, err)
require.Equal(t, expect, buf.Bytes())
Expand Down Expand Up @@ -175,4 +210,25 @@ func TestEncodeCompactIntegers(t *testing.T) {
})
}
})
t.Run("[]uint16", func(t *testing.T) {
for _, tc := range uint16SliceTestCases() {
t.Run("", func(t *testing.T) {
encodeTest(t, tc.value, tc.expect)
})
}
})
t.Run("[]uint32", func(t *testing.T) {
for _, tc := range uint32SliceTestCases() {
t.Run("", func(t *testing.T) {
encodeTest(t, tc.value, tc.expect)
})
}
})
t.Run("[]uint64", func(t *testing.T) {
for _, tc := range uint64SliceTestCases() {
t.Run("", func(t *testing.T) {
encodeTest(t, tc.value, tc.expect)
})
}
})
}
14 changes: 12 additions & 2 deletions generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ const (

// nolint
package {{ .Package }}

import (
"github.com/spacemeshos/go-scale"
{{ range $pkg, $short := .Imported }}"{{ $pkg }}"
Expand Down Expand Up @@ -278,6 +278,7 @@ func getDecodeModifier(parentType reflect.Type, field reflect.StructField) strin

func getScaleType(parentType reflect.Type, field reflect.StructField) (scaleType, error) {
decodeModifier := getDecodeModifier(parentType, field)
encodableType := reflect.TypeOf((*Encodable)(nil)).Elem()

switch field.Type.Kind() {
case reflect.Bool:
Expand Down Expand Up @@ -324,9 +325,18 @@ func getScaleType(parentType reflect.Type, field reflect.StructField) (scaleType
if maxElements == 0 {
return scaleType{}, errors.New("slices must have max scale tag")
}
if field.Type.Elem().Kind() == reflect.Uint8 {
if field.Type.Elem().Kind() == reflect.Uint8 && !field.Type.Elem().Implements(encodableType) {
return scaleType{Name: "ByteSliceWithLimit", Args: fmt.Sprintf(", %d", maxElements)}, nil
}
if field.Type.Elem().Kind() == reflect.Uint16 && !field.Type.Elem().Implements(encodableType) {
return scaleType{Name: "Uint16SliceWithLimit", Args: fmt.Sprintf(", %d", maxElements)}, nil
}
if field.Type.Elem().Kind() == reflect.Uint32 && !field.Type.Elem().Implements(encodableType) {
return scaleType{Name: "Uint32SliceWithLimit", Args: fmt.Sprintf(", %d", maxElements)}, nil
}
if field.Type.Elem().Kind() == reflect.Uint64 && !field.Type.Elem().Implements(encodableType) {
return scaleType{Name: "Uint64SliceWithLimit", Args: fmt.Sprintf(", %d", maxElements)}, nil
}
return scaleType{Name: "StructSliceWithLimit", Args: fmt.Sprintf(", %d", maxElements)}, nil
case reflect.Array:
if field.Type.Elem().Kind() == reflect.Uint8 {
Expand Down
Loading