From 41f9c504240dfb6f51d200d5e66f2b82af3c82e7 Mon Sep 17 00:00:00 2001 From: Alex Vanin Date: Fri, 14 Aug 2020 13:57:19 +0300 Subject: [PATCH] Add proto marshal helper for bytes Signed-off-by: Alex Vanin --- util/proto/marshal.go | 44 ++++++++++++++ util/proto/marshal_test.go | 118 +++++++++++++++++++++++++++++++++++++ util/proto/test/test.pb.go | Bin 0 -> 7825 bytes util/proto/test/test.proto | 7 +++ 4 files changed, 169 insertions(+) create mode 100644 util/proto/marshal.go create mode 100644 util/proto/marshal_test.go create mode 100644 util/proto/test/test.pb.go create mode 100644 util/proto/test/test.proto diff --git a/util/proto/marshal.go b/util/proto/marshal.go new file mode 100644 index 0000000..cd9c493 --- /dev/null +++ b/util/proto/marshal.go @@ -0,0 +1,44 @@ +/* +This package contains help functions for stable marshaller. Their usage is +totally optional. One can implement fast stable marshaller without these +runtime function calls. +*/ + +package proto + +import ( + "encoding/binary" + "math/bits" +) + +func BytesMarshal(field int, buf, v []byte) (int, error) { + if len(v) == 0 { + return 0, nil + } + + // buf length check can prevent panic at PutUvarint, but it will make + // marshaller a bit slower. + + prefix := field<<3 | 0x2 + i := binary.PutUvarint(buf, uint64(prefix)) + i += binary.PutUvarint(buf[i:], uint64(len(v))) + i += copy(buf[i:], v) + + return i, nil +} + +func BytesSize(field int, v []byte) int { + ln := len(v) + if ln == 0 { + return 0 + } + + prefix := field<<3 | 0x2 + + return VarUIntSize(uint64(prefix)) + VarUIntSize(uint64(ln)) + ln +} + +// varUIntSize returns length of varint byte sequence for uint64 value 'x'. +func VarUIntSize(x uint64) int { + return (bits.Len64(x|1) + 6) / 7 +} diff --git a/util/proto/marshal_test.go b/util/proto/marshal_test.go new file mode 100644 index 0000000..ccc1cca --- /dev/null +++ b/util/proto/marshal_test.go @@ -0,0 +1,118 @@ +package proto_test + +import ( + "testing" + + "github.com/nspcc-dev/neofs-api-go/util/proto" + "github.com/nspcc-dev/neofs-api-go/util/proto/test" + "github.com/pkg/errors" + "github.com/stretchr/testify/require" +) + +type stablePrimitives struct { + FieldA []byte +} + +func (s *stablePrimitives) stableMarshal(buf []byte) ([]byte, error) { + if s == nil { + return []byte{}, nil + } + + if buf == nil { + buf = make([]byte, s.stableSize()) + } + + var ( + i, offset int + ) + + offset, err := proto.BytesMarshal(1, buf, s.FieldA) + if err != nil { + return nil, errors.Wrap(err, "can't marshal field a") + } + i += offset + + return buf, nil +} + +func (s *stablePrimitives) stableMarshalWrongFieldNum(buf []byte) ([]byte, error) { + if s == nil { + return []byte{}, nil + } + + if buf == nil { + buf = make([]byte, s.stableSize()) + } + + var ( + i, offset int + ) + + offset, err := proto.BytesMarshal(1+1, buf, s.FieldA) + if err != nil { + return nil, errors.Wrap(err, "can't marshal field a") + } + i += offset + + return buf, nil +} + +func (s *stablePrimitives) stableSize() int { + return proto.BytesSize(1, s.FieldA) +} + +func TestBytesMarshal(t *testing.T) { + t.Run("not empty", func(t *testing.T) { + data := []byte("Hello World") + testBytesMarshal(t, data, false) + testBytesMarshal(t, data, true) + }) + + t.Run("empty", func(t *testing.T) { + testBytesMarshal(t, []byte{}, false) + }) + + t.Run("nil", func(t *testing.T) { + testBytesMarshal(t, nil, false) + }) +} + +func testBytesMarshal(t *testing.T, data []byte, wrongField bool) { + var ( + wire []byte + err error + + custom = stablePrimitives{FieldA: data} + transport = test.Primitives{FieldA: data} + ) + + if !wrongField { + wire, err = custom.stableMarshal(nil) + } else { + wire, err = custom.stableMarshalWrongFieldNum(nil) + } + require.NoError(t, err) + + wireGen, err := transport.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) + + if !wrongField { + require.Len(t, result.FieldA, len(data)) + if len(data) > 0 { + require.Equal(t, data, result.FieldA) + } + } else { + require.Len(t, result.FieldA, 0) + } +} diff --git a/util/proto/test/test.pb.go b/util/proto/test/test.pb.go new file mode 100644 index 0000000000000000000000000000000000000000..5db45e5e93083bb3f11eb3bfa1ed9caf1e76b93d GIT binary patch literal 7825 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 literal 0 HcmV?d00001 diff --git a/util/proto/test/test.proto b/util/proto/test/test.proto new file mode 100644 index 0000000..cf51a44 --- /dev/null +++ b/util/proto/test/test.proto @@ -0,0 +1,7 @@ +syntax = "proto3"; + +package test; + +message Primitives { + bytes field_a = 1; +} \ No newline at end of file