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

Encoding uint pointers #85

Merged
merged 4 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
38 changes: 21 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,27 @@

## Types

golang | notes
------------|-------------------------------------------------------------------
[]byte | length prefixed byte array with length as u32 compact integer
string | same as []byte
[...]byte | appended to the result
bool | 1 byte, 0 for false, 1 for true
Object{} | concatenation of fields
*Object{} | Option. 0 for nil, 1 for Object{}. if 1 - decode Object{}
uint8 | compact u8 [TODO no need for compact u8]
uint16 | compact u16
uint32 | compact u32
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
| golang | notes | example |
| ----------- | ------------------------------------------------------------------ | ----------------------------------------------------- |
| []byte | length prefixed byte array with length as u32 compact integer |
| string | same as []byte |
| [...]byte | appended to the result |
| bool | 1 byte, 0 for false, 1 for true |
| Object{} | concatenation of fields |
| *Object{} | Option. 0 for nil, 1 for Object{}. if 1 - decode Object{} |
| uint8 | compact u8 [TODO no need for compact u8] |
| uint16 | compact u16 |
| uint32 | compact u32 |
| uint34 | compact u64 |
| *uint8 | Option (0 for nil, 1 otherwise), followed by compact u8 | `&64 -> 01FC`; `&255 -> 01FD03` |
| *uint16 | Option (0 for nil, 1 otherwise), followed by compact u16 | `&255 -> 01FD03` |
| *uint32 | Option (0 for nil, 1 otherwise), followed by compact u32 | `&255 -> 01FD03` |
| *uint64 | Option (0 for nil, 1 otherwise), followed by compact u64 | `&255 -> 01FD03` |
| []uint16 | length prefixed (compact u32) followed by compact u16s | `[4, 15, 23, u16::MAX] -> 10103C5CFEFF0300` |
| []uint32 | length prefixed (compact u32) followed by compact u32s | `[4, 15, 23, u32::MAX] -> 10103C5C03FFFFFFFF` |
| []uint64 | length prefixed (compact u32) followed by compact u64s | `[4, 15, 23, u64::MAX] -> 10103C5C13FFFFFFFFFFFFFFFF` |
| [...]Object | array with objects. encoded by consecutively encoding every object |
| []Object | slice with objects. prefixed with compact u32 |

Not implemented:

Expand Down
52 changes: 52 additions & 0 deletions decoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -523,3 +523,55 @@
}
return &empty, total + n, nil
}

func DecodeCompact8Ptr(d *Decoder) (*uint8, int, error) {
exists, total, err := DecodeBool(d)
if !exists || err != nil {
return nil, total, err
}

v, n, err := DecodeCompact8(d)
if err != nil {
return nil, 0, err

Check warning on line 535 in decoder.go

View check run for this annotation

Codecov / codecov/patch

decoder.go#L535

Added line #L535 was not covered by tests
}
return &v, total + n, nil
}

func DecodeCompact16Ptr(d *Decoder) (*uint16, int, error) {
exists, total, err := DecodeBool(d)
if !exists || err != nil {
return nil, total, err
}

v, n, err := DecodeCompact16(d)
if err != nil {
return nil, 0, err

Check warning on line 548 in decoder.go

View check run for this annotation

Codecov / codecov/patch

decoder.go#L548

Added line #L548 was not covered by tests
}
return &v, total + n, nil
}

func DecodeCompact32Ptr(d *Decoder) (*uint32, int, error) {
exists, total, err := DecodeBool(d)
if !exists || err != nil {
return nil, total, err
}

v, n, err := DecodeCompact32(d)
if err != nil {
return nil, 0, err

Check warning on line 561 in decoder.go

View check run for this annotation

Codecov / codecov/patch

decoder.go#L561

Added line #L561 was not covered by tests
}
return &v, total + n, nil
}

func DecodeCompact64Ptr(d *Decoder) (*uint64, int, error) {
exists, total, err := DecodeBool(d)
if !exists || err != nil {
return nil, total, err
}

v, n, err := DecodeCompact64(d)
if err != nil {
return nil, 0, err

Check warning on line 574 in decoder.go

View check run for this annotation

Codecov / codecov/patch

decoder.go#L574

Added line #L574 was not covered by tests
}
return &v, total + n, nil
}
48 changes: 48 additions & 0 deletions decoder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,14 @@ func testEncode(tb testing.TB, value any) []byte {
_, err = EncodeCompact32(enc, val)
case uint64:
_, err = EncodeCompact64(enc, val)
case *uint8:
_, err = EncodeCompact8Ptr(enc, val)
case *uint16:
_, err = EncodeCompact16Ptr(enc, val)
case *uint32:
_, err = EncodeCompact32Ptr(enc, val)
case *uint64:
_, err = EncodeCompact64Ptr(enc, val)
case []byte:
_, err = EncodeByteSlice(enc, val)
case []uint16:
Expand Down Expand Up @@ -69,6 +77,14 @@ func expectEqual(tb testing.TB, value any, r io.Reader) {
rst, _, err = DecodeCompact32(dec)
case uint64:
rst, _, err = DecodeCompact64(dec)
case *uint8:
rst, _, err = DecodeCompact8Ptr(dec)
case *uint16:
rst, _, err = DecodeCompact16Ptr(dec)
case *uint32:
rst, _, err = DecodeCompact32Ptr(dec)
case *uint64:
rst, _, err = DecodeCompact64Ptr(dec)
case []byte:
rst, _, err = DecodeByteSlice(dec)
case []uint16:
Expand Down Expand Up @@ -107,6 +123,38 @@ func TestReadFull(t *testing.T) {
desc: "uint64",
expect: uint64(math.MaxUint64),
},
{
desc: "*uint8",
expect: intPtr[uint8](math.MaxUint8),
},
{
desc: "nil *uint8",
expect: (*uint8)(nil),
},
{
desc: "*uint16",
expect: intPtr[uint16](math.MaxUint8),
},
{
desc: "nil *uint16",
expect: (*uint16)(nil),
},
{
desc: "*uint32",
expect: intPtr[uint32](math.MaxUint8),
},
{
desc: "nil *uint32",
expect: (*uint32)(nil),
},
{
desc: "*uint64",
expect: intPtr[uint64](math.MaxUint8),
},
{
desc: "nil *uint64",
expect: (*uint64)(nil),
},
{
desc: "byte slice",
expect: []byte("dsa1232131312dsada123312"),
Expand Down
60 changes: 60 additions & 0 deletions encoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,66 @@
return total + n, nil
}

func EncodeCompact8Ptr(e *Encoder, value *uint8) (int, error) {
if value == nil {
return EncodeBool(e, false)
}
total, err := EncodeBool(e, true)
if err != nil {
return 0, err

Check warning on line 374 in encoder.go

View check run for this annotation

Codecov / codecov/patch

encoder.go#L374

Added line #L374 was not covered by tests
}
n, err := EncodeCompact8(e, *value)
if err != nil {
return 0, err

Check warning on line 378 in encoder.go

View check run for this annotation

Codecov / codecov/patch

encoder.go#L378

Added line #L378 was not covered by tests
}
return total + n, err
}

func EncodeCompact16Ptr(e *Encoder, value *uint16) (int, error) {
if value == nil {
return EncodeBool(e, false)
}
total, err := EncodeBool(e, true)
if err != nil {
return 0, err

Check warning on line 389 in encoder.go

View check run for this annotation

Codecov / codecov/patch

encoder.go#L389

Added line #L389 was not covered by tests
}
n, err := EncodeCompact16(e, *value)
if err != nil {
return 0, err

Check warning on line 393 in encoder.go

View check run for this annotation

Codecov / codecov/patch

encoder.go#L393

Added line #L393 was not covered by tests
}
return total + n, err
}

func EncodeCompact32Ptr(e *Encoder, value *uint32) (int, error) {
if value == nil {
return EncodeBool(e, false)
}
total, err := EncodeBool(e, true)
if err != nil {
return 0, err

Check warning on line 404 in encoder.go

View check run for this annotation

Codecov / codecov/patch

encoder.go#L404

Added line #L404 was not covered by tests
}
n, err := EncodeCompact32(e, *value)
if err != nil {
return 0, err

Check warning on line 408 in encoder.go

View check run for this annotation

Codecov / codecov/patch

encoder.go#L408

Added line #L408 was not covered by tests
}
return total + n, err
}

func EncodeCompact64Ptr(e *Encoder, value *uint64) (int, error) {
if value == nil {
return EncodeBool(e, false)
}
total, err := EncodeBool(e, true)
if err != nil {
return 0, err

Check warning on line 419 in encoder.go

View check run for this annotation

Codecov / codecov/patch

encoder.go#L419

Added line #L419 was not covered by tests
}
n, err := EncodeCompact64(e, *value)
if err != nil {
return 0, err

Check warning on line 423 in encoder.go

View check run for this annotation

Codecov / codecov/patch

encoder.go#L423

Added line #L423 was not covered by tests
}
return total + n, err
}

func EncodeStruct[V any, H EncodablePtr[V]](e *Encoder, value V) (int, error) {
n, err := H(&value).EncodeScale(e)
if err != nil {
Expand Down
102 changes: 102 additions & 0 deletions encoder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,72 @@ func uint64TestCases() []compactTestCase[uint64] {
}
}

func intPtr[T ~uint8 | ~uint16 | ~uint32 | ~uint64](v T) *T {
return &v
}

func uint8PtrTestCases() []compactTestCase[*uint8] {
return []compactTestCase[*uint8]{
{nil, []byte{0}},
{intPtr[uint8](1), []byte{1, 0b0000_0100}},
{intPtr[uint8](maxUint6), []byte{1, 0b1111_1100}},
{intPtr[uint8](maxUint8), []byte{1, 0b1111_1101, 0b0000_0011}},
}
}

func uint16PtrTestCases() []compactTestCase[*uint16] {
return []compactTestCase[*uint16]{
{nil, []byte{0}},
{intPtr[uint16](maxUint8), []byte{1, 0b1111_1101, 0b0000_0011}},
{intPtr[uint16](maxUint16), []byte{1, 0b1111_1110, 0b1111_1111, 0b0000_0011, 0b0000_0000}},
}
}

func uint32PtrTestCases() []compactTestCase[*uint32] {
return []compactTestCase[*uint32]{
{nil, []byte{0}},
{intPtr[uint32](maxUint8), []byte{1, 0b1111_1101, 0b0000_0011}},
{intPtr[uint32](maxUint16), []byte{1, 0b1111_1110, 0b1111_1111, 0b0000_0011, 0b0000_0000}},
{intPtr[uint32](maxUint30), []byte{1, 0b1111_1110, 0b1111_1111, 0b1111_1111, 0b1111_1111}},
{intPtr[uint32](math.MaxUint32), []byte{
1,
0b0000_0011,
0b1111_1111,
0b1111_1111,
0b1111_1111,
0b1111_1111,
}},
}
}

func uint64PtrTestCases() []compactTestCase[*uint64] {
return []compactTestCase[*uint64]{
{nil, []byte{0}},
{intPtr[uint64](maxUint8), []byte{1, 0b1111_1101, 0b0000_0011}},
{intPtr[uint64](maxUint16), []byte{1, 0b1111_1110, 0b1111_1111, 0b0000_0011, 0b0000_0000}},
{intPtr[uint64](math.MaxUint32), []byte{
1,
0b0000_0011,
0b1111_1111,
0b1111_1111,
0b1111_1111,
0b1111_1111,
}},
{intPtr[uint64](math.MaxUint64), []byte{
1,
0b0001_0011,
0b1111_1111,
0b1111_1111,
0b1111_1111,
0b1111_1111,
0b1111_1111,
0b1111_1111,
0b1111_1111,
0b1111_1111,
}},
}
}

func mustDecodeHex(hexStr string) []byte {
b, err := hex.DecodeString(hexStr)
if err != nil {
Expand Down Expand Up @@ -168,6 +234,14 @@ func encodeTest[T any](t *testing.T, value T, expect []byte) {
_, err = EncodeCompact32(enc, typed)
case uint64:
_, err = EncodeCompact64(enc, typed)
case *uint8:
_, err = EncodeCompact8Ptr(enc, typed)
case *uint16:
_, err = EncodeCompact16Ptr(enc, typed)
case *uint32:
_, err = EncodeCompact32Ptr(enc, typed)
case *uint64:
_, err = EncodeCompact64Ptr(enc, typed)
case []uint16:
_, err = EncodeUint16Slice(enc, typed)
case []uint32:
Expand Down Expand Up @@ -210,6 +284,34 @@ func TestEncodeCompactIntegers(t *testing.T) {
})
}
})
t.Run("*uint8", func(t *testing.T) {
for _, tc := range uint8PtrTestCases() {
t.Run("", func(t *testing.T) {
encodeTest(t, tc.value, tc.expect)
})
}
})
t.Run("*uint16", func(t *testing.T) {
for _, tc := range uint16PtrTestCases() {
t.Run("", func(t *testing.T) {
encodeTest(t, tc.value, tc.expect)
})
}
})
t.Run("*uint32", func(t *testing.T) {
for _, tc := range uint32PtrTestCases() {
t.Run("", func(t *testing.T) {
encodeTest(t, tc.value, tc.expect)
})
}
})
t.Run("*uint64", func(t *testing.T) {
for _, tc := range uint64PtrTestCases() {
t.Run("", func(t *testing.T) {
encodeTest(t, tc.value, tc.expect)
})
}
})
t.Run("[]uint16", func(t *testing.T) {
for _, tc := range uint16SliceTestCases() {
t.Run("", func(t *testing.T) {
Expand Down
12 changes: 12 additions & 0 deletions generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,18 @@ func getScaleType(parentType reflect.Type, field reflect.StructField) (scaleType
case reflect.Struct:
return scaleType{Name: "Object"}, nil
case reflect.Ptr:
if field.Type.Elem().Kind() == reflect.Uint8 && !field.Type.Elem().Implements(encodableType) {
return scaleType{Name: "Compact8Ptr"}, nil
}
if field.Type.Elem().Kind() == reflect.Uint16 && !field.Type.Elem().Implements(encodableType) {
return scaleType{Name: "Compact16Ptr"}, nil
}
if field.Type.Elem().Kind() == reflect.Uint32 && !field.Type.Elem().Implements(encodableType) {
return scaleType{Name: "Compact32Ptr"}, nil
}
if field.Type.Elem().Kind() == reflect.Uint64 && !field.Type.Elem().Implements(encodableType) {
return scaleType{Name: "Compact64Ptr"}, nil
}
return scaleType{Name: "Option"}, nil
case reflect.Slice:
if field.Type.Elem().Kind() == reflect.Slice && field.Type.Elem().Elem().Kind() == reflect.Uint8 {
Expand Down
Loading