diff --git a/util/proto/marshal.go b/util/proto/marshal.go index 8570f6a..3200176 100644 --- a/util/proto/marshal.go +++ b/util/proto/marshal.go @@ -67,10 +67,64 @@ func BoolSize(field int, v bool) int { } prefix := field << 3 + return VarUIntSize(uint64(prefix)) + 1 // bool is always 1 byte long } +func UInt64Marshal(field int, buf []byte, v uint64) (int, error) { + if v == 0 { + return 0, nil + } + + prefix := field << 3 + + // buf length check can prevent panic at PutUvarint, but it will make + // marshaller a bit slower. + i := binary.PutUvarint(buf, uint64(prefix)) + i += binary.PutUvarint(buf[i:], v) + + return i, nil +} + +func UInt64Size(field int, v uint64) int { + if v == 0 { + return 0 + } + + prefix := field << 3 + + return VarUIntSize(uint64(prefix)) + VarUIntSize(v) +} + +func Int64Marshal(field int, buf []byte, v int64) (int, error) { + return UInt64Marshal(field, buf, uint64(v)) +} + +func Int64Size(field int, v int64) int { + return UInt64Size(field, uint64(v)) +} + +func UInt32Marshal(field int, buf []byte, v uint32) (int, error) { + return UInt64Marshal(field, buf, uint64(v)) +} + +func UInt32Size(field int, v uint32) int { + return UInt64Size(field, uint64(v)) +} + +func Int32Marshal(field int, buf []byte, v int32) (int, error) { + return UInt64Marshal(field, buf, uint64(v)) +} + +func Int32Size(field int, v int32) int { + return UInt64Size(field, uint64(v)) +} + // varUIntSize returns length of varint byte sequence for uint64 value 'x'. func VarUIntSize(x uint64) int { return (bits.Len64(x|1) + 6) / 7 } + +func NestedStructurePrefixSize(field int64) int { + return VarUIntSize(uint64(field<<3 | 0x02)) +} diff --git a/util/proto/marshal_test.go b/util/proto/marshal_test.go index 36847dd..0abcef9 100644 --- a/util/proto/marshal_test.go +++ b/util/proto/marshal_test.go @@ -1,6 +1,7 @@ package proto_test import ( + "math" "testing" "github.com/nspcc-dev/neofs-api-go/util/proto" @@ -13,6 +14,10 @@ type stablePrimitives struct { FieldA []byte FieldB string FieldC bool + FieldD int32 + FieldE uint32 + FieldF int64 + FieldG uint64 } func (s *stablePrimitives) stableMarshal(buf []byte, wrongField bool) ([]byte, error) { @@ -58,13 +63,57 @@ func (s *stablePrimitives) stableMarshal(buf []byte, wrongField bool) ([]byte, e } i += offset + fieldNum = 201 + if wrongField { + fieldNum++ + } + offset, err = proto.Int32Marshal(fieldNum, buf, s.FieldD) + if err != nil { + return nil, errors.Wrap(err, "can't marshal field d") + } + i += offset + + fieldNum = 202 + if wrongField { + fieldNum++ + } + offset, err = proto.UInt32Marshal(fieldNum, buf, s.FieldE) + if err != nil { + return nil, errors.Wrap(err, "can't marshal field e") + } + i += offset + + fieldNum = 203 + if wrongField { + fieldNum++ + } + offset, err = proto.Int64Marshal(fieldNum, buf, s.FieldF) + if err != nil { + return nil, errors.Wrap(err, "can't marshal field f") + } + i += offset + + fieldNum = 204 + if wrongField { + fieldNum++ + } + offset, err = proto.UInt64Marshal(fieldNum, buf, s.FieldG) + if err != nil { + return nil, errors.Wrap(err, "can't marshal field g") + } + i += offset + return buf, nil } func (s *stablePrimitives) stableSize() int { return proto.BytesSize(1, s.FieldA) + proto.StringSize(2, s.FieldB) + - proto.BoolSize(200, s.FieldC) + proto.BoolSize(200, s.FieldC) + + proto.Int32Size(201, s.FieldD) + + proto.UInt32Size(202, s.FieldE) + + proto.Int64Size(203, s.FieldF) + + proto.UInt64Size(204, s.FieldG) } func TestBytesMarshal(t *testing.T) { @@ -106,6 +155,60 @@ func TestBoolMarshal(t *testing.T) { }) } +func TestInt32Marshal(t *testing.T) { + t.Run("zero", func(t *testing.T) { + testInt32Marshal(t, 0, false) + }) + + t.Run("positive", func(t *testing.T) { + testInt32Marshal(t, math.MaxInt32, false) + testInt32Marshal(t, math.MaxInt32, true) + }) + + t.Run("negative", func(t *testing.T) { + testInt32Marshal(t, math.MinInt32, false) + testInt32Marshal(t, math.MinInt32, true) + }) +} + +func TestUInt32Marshal(t *testing.T) { + t.Run("zero", func(t *testing.T) { + testUInt32Marshal(t, 0, false) + }) + + t.Run("non zero", func(t *testing.T) { + testUInt32Marshal(t, math.MaxUint32, false) + testUInt32Marshal(t, math.MaxUint32, true) + }) +} + +func TestInt64Marshal(t *testing.T) { + t.Run("zero", func(t *testing.T) { + testInt32Marshal(t, 0, false) + }) + + t.Run("positive", func(t *testing.T) { + testInt64Marshal(t, math.MaxInt64, false) + testInt64Marshal(t, math.MaxInt64, true) + }) + + t.Run("negative", func(t *testing.T) { + testInt64Marshal(t, math.MinInt64, false) + testInt64Marshal(t, math.MinInt64, true) + }) +} + +func TestUInt64Marshal(t *testing.T) { + t.Run("zero", func(t *testing.T) { + testUInt64Marshal(t, 0, false) + }) + + t.Run("non zero", func(t *testing.T) { + testUInt64Marshal(t, math.MaxUint64, false) + testUInt64Marshal(t, math.MaxUint64, true) + }) +} + func testBytesMarshal(t *testing.T, data []byte, wrongField bool) { var ( wire []byte @@ -210,3 +313,88 @@ func testBoolMarshal(t *testing.T, b bool, wrongField bool) { require.False(t, false, result.FieldC) } } + +func testIntMarshal(t *testing.T, c stablePrimitives, tr test.Primitives, wrongField bool) *test.Primitives { + var ( + wire []byte + err error + ) + wire, err = c.stableMarshal(nil, wrongField) + require.NoError(t, err) + + wireGen, err := tr.Marshal() + require.NoError(t, err) + + if !wrongField { + // we can check equality because single field cannot be unstable marshalled + require.Equal(t, wireGen, wire) + } else { + require.NotEqual(t, wireGen, wire) + } + + result := new(test.Primitives) + err = result.Unmarshal(wire) + require.NoError(t, err) + + return result +} + +func testInt32Marshal(t *testing.T, n int32, wrongField bool) { + var ( + custom = stablePrimitives{FieldD: n} + transport = test.Primitives{FieldD: n} + ) + + result := testIntMarshal(t, custom, transport, wrongField) + + if !wrongField { + require.Equal(t, n, result.FieldD) + } else { + require.EqualValues(t, 0, result.FieldD) + } +} + +func testUInt32Marshal(t *testing.T, n uint32, wrongField bool) { + var ( + custom = stablePrimitives{FieldE: n} + transport = test.Primitives{FieldE: n} + ) + + result := testIntMarshal(t, custom, transport, wrongField) + + if !wrongField { + require.Equal(t, n, result.FieldE) + } else { + require.EqualValues(t, 0, result.FieldE) + } +} + +func testInt64Marshal(t *testing.T, n int64, wrongField bool) { + var ( + custom = stablePrimitives{FieldF: n} + transport = test.Primitives{FieldF: n} + ) + + result := testIntMarshal(t, custom, transport, wrongField) + + if !wrongField { + require.Equal(t, n, result.FieldF) + } else { + require.EqualValues(t, 0, result.FieldF) + } +} + +func testUInt64Marshal(t *testing.T, n uint64, wrongField bool) { + var ( + custom = stablePrimitives{FieldG: n} + transport = test.Primitives{FieldG: n} + ) + + result := testIntMarshal(t, custom, transport, wrongField) + + if !wrongField { + require.Equal(t, n, result.FieldG) + } else { + require.EqualValues(t, 0, result.FieldG) + } +} diff --git a/util/proto/test/test.pb.go b/util/proto/test/test.pb.go index 910812c..6790854 100644 Binary files a/util/proto/test/test.pb.go and b/util/proto/test/test.pb.go differ diff --git a/util/proto/test/test.proto b/util/proto/test/test.proto index 4be07a4..d3b43e6 100644 --- a/util/proto/test/test.proto +++ b/util/proto/test/test.proto @@ -6,4 +6,8 @@ message Primitives { bytes field_a = 1; string field_b = 2; bool field_c = 200; + int32 field_d = 201; + uint32 field_e = 202; + int64 field_f = 203; + uint64 field_g = 204; } \ No newline at end of file